| """Rules for running JavaScript programs under Bazel, as tools or with `bazel run` or `bazel test`. |
| |
| For example, this binary references the `acorn` npm package which was already linked |
| using an API like `npm_link_all_packages`. |
| |
| ```starlark |
| load("@aspect_rules_js//js:defs.bzl", "js_binary", "js_test") |
| |
| js_binary( |
| name = "bin", |
| # Reference the location where the acorn npm module was linked in the root Bazel package |
| data = ["//:node_modules/acorn"], |
| entry_point = "require_acorn.js", |
| ) |
| ``` |
| """ |
| |
| load("@aspect_bazel_lib//lib:copy_to_bin.bzl", "COPY_FILE_TO_BIN_TOOLCHAINS") |
| load("@aspect_bazel_lib//lib:directory_path.bzl", _LegacyDirectoryPathInfo = "DirectoryPathInfo") |
| load("@aspect_bazel_lib//lib:expand_make_vars.bzl", "expand_locations", "expand_variables") |
| load("@aspect_bazel_lib//lib:windows_utils.bzl", "create_windows_native_launcher_script") |
| load("@bazel_lib//lib:directory_path.bzl", "DirectoryPathInfo") |
| load(":bash.bzl", "BASH_INITIALIZE_RUNFILES") |
| load(":js_helpers.bzl", "LOG_LEVELS", "envs_for_log_level", "gather_runfiles") |
| |
| _DOC = """Execute a program in the Node.js runtime. |
| |
| The version of Node.js is determined by Bazel's toolchain selection. In the WORKSPACE you used |
| `nodejs_register_toolchains` to provide options to Bazel. Then Bazel selects from these options |
| based on the requested target platform. Use the |
| [`--toolchain_resolution_debug`](https://docs.bazel.build/versions/main/command-line-reference.html#flag--toolchain_resolution_debug) |
| Bazel option to see more detail about the selection. |
| |
| All [common binary attributes](https://bazel.build/reference/be/common-definitions#common-attributes-binaries) are supported |
| including `args` as the list of arguments passed Node.js. |
| |
| Node.js execution is performed by a shell script that sets environment variables and runs the Node.js binary with the `entry_point` script. |
| The shell script is located relative to the directory containing the `js_binary` at `\\{name\\}_/\\{name\\}` similar to other rulesets |
| such as rules_go. See [PR #1690](https://github.com/aspect-build/rules_js/pull/1690) for more information on this naming scheme. |
| |
| The following environment variables are made available to the Node.js runtime based on available Bazel [Make variables](https://bazel.build/reference/be/make-variables#predefined_variables): |
| |
| * JS_BINARY__BINDIR: the WORKSPACE-relative Bazel bin directory; equivalent to the `$(BINDIR)` Make variable of the `js_binary` target |
| * JS_BINARY__COMPILATION_MODE: One of `fastbuild`, `dbg`, or `opt` as set by [`--compilation_mode`](https://bazel.build/docs/user-manual#compilation-mode); equivalent to `$(COMPILATION_MODE)` Make variable of the `js_binary` target |
| * JS_BINARY__TARGET_CPU: the target cpu architecture; equivalent to `$(TARGET_CPU)` Make variable of the `js_binary` target |
| |
| The following environment variables are made available to the Node.js runtime based on the rule context: |
| |
| * JS_BINARY__BUILD_FILE_PATH: the WORKSPACE-relative path to the BUILD file of the Bazel target being run; equivalent to `ctx.build_file_path` of the `js_binary` target's rule context |
| * JS_BINARY__PACKAGE: the package of the Bazel target being run; equivalent to `ctx.label.package` of the `js_binary` target's rule context |
| * JS_BINARY__TARGET: the full label of the Bazel target being run; a stringified version of `ctx.label` of the `js_binary` target's rule context |
| * JS_BINARY__TARGET_NAME: the name of the Bazel target being run; equivalent to `ctx.label.name` of the `js_binary` target's rule context |
| * JS_BINARY__WORKSPACE: the Bazel workspace name; equivalent to `ctx.workspace_name` of the `js_binary` target's rule context |
| |
| The following environment variables are made available to the Node.js runtime based the runtime environment: |
| |
| * JS_BINARY__NODE_BINARY: the Node.js binary path run by the `js_binary` target |
| * JS_BINARY__NPM_BINARY: the npm binary path; this is available when [`include_npm`](https://docs.aspect.build/rules/aspect_rules_js/docs/js_binary#include_npm) is `True` on the `js_binary` target |
| * JS_BINARY__NODE_WRAPPER: the Node.js wrapper script used to run Node.js which is available as `node` on the `PATH` at runtime |
| * JS_BINARY__RUNFILES: the absolute path to the Bazel runfiles directory |
| * JS_BINARY__EXECROOT: the absolute path to the root of the execution root for the action; if in the sandbox, this path absolute path to the root of the execution root within the sandbox |
| """ |
| |
| _ATTRS = { |
| "chdir": attr.string( |
| doc = """Working directory to run the binary or test in, relative to the workspace. |
| |
| By default, `js_binary` runs in the root of the output tree. |
| |
| To run in the directory containing the `js_binary` use |
| |
| chdir = package_name() |
| |
| (or if you're in a macro, use `native.package_name()`) |
| |
| WARNING: this will affect other paths passed to the program, either as arguments or in configuration files, |
| which are workspace-relative. |
| |
| You may need `../../` segments to re-relativize such paths to the new working directory. |
| In a `BUILD` file you could do something like this to point to the output path: |
| |
| ```python |
| js_binary( |
| ... |
| chdir = package_name(), |
| # ../.. segments to re-relative paths from the chdir back to workspace; |
| # add an additional 3 segments to account for running js_binary running |
| # in the root of the output tree |
| args = ["/".join([".."] * len(package_name().split("/"))) + "$(rootpath //path/to/some:file)"], |
| ) |
| ```""", |
| ), |
| "data": attr.label_list( |
| allow_files = True, |
| doc = """Runtime dependencies of the program. |
| |
| The transitive closure of the `data` dependencies will be available in |
| the .runfiles folder for this binary/test. |
| |
| NB: `data` files are copied to the Bazel output tree before being passed |
| as inputs to runfiles. See `copy_data_to_bin` docstring for more info. |
| """, |
| ), |
| "entry_point": attr.label( |
| allow_files = True, |
| doc = """The main script which is evaluated by node.js. |
| |
| This is the module referenced by the `require.main` property in the runtime. |
| |
| This must be a target that provides a single file or a `DirectoryPathInfo` |
| from `@bazel_lib//lib::directory_path.bzl`. |
| |
| See https://github.com/bazel-contrib/bazel-lib/blob/main/docs/directory_path.md |
| for more info on creating a target that provides a `Info`. |
| """, |
| mandatory = True, |
| ), |
| "enable_runfiles": attr.bool( |
| mandatory = True, |
| doc = """Whether runfiles are enabled in the current build configuration. |
| |
| Typical usage of this rule is via a macro which automatically sets this |
| attribute based on a `config_setting` rule. |
| """, |
| ), |
| "env": attr.string_dict( |
| doc = """Environment variables of the action. |
| |
| Subject to [$(location)](https://bazel.build/reference/be/make-variables#predefined_label_variables) |
| and ["Make variable"](https://bazel.build/reference/be/make-variables) substitution if `expand_env` is set to True. |
| """, |
| ), |
| "expand_args": attr.bool( |
| default = True, |
| doc = """Enables [$(location)](https://bazel.build/reference/be/make-variables#predefined_label_variables) |
| and ["Make variable"](https://bazel.build/reference/be/make-variables) substitution for `fixed_args`. |
| |
| This comes at some analysis-time cost even for a set of args that does not have any expansions.""", |
| ), |
| "expand_env": attr.bool( |
| default = True, |
| doc = """Enables [$(location)](https://bazel.build/reference/be/make-variables#predefined_label_variables) |
| and ["Make variable"](https://bazel.build/reference/be/make-variables) substitution for `env`. |
| |
| This comes at some analysis-time cost even for a set of envs that does not have any expansions.""", |
| ), |
| "fixed_args": attr.string_list( |
| doc = """Fixed command line arguments to pass to the Node.js when this |
| binary target is executed. |
| |
| Subject to [$(location)](https://bazel.build/reference/be/make-variables#predefined_label_variables) |
| and ["Make variable"](https://bazel.build/reference/be/make-variables) substitution if `expand_args` is set to True. |
| |
| Unlike the built-in `args`, which are only passed to the target when it is |
| executed either by the `bazel run` command or as a test, `fixed_args` are baked |
| into the generated launcher script so are always passed even when the binary |
| target is run outside of Bazel directly from the launcher script. |
| |
| `fixed_args` are passed before the ones specified in `args` and before ones |
| that are specified on the `bazel run` or `bazel test` command line. |
| |
| See https://bazel.build/reference/be/common-definitions#common-attributes-binaries |
| for more info on the built-in `args` attribute. |
| """, |
| ), |
| "node_options": attr.string_list( |
| doc = """Options to pass to the node invocation on the command line. |
| |
| https://nodejs.org/api/cli.html |
| |
| These options are passed directly to the node invocation on the command line. |
| Options passed here will take precendence over options passed via |
| the NODE_OPTIONS environment variable. Options passed here are not added |
| to the NODE_OPTIONS environment variable so will not be automatically |
| picked up by child processes that inherit that enviroment variable. |
| """, |
| ), |
| "expected_exit_code": attr.int( |
| doc = """The expected exit code. |
| |
| Can be used to write tests that are expected to fail.""", |
| default = 0, |
| ), |
| "log_level": attr.string( |
| doc = """Set the logging level. |
| |
| Log from are written to stderr. They will be supressed on success when running as the tool |
| of a js_run_binary when silent_on_success is True. In that case, they will be shown |
| only on a build failure along with the stdout & stderr of the node tool being run. |
| |
| Log levels: {}""".format(", ".join(LOG_LEVELS.keys())), |
| values = LOG_LEVELS.keys(), |
| default = "error", |
| ), |
| "patch_node_fs": attr.bool( |
| doc = """Patch the to Node.js `fs` API (https://nodejs.org/api/fs.html) for this node program |
| to prevent the program from following symlinks out of the execroot, runfiles and the sandbox. |
| |
| When enabled, `js_binary` patches the Node.js sync and async `fs` API functions `lstat`, |
| `readlink`, `realpath`, `readdir` and `opendir` so that the node program being |
| run cannot resolve symlinks out of the execroot and the runfiles tree. When in the sandbox, |
| these patches prevent the program being run from resolving symlinks out of the sandbox. |
| |
| When disabled, node programs can leave the execroot, runfiles and sandbox by following symlinks |
| which can lead to non-hermetic behavior.""", |
| default = True, |
| ), |
| "include_sources": attr.bool( |
| doc = """When True, `sources` from `JsInfo` providers in `data` targets are included in the runfiles of the target.""", |
| default = True, |
| ), |
| "include_transitive_sources": attr.bool( |
| doc = """When True, `transitive_sources` from `JsInfo` providers in `data` targets are included in the runfiles of the target.""", |
| default = True, |
| ), |
| "include_types": attr.bool( |
| doc = """When True, `types` from `JsInfo` providers in `data` targets are included in the runfiles of the target. |
| |
| Defaults to False since types are generally not needed at runtime and introducing them could slow down developer round trip |
| time due to having to generate typings on source file changes. |
| |
| NB: These are types from direct `data` dependencies only. You may also need to set `include_transitive_types` to True.""", |
| default = False, |
| ), |
| "include_transitive_types": attr.bool( |
| doc = """When True, `transitive_types` from `JsInfo` providers in `data` targets are included in the runfiles of the target. |
| |
| Defaults to False since types are generally not needed at runtime and introducing them could slow down developer round trip |
| time due to having to generate typings on source file changes.""", |
| default = False, |
| ), |
| "include_npm_sources": attr.bool( |
| doc = """When True, files in `npm_sources` from `JsInfo` providers in `data` targets are included in the runfiles of the target. |
| |
| `transitive_files` from `NpmPackageStoreInfo` providers in `data` targets are also included in the runfiles of the target. |
| """, |
| default = True, |
| ), |
| "preserve_symlinks_main": attr.bool( |
| doc = """When True, the --preserve-symlinks-main flag is passed to node. |
| |
| This prevents node from following an ESM entry script out of runfiles and the sandbox. This can happen for `.mjs` |
| ESM entry points where the fs node patches, which guard the runfiles and sandbox, are not applied. |
| See https://github.com/aspect-build/rules_js/issues/362 for more information. Once #362 is resolved, |
| the default for this attribute can be set to False. |
| |
| This flag was added in Node.js v10.2.0 (released 2018-05-23). If your node toolchain is configured to use a |
| Node.js version older than this you'll need to set this attribute to False. |
| |
| See https://nodejs.org/api/cli.html#--preserve-symlinks-main for more information. |
| """, |
| default = True, |
| ), |
| "no_copy_to_bin": attr.label_list( |
| allow_files = True, |
| doc = """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_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_to_bin` docstring for more info. |
| """, |
| ), |
| "copy_data_to_bin": attr.bool( |
| doc = """When True, `data` files and the `entry_point` file are copied to the Bazel output tree before being passed |
| as inputs to runfiles. |
| |
| Defaults to True so that a `js_binary` with the default value is compatible with `js_run_binary` with |
| `use_execroot_entry_point` set to True, the default there. |
| |
| Setting this to False is more optimal in terms of inputs, but there is a yet unresolved issue of ESM imports |
| skirting the node fs patches and escaping the sandbox: https://github.com/aspect-build/rules_js/issues/362. |
| This is hit in some popular test runners such as mocha, which use native `import()` statements |
| (https://github.com/aspect-build/rules_js/pull/353). When set to False, a program such as mocha that uses ESM |
| imports may escape the execroot by following symlinks into the source tree. When set to True, such a program |
| would escape the sandbox but will end up in the output tree where `node_modules` and other inputs required |
| will be available. |
| """, |
| default = True, |
| ), |
| "include_npm": attr.bool( |
| doc = """When True, npm is included in the runfiles of the target. |
| |
| An npm binary is also added on the PATH so tools can spawn npm processes. This is a bash script |
| on Linux and MacOS and a batch script on Windows. |
| |
| A minimum of rules_nodejs version 5.7.0 is required which contains the Node.js toolchain changes |
| to use npm. |
| """, |
| ), |
| "node_toolchain": attr.label( |
| doc = """The Node.js toolchain to use for this target. |
| |
| See https://bazel-contrib.github.io/rules_nodejs/Toolchains.html |
| |
| Typically this is left unset so that Bazel automatically selects the right Node.js toolchain |
| for the target platform. See https://bazel.build/extending/toolchains#toolchain-resolution |
| for more information. |
| """, |
| ), |
| "_launcher_template": attr.label( |
| default = Label("//js/private:js_binary.sh.tpl"), |
| allow_single_file = True, |
| ), |
| "_node_wrapper_sh": attr.label( |
| default = Label("//js/private:node_wrapper.sh"), |
| allow_single_file = True, |
| ), |
| "_node_wrapper_bat": attr.label( |
| default = Label("//js/private:node_wrapper.bat"), |
| allow_single_file = True, |
| ), |
| "_npm_wrapper_sh": attr.label( |
| default = Label("//js/private:npm_wrapper.sh"), |
| allow_single_file = True, |
| ), |
| "_npm_wrapper_bat": attr.label( |
| default = Label("//js/private:npm_wrapper.bat"), |
| allow_single_file = True, |
| ), |
| "_windows_constraint": attr.label(default = "@platforms//os:windows"), |
| "_node_patches_files": attr.label_list( |
| allow_files = True, |
| default = [Label("@aspect_rules_js//js/private/node-patches:fs.cjs")], |
| ), |
| "_node_patches": attr.label( |
| allow_single_file = True, |
| default = Label("@aspect_rules_js//js/private/node-patches:register.cjs"), |
| ), |
| } |
| |
| _ENV_SET = """export {var}=\"{value}\"""" |
| _ENV_SET_IFF_NOT_SET = """if [[ -z "${{{var}:-}}" ]]; then export {var}=\"{value}\"; fi""" |
| _NODE_OPTION = """JS_BINARY__NODE_OPTIONS+=(\"{value}\")""" |
| |
| # Do the opposite of _to_manifest_path in |
| # https://github.com/bazelbuild/rules_nodejs/blob/8b5d27400db51e7027fe95ae413eeabea4856f8e/nodejs/toolchain.bzl#L50 |
| # to get back to the short_path. |
| # TODO(3.0): remove this after a grace period for the DEPRECATED toolchain attributes |
| # buildifier: disable=unused-variable |
| def _deprecated_target_tool_path_to_short_path(tool_path): |
| return ("../" + tool_path[len("external/"):]) if tool_path.startswith("external/") else tool_path |
| |
| def _expand_env_if_needed(ctx, value): |
| if ctx.attr.expand_env: |
| return " ".join([expand_variables(ctx, exp, attribute_name = "env") for exp in expand_locations(ctx, value, ctx.attr.data).split(" ")]) |
| return value |
| |
| def _bash_launcher(ctx, nodeinfo, entry_point_path, log_prefix_rule_set, log_prefix_rule, fixed_args, fixed_env, is_windows): |
| # Explicitly disable node fs patches on Windows: |
| # https://github.com/aspect-build/rules_js/issues/1137 |
| if is_windows: |
| fixed_env = dict(fixed_env, **{"JS_BINARY__PATCH_NODE_FS": "0"}) |
| |
| envs = [ |
| _ENV_SET.format(var = key, value = _expand_env_if_needed(ctx, value)) |
| for key, value in fixed_env.items() |
| ] + [ |
| _ENV_SET.format(var = key, value = _expand_env_if_needed(ctx, value)) |
| for key, value in ctx.attr.env.items() |
| ] |
| |
| # Add common and useful make variables to the environment |
| makevars = { |
| "JS_BINARY__BINDIR": "$(BINDIR)", |
| "JS_BINARY__COMPILATION_MODE": "$(COMPILATION_MODE)", |
| "JS_BINARY__TARGET_CPU": "$(TARGET_CPU)", |
| } |
| for (key, value) in makevars.items(): |
| envs.append(_ENV_SET.format( |
| var = key, |
| value = ctx.expand_make_variables("env", value, {}), |
| )) |
| |
| # Add rule context variables to the environment |
| builtins = { |
| "JS_BINARY__BUILD_FILE_PATH": ctx.build_file_path, |
| "JS_BINARY__PACKAGE": ctx.label.package, |
| "JS_BINARY__TARGET_NAME": ctx.label.name, |
| "JS_BINARY__TARGET": "{}//{}:{}".format( |
| "@" + ctx.label.repo_name if ctx.label.repo_name else "", |
| ctx.label.package, |
| ctx.label.name, |
| ), |
| "JS_BINARY__WORKSPACE": ctx.workspace_name, |
| } |
| if is_windows and not ctx.attr.enable_runfiles: |
| builtins["JS_BINARY__NO_RUNFILES"] = "1" |
| for (key, value) in builtins.items(): |
| envs.append(_ENV_SET.format(var = key, value = value)) |
| |
| if ctx.attr.patch_node_fs: |
| # Set patch node fs API env if not already set to allow js_run_binary to override |
| envs.append(_ENV_SET_IFF_NOT_SET.format(var = "JS_BINARY__PATCH_NODE_FS", value = "1")) |
| |
| if ctx.attr.expected_exit_code: |
| envs.append(_ENV_SET.format( |
| var = "JS_BINARY__EXPECTED_EXIT_CODE", |
| value = str(ctx.attr.expected_exit_code), |
| )) |
| |
| if ctx.attr.copy_data_to_bin: |
| # Set an environment variable to flag that we have copied js_binary data to bin |
| envs.append(_ENV_SET.format(var = "JS_BINARY__COPY_DATA_TO_BIN", value = "1")) |
| |
| if ctx.attr.chdir: |
| # Set chdir env if not already set to allow js_run_binary to override |
| envs.append(_ENV_SET_IFF_NOT_SET.format(var = "JS_BINARY__CHDIR", value = _expand_env_if_needed(ctx, ctx.attr.chdir))) |
| |
| # Set log envs iff not already set to allow js_run_binary to override |
| for env in envs_for_log_level(ctx.attr.log_level): |
| envs.append(_ENV_SET_IFF_NOT_SET.format(var = env, value = "1")) |
| |
| node_options = [] |
| for node_option in ctx.attr.node_options: |
| node_options.append(_NODE_OPTION.format(value = _expand_env_if_needed(ctx, node_option))) |
| if ctx.attr.preserve_symlinks_main and "--preserve-symlinks-main" not in node_options: |
| node_options.append(_NODE_OPTION.format(value = "--preserve-symlinks-main")) |
| |
| if ctx.attr.expand_args: |
| fixed_args = [expand_variables(ctx, expand_locations(ctx, fixed_arg, ctx.attr.data)) for fixed_arg in fixed_args] |
| |
| toolchain_files = [] |
| if is_windows: |
| node_wrapper = ctx.actions.declare_file("%s_node_bin/node.bat" % ctx.label.name) |
| ctx.actions.expand_template( |
| template = ctx.file._node_wrapper_bat, |
| output = node_wrapper, |
| substitutions = {}, |
| is_executable = True, |
| ) |
| else: |
| node_wrapper = ctx.actions.declare_file("%s_node_bin/node" % ctx.label.name) |
| ctx.actions.expand_template( |
| template = ctx.file._node_wrapper_sh, |
| output = node_wrapper, |
| substitutions = {}, |
| is_executable = True, |
| ) |
| toolchain_files.append(node_wrapper) |
| |
| npm_path = "" |
| if ctx.attr.include_npm: |
| if hasattr(nodeinfo, "npm"): |
| npm_path = nodeinfo.npm.short_path if nodeinfo.npm else nodeinfo.npm_path |
| else: |
| # TODO(3.0): drop support for deprecated toolchain attributes |
| npm_path = _deprecated_target_tool_path_to_short_path(nodeinfo.npm_path) |
| if is_windows: |
| npm_wrapper = ctx.actions.declare_file("%s_node_bin/npm.bat" % ctx.label.name) |
| ctx.actions.expand_template( |
| template = ctx.file._npm_wrapper_bat, |
| output = npm_wrapper, |
| substitutions = {}, |
| is_executable = True, |
| ) |
| else: |
| npm_wrapper = ctx.actions.declare_file("%s_node_bin/npm" % ctx.label.name) |
| ctx.actions.expand_template( |
| template = ctx.file._npm_wrapper_sh, |
| output = npm_wrapper, |
| substitutions = {}, |
| is_executable = True, |
| ) |
| toolchain_files.append(npm_wrapper) |
| |
| if hasattr(nodeinfo, "node"): |
| node_path = nodeinfo.node.short_path if nodeinfo.node else nodeinfo.node_path |
| else: |
| # TODO(3.0): drop support for deprecated toolchain attributes |
| node_path = _deprecated_target_tool_path_to_short_path(nodeinfo.target_tool_path) |
| |
| launcher_subst = { |
| "{{target_label}}": str(ctx.label), |
| "{{template_label}}": str(ctx.attr._launcher_template.label), |
| "{{entry_point_label}}": str(ctx.attr.entry_point.label), |
| "{{entry_point_path}}": entry_point_path, |
| "{{envs}}": "\n".join(envs), |
| "{{fixed_args}}": " ".join(fixed_args), |
| "{{initialize_runfiles}}": BASH_INITIALIZE_RUNFILES, |
| "{{log_prefix_rule_set}}": log_prefix_rule_set, |
| "{{log_prefix_rule}}": log_prefix_rule, |
| "{{node_options}}": "\n".join(node_options), |
| "{{node_patches}}": ctx.file._node_patches.short_path, |
| "{{node_wrapper}}": node_wrapper.short_path, |
| "{{node}}": node_path, |
| "{{npm}}": npm_path, |
| "{{workspace_name}}": ctx.workspace_name, |
| } |
| |
| # The '_' avoids collisions with another file matching the label name. |
| # For example, test and test/my.spec.ts. This naming scheme is borrowed from rules_go: |
| # https://github.com/bazelbuild/rules_go/blob/f3cc8a2d670c7ccd5f45434ab226b25a76d44de1/go/private/context.bzl#L144 |
| launcher = ctx.actions.declare_file("{}_/{}".format(ctx.label.name, ctx.label.name)) |
| ctx.actions.expand_template( |
| template = ctx.file._launcher_template, |
| output = launcher, |
| substitutions = launcher_subst, |
| is_executable = True, |
| ) |
| |
| return launcher, toolchain_files |
| |
| def _create_launcher(ctx, log_prefix_rule_set, log_prefix_rule, fixed_args = [], fixed_env = {}): |
| is_windows = ctx.target_platform_has_constraint(ctx.attr._windows_constraint[platform_common.ConstraintValueInfo]) |
| |
| if ctx.attr.node_toolchain: |
| nodeinfo = ctx.attr.node_toolchain[platform_common.ToolchainInfo].nodeinfo |
| else: |
| nodeinfo = ctx.toolchains["@rules_nodejs//nodejs:toolchain_type"].nodeinfo |
| |
| directory_path_provider = DirectoryPathInfo if DirectoryPathInfo in ctx.attr.entry_point else _LegacyDirectoryPathInfo |
| if directory_path_provider in ctx.attr.entry_point: |
| entry_point = ctx.attr.entry_point[directory_path_provider].directory |
| entry_point_path = "/".join([ |
| ctx.attr.entry_point[directory_path_provider].directory.short_path, |
| ctx.attr.entry_point[directory_path_provider].path, |
| ]) |
| else: |
| if len(ctx.files.entry_point) != 1: |
| fail("entry_point must be a single file or a target that provides a DirectoryPathInfo") |
| entry_point = ctx.files.entry_point[0] |
| entry_point_path = entry_point.short_path |
| |
| bash_launcher, toolchain_files = _bash_launcher(ctx, nodeinfo, entry_point_path, log_prefix_rule_set, log_prefix_rule, fixed_args, fixed_env, is_windows) |
| launcher = create_windows_native_launcher_script(ctx, bash_launcher) if is_windows else bash_launcher |
| |
| launcher_files = [bash_launcher] |
| launcher_files.extend(toolchain_files) |
| if hasattr(nodeinfo, "node"): |
| if nodeinfo.node: |
| launcher_files.append(nodeinfo.node) |
| else: |
| # TODO(3.0): drop support for deprecated toolchain attributes |
| launcher_files.extend(nodeinfo.tool_files) |
| |
| launcher_files.extend(ctx.files._node_patches_files + [ctx.file._node_patches]) |
| transitive_launcher_files = None |
| if ctx.attr.include_npm: |
| if hasattr(nodeinfo, "npm_sources"): |
| transitive_launcher_files = nodeinfo.npm_sources |
| else: |
| # TODO(3.0): drop support for deprecated toolchain attributes |
| if not hasattr(nodeinfo, "npm_files"): |
| fail("include_npm requires a minimum @rules_nodejs version of 5.7.0") |
| launcher_files.extend(nodeinfo.npm_files) |
| |
| runfiles = gather_runfiles( |
| ctx = ctx, |
| data = ctx.attr.data, |
| data_files = [entry_point] + ctx.files.data, |
| copy_data_files_to_bin = ctx.attr.copy_data_to_bin, |
| no_copy_to_bin = ctx.files.no_copy_to_bin, |
| include_sources = ctx.attr.include_sources, |
| include_types = ctx.attr.include_types, |
| include_transitive_sources = ctx.attr.include_transitive_sources, |
| include_transitive_types = ctx.attr.include_transitive_types, |
| include_npm_sources = ctx.attr.include_npm_sources, |
| ).merge(ctx.runfiles( |
| files = launcher_files, |
| transitive_files = transitive_launcher_files, |
| )) |
| |
| return struct( |
| executable = launcher, |
| runfiles = runfiles, |
| ) |
| |
| def _js_binary_impl(ctx): |
| launcher = _create_launcher( |
| ctx, |
| log_prefix_rule_set = "aspect_rules_js", |
| log_prefix_rule = "js_test" if ctx.attr.testonly else "js_binary", |
| fixed_args = ctx.attr.fixed_args, |
| ) |
| runfiles = launcher.runfiles |
| |
| providers = [] |
| |
| # Create RunEnvironmentInfo provider with both env and env_inherit (if available) |
| run_env_info_kwargs = {} |
| |
| if ctx.attr.env: |
| action_context_env_expanded = {} |
| for key, value in ctx.attr.env.items(): |
| action_context_env_expanded[key] = _expand_env_if_needed(ctx, value) |
| run_env_info_kwargs["environment"] = action_context_env_expanded |
| |
| # Add inherited environment variables (for js_test) |
| if hasattr(ctx.attr, "env_inherit"): |
| run_env_info_kwargs["inherited_environment"] = ctx.attr.env_inherit |
| |
| # Only create provider if we have something to provide |
| if run_env_info_kwargs: |
| providers.append(RunEnvironmentInfo(**run_env_info_kwargs)) |
| |
| if ctx.attr.testonly and ctx.configuration.coverage_enabled: |
| # We have to propagate _lcov_merger runfiles since bazel does not treat _lcov_merger as a proper tool. |
| # See: https://github.com/bazelbuild/bazel/issues/4033 |
| # This is optional because: |
| # - We do not want to require it for js_binary targets |
| # (but we cannot distinguish js_binary from js_test here, see #2229). |
| # - It is not required anymore on bazel 8 |
| # (https://github.com/bazelbuild/bazel/issues/4033#issuecomment-2507162290) |
| # TODO: Remove once bazel<8 support is dropped. |
| if hasattr(ctx.attr, "_lcov_merger"): |
| runfiles = runfiles.merge(ctx.attr._lcov_merger[DefaultInfo].default_runfiles) |
| providers.append( |
| coverage_common.instrumented_files_info( |
| ctx, |
| source_attributes = ["data"], |
| dependency_attributes = ["data"], |
| # TODO: check if there is more extensions |
| # TODO: .ts should not be here since we ought to only instrument transpiled files? |
| extensions = [ |
| "mjs", |
| "mts", |
| "cjs", |
| "cts", |
| "ts", |
| "js", |
| "jsx", |
| "tsx", |
| ], |
| ), |
| ) |
| |
| return providers + [ |
| DefaultInfo( |
| executable = launcher.executable, |
| runfiles = runfiles, |
| ), |
| ] |
| |
| js_binary_lib = struct( |
| attrs = _ATTRS, |
| create_launcher = _create_launcher, |
| implementation = _js_binary_impl, |
| toolchains = [ |
| # TODO: on Windows this toolchain is never referenced |
| "@bazel_tools//tools/sh:toolchain_type", |
| "@rules_nodejs//nodejs:toolchain_type", |
| ] + COPY_FILE_TO_BIN_TOOLCHAINS, |
| ) |
| |
| js_binary = rule( |
| doc = _DOC, |
| implementation = js_binary_lib.implementation, |
| attrs = js_binary_lib.attrs, |
| executable = True, |
| toolchains = js_binary_lib.toolchains, |
| ) |
| |
| js_test = rule( |
| doc = """Identical to js_binary, but usable under `bazel test`. |
| |
| All [common test attributes](https://bazel.build/reference/be/common-definitions#common-attributes-tests) are |
| supported including `args` as the list of arguments passed Node.js. |
| |
| Bazel will set environment variables when a test target is run under `bazel test` and `bazel run` |
| that a test runner can use. |
| |
| A runner can write arbitrary outputs files it wants Bazel to pickup and save with the test logs to |
| `TEST_UNDECLARED_OUTPUTS_DIR`. These get zipped up and saved along with the test logs. |
| |
| JUnit XML reports can be written to `XML_OUTPUT_FILE` for Bazel to consume. |
| |
| `TEST_TMPDIR` is an absolute path to a private writeable directory that the test runner can use for |
| creating temporary files. |
| |
| LCOV coverage reports can be written to `COVERAGE_OUTPUT_FILE` when running under `bazel coverage` |
| or if the `--coverage` flag is set. |
| |
| See the Bazel [Test encyclopedia](https://bazel.build/reference/test-encyclopedia) for details on |
| the contract between Bazel and a test runner.""", |
| implementation = js_binary_lib.implementation, |
| attrs = dict(js_binary_lib.attrs, **{ |
| "env_inherit": attr.string_list( |
| default = [], |
| doc = "Specifies additional environment variables to inherit from the external environment when the test is executed by bazel test.", |
| ), |
| # TODO: Remove once bazel<8 support is dropped. |
| # See comment at usage site in the rule impl for more. |
| "_lcov_merger": attr.label( |
| executable = True, |
| default = Label("//js/private/coverage:merger"), |
| cfg = "exec", |
| ), |
| }), |
| test = True, |
| toolchains = js_binary_lib.toolchains, |
| ) |