blob: 9a684397941c0ce96355c62c504e5a3e9ff720a6 [file] [log] [blame]
# 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"]))