blob: d7a5f5008e593f774dd2ea8f0df58232328f4aba [file]
"""`js_library` helper functions.
"""
load("@aspect_bazel_lib//lib:copy_to_bin.bzl", "copy_file_to_bin_action")
load("//npm:providers.bzl", "NpmPackageStoreInfo")
load(":js_info.bzl", "JsInfo")
DOWNSTREAM_LINKED_NPM_DEPS_DOCSTRING = """If this list contains linked npm packages, npm package store targets or other targets that provide
`JsInfo`, `NpmPackageStoreInfo` providers are gathered from `JsInfo`. This is done directly from
the `npm_package_store_deps` field of these. For linked npm package targets, the underlying
`npm_package_store` target(s) that back the links is used. Gathered `NpmPackageStoreInfo`
providers are propagated to the direct dependencies of downstream linked `npm_package` targets.
NB: Linked npm package targets that are "dev" dependencies do not forward their underlying
`npm_package_store` target(s) through `npm_package_store_deps` and will therefore not be
propagated to the direct dependencies of downstream linked `npm_package` targets. npm packages
that come in from `npm_translate_lock` are considered "dev" dependencies if they are have
`dev: true` set in the pnpm lock file. This should be all packages that are only listed as
"devDependencies" in all `package.json` files within the pnpm workspace. This behavior is
intentional to mimic how `devDependencies` work in published npm packages.
"""
# This attribute is exposed in //js:libs.bzl so that downstream build rules can use it
JS_LIBRARY_DATA_ATTR = attr.label_list(
doc = """Runtime dependencies to include in binaries/tests that depend on this target.
The transitive npm dependencies, transitive sources, default outputs and runfiles of targets in the `data` attribute
are added to the runfiles of this target. They should appear in the '*.runfiles' area of any executable which has
a runtime dependency on this target.
{downstream_linked_npm_deps}
""".format(
downstream_linked_npm_deps = DOWNSTREAM_LINKED_NPM_DEPS_DOCSTRING,
),
allow_files = True,
)
def gather_transitive_sources(sources, targets):
"""Gathers transitive sources from a list of direct sources and targets
Args:
sources: list or depset of direct sources which should be included in `transitive_sources`
targets: list of targets to gather `transitive_sources` from `JsInfo`
Returns:
A depset of transitive sources
"""
if type(sources) == "list":
sources = depset(sources)
transitive = [
target[JsInfo].transitive_sources
for target in targets
if JsInfo in target and hasattr(target[JsInfo], "transitive_sources")
]
return depset([], transitive = [sources] + transitive)
def gather_transitive_declarations(declarations, targets):
"""Gathers transitive declarations from a list of direct declarations and targets
Args:
declarations: list or depset of direct sources which should be included in `transitive_declarations`
targets: list of targets to gather `transitive_declarations` from `JsInfo`
Returns:
A depset of transitive sources
"""
if type(declarations) == "list":
declarations = depset(declarations)
transitive = [
target[JsInfo].transitive_declarations
for target in targets
if JsInfo in target and hasattr(target[JsInfo], "transitive_declarations")
]
return depset([], transitive = [declarations] + transitive)
def gather_npm_linked_packages(srcs, deps):
"""Gathers npm linked packages from a list of srcs and deps targets
Args:
srcs: source targets; these typically come from the `srcs` and/or `data` attributes of a rule
deps: dep targets; these typically come from the `deps` attribute of a rule
Returns:
Depset of npm linked package files
"""
return depset([], transitive = [
target[JsInfo].npm_linked_packages
for target in srcs + deps
if JsInfo in target and hasattr(target[JsInfo], "npm_linked_packages")
])
def gather_npm_package_store_deps(targets):
"""Gathers NpmPackageStoreInfo providers from the list of targets
Args:
targets: the list of targets to gather from
Returns:
A depset of npm package stores gathered
"""
# npm_package_store_deps
npm_package_store_deps = [
target[JsInfo].npm_package_store_deps
for target in targets
if JsInfo in target
]
return depset([], transitive = npm_package_store_deps)
def copy_js_file_to_bin_action(ctx, file):
if ctx.label.workspace_name != file.owner.workspace_name or ctx.label.package != file.owner.package:
msg = """
Expected to find source file {file_basename} in '{this_package}', but instead it is in '{file_package}'.
All source and data files that are not in the Bazel output tree must be in the same package as the
target so that they can be copied to the output tree in an action.
See https://docs.aspect.build/rules/aspect_rules_js/docs/#javascript for more context on why this is required.
Either move {file_basename} to '{this_package}', or add a 'js_library'
target in {file_basename}'s package and add that target to the deps of {this_target}:
buildozer 'new_load @aspect_rules_js//js:defs.bzl js_library' {file_package}:__pkg__
buildozer 'new js_library {new_target_name}' {file_package}:__pkg__
buildozer 'add srcs {file_basename}' {file_package}:{new_target_name}
buildozer 'add visibility {this_package}:__pkg__' {file_package}:{new_target_name}
buildozer 'remove srcs {file_package}:{file_basename}' {this_target}
buildozer 'add srcs {file_package}:{new_target_name}' {this_target}
For exceptional cases where copying is not possible or not suitable for an input file such as
a file in an external repository, exceptions can be added to 'no_copy_to_bin'. In most cases,
this option is not needed.
""".format(
file_basename = file.basename,
file_package = "%s//%s" % (file.owner.workspace_name, file.owner.package),
new_target_name = file.basename.replace(".", "_"),
this_package = "%s//%s" % (ctx.label.workspace_name, ctx.label.package),
this_target = ctx.label,
)
fail(msg)
return copy_file_to_bin_action(ctx, file)
def gather_runfiles(
ctx,
sources,
data,
deps,
data_files = [],
copy_data_files_to_bin = False,
no_copy_to_bin = [],
include_sources = True,
include_transitive_sources = True,
include_declarations = False,
include_transitive_declarations = False,
include_npm_linked_packages = True):
"""Creates a runfiles object containing files in `sources`, default outputs from `data` and transitive runfiles from `data` & `deps`.
As a defense in depth against `data` & `deps` targets not supplying all required runfiles, also
gathers the transitive sources & transitive npm linked packages from the `JsInfo` &
`NpmPackageStoreInfo` providers of `data` & `deps` targets.
See https://bazel.build/extending/rules#runfiles for more info on providing runfiles in build rules.
Args:
ctx: the rule context
sources: list or depset of files which should be included in runfiles
deps: list of dependency targets; only transitive runfiles are gather from these targets
data: list of data targets; default outputs and transitive runfiles are gather from these targets
See https://bazel.build/reference/be/common-definitions#typical.data and
https://bazel.build/concepts/dependencies#data-dependencies for more info and guidance
on common usage of the `data` attribute in build rules.
data_files: a list of data files which should be included in runfiles
Data files that are source files are copied to the Bazel output tree when
`copy_data_files_to_bin` is set to `True`.
copy_data_files_to_bin: When True, `data` files that are source files and are copied to the
Bazel output tree before being passed to returned runfiles.
no_copy_to_bin: List of files to not copy to the Bazel output tree when `copy_data_to_bin` is True.
This is useful for exceptional cases where a `copy_data_files_to_bin` is not possible or not suitable for an input
file such as a file in an external repository. In most cases, this option is not needed.
See `copy_data_files_to_bin` docstring for more info.
include_sources: see js_info_files documentation
include_transitive_sources: see js_info_files documentation
include_declarations: see js_info_files documentation
include_transitive_declarations: see js_info_files documentation
include_npm_linked_packages: see js_info_files documentation
Returns:
A [runfiles](https://bazel.build/rules/lib/runfiles) object created with [ctx.runfiles](https://bazel.build/rules/lib/ctx#runfiles).
"""
# Includes sources
if type(sources) == "list":
sources = depset(sources)
transitive_files_depsets = [sources]
# Gather the default outputs of data targets
transitive_files_depsets.extend([
target[DefaultInfo].files
for target in data
])
# Gather files from JsInfo providers of data & deps
transitive_files_depsets.append(gather_files_from_js_providers(
targets = data + deps,
include_sources = include_sources,
include_transitive_sources = include_transitive_sources,
include_declarations = include_declarations,
include_transitive_declarations = include_transitive_declarations,
include_npm_linked_packages = include_npm_linked_packages,
))
files_runfiles = []
for d in data_files:
if copy_data_files_to_bin and d.is_source and d not in no_copy_to_bin:
files_runfiles.append(copy_js_file_to_bin_action(ctx, d))
else:
files_runfiles.append(d)
if len(files_runfiles) > 0:
transitive_files_depsets.append(depset(files_runfiles))
# Merge the above with the transitive runfiles of data & deps.
return ctx.runfiles(
files = files_runfiles,
transitive_files = depset(transitive = transitive_files_depsets),
).merge_all([
target[DefaultInfo].default_runfiles
for target in data + deps
])
LOG_LEVELS = {
"fatal": 1,
"error": 2,
"warn": 3,
"info": 4,
"debug": 5,
}
def envs_for_log_level(log_level):
"""Returns a list environment variables to set for a given log level
Args:
log_level: The log level string value
Returns:
A list of environment variables to set to turn on the js_binary runtime
logs for the given log level. Typically, they are each set to "1".
"""
if log_level not in LOG_LEVELS.keys():
msg = "log_level must be one of {} but got {}".format(LOG_LEVELS.keys(), log_level)
fail(msg)
envs = []
log_level_numeric = LOG_LEVELS[log_level]
if log_level_numeric >= LOG_LEVELS["fatal"]:
envs.append("JS_BINARY__LOG_FATAL")
if log_level_numeric >= LOG_LEVELS["error"]:
envs.append("JS_BINARY__LOG_ERROR")
if log_level_numeric >= LOG_LEVELS["warn"]:
envs.append("JS_BINARY__LOG_WARN")
if log_level_numeric >= LOG_LEVELS["info"]:
envs.append("JS_BINARY__LOG_INFO")
if log_level_numeric >= LOG_LEVELS["debug"]:
envs.append("JS_BINARY__LOG_DEBUG")
return envs
def gather_files_from_js_providers(
targets,
include_sources,
include_transitive_sources,
include_declarations,
include_transitive_declarations,
include_npm_linked_packages):
"""Gathers files from JsInfo and NpmPackageStoreInfo providers.
Args:
targets: list of target to gather from
include_sources: see js_info_files documentation
include_transitive_sources: see js_info_files documentation
include_declarations: see js_info_files documentation
include_transitive_declarations: see js_info_files documentation
include_npm_linked_packages: see js_info_files documentation
Returns:
A depset of files
"""
files_depsets = []
if include_sources:
files_depsets = [
target[JsInfo].sources
for target in targets
if JsInfo in target and hasattr(target[JsInfo], "sources")
]
if include_transitive_sources:
files_depsets.extend([
target[JsInfo].transitive_sources
for target in targets
if JsInfo in target and hasattr(target[JsInfo], "transitive_sources")
])
if include_declarations:
files_depsets.extend([
target[JsInfo].declarations
for target in targets
if JsInfo in target and hasattr(target[JsInfo], "declarations")
])
if include_transitive_declarations:
files_depsets.extend([
target[JsInfo].transitive_declarations
for target in targets
if JsInfo in target and hasattr(target[JsInfo], "transitive_declarations")
])
if include_npm_linked_packages:
files_depsets.extend([
target[JsInfo].npm_linked_packages
for target in targets
if JsInfo in target and hasattr(target[JsInfo], "npm_linked_packages")
])
files_depsets.extend([
target[NpmPackageStoreInfo].transitive_files
for target in targets
if NpmPackageStoreInfo in target and hasattr(target[NpmPackageStoreInfo], "transitive_files")
])
return depset([], transitive = files_depsets)