| # Copyright 2017 The Bazel Authors. All rights reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """Used for compilation by the different implementations of build_defs.bzl. |
| """ |
| |
| # TODO(plf): Enforce this at analysis time. |
| def assert_js_or_typescript_deps(ctx): |
| for dep in ctx.attr.deps: |
| if not hasattr(dep, "typescript") and not hasattr(dep, "js"): |
| fail( |
| ("%s is neither a TypeScript nor a JS producing rule." % dep.label) + |
| "\nDependencies must be ts_library, ts_declaration, or " + |
| # TODO(plf): Leaving this here for now, but this message does not |
| # make sense in opensource. |
| "JavaScript library rules (js_library, pinto_library, etc, but " + |
| "also proto_library, soy_js).\n") |
| |
| def _collect_transitive_dts(ctx): |
| all_deps_declarations = set() |
| type_blacklisted_declarations = set() |
| for extra in ctx.files._additional_d_ts: |
| all_deps_declarations += set([extra]) |
| for dep in ctx.attr.deps: |
| if hasattr(dep, "typescript"): |
| all_deps_declarations += dep.typescript.transitive_declarations |
| type_blacklisted_declarations += ( |
| dep.typescript.type_blacklisted_declarations) |
| return struct( |
| transitive_declarations=list(all_deps_declarations), |
| type_blacklisted_declarations=list(type_blacklisted_declarations) |
| ) |
| |
| def _outputs(ctx, label, input_file): |
| """Returns closure js, devmode js, and .d.ts output files for |input_file|. |
| |
| Args: |
| ctx: ctx. |
| label: Label. package label. |
| input_file: File. the input_file |
| Returns: |
| A three-tuple of files (.closure.js, .js, .d.ts). |
| """ |
| dot = input_file.short_path.rfind(".") |
| beginning = len(label.package) |
| if label.package: |
| beginning += 1 |
| basename = input_file.short_path[beginning:dot] |
| return (ctx.new_file(basename + ".closure.js"), |
| ctx.new_file(basename + ".js"), |
| ctx.new_file(basename + ".d.ts")) |
| |
| def compile_ts(ctx, |
| is_library, |
| extra_dts_files=[], |
| compile_action=None, |
| devmode_compile_action=None, |
| jsx_factory=None, |
| tsc_wrapped_tsconfig=None): |
| """Creates actions to compile TypeScript code. |
| |
| This rule is shared between ts_library and ts_declaration. |
| |
| Args: |
| ctx: ctx. |
| is_library: boolean. False if only compiling .dts files. |
| extra_dts_files: list. Additional dts files to pass for compilation, |
| not included in the transitive closure of declarations. |
| compile_action: function. Creates the compilation action. |
| devmode_compile_action: function. Creates the compilation action |
| for devmode. |
| jsx_factory: optional string. Enables overriding jsx pragma. |
| tsc_wrapped_tsconfig: function. |
| Returns: |
| struct that will be returned by the rule implementation. |
| """ |
| assert_js_or_typescript_deps(ctx) |
| |
| ### Collect srcs and outputs. |
| srcs = ctx.files.srcs |
| transpiled_closure_js = [] |
| transpiled_devmode_js = [] |
| src_declarations = [] # d.ts found in inputs. |
| gen_declarations = [] # d.ts generated by the TypeScript compiler. |
| tsickle_externs = [] # externs.js generated by tsickle, if any. |
| has_sources = False |
| |
| # Compile the sources, if any. (For a ts_declaration rule this will |
| # type-check the d.ts sources and potentially generate externs.) |
| for src in ctx.attr.srcs: |
| # 'x/y.ts' ==> 'x/y.js' |
| if src.label.package != ctx.label.package: |
| # Sources can be in sub-folders, but not in sub-packages. |
| fail("Sources must be in the same package as the ts_library rule, " + |
| "but %s is not in %s" % (src.label, ctx.label.package), "srcs") |
| |
| for f in src.files: |
| has_sources = True |
| if is_library: |
| if f.path.endswith(".d.ts"): |
| fail("srcs must not contain any type declarations (.d.ts files), " + |
| "but %s contains %s" % (src.label, f.short_path), "srcs") |
| outs = _outputs(ctx, src.label, f) |
| transpiled_closure_js += [outs[0]] |
| transpiled_devmode_js += [outs[1]] |
| gen_declarations += [outs[2]] |
| else: |
| if not f.path.endswith(".d.ts"): |
| fail("srcs must contain only type declarations (.d.ts files), " + |
| "but %s contains %s" % (src.label, f.short_path), "srcs") |
| src_declarations += [f] |
| |
| if has_sources and ctx.attr.runtime != "nodejs": |
| # Note: setting this variable controls whether sickle is run at all. |
| tsickle_externs = [ctx.new_file(ctx.label.name + ".externs.js")] |
| |
| transitive_dts = _collect_transitive_dts(ctx) |
| input_declarations = transitive_dts.transitive_declarations + src_declarations |
| type_blacklisted_declarations = transitive_dts.type_blacklisted_declarations |
| if not is_library and not ctx.attr.generate_externs: |
| type_blacklisted_declarations += ctx.files.srcs |
| |
| # A manifest listing the order of this rule's *.ts files (non-transitive) |
| # Only generated if the rule has any sources. |
| devmode_manifest = None |
| |
| if has_sources: |
| compilation_inputs = input_declarations + extra_dts_files + srcs |
| tsickle_externs_path = tsickle_externs[0] if tsickle_externs else None |
| |
| # Calculate allowed dependencies for strict deps enforcement. |
| allowed_deps = srcs # A target's sources may depend on each other. |
| for dep in ctx.attr.deps: |
| if hasattr(dep, "typescript"): |
| allowed_deps += dep.typescript.declarations |
| allowed_deps += extra_dts_files |
| |
| tsconfig_json_es6 = tsc_wrapped_tsconfig( |
| ctx, |
| compilation_inputs, |
| srcs, |
| jsx_factory=jsx_factory, |
| tsickle_externs=tsickle_externs_path, |
| type_blacklisted_declarations=type_blacklisted_declarations, |
| allowed_deps=allowed_deps) |
| |
| inputs = compilation_inputs + [tsconfig_json_es6] |
| outputs = transpiled_closure_js + tsickle_externs |
| compile_action(ctx, inputs, outputs, tsconfig_json_es6.path) |
| |
| devmode_manifest = ctx.new_file(ctx.label.name + ".es5.MF") |
| tsconfig_json_es5 = tsc_wrapped_tsconfig( |
| ctx, |
| compilation_inputs, |
| srcs, |
| jsx_factory=jsx_factory, |
| devmode_manifest=devmode_manifest, |
| allowed_deps=allowed_deps) |
| inputs = compilation_inputs + [tsconfig_json_es5] |
| outputs = ( |
| transpiled_devmode_js + gen_declarations + [devmode_manifest]) |
| devmode_compile_action(ctx, inputs, outputs, tsconfig_json_es5.path) |
| |
| # TODO(martinprobst): Merge the generated .d.ts files, and enforce strict |
| # deps (do not re-export transitive types from the transitive closure). |
| transitive_decls = input_declarations + gen_declarations |
| |
| if is_library: |
| es6_sources = set(transpiled_closure_js + tsickle_externs) |
| es5_sources = set(transpiled_devmode_js) |
| else: |
| es6_sources = set(tsickle_externs) |
| es5_sources = set(tsickle_externs) |
| devmode_manifest = None |
| |
| # Downstream rules see the .d.ts files produced or declared by this rule |
| declarations = gen_declarations + src_declarations |
| |
| if not srcs: |
| # Re-export sources from deps. |
| # TODO(b/30018387): introduce an "exports" attribute. |
| for dep in ctx.attr.deps: |
| if hasattr(dep, "typescript"): |
| declarations += dep.typescript.declarations |
| |
| return struct( |
| files=set(declarations), |
| runfiles=ctx.runfiles( |
| # Note: don't include files=... here, or they will *always* be built |
| # by any dependent rule, regardless of whether it needs them. |
| # But these attributes are needed to pass along any input runfiles: |
| collect_default=True, |
| collect_data=True, |
| ), |
| # TODO(martinprobst): Prune transitive deps, only re-export what's needed. |
| typescript=struct( |
| declarations=declarations, |
| transitive_declarations=transitive_decls, |
| es6_sources=es6_sources, |
| es5_sources=es5_sources, |
| devmode_manifest=devmode_manifest, |
| js_typings=ctx.outputs._js_typings, |
| type_blacklisted_declarations=type_blacklisted_declarations, |
| tsickle_externs=tsickle_externs, |
| ), |
| # Expose the tags so that a Skylark aspect can access them. |
| tags=ctx.attr.tags, |
| instrumented_files=struct( |
| extensions=["ts"], |
| source_attributes=["srcs"], |
| dependency_attributes=["deps", "runtime_deps"])) |