blob: dc3272ce35ddf14408cb70805bb9bad3ecd2a1bb [file]
# 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.
"""Install NodeJS & Yarn
This is a set of repository rules for setting up hermetic copies of NodeJS and Yarn.
See https://docs.bazel.build/versions/master/skylark/repository_rules.html
"""
load("//internal/common:check_bazel_version.bzl", "check_bazel_version")
load("//internal/common:check_version.bzl", "check_version")
load("//internal/common:os_name.bzl", "OS_ARCH_NAMES", "is_windows_os", "os_name")
load("//internal/npm_install:npm_install.bzl", "yarn_install")
load("//third_party/github.com/bazelbuild/bazel-skylib:lib/paths.bzl", "paths")
load("//toolchains/node:node_toolchain_configure.bzl", "node_toolchain_configure")
_DOC = """To be run in user's WORKSPACE to install rules_nodejs dependencies.
This rule sets up node, npm, and yarn.
The versions of these tools can be specified in one of three ways:
- Normal Usage:
Specify no explicit versions. This will download and use the latest NodeJS & Yarn that were available when the
version of rules_nodejs you're using was released.
- Forced version(s):
You can select the version of NodeJS and/or Yarn to download & use by specifying it when you call node_repositories,
but you must use a value that matches a known version.
- Using a custom version:
You can pass in a custom list of NodeJS and/or Yarn repositories and URLs for node_resositories to use.
- Using a local version:
To avoid downloads, you can check in vendored copies of NodeJS and/or Yarn and set vendored_node and or vendored_yarn
to point to those before calling node_repositories.
This rule exposes the `@nodejs` workspace containing some rules the user can call later:
- Run node: `bazel run @nodejs//:node path/to/program.js`
- Install dependencies using npm: `bazel run @nodejs//:npm install`
- Install dependencies using yarn: `bazel run @nodejs//:yarn`
Note that the dependency installation scripts will run in each subpackage indicated by the `package_json` attribute.
This approach uses npm/yarn as the package manager. You could instead have Bazel act as the package manager, running the install behind the scenes.
See the `npm_install` and `yarn_install` rules, and the discussion in the README.
Example:
```
load("@build_bazel_rules_nodejs//:index.bzl", "node_repositories")
node_repositories(package_json = ["//:package.json", "//subpkg:package.json"])
```
Running `bazel run @nodejs//:yarn` in this repo would create `/node_modules` and `/subpkg/node_modules`.
"""
_ATTRS = {
"node_repositories": attr.string_list_dict(
default = {
# 10.10.0
"10.10.0-darwin_amd64": ("node-v10.10.0-darwin-x64.tar.gz", "node-v10.10.0-darwin-x64", "00b7a8426e076e9bf9d12ba2d571312e833fe962c70afafd10ad3682fdeeaa5e"),
"10.10.0-linux_amd64": ("node-v10.10.0-linux-x64.tar.xz", "node-v10.10.0-linux-x64", "686d2c7b7698097e67bcd68edc3d6b5d28d81f62436c7cf9e7779d134ec262a9"),
"10.10.0-windows_amd64": ("node-v10.10.0-win-x64.zip", "node-v10.10.0-win-x64", "70c46e6451798be9d052b700ce5dadccb75cf917f6bf0d6ed54344c856830cfb"),
# 10.13.0
"10.13.0-darwin_amd64": ("node-v10.13.0-darwin-x64.tar.gz", "node-v10.13.0-darwin-x64", "815a5d18516934a3963ace9f0574f7d41f0c0ce9186a19be3d89e039e57598c5"),
"10.13.0-linux_amd64": ("node-v10.13.0-linux-x64.tar.xz", "node-v10.13.0-linux-x64", "0dc6dba645550b66f8f00541a428c29da7c3cde32fb7eda2eb626a9db3bbf08d"),
"10.13.0-windows_amd64": ("node-v10.13.0-win-x64.zip", "node-v10.13.0-win-x64", "eb09c9e9677f1919ec1ca78623c09b2a718ec5388b72b7662d5c41e5f628a52c"),
# 10.16.0
"10.16.0-darwin_amd64": ("node-v10.16.0-darwin-x64.tar.gz", "node-v10.16.0-darwin-x64", "6c009df1b724026d84ae9a838c5b382662e30f6c5563a0995532f2bece39fa9c"),
"10.16.0-linux_amd64": ("node-v10.16.0-linux-x64.tar.xz", "node-v10.16.0-linux-x64", "1827f5b99084740234de0c506f4dd2202a696ed60f76059696747c34339b9d48"),
"10.16.0-windows_amd64": ("node-v10.16.0-win-x64.zip", "node-v10.16.0-win-x64", "aa22cb357f0fb54ccbc06b19b60e37eefea5d7dd9940912675d3ed988bf9a059"),
# 10.3.0
"10.3.0-darwin_amd64": ("node-v10.3.0-darwin-x64.tar.gz", "node-v10.3.0-darwin-x64", "0bb5b7e3fe8cccda2abda958d1eb0408f1518a8b0cb58b75ade5d507cd5d6053"),
"10.3.0-linux_amd64": ("node-v10.3.0-linux-x64.tar.xz", "node-v10.3.0-linux-x64", "eb3c3e2585494699716ad3197c8eedf4003d3f110829b30c5a0dc34414c47423"),
"10.3.0-windows_amd64": ("node-v10.3.0-win-x64.zip", "node-v10.3.0-win-x64", "65d586afb087406a2800d8e51f664c88b26d510f077b85a3b177a1bb79f73677"),
# 10.9.0
"10.9.0-darwin_amd64": ("node-v10.9.0-darwin-x64.tar.gz", "node-v10.9.0-darwin-x64", "3c4fe75dacfcc495a432a7ba2dec9045cff359af2a5d7d0429c84a424ef686fc"),
"10.9.0-linux_amd64": ("node-v10.9.0-linux-x64.tar.xz", "node-v10.9.0-linux-x64", "c5acb8b7055ee0b6ac653dc4e458c5db45348cecc564b388f4ed1def84a329ff"),
"10.9.0-windows_amd64": ("node-v10.9.0-win-x64.zip", "node-v10.9.0-win-x64", "6a75cdbb69d62ed242d6cbf0238a470bcbf628567ee339d4d098a5efcda2401e"),
# 12.13.0
"12.13.0-darwin_amd64": ("node-v12.13.0-darwin-x64.tar.gz", "node-v12.13.0-darwin-x64", "49a7374670a111b033ce16611b20fd1aafd3296bbc662b184fe8fb26a29c22cc"),
"12.13.0-linux_amd64": ("node-v12.13.0-linux-x64.tar.xz", "node-v12.13.0-linux-x64", "7a57ef2cb3036d7eacd50ae7ba07245a28336a93652641c065f747adb2a356d9"),
"12.13.0-windows_amd64": ("node-v12.13.0-win-x64.zip", "node-v12.13.0-win-x64", "6f920cebeecb4957b4ef0def6d9b04c49d4582864f8d1a207ce8d0665865781a"),
# 8.11.1
"8.11.1-darwin_amd64": ("node-v8.11.1-darwin-x64.tar.gz", "node-v8.11.1-darwin-x64", "5c7b05899ff56910a2b8180f139d48612f349ac2c5d20f08dbbeffbed9e3a089"),
"8.11.1-linux_amd64": ("node-v8.11.1-linux-x64.tar.xz", "node-v8.11.1-linux-x64", "6617e245fa0f7fbe0e373e71d543fea878315324ab31dc64b4eba10e42d04c11"),
"8.11.1-windows_amd64": ("node-v8.11.1-win-x64.zip", "node-v8.11.1-win-x64", "7d49b59c2b5d73a14c138e8a215d558a64a5241cd5035d9824f608e7bba097b1"),
# 8.12.0
"8.12.0-darwin_amd64": ("node-v8.12.0-darwin-x64.tar.gz", "node-v8.12.0-darwin-x64", "ca131b84dfcf2b6f653a6521d31f7a108ad7d83f4d7e781945b2eca8172064aa"),
"8.12.0-linux_amd64": ("node-v8.12.0-linux-x64.tar.xz", "node-v8.12.0-linux-x64", "29a20479cd1e3a03396a4e74a1784ccdd1cf2f96928b56f6ffa4c8dae40c88f2"),
"8.12.0-windows_amd64": ("node-v8.12.0-win-x64.zip", "node-v8.12.0-win-x64", "9b22c9b23148b61ea0052826b3ac0255b8a3a542c125272b8f014f15bf11b091"),
# 8.9.1
"8.9.1-darwin_amd64": ("node-v8.9.1-darwin-x64.tar.gz", "node-v8.9.1-darwin-x64", "05c992a6621d28d564b92bf3051a5dc0adf83839237c0d4653a8cdb8a1c73b94"),
"8.9.1-linux_amd64": ("node-v8.9.1-linux-x64.tar.xz", "node-v8.9.1-linux-x64", "8be82805f7c1ab3e64d4569fb9a90ded2de78dd27cadbb91bad1bf975dae1e2d"),
"8.9.1-windows_amd64": ("node-v8.9.1-win-x64.zip", "node-v8.9.1-win-x64", "db89c6e041da359561fbe7da075bb4f9881a0f7d3e98c203e83732cfb283fa4a"),
# 9.11.1
"9.11.1-darwin_amd64": ("node-v9.11.1-darwin-x64.tar.gz", "node-v9.11.1-darwin-x64", "7b1fb394aa41a62b477e36df16644bd383cc9084808511f6cd318b835a06aac6"),
"9.11.1-linux_amd64": ("node-v9.11.1-linux-x64.tar.xz", "node-v9.11.1-linux-x64", "4d27a95d5c2f1c8ef99118794c9c4903e63963418d3e16ca7576760cff39879b"),
"9.11.1-windows_amd64": ("node-v9.11.1-win-x64.zip", "node-v9.11.1-win-x64", "0a3566d57ccb7fed95d18fc6c3bc1552a1b1e4753f9bc6c5d45e04f325e1ee53"),
},
doc = """Custom list of node repositories to use
A dictionary mapping NodeJS versions to sets of hosts and their corresponding (filename, strip_prefix, sha256) tuples.
You should list a node binary for every platform users have, likely Mac, Windows, and Linux.
For example,
```python
node_repositories(
node_repositories = {
"10.10.0-darwin_amd64": ("node-v10.10.0-darwin-x64.tar.gz", "node-v10.10.0-darwin-x64", "00b7a8426e076e9bf9d12ba2d571312e833fe962c70afafd10ad3682fdeeaa5e"),
"10.10.0-linux_amd64": ("node-v10.10.0-linux-x64.tar.xz", "node-v10.10.0-linux-x64", "686d2c7b7698097e67bcd68edc3d6b5d28d81f62436c7cf9e7779d134ec262a9"),
"10.10.0-windows_amd64": ("node-v10.10.0-win-x64.zip", "node-v10.10.0-win-x64", "70c46e6451798be9d052b700ce5dadccb75cf917f6bf0d6ed54344c856830cfb"),
},
)
```
""",
),
"node_urls": attr.string_list(
default = [
"https://mirror.bazel.build/nodejs.org/dist/v{version}/{filename}",
"https://nodejs.org/dist/v{version}/{filename}",
],
doc = """custom list of URLs to use to download NodeJS
Each entry is a template for downloading a node distribution.
The `{version}` parameter is substituted with the `node_version` attribute,
and `{filename}` with the matching entry from the `node_repositories` attribute.
For example, given
```python
node_repositories(
node_version = "10.10.0",
node_repositories = {"10.10.0-darwin_amd64": ("node-v10.10.0-darwin-x64.tar.gz", "node-v10.10.0-darwin-x64", "00b7a8426e076e9bf9d12ba2d571312e833fe962c70afafd10ad3682fdeeaa5e")},
node_urls = ["https://mycorpproxy/mirror/node/v{version}/{filename}"],
)
```
A Mac client will try to download node from `https://mycorpproxy/mirror/node/v10.10.0/node-v10.10.0-darwin-x64.tar.gz`
and expect that file to have sha256sum `00b7a8426e076e9bf9d12ba2d571312e833fe962c70afafd10ad3682fdeeaa5e`
""",
),
"node_version": attr.string(
default = "12.13.0",
doc = "the specific version of NodeJS to install or, if vendored_node is specified, the vendored version of node",
),
"package_json": attr.label_list(
doc = """a list of labels, which indicate the package.json files that will be installed
when you manually run the package manager, e.g. with
`bazel run @nodejs//:yarn` or `bazel run @nodejs//:npm install`.
If you use bazel-managed dependencies, you can omit this attribute.""",
),
"preserve_symlinks": attr.bool(
default = True,
doc = """Turn on --node_options=--preserve-symlinks for nodejs_binary and nodejs_test rules.
The default for this is currently True but the options is deprecated and will be removed in the future.
When this option is turned on, node will preserve the symlinked path for resolves instead of the default
behavior of resolving to the real path. This means that all required files must be in be included in your
runfiles as it prevents the default behavior of potentially resolving outside of the runfiles. For example,
all required files need to be included in your node_modules filegroup. This option is desirable as it gives
a stronger guarantee of hermiticity which is required for remote execution.""",
),
"vendored_node": attr.label(
allow_single_file = True,
doc = """the local path to a pre-installed NodeJS runtime.
If set then also set node_version to the version that of node that is vendored.
Bazel will automatically turn on features such as --preserve-symlinks-main if they
are supported by the node version being used.""",
),
"vendored_yarn": attr.label(
allow_single_file = True,
doc = "the local path to a pre-installed yarn tool",
),
"yarn_repositories": attr.string_list_dict(
default = {
"1.12.1": ("yarn-v1.12.1.tar.gz", "yarn-v1.12.1", "09bea8f4ec41e9079fa03093d3b2db7ac5c5331852236d63815f8df42b3ba88d"),
"1.12.3": ("yarn-v1.12.3.tar.gz", "yarn-v1.12.3", "02cd4b589ec22c4bdbd2bc5ebbfd99c5e99b07242ad68a539cb37896b93a24f2"),
"1.13.0": ("yarn-v1.13.0.tar.gz", "yarn-v1.13.0", "125d40ebf621ebb08e3f66a618bd2cc5cd77fa317a312900a1ab4360ed38bf14"),
"1.19.1": ("yarn-v1.19.1.tar.gz", "yarn-v1.19.1", "34293da6266f2aae9690d59c2d764056053ff7eebc56b80b8df05010c3da9343"),
"1.3.2": ("yarn-v1.3.2.tar.gz", "yarn-v1.3.2", "6cfe82e530ef0837212f13e45c1565ba53f5199eec2527b85ecbcd88bf26821d"),
"1.5.1": ("yarn-v1.5.1.tar.gz", "yarn-v1.5.1", "cd31657232cf48d57fdbff55f38bfa058d2fb4950450bd34af72dac796af4de1"),
"1.6.0": ("yarn-v1.6.0.tar.gz", "yarn-v1.6.0", "a57b2fdb2bfeeb083d45a883bc29af94d5e83a21c25f3fc001c295938e988509"),
"1.9.2": ("yarn-v1.9.2.tar.gz", "yarn-v1.9.2", "3ad69cc7f68159a562c676e21998eb21b44138cae7e8fe0749a7d620cf940204"),
"1.9.4": ("yarn-v1.9.4.tar.gz", "yarn-v1.9.4", "7667eb715077b4bad8e2a832e7084e0e6f1ba54d7280dc573c8f7031a7fb093e"),
},
doc = """Custom list of yarn repositories to use.
Dictionary mapping Yarn versions to their corresponding (filename, strip_prefix, sha256) tuples.
For example,
```python
node_repositories(
yarn_repositories = {
"1.12.1": ("yarn-v1.12.1.tar.gz", "yarn-v1.12.1", "09bea8f4ec41e9079fa03093d3b2db7ac5c5331852236d63815f8df42b3ba88d"),
},
)
```
""",
),
"yarn_urls": attr.string_list(
default = [
"https://mirror.bazel.build/github.com/yarnpkg/yarn/releases/download/v{version}/{filename}",
"https://github.com/yarnpkg/yarn/releases/download/v{version}/{filename}",
],
doc = """custom list of URLs to use to download Yarn
Each entry is a template, similar to the `node_urls` attribute, using `yarn_version` and `yarn_repositories` in the substitutions.
For example,
```python
node_repositories(
yarn_repositories = {
"1.12.1": ("yarn-v1.12.1.tar.gz", "yarn-v1.12.1", "09bea8f4ec41e9079fa03093d3b2db7ac5c5331852236d63815f8df42b3ba88d"),
},
yarn_version = "1.12.1",
yarn_urls = "https://github.com/yarnpkg/yarn/releases/download/v{version}/{filename}",
)
```
Will download yarn from https://github.com/yarnpkg/yarn/releases/download/v1.2.1/yarn-v1.12.1.tar.gz`
and expect the file to have sha256sum `09bea8f4ec41e9079fa03093d3b2db7ac5c5331852236d63815f8df42b3ba88d`.
""",
),
"yarn_version": attr.string(
doc = "the specific version of Yarn to install",
default = "1.19.1",
),
}
BUILT_IN_NODE_PLATFORMS = [
"darwin_amd64",
"linux_amd64",
"windows_amd64",
]
NODE_EXTRACT_DIR = "bin/nodejs"
YARN_EXTRACT_DIR = "bin/yarnpkg"
GET_SCRIPT_DIR = """
# From stackoverflow.com
SOURCE="${BASH_SOURCE[0]}"
# Resolve $SOURCE until the file is no longer a symlink
while [ -h "$SOURCE" ]; do
DIR="$(cd -P "$(dirname "$SOURCE" )" >/dev/null && pwd)"
SOURCE="$(readlink "$SOURCE")"
# if $SOURCE was a relative symlink, we need to resolve it relative to the
# path where the symlink file was located.
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE"
done
SCRIPT_DIR="$(cd -P "$( dirname "$SOURCE" )" >/dev/null && pwd)"
"""
def _download_node(repository_ctx):
"""Used to download a NodeJS runtime package.
Args:
repository_ctx: The repository rule context
"""
if repository_ctx.attr.vendored_node:
return
# The host is baked into the repository name by design.
# Current these workspaces are:
# @nodejs_PLATFORM where PLATFORM is one of BUILT_IN_NODE_PLATFORMS
host_os = repository_ctx.name.split("nodejs_", 1)[1]
node_version = repository_ctx.attr.node_version
node_repositories = repository_ctx.attr.node_repositories
node_urls = repository_ctx.attr.node_urls
# Download node & npm
version_host_os = "%s-%s" % (node_version, host_os)
if not version_host_os in node_repositories:
fail("Unknown NodeJS version-host %s" % version_host_os)
filename, strip_prefix, sha256 = node_repositories[version_host_os]
repository_ctx.download_and_extract(
url = [url.format(version = node_version, filename = filename) for url in node_urls],
output = NODE_EXTRACT_DIR,
stripPrefix = strip_prefix,
sha256 = sha256,
)
def _download_yarn(repository_ctx):
"""Used to download a yarn tool package.
Args:
repository_ctx: The repository rule context
"""
if repository_ctx.attr.vendored_yarn:
return
yarn_version = repository_ctx.attr.yarn_version
yarn_repositories = repository_ctx.attr.yarn_repositories
yarn_urls = repository_ctx.attr.yarn_urls
if yarn_version in yarn_repositories:
filename, strip_prefix, sha256 = yarn_repositories[yarn_version]
else:
fail("Unknown Yarn version %s" % yarn_version)
repository_ctx.download_and_extract(
url = [url.format(version = yarn_version, filename = filename) for url in yarn_urls],
output = YARN_EXTRACT_DIR,
stripPrefix = strip_prefix,
sha256 = sha256,
)
def _prepare_node(repository_ctx):
"""Sets up BUILD files and shell wrappers for the versions of NodeJS, npm & yarn just set up.
Windows and other OSes set up the node runtime with different names and paths, which we hide away via
the BUILD file here.
In addition, we create a bash script wrapper around NPM that passes a given NPM command to all package.json labels
passed into here.
Finally, we create a reusable template bash script around NPM that is used by rules like npm_package to access
NPM.
Args:
repository_ctx: The repository rule context
"""
# TODO: Maybe we want to encode the OS as a specific attribute rather than do it based on naming?
is_windows = "_windows_" in repository_ctx.attr.name
if repository_ctx.attr.vendored_node:
node_exec = "/".join([f for f in [
"../../..",
repository_ctx.attr.vendored_node.workspace_root,
repository_ctx.attr.vendored_node.package,
repository_ctx.attr.vendored_node.name,
"bin/node" if not is_windows else "node.exe",
] if f])
node_exec_label = "@%s//%s:%s/%s" % (
repository_ctx.attr.vendored_node.workspace_name,
repository_ctx.attr.vendored_node.package,
repository_ctx.attr.vendored_node.name,
"bin/node" if not is_windows else "node.exe",
)
npm_script = "/".join([f for f in [
"../../..",
repository_ctx.attr.vendored_node.workspace_root,
repository_ctx.attr.vendored_node.package,
repository_ctx.attr.vendored_node.name,
"lib/node_modules/npm/bin/npm-cli.js" if not is_windows else "node_modules/npm/bin/npm-cli.js",
] if f])
else:
node_exec = ("%s/bin/node" % NODE_EXTRACT_DIR) if not is_windows else ("%s/node.exe" % NODE_EXTRACT_DIR)
npm_script = ("%s/lib/node_modules/npm/bin/npm-cli.js" % NODE_EXTRACT_DIR) if not is_windows else ("%s/node_modules/npm/bin/npm-cli.js" % NODE_EXTRACT_DIR)
node_exec_label = node_exec
if repository_ctx.attr.vendored_yarn:
yarn_script = "/".join([f for f in [
"../../..",
repository_ctx.attr.vendored_yarn.workspace_root,
repository_ctx.attr.vendored_yarn.package,
repository_ctx.attr.vendored_yarn.name,
"bin/yarn.js",
] if f])
else:
yarn_script = "%s/bin/yarn.js" % YARN_EXTRACT_DIR
node_entry = "bin/node" if not is_windows else "bin/node.cmd"
npm_node_repositories_entry = "bin/npm_node_repositories" if not is_windows else "bin/npm_node_repositories.cmd"
yarn_node_repositories_entry = "bin/yarn_node_repositories" if not is_windows else "bin/yarn_node_repositories.cmd"
node_exec_relative = node_exec if repository_ctx.attr.vendored_node else paths.relativize(node_exec, "bin")
npm_script_relative = npm_script if repository_ctx.attr.vendored_node else paths.relativize(npm_script, "bin")
yarn_script_relative = yarn_script if repository_ctx.attr.vendored_yarn else paths.relativize(yarn_script, "bin")
if not repository_ctx.attr.preserve_symlinks:
print("\nWARNING: The preserve_symlinks option is deprecated and will go away in the future.\n")
if repository_ctx.attr.preserve_symlinks:
# --preserve-symlinks-main flag added in node 10.2.0
# See https://nodejs.org/api/cli.html#cli_preserve_symlinks_main
preserve_symlinks_main_support = check_version(repository_ctx.attr.node_version, "10.2.0")
if preserve_symlinks_main_support:
node_args = "--preserve-symlinks --preserve-symlinks-main"
node_repo_args = "\"--node_options=--preserve-symlinks --node_options=--preserve-symlinks-main\""
else:
node_args = "--preserve-symlinks"
node_repo_args = "--node_options=--preserve-symlinks"
else:
node_args = ""
node_repo_args = ""
# The entry points for node for osx/linux and windows
if not is_windows:
# Sets PATH and runs the application
repository_ctx.file("bin/node", content = """#!/usr/bin/env bash
# Generated by node_repositories.bzl
# Immediately exit if any command fails.
set -e
{get_script_dir}
export PATH="$SCRIPT_DIR":$PATH
exec "$SCRIPT_DIR/{node}" {args} "$@"
""".format(
get_script_dir = GET_SCRIPT_DIR,
node = node_exec_relative,
args = node_args,
))
else:
# Sets PATH for node, npm & yarn and run user script
repository_ctx.file("bin/node.cmd", content = """
@echo off
SET SCRIPT_DIR=%~dp0
SET PATH=%SCRIPT_DIR%;%PATH%
CALL "%SCRIPT_DIR%\\{node}" {args} %*
""".format(node = node_exec_relative, args = node_args))
# Shell script to set repository arguments for node used by nodejs_binary & nodejs_test launcher
repository_ctx.file("bin/node_repo_args.sh", content = """#!/usr/bin/env bash
# Immediately exit if any command fails.
set -e
# Generated by node_repositories.bzl
export NODE_REPOSITORY_ARGS={args}
""".format(args = node_repo_args), executable = True)
# The entry points for npm for osx/linux and windows
# Runs npm using appropriate node entry point
# --scripts-prepend-node-path is set to false since the correct paths
# for the Bazel entry points of node, npm & yarn are set in the node
# entry point
if not is_windows:
# Npm entry point
repository_ctx.file(
"bin/npm",
content = """#!/usr/bin/env bash
# Generated by node_repositories.bzl
# Immediately exit if any command fails.
set -e
{get_script_dir}
"$SCRIPT_DIR/{node}" "$SCRIPT_DIR/{script}" --scripts-prepend-node-path=false "$@"
""".format(
get_script_dir = GET_SCRIPT_DIR,
node = paths.relativize(node_entry, "bin"),
script = npm_script_relative,
),
executable = True,
)
# Npm entry point for node_repositories
repository_ctx.file("bin/npm_node_repositories", content = """#!/usr/bin/env bash
# Generated by node_repositories.bzl
# Immediately exit if any command fails.
set -e
# Executes the given npm command over each of the package.json folders provided in node_repositories.
""" + GET_SCRIPT_DIR + "".join([
"""
echo Running npm "$@" in {root}
(cd "{root}"; "$SCRIPT_DIR/{node}" "$SCRIPT_DIR/{script}" --scripts-prepend-node-path=false "$@")
""".format(
root = repository_ctx.path(package_json).dirname,
node = paths.relativize(node_entry, "bin"),
script = npm_script_relative,
)
for package_json in repository_ctx.attr.package_json
]), executable = True)
else:
# Npm entry point
repository_ctx.file(
"bin/npm.cmd",
content = """@echo off
SET SCRIPT_DIR=%~dp0
"%SCRIPT_DIR%\\{node}" "%SCRIPT_DIR%\\{script}" --scripts-prepend-node-path=false %*
""".format(
node = paths.relativize(node_entry, "bin"),
script = npm_script_relative,
),
executable = True,
)
# Npm entry point for node_repositories
repository_ctx.file("bin/npm_node_repositories.cmd", content = """@echo off
""" + "".join([
"""
SET SCRIPT_DIR=%~dp0
echo Running npm %* in {root}
cd "{root}"
call "%SCRIPT_DIR%\\{node}" "%SCRIPT_DIR%\\{script}" --scripts-prepend-node-path=false %*
if %errorlevel% neq 0 exit /b %errorlevel%
""".format(
root = repository_ctx.path(package_json).dirname,
node = paths.relativize(node_entry, "bin"),
script = npm_script_relative,
)
for package_json in repository_ctx.attr.package_json
]), executable = True)
# This template file is used by the packager tool and the npm_package rule.
# `yarn publish` is not ready for use under Bazel, see https://github.com/yarnpkg/yarn/issues/610
repository_ctx.file("run_npm.sh.template", content = """
"{node}" "{script}" TMPL_args "$@"
""".format(
node = repository_ctx.path(node_entry),
script = repository_ctx.path(npm_script),
))
# The entry points for yarn for osx/linux and windows
# Runs yarn using appropriate node entry point
if not is_windows:
# Yarn entry point
repository_ctx.file(
"bin/yarn",
content = """#!/usr/bin/env bash
# Generated by node_repositories.bzl
# Immediately exit if any command fails.
set -e
{get_script_dir}
"$SCRIPT_DIR/{node}" "$SCRIPT_DIR/{script}" "$@"
""".format(
get_script_dir = GET_SCRIPT_DIR,
node = paths.relativize(node_entry, "bin"),
script = yarn_script_relative,
),
executable = True,
)
# Yarn entry point for node_repositories
repository_ctx.file("bin/yarn_node_repositories", content = """#!/usr/bin/env bash
# Generated by node_repositories.bzl
# Immediately exit if any command fails.
set -e
# Executes the given yarn command over each of the package.json folders provided in node_repositories.
""" + GET_SCRIPT_DIR + "".join([
"""
echo Running yarn --cwd "{root}" "$@"
"$SCRIPT_DIR/{node}" "$SCRIPT_DIR/{script}" --cwd "{root}" "$@"
""".format(
root = repository_ctx.path(package_json).dirname,
node = paths.relativize(node_entry, "bin"),
script = yarn_script_relative,
)
for package_json in repository_ctx.attr.package_json
]), executable = True)
else:
# Yarn entry point
repository_ctx.file(
"bin/yarn.cmd",
content = """@echo off
SET SCRIPT_DIR=%~dp0
"%SCRIPT_DIR%\\{node}" "%SCRIPT_DIR%\\{script}" %*
""".format(
node = paths.relativize(node_entry, "bin"),
script = yarn_script_relative,
),
executable = True,
)
# Yarn entry point for node_repositories
repository_ctx.file("bin/yarn_node_repositories.cmd", content = """@echo off
SET SCRIPT_DIR=%~dp0
""" + "".join([
"""
echo Running yarn --cwd "{root}" %*
CALL "%SCRIPT_DIR%\\{node}" "%SCRIPT_DIR%\\{script}" --cwd "{root}" %*
if %errorlevel% neq 0 exit /b %errorlevel%
""".format(
root = repository_ctx.path(package_json).dirname,
node = paths.relativize(node_entry, "bin"),
script = yarn_script_relative,
)
for package_json in repository_ctx.attr.package_json
]), executable = True)
# Base BUILD file for this repository
repository_ctx.file("BUILD.bazel", content = """# Generated by node_repositories.bzl
package(default_visibility = ["//visibility:public"])
exports_files([
"run_npm.sh.template",
"bin/node_repo_args.sh",{exported_node_bin}
"bin/node{entry_ext}",
"bin/npm{entry_ext}",
"bin/npm_node_repositories{entry_ext}",
"bin/yarn{entry_ext}",
"bin/yarn_node_repositories{entry_ext}",
])
alias(name = "node_bin", actual = "{node_bin_actual}")
alias(name = "node", actual = "{node_actual}")
alias(name = "npm", actual = "{npm_actual}")
alias(name = "yarn", actual = "{yarn_actual}")
""".format(
entry_ext = ".cmd" if is_windows else "",
exported_node_bin = "" if repository_ctx.attr.vendored_node else ("\n \"%s\"," % node_exec_label),
node_bin_actual = node_exec_label,
node_actual = node_entry,
npm_actual = npm_node_repositories_entry,
yarn_actual = yarn_node_repositories_entry,
))
def _nodejs_repo_impl(repository_ctx):
_download_node(repository_ctx)
_download_yarn(repository_ctx)
_prepare_node(repository_ctx)
# Users should call the `node_repositories` wrapper macro.
# This is exposed for stardoc.
node_repositories_rule = repository_rule(
_nodejs_repo_impl,
doc = _DOC,
attrs = _ATTRS,
)
def _nodejs_host_os_alias_impl(repository_ctx):
is_windows_host = is_windows_os(repository_ctx)
file_ending = ".cmd" if is_windows_host else ""
node_repository = "@nodejs_%s" % os_name(repository_ctx)
if repository_ctx.attr.vendored_node:
node_bin_repository = "@%s" % repository_ctx.attr.vendored_node.workspace_name
actual_node_bin = "/".join([f for f in [
repository_ctx.attr.vendored_node.package,
repository_ctx.attr.vendored_node.name,
"bin/node" if not is_windows_host else "node.exe",
] if f])
else:
node_bin_repository = node_repository
actual_node_bin = "%s/%s" % (
NODE_EXTRACT_DIR,
"node.exe" if is_windows_host else "bin/node",
)
repository_ctx.template(
"BUILD.bazel",
Label("@build_bazel_rules_nodejs//internal/node:BUILD.nodejs_host_os_alias.tpl"),
substitutions = {
"TEMPLATE__npm_node_repositories": "%s//:bin/npm_node_repositories%s" % (node_repository, file_ending),
"TEMPLATE__yarn_node_repositories": "%s//:bin/yarn_node_repositories%s" % (node_repository, file_ending),
"TEMPLATE_actual_node_bin": "%s//:%s" % (node_bin_repository, actual_node_bin),
"TEMPLATE_node_repo_args": "%s//:bin/node_repo_args.sh" % node_repository,
"TEMPLATE_npm": "%s//:bin/npm%s" % (node_repository, file_ending),
"TEMPLATE_run_npm": "%s//:run_npm.sh.template" % node_repository,
"TEMPLATE_wrapped_node_bin": "%s//:bin/node%s" % (node_repository, file_ending),
"TEMPLATE_yarn": "%s//:bin/yarn%s" % (node_repository, file_ending),
},
executable = False,
)
_nodejs_repo_host_os_alias = repository_rule(
_nodejs_host_os_alias_impl,
attrs = {
"vendored_node": attr.label(allow_single_file = True),
},
)
def node_repositories(**kwargs):
"""
Wrapper macro around node_repositories_rule to call it for each platform, register bazel toolchains,
and make other convenience repositories.
Note, the documentation is generated from the node_repositories_rule, not this macro.
"""
# 0.14.0: @bazel_tools//tools/bash/runfiles is required for nodejs
# 0.17.1: allow @ in package names is required for fine grained deps
# 0.21.0: repository_ctx.report_progress API
check_bazel_version(
message = """
A minimum Bazel version of 0.21.0 is required to use build_bazel_rules_nodejs.
""",
minimum_bazel_version = "0.21.0",
)
vendored_node = kwargs.pop("vendored_node", None)
# This needs to be setup so toolchains can access nodejs for all different versions
for os_arch_name in OS_ARCH_NAMES:
os_name = "_".join(os_arch_name)
node_repository_name = "nodejs_%s" % os_name
_maybe(
node_repositories_rule,
name = node_repository_name,
vendored_node = vendored_node,
**kwargs
)
native.register_toolchains("@build_bazel_rules_nodejs//toolchains/node:node_%s_toolchain" % os_arch_name[0])
node_toolchain_configure(
name = "%s_config" % node_repository_name,
target_tool = "@%s//:node_bin" % node_repository_name,
)
# This "nodejs" repo is just for convinience so one does not have to target @nodejs_<os_name>//...
# All it does is create aliases to the @nodejs_<host_os>_<host_arch> repository
_maybe(
_nodejs_repo_host_os_alias,
name = "nodejs",
vendored_node = vendored_node,
)
_maybe(
yarn_install,
name = "build_bazel_rules_nodejs_rollup_deps",
package_json = "@build_bazel_rules_nodejs//internal/rollup:package.json",
yarn_lock = "@build_bazel_rules_nodejs//internal/rollup:yarn.lock",
data = ["@build_bazel_rules_nodejs//internal/rollup:postinstall-patches.js"],
# Do not symlink node_modules as when used in downstream repos we should not create
# node_modules folders in the @build_bazel_rules_nodejs external repository. This is
# not supported by managed_directories.
symlink_node_modules = False,
)
def _maybe(repo_rule, name, **kwargs):
if name not in native.existing_rules():
repo_rule(name = name, **kwargs)