mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
Rename the npm2 workspace to npm and use the latest version of devinfra which drops rules_nodejs PR Close #63093
203 lines
7 KiB
Python
203 lines
7 KiB
Python
"""Bazel rules and macros for running tsec over a ng_module or ts_library."""
|
|
|
|
load("@aspect_rules_js//js:providers.bzl", "JsInfo")
|
|
load("@aspect_rules_ts//ts:defs.bzl", "TsConfigInfo")
|
|
load("@bazel_skylib//lib:new_sets.bzl", "sets")
|
|
load("@npm//:tsec/package_json.bzl", tsec = "bin")
|
|
|
|
TsecTargetInfo = provider(
|
|
"Attributes required for tsec_test to generate tsconfig.json",
|
|
fields = ["srcs", "deps"],
|
|
)
|
|
|
|
def _capture_tsec_attrs_aspect_impl(_, ctx):
|
|
"""Capture certain srcs and deps from of `the ts_project` into a TsecTargetInfo provider."""
|
|
deps = []
|
|
for dep in getattr(ctx.rule.attr, "deps", []):
|
|
if JsInfo in dep:
|
|
deps.append(dep[JsInfo].transitive_types)
|
|
deps.append(dep[JsInfo].types)
|
|
|
|
srcs = getattr(ctx.rule.attr, "srcs", [])
|
|
|
|
return [
|
|
TsecTargetInfo(
|
|
srcs = srcs,
|
|
deps = depset(transitive = deps),
|
|
),
|
|
]
|
|
|
|
_capture_tsec_attrs_aspect = aspect(
|
|
implementation = _capture_tsec_attrs_aspect_impl,
|
|
attr_aspects = ["deps"],
|
|
)
|
|
|
|
def _tsec_config_impl(ctx):
|
|
deps = []
|
|
use_runfiles = ctx.attr.use_runfiles
|
|
base_tsconfig = None
|
|
tsconfig = ctx.attr.tsconfig
|
|
|
|
# Gather all base tsconfig files and the exemption list.
|
|
if tsconfig:
|
|
if TsConfigInfo not in tsconfig:
|
|
fail("`tsconfig` must be a ts_config target")
|
|
deps.extend(tsconfig[TsConfigInfo].deps.to_list())
|
|
base_tsconfig = tsconfig.files.to_list()[0]
|
|
|
|
deps.append(ctx.file._exemption)
|
|
|
|
out = ctx.outputs.out
|
|
ts_target = ctx.attr.target
|
|
|
|
######################################################################################
|
|
### GENERATE A TSCONFIG DICT TO ENABLE THE TSEC PLUGIN AND LIST ALL AVAILABLE FILE ###
|
|
######################################################################################
|
|
tsconfig = {}
|
|
pkg_base_dir = "/".join([".."] * len(ts_target.label.package.split("/")))
|
|
|
|
# With runfiles, the location of the source code is the same as the generated .d.ts, i.e., the workspace root.
|
|
# Without runfiles, the source code remains in the Bazel package folder in the source tree, so `src_base_dir`
|
|
# has to go to the execroot first and further back to the source tree.
|
|
src_base_dir = pkg_base_dir if use_runfiles else "/".join([".."] * len(ctx.bin_dir.path.split("/")) + [pkg_base_dir])
|
|
|
|
# The relative path to the base directory
|
|
base = src_base_dir if base_tsconfig.is_source else pkg_base_dir
|
|
|
|
# Extend the provided tsconfig file.
|
|
tsconfig["extends"] = base + "/" + base_tsconfig.short_path
|
|
|
|
# Create the compilerOption field for the tsconfig file.
|
|
compiler_options = {
|
|
"noEmit": True,
|
|
"plugins": [{
|
|
"name": "tsec",
|
|
"exemptionConfig": base + "/" + ctx.file._exemption.short_path,
|
|
}],
|
|
}
|
|
|
|
if not use_runfiles:
|
|
compiler_options["rootDirs"] = [src_base_dir, src_base_dir + "/" + ctx.bin_dir.path]
|
|
|
|
tsconfig["compilerOptions"] = compiler_options
|
|
|
|
# Generate the list of files available for the tsc execution.
|
|
files = sets.make()
|
|
|
|
# Helper function for adding a file to the `files` set.
|
|
def add_file_to_set(file):
|
|
if file.extension not in ["ts", "tsx"]:
|
|
return
|
|
base = src_base_dir if file.is_source else pkg_base_dir
|
|
sets.insert(files, base + "/" + file.short_path)
|
|
|
|
# Add all of the source typescript files from the target to the files list.
|
|
for src in ts_target[TsecTargetInfo].srcs:
|
|
for f in src.files.to_list():
|
|
add_file_to_set(f)
|
|
|
|
# Add all of the source typescript files from the target's deps to the files list.
|
|
for f in ts_target[TsecTargetInfo].deps.to_list():
|
|
add_file_to_set(f)
|
|
|
|
# Add the list of files to the tsconfig
|
|
tsconfig["files"] = sets.to_list(files)
|
|
######################################################################################
|
|
|
|
# Write the generated tsconfig file and place it in the output
|
|
ctx.actions.write(output = out, content = json.encode(tsconfig))
|
|
deps.append(out)
|
|
|
|
return [DefaultInfo(files = depset(deps))]
|
|
|
|
_tsec_config = rule(
|
|
implementation = _tsec_config_impl,
|
|
attrs = {
|
|
"target": attr.label(
|
|
mandatory = True,
|
|
aspects = [_capture_tsec_attrs_aspect],
|
|
doc = """The ts_library target for which the tsconfig is generated.""",
|
|
),
|
|
"tsconfig": attr.label(
|
|
doc = """Base tsconfig to extend from.""",
|
|
),
|
|
"use_runfiles": attr.bool(mandatory = True),
|
|
"out": attr.output(mandatory = True),
|
|
"_exemption": attr.label(
|
|
default = "//packages:tsec_exemption",
|
|
allow_single_file = True,
|
|
),
|
|
},
|
|
doc = """Generate the tsconfig.json for a tsec_test. """,
|
|
)
|
|
|
|
def _all_transitive_deps_impl(ctx):
|
|
if TsecTargetInfo not in ctx.attr.target:
|
|
fail("`target` must be a ts_project target")
|
|
|
|
# A list of all files that are transitively depended on by the target.
|
|
files = []
|
|
for s in ctx.attr.target[TsecTargetInfo].srcs:
|
|
files.extend(s.files.to_list())
|
|
|
|
for file in ctx.attr.target[TsecTargetInfo].deps.to_list():
|
|
# Skip all files that are external as they cannot be copied into the output_tree
|
|
if file.owner.workspace_root.startswith("external"):
|
|
continue
|
|
files.append(file)
|
|
|
|
return [DefaultInfo(files = depset(files))]
|
|
|
|
_all_transitive_deps = rule(
|
|
implementation = _all_transitive_deps_impl,
|
|
attrs = {"target": attr.label(aspects = [_capture_tsec_attrs_aspect])},
|
|
doc = """Expand all transitive dependencies needed to run `_tsec_test`.""",
|
|
)
|
|
|
|
def tsec_test(name, target, tsconfig, use_runfiles_on_windows = True):
|
|
"""Run tsec over a ts_library or ng_module target to check its compatibility with Trusted Types.
|
|
|
|
This rule DOES NOT check transitive dependencies.
|
|
Args:
|
|
name: name of the tsec test
|
|
target: the ts_library or ng_module target to be checked
|
|
tsconfig: the ts_config target used for configuring tsec
|
|
use_runfiles_on_windows: whether to force using runfiles on Windows
|
|
"""
|
|
tsec_tsconfig_name = "%s_tsconfig" % name
|
|
generated_tsconfig = "%s_tsconfig.json" % name
|
|
|
|
use_runfiles = use_runfiles_on_windows or select({
|
|
"@platforms//os:windows": False,
|
|
"//conditions:default": True,
|
|
})
|
|
|
|
_tsec_config(
|
|
name = tsec_tsconfig_name,
|
|
testonly = True,
|
|
tags = ["tsec"],
|
|
target = target,
|
|
tsconfig = tsconfig,
|
|
use_runfiles = use_runfiles,
|
|
out = generated_tsconfig,
|
|
)
|
|
|
|
all_transitive_deps_name = "%s_deps" % name
|
|
_all_transitive_deps(
|
|
name = all_transitive_deps_name,
|
|
testonly = True,
|
|
tags = ["tsec"],
|
|
target = target,
|
|
)
|
|
|
|
tsec.tsec_test(
|
|
name = name,
|
|
data = [
|
|
tsec_tsconfig_name,
|
|
target,
|
|
all_transitive_deps_name,
|
|
generated_tsconfig,
|
|
],
|
|
tags = ["tsec"],
|
|
fixed_args = ["-p", "$(rootpath %s)" % generated_tsconfig],
|
|
)
|