Expose declarations only via DeclarationInfo PiperOrigin-RevId: 289484723
diff --git a/internal/common/compilation.bzl b/internal/common/compilation.bzl index c11b71c..514202a 100644 --- a/internal/common/compilation.bzl +++ b/internal/common/compilation.bzl
@@ -55,7 +55,13 @@ default = [], providers = [JsInfo], ), - "deps": attr.label_list(aspects = DEPS_ASPECTS), + "deps": attr.label_list( + aspects = DEPS_ASPECTS, + # TODO(b/139705078): require all deps have a DeclarationInfo provider + # and remove assert_js_or_typescript_deps which is attempting + # to enforce the same thing as this. + # providers = [DeclarationInfo], + ), "_additional_d_ts": _ADDITIONAL_D_TS, } @@ -75,85 +81,48 @@ # Fallback to `ctx.attr.deps`. deps = deps if deps != None else ctx.attr.deps for dep in deps: - if not hasattr(dep, "typescript") and not JsInfo in dep: + if not hasattr(dep, "typescript") and not JsInfo in dep and not DeclarationInfo in dep: allowed_deps_msg = "Dependencies must be ts_library" fail("%s is neither a TypeScript nor a JS producing rule.\n%s\n" % (dep.label, allowed_deps_msg)) _DEPSET_TYPE = type(depset()) -def _check_ts_provider(dep): - """Verifies the type shape of the typescript provider in dep, if it has one. - """ - - # Under Bazel, some third parties have created typescript providers which may not be compatible. - # Rather than users getting an obscure error later, explicitly check them and point to the - # target that created the bad provider. - # TODO(alexeagle): remove this after some transition period, maybe mid-2019 - if hasattr(dep, "typescript"): - if type(dep.typescript.declarations) != _DEPSET_TYPE: - fail("typescript provider in %s defined declarations as a %s rather than a depset" % ( - dep.label, - type(dep.typescript.declarations), - )) - if type(dep.typescript.transitive_declarations) != _DEPSET_TYPE: - fail("typescript provider in %s defined transitive_declarations as a %s rather than a depset" % ( - dep.label, - type(dep.typescript.transitive_declarations), - )) - if type(dep.typescript.type_blacklisted_declarations) != _DEPSET_TYPE: - fail("typescript provider in %s defined type_blacklisted_declarations as a %s rather than a depset" % ( - dep.label, - type(dep.typescript.type_blacklisted_declarations), - )) - return dep - -def _collect_dep_declarations(ctx, deps): - """Collects .d.ts files from typescript and javascript dependencies. +def _collect_dep_declarations(ctx, declaration_infos): + """Flattens DeclarationInfo from typescript and javascript dependencies. Args: ctx: ctx. - deps: dependent targets, generally ctx.attr.deps + declaration_infos: list of DeclarationInfo collected from dependent targets Returns: A struct of depsets for direct, transitive and type-blacklisted declarations. """ - deps_and_helpers = [ - _check_ts_provider(dep) - for dep in deps + getattr(ctx.attr, "_helpers", []) - if hasattr(dep, "typescript") - ] - # .d.ts files from direct dependencies, ok for strict deps - direct_deps_declarations = [dep.typescript.declarations for dep in deps_and_helpers] + direct_deps_declarations = [dep.declarations for dep in declaration_infos] # all reachable .d.ts files from dependencies. - transitive_deps_declarations = [ - dep.typescript.transitive_declarations - for dep in deps_and_helpers - ] + transitive_deps_declarations = [dep.transitive_declarations for dep in declaration_infos] # all reachable .d.ts files from node_modules attribute (if it has a typescript provider) - if hasattr(ctx.attr, "node_modules") and hasattr(ctx.attr.node_modules, "typescript"): - transitive_deps_declarations += [ctx.attr.node_modules.typescript.transitive_declarations] + if hasattr(ctx.attr, "node_modules"): + if DeclarationInfo in ctx.attr.node_modules: + transitive_deps_declarations.append(ctx.attr.node_modules[DeclarationInfo].transitive_declarations) + elif hasattr(ctx.attr.node_modules, "typescript"): + # TODO(b/139705078): remove this case after bazel BUILD file generation for node_modules is updated + transitive_deps_declarations.append([ctx.attr.node_modules.typescript.transitive_declarations]) # .d.ts files whose types tsickle will not emit (used for ts_declaration(generate_externs=False). - type_blacklisted_declarations = [ - dep.typescript.type_blacklisted_declarations - for dep in deps_and_helpers - ] + type_blacklisted_declarations = [dep.type_blacklisted_declarations for dep in declaration_infos] # If a tool like github.com/angular/clutz can create .d.ts from type annotated .js # its output will be collected here. - return struct( - direct = depset(transitive = direct_deps_declarations), - transitive = depset( - [extra for extra in ctx.files._additional_d_ts], - transitive = transitive_deps_declarations, - ), - type_blacklisted = depset(transitive = type_blacklisted_declarations), + return DeclarationInfo( + declarations = depset(transitive = direct_deps_declarations), + transitive_declarations = depset(ctx.files._additional_d_ts, transitive = transitive_deps_declarations), + type_blacklisted_declarations = depset(transitive = type_blacklisted_declarations), ) def _should_generate_externs(ctx): @@ -224,7 +193,7 @@ ctx, is_library, srcs = None, - deps = None, + declaration_infos = None, compile_action = None, devmode_compile_action = None, jsx_factory = None, @@ -239,7 +208,7 @@ ctx: ctx. is_library: boolean. False if only compiling .dts files. srcs: label list. Explicit list of sources to be used instead of ctx.attr.srcs. - deps: label list. Explicit list of deps to be used instead of ctx.attr.deps. + declaration_infos: list of DeclarationInfo. Explicit list of declarations to be used instead of those on ctx.attr.deps. compile_action: function. Creates the compilation action. devmode_compile_action: function. Creates the compilation action for devmode. @@ -254,16 +223,28 @@ ### Collect srcs and outputs. srcs = srcs if srcs != None else ctx.attr.srcs - deps = deps if deps != None else ctx.attr.deps + if declaration_infos == None: + if not hasattr(ctx.attr, "deps"): + fail("compile_ts must either be called from a rule with a deps attr, or must be given declaration_infos") + + # Validate the user inputs. + # TODO(b/139705078): remove this when we require DeclarationInfo provider on deps + assert_js_or_typescript_deps(ctx, ctx.attr.deps) + + # By default, we collect dependencies from the ctx, when used as a rule + declaration_infos = [ + d[DeclarationInfo] + for d in ctx.attr.deps + getattr(ctx.attr, "_helpers", []) + # TODO(b/139705078): remove this when we require DeclarationInfo provider on deps + if DeclarationInfo in d + ] + tsconfig = tsconfig if tsconfig != None else ctx.outputs.tsconfig srcs_files = [f for t in srcs for f in t.files.to_list()] src_declarations = [] # d.ts found in inputs. tsickle_externs = [] # externs.js generated by tsickle, if any. has_sources = False - # Validate the user inputs. - assert_js_or_typescript_deps(ctx, deps) - for src in srcs: if src.label.package != ctx.label.package: # Sources can be in sub-folders, but not in sub-packages. @@ -298,9 +279,9 @@ # Note: setting this variable controls whether tsickle is run at all. tsickle_externs = [ctx.actions.declare_file(ctx.label.name + ".externs.js")] - dep_declarations = _collect_dep_declarations(ctx, deps) - input_declarations = depset(src_declarations, transitive = [dep_declarations.transitive]) - type_blacklisted_declarations = dep_declarations.type_blacklisted + dep_declarations = _collect_dep_declarations(ctx, declaration_infos) + + type_blacklisted_declarations = dep_declarations.type_blacklisted_declarations if not is_library and not _should_generate_externs(ctx): type_blacklisted_declarations = depset(srcs_files, transitive = [type_blacklisted_declarations]) @@ -319,7 +300,7 @@ if "TYPESCRIPT_PERF_TRACE_TARGET" in ctx.var: perf_trace = str(ctx.label) == ctx.var["TYPESCRIPT_PERF_TRACE_TARGET"] - compilation_inputs = dep_declarations.transitive.to_list() + srcs_files + compilation_inputs = dep_declarations.transitive_declarations.to_list() + srcs_files tsickle_externs_path = tsickle_externs[0] if tsickle_externs else None # Calculate allowed dependencies for strict deps enforcement. @@ -327,7 +308,7 @@ # A target's sources may depend on each other, srcs_files, # or on a .d.ts from a direct dependency - transitive = [dep_declarations.direct], + transitive = [dep_declarations.declarations], ) tsconfig_es6 = tsc_wrapped_tsconfig( @@ -429,7 +410,7 @@ # TODO(martinprobst): Merge the generated .d.ts files, and enforce strict # deps (do not re-export transitive types from the transitive closure). - transitive_decls = depset(src_declarations + gen_declarations, transitive = [dep_declarations.transitive]) + transitive_decls = depset(src_declarations + gen_declarations, transitive = [dep_declarations.transitive_declarations]) # both ts_library and ts_declarations generate .mjs files: # - for libraries, this is the ES6/production code @@ -451,9 +432,8 @@ if not srcs_files: # Re-export sources from deps. # TODO(b/30018387): introduce an "exports" attribute. - for dep in deps: - if hasattr(dep, "typescript"): - declarations_depsets.append(dep.typescript.declarations) + for dep in declaration_infos: + declarations_depsets.append(dep.declarations) files_depsets.extend(declarations_depsets) # If this is a ts_declaration, add tsickle_externs to the outputs list to @@ -463,11 +443,17 @@ files_depsets.append(depset(tsickle_externs)) transitive_es6_sources_sets = [es6_sources] - for dep in deps: + for dep in getattr(ctx.attr, "deps", []): if hasattr(dep, "typescript"): transitive_es6_sources_sets += [dep.typescript.transitive_es6_sources] transitive_es6_sources = depset(transitive = transitive_es6_sources_sets) + declarations_provider = DeclarationInfo( + declarations = depset(transitive = declarations_depsets), + transitive_declarations = transitive_decls, + type_blacklisted_declarations = type_blacklisted_declarations, + ) + return { "providers": [ DefaultInfo( @@ -484,12 +470,11 @@ es5_sources = es5_sources, es6_sources = es6_sources, ), - # TODO(martinprobst): Prune transitive deps, see go/dtspruning - DeclarationInfo( - declarations = depset(transitive = declarations_depsets), - transitive_declarations = transitive_decls, - ), + declarations_provider, ], + # Also expose the DeclarationInfo as a named provider so that aspect implementations can reference it + # Otherwise they would be forced to reference it by a numeric index out of the "providers" list above. + "declarations": declarations_provider, "instrumented_files": { "dependency_attributes": ["deps", "runtime_deps"], "extensions": ["ts"], @@ -502,21 +487,16 @@ # Expose the tags so that a Skylark aspect can access them. "tags": ctx.attr.tags if hasattr(ctx.attr, "tags") else ctx.rule.attr.tags, "typescript": { - # TODO(b/139705078): remove when consumers migrated to DeclarationInfo - "declarations": depset(transitive = declarations_depsets), "devmode_manifest": devmode_manifest, "es5_sources": es5_sources, "es6_sources": es6_sources, "replay_params": replay_params, - # TODO(b/139705078): remove when consumers migrated to DeclarationInfo - "transitive_declarations": transitive_decls, "transitive_es6_sources": transitive_es6_sources, "tsickle_externs": tsickle_externs, - "type_blacklisted_declarations": type_blacklisted_declarations, }, } -def ts_providers_dict_to_struct(d, preserve_js_struct_field = False): +def ts_providers_dict_to_struct(d): """ Converts a dict to a struct, recursing into a single level of nested dicts. This allows users of compile_ts to modify or augment the returned dict before @@ -524,17 +504,30 @@ Args: d: the dict to convert - preserve_js_struct_field: whether to preserve the js provider as a "js" field. - Please only set this to True if you are using this struct in another provider. - e.g. MyProvider(some_field = ts_providers_dict_to_struct(d, preserve_js_struct_field = True)) - *Do not use* if returning the struct from a rule. Returns: An immutable struct created from the input dict """ + # These keys are present in the dict so that aspects can reference them, + # however they should not be output as legacy providers since we have modern + # symbol-typed providers for them. + js_provider = d.pop("js", None) + declarations_provider = d.pop("declarations", None) + + # Promote the "js" string-typed provider to a modern provider + if js_provider: + # Create a new providers list rather than modify the existing list + d["providers"] = d.get("providers", []) + [js_provider] for key, value in d.items(): if key != "output_groups" and type(value) == type({}): d[key] = struct(**value) - return struct(**d) + result = struct(**d) + + # Restore the elements we removed, to avoid side-effect of mutating the argument + if js_provider: + d["js"] = js_provider + if declarations_provider: + d["declarations"] = declarations_provider + return result