blob: a4b67670d2716783a1a24009df074381a8b2d2b3 [file]
"ts_project rule"
load("@bazel_skylib//lib:dicts.bzl", "dicts")
load("@rules_nodejs//nodejs/private/providers:declaration_info.bzl", "DeclarationInfo", "declaration_info")
load("@rules_nodejs//nodejs/private/providers:js_providers.bzl", "js_module_info")
load(":ts_lib.bzl", "COMPILER_OPTION_ATTRS", "OUTPUT_ATTRS", "STD_ATTRS", "ValidOptionsInfo", _lib = "lib")
load(":ts_config.bzl", "TsConfigInfo")
load(":ts_validate_options.bzl", _validate_lib = "lib")
def _ts_project_impl(ctx, run_action = None, ExternalNpmPackageInfo = None):
"""Creates the action which spawns `tsc`.
This function has two extra arguments that are particular to how it's called
within build_bazel_rules_nodejs and @bazel/typescript npm package.
Other TS rule implementations wouldn't need to pass these:
Args:
ctx: starlark rule execution context
run_action: used with the build_bazel_rules_nodejs linker, by default we use ctx.actions.run
ExternalNpmPackageInfo: a provider symbol specific to the build_bazel_rules_nodejs linker
Returns:
list of providers
"""
srcs = [_lib.relative_to_package(src.path, ctx) for src in ctx.files.srcs]
# Recalculate outputs inside the rule implementation.
# The outs are first calculated in the macro in order to try to predetermine outputs so they can be declared as
# outputs on the rule. This provides the benefit of being able to reference an output file with a label.
# However, it is not possible to evaluate files in outputs of other rules such as filegroup, therefore the outs are
# recalculated here.
typings_out_dir = ctx.attr.declaration_dir or ctx.attr.out_dir
js_outs = _lib.declare_outputs(ctx, [] if not ctx.attr.transpile else _lib.calculate_js_outs(srcs, ctx.attr.out_dir, ctx.attr.root_dir, ctx.attr.allow_js, ctx.attr.preserve_jsx, ctx.attr.emit_declaration_only))
map_outs = _lib.declare_outputs(ctx, [] if not ctx.attr.transpile else _lib.calculate_map_outs(srcs, ctx.attr.out_dir, ctx.attr.root_dir, ctx.attr.source_map, ctx.attr.preserve_jsx, ctx.attr.emit_declaration_only))
typings_outs = _lib.declare_outputs(ctx, _lib.calculate_typings_outs(srcs, typings_out_dir, ctx.attr.root_dir, ctx.attr.declaration, ctx.attr.composite, ctx.attr.allow_js))
typing_maps_outs = _lib.declare_outputs(ctx, _lib.calculate_typing_maps_outs(srcs, typings_out_dir, ctx.attr.root_dir, ctx.attr.declaration_map, ctx.attr.allow_js))
arguments = ctx.actions.args()
execution_requirements = {}
progress_prefix = "Compiling TypeScript project"
if ctx.attr.supports_workers:
# Set to use a multiline param-file for worker mode
arguments.use_param_file("@%s", use_always = True)
arguments.set_param_file_format("multiline")
execution_requirements["supports-workers"] = "1"
execution_requirements["worker-key-mnemonic"] = "TsProject"
progress_prefix = "Compiling TypeScript project (worker mode)"
# Add user specified arguments *before* rule supplied arguments
arguments.add_all(ctx.attr.args)
arguments.add_all([
"--project",
ctx.file.tsconfig.path,
"--outDir",
_lib.join(ctx.bin_dir.path, ctx.label.workspace_root, ctx.label.package, ctx.attr.out_dir),
"--rootDir",
_lib.calculate_root_dir(ctx),
])
if len(typings_outs) > 0:
declaration_dir = ctx.attr.declaration_dir if ctx.attr.declaration_dir else ctx.attr.out_dir
arguments.add_all([
"--declarationDir",
_lib.join(ctx.bin_dir.path, ctx.label.workspace_root, ctx.label.package, declaration_dir),
])
# When users report problems, we can ask them to re-build with
# --define=VERBOSE_LOGS=1
# so anything that's useful to diagnose rule failures belongs here
if "VERBOSE_LOGS" in ctx.var.keys():
arguments.add_all([
# What files were in the ts.Program
"--listFiles",
# Did tsc write all outputs to the place we expect to find them?
"--listEmittedFiles",
# Why did module resolution fail?
"--traceResolution",
# Why was the build slow?
"--diagnostics",
"--extendedDiagnostics",
])
transitive_inputs = []
inputs = ctx.files.srcs[:]
for dep in ctx.attr.deps:
if TsConfigInfo in dep:
transitive_inputs.append(dep[TsConfigInfo].deps)
if ExternalNpmPackageInfo != None and ExternalNpmPackageInfo in dep:
# TODO: we could maybe filter these to be tsconfig.json or *.d.ts only
# we don't expect tsc wants to read any other files from npm packages.
transitive_inputs.append(dep[ExternalNpmPackageInfo].sources)
if DeclarationInfo in dep:
transitive_inputs.append(dep[DeclarationInfo].transitive_declarations)
if ValidOptionsInfo in dep:
transitive_inputs.append(depset([dep[ValidOptionsInfo].marker]))
# Gather TsConfig info from both the direct (tsconfig) and indirect (extends) attribute
tsconfig_inputs = _validate_lib.tsconfig_inputs(ctx)
transitive_inputs.append(tsconfig_inputs)
# We do not try to predeclare json_outs, because their output locations generally conflict with their path in the source tree.
# (The exception is when out_dir is used, then the .json output is a different path than the input.)
# However tsc will copy .json srcs to the output tree so we want to declare these outputs to include along with .js Default outs
# NB: We don't have emit_declaration_only setting here, so use presence of any JS outputs as an equivalent.
# tsc will only produce .json if it also produces .js
if len(js_outs):
pkg_len = len(ctx.label.package) + 1 if len(ctx.label.package) else 0
rootdir_replace_pattern = ctx.attr.root_dir + "/" if ctx.attr.root_dir else ""
json_outs = _lib.declare_outputs(ctx, [
_lib.join(ctx.attr.out_dir, src.short_path[pkg_len:].replace(rootdir_replace_pattern, ""))
for src in ctx.files.srcs
if src.basename.endswith(".json") and src.is_source
])
else:
json_outs = []
outputs = json_outs + js_outs + map_outs + typings_outs + typing_maps_outs
if ctx.outputs.buildinfo_out:
arguments.add_all([
"--tsBuildInfoFile",
ctx.outputs.buildinfo_out.path,
])
outputs.append(ctx.outputs.buildinfo_out)
runtime_outputs = json_outs + js_outs + map_outs
typings_outputs = typings_outs + typing_maps_outs + [s for s in ctx.files.srcs if s.path.endswith(".d.ts")]
if not js_outs and not typings_outputs and not ctx.attr.deps:
label = "//{}:{}".format(ctx.label.package, ctx.label.name)
if ctx.attr.transpile:
no_outs_msg = """ts_project target %s is configured to produce no outputs.
This might be because
- you configured it with `noEmit`
- the `srcs` are empty
""" % label
else:
no_outs_msg = "ts_project target %s with custom transpiler needs `declaration = True`." % label
fail(no_outs_msg + """
This is an error because Bazel does not run actions unless their outputs are needed for the requested targets to build.
""")
if ctx.attr.transpile:
default_outputs_depset = depset(runtime_outputs) if len(runtime_outputs) else depset(typings_outputs)
else:
# We must avoid tsc writing any JS files in this case, as tsc was only run for typings, and some other
# action will try to write the JS files. We must avoid collisions where two actions write the same file.
arguments.add("--emitDeclarationOnly")
# We don't produce any DefaultInfo outputs in this case, because we avoid running the tsc action
# unless the DeclarationInfo is requested.
default_outputs_depset = depset([])
if len(outputs) > 0:
run_action_kwargs = {
"inputs": depset(inputs, transitive = transitive_inputs),
"arguments": [arguments],
"outputs": outputs,
"mnemonic": "TsProject",
"execution_requirements": execution_requirements,
"progress_message": "%s %s [tsc -p %s]" % (
progress_prefix,
ctx.label,
ctx.file.tsconfig.short_path,
),
}
if run_action != None:
run_action(
ctx,
link_workspace_root = ctx.attr.link_workspace_root,
executable = "tsc",
**run_action_kwargs
)
else:
ctx.actions.run(
executable = ctx.executable.tsc,
**run_action_kwargs
)
providers = [
# DefaultInfo is what you see on the command-line for a built library,
# and determines what files are used by a simple non-provider-aware
# downstream library.
# Only the JavaScript outputs are intended for use in non-TS-aware
# dependents.
DefaultInfo(
files = default_outputs_depset,
runfiles = ctx.runfiles(
transitive_files = depset(ctx.files.data, transitive = [
default_outputs_depset,
]),
collect_default = True,
),
),
js_module_info(
sources = depset(runtime_outputs),
deps = ctx.attr.deps,
),
TsConfigInfo(deps = depset(transitive = [tsconfig_inputs] + [
dep[TsConfigInfo].deps
for dep in ctx.attr.deps
if TsConfigInfo in dep
])),
coverage_common.instrumented_files_info(
ctx,
source_attributes = ["srcs"],
dependency_attributes = ["deps"],
extensions = ["ts", "tsx"],
),
]
# Only provide DeclarationInfo if there are some typings.
# Improves error messaging if a ts_project is missing declaration = True
typings_in_deps = [d for d in ctx.attr.deps if DeclarationInfo in d]
if len(typings_outputs) or len(typings_in_deps):
providers.append(declaration_info(depset(typings_outputs), typings_in_deps))
providers.append(OutputGroupInfo(types = depset(typings_outputs)))
return providers
ts_project = struct(
implementation = _ts_project_impl,
attrs = dicts.add(COMPILER_OPTION_ATTRS, STD_ATTRS, OUTPUT_ATTRS),
)