Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Node.js toolchain support #898

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ bzl_library(
"//internal/jasmine_node_test:bzl",
"//internal/npm_package:bzl",
"//internal/rollup:bzl",
"//toolchains/node:bzl",
],
)

Expand Down Expand Up @@ -68,6 +69,7 @@ npm_package(
"//internal/npm_install:package_contents",
"//internal/npm_package:package_contents",
"//internal/web_package:package_contents",
"//toolchains/node:package_contents",
],
)

Expand Down
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,24 @@ Note: the arguments passed to `bazel run` after `--` are forwarded to the execut

[bazel instructions]: https://docs.bazel.build/versions/master/install.html

### Toolchains

When you add `node_repositories()` to your `WORKSPACE` file it will setup a node toolchain for all currently supported platforms, Linux, macOS and Windows. Amongst other things this adds support for cross-compilations as well as Remote Build Execution support. For more detailed information also see [Bazel Toolchains](https://docs.bazel.build/versions/master/toolchains.html).

If you have an advanced use-case you can also register your own toolchains and call `node_toolchain_configure` directly to manually setup a toolchain.

#### Cross-compilation

Toolchains allow us to support cross-compilation, e.g. building a linux binary from mac or windows. To tell Bazel to provide a toolchain for a different platform you have to pass in the `--platforms` flag. Currently supported values are:

- `@build_bazel_rules_nodejs//toolchains/node:linux_amd64`
- `@build_bazel_rules_nodejs//toolchains/node:darwin_amd64`
- `@build_bazel_rules_nodejs//toolchains/node:windows_amd64`

So if for example you want to build a docker image from a non-linux platform you would run `bazel build --platforms=@build_bazel_rules_nodejs//toolchains/node:linux_amd64 //app`, which will ensure that the linux nodejs binary is downloaded and provided to the nodejs_binary target.

Note: The toolchain currently only provides a platform-specific nodejs binary. Any native modules will still be fetched/built, by npm/yarn, for your host platform, so they will not work on the target platform. Support for cross-compilation with native dependencies will follow.

## Usage

### Running a program from npm
Expand Down
29 changes: 23 additions & 6 deletions internal/common/os_name.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,38 @@
"""Helper function for repository rules
"""

def os_name(repository_ctx):
OS_ARCH_NAMES = [
("darwin", "amd64"),
("windows", "amd64"),
("linux", "amd64"),
]

OS_NAMES = ["_".join(os_arch_name) for os_arch_name in OS_ARCH_NAMES]

def os_name(rctx):
"""Get the os name for a repository rule

Args:
repository_ctx: The repository rule context
rctx: The repository rule context

Returns:
A string describing the os for a repository rule
"""
os_name = repository_ctx.os.name.lower()
os_name = rctx.os.name.lower()
if os_name.startswith("mac os"):
return "darwin_amd64"
return OS_NAMES[0]
elif os_name.find("windows") != -1:
return "windows_amd64"
return OS_NAMES[1]
elif os_name.startswith("linux"):
return "linux_amd64"
return OS_NAMES[2]
else:
fail("Unsupported operating system: " + os_name)

def is_darwin_os(rctx):
return os_name(rctx) == OS_NAMES[0]

def is_windows_os(rctx):
return os_name(rctx) == OS_NAMES[1]

def is_linux_os(rctx):
return os_name(rctx) == OS_NAMES[2]
5 changes: 2 additions & 3 deletions internal/copy_repository/copy_repository.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,14 @@
"""Custom copy_repository rule used by npm_install and yarn_install.
"""

load("@build_bazel_rules_nodejs//internal/common:os_name.bzl", "os_name")
load("@build_bazel_rules_nodejs//internal/common:os_name.bzl", "is_windows_os")

def _copy_file(rctx, src):
rctx.template(src.basename, src)

def _copy_repository_impl(rctx):
src_path = "/".join(str(rctx.path(rctx.attr.marker_file)).split("/")[:-1])
is_windows = os_name(rctx).find("windows") != -1
if is_windows:
if is_windows_os(rctx):
_copy_file(rctx, rctx.path(Label("@build_bazel_rules_nodejs//internal/copy_repository:_copy.bat")))
result = rctx.execute(["cmd.exe", "/C", "_copy.bat", src_path.replace("/", "\\"), "."])
else:
Expand Down
6 changes: 5 additions & 1 deletion internal/node/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ exports_files([
"node_repositories.bzl", # Exported to be consumed for generating skydoc.
"node_launcher.sh",
"node_loader.js",
"BUILD.nodejs_host_os_alias.tpl",
])

filegroup(
Expand All @@ -39,6 +40,9 @@ filegroup(
"*.bzl",
"*.js",
"*.sh",
]) + ["BUILD.bazel"],
]) + [
"BUILD.bazel",
"BUILD.nodejs_host_os_alias.tpl",
],
visibility = ["//:__pkg__"],
)
27 changes: 27 additions & 0 deletions internal/node/BUILD.nodejs_host_os_alias.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Generated by node_repositories.bzl
package(default_visibility = ["//visibility:public"])

# aliases for exports_files
alias(name = "run_npm.sh.template", actual = "TEMPLATE_run_npm")
alias(name = "bin/node_repo_args.sh", actual = "TEMPLATE_node_repo_args")
# windows specific aliases
alias(name = "bin/nodejs/node.exe", actual = "TEMPLATE_actual_node_bin")
alias(name = "bin/node.cmd", actual = "TEMPLATE_wrapped_node_bin")
alias(name = "bin/npm.cmd", actual = "TEMPLATE_npm")
alias(name = "bin/npm_node_repositories.cmd", actual = "TEMPLATE__npm_node_repositories")
alias(name = "bin/yarn.cmd", actual = "TEMPLATE_yarn")
alias(name = "bin/yarn_node_repositories.cmd", actual = "TEMPLATE__yarn_node_repositories")
# linux/mac specific aliases
alias(name = "bin/nodejs/bin/node", actual = "TEMPLATE_actual_node_bin")
alias(name = "bin/node", actual = "TEMPLATE_wrapped_node_bin")
alias(name = "bin/npm", actual = "TEMPLATE_npm")
alias(name = "bin/npm_node_repositories", actual = "TEMPLATE__npm_node_repositories")
alias(name = "bin/yarn", actual = "TEMPLATE_yarn")
alias(name = "bin/yarn_node_repositories", actual = "TEMPLATE__yarn_node_repositories")


# aliases for other aliases
alias(name = "node_bin", actual = "TEMPLATE_actual_node_bin")
alias(name = "node", actual = "TEMPLATE_wrapped_node_bin")
alias(name = "npm", actual = "TEMPLATE__npm_node_repositories")
alias(name = "yarn", actual = "TEMPLATE__yarn_node_repositories")
86 changes: 0 additions & 86 deletions internal/node/generate_build_file.js

This file was deleted.

62 changes: 35 additions & 27 deletions internal/node/node.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

"""Executing programs

These rules run the node binary with the given sources.
These rules run the node executable with the given sources.

They support module mapping: any targets in the transitive dependencies with
a `module_name` attribute can be `require`d by that name.
Expand Down Expand Up @@ -125,7 +125,6 @@ def _short_path_to_manifest_path(ctx, short_path):
return ctx.workspace_name + "/" + short_path

def _nodejs_binary_impl(ctx):
node = ctx.file.node
node_modules = depset(ctx.files.node_modules)

# Also include files from npm fine grained deps as inputs.
Expand Down Expand Up @@ -164,25 +163,38 @@ def _nodejs_binary_impl(ctx):
if hasattr(ctx.attr, "expected_exit_code"):
expected_exit_code = ctx.attr.expected_exit_code

substitutions = {
"TEMPLATED_args": " ".join([
expand_location_into_runfiles(ctx, a)
for a in ctx.attr.templated_args
]),
"TEMPLATED_env_vars": env_vars,
"TEMPLATED_expected_exit_code": str(expected_exit_code),
"TEMPLATED_node": _short_path_to_manifest_path(ctx, node.short_path),
"TEMPLATED_repository_args": _short_path_to_manifest_path(ctx, ctx.file._repository_args.short_path),
"TEMPLATED_script_path": script_path,
}
ctx.actions.expand_template(
template = ctx.file._launcher_template,
output = ctx.outputs.script,
substitutions = substitutions,
is_executable = True,
)
node_tool_info = ctx.toolchains["@build_bazel_rules_nodejs//toolchains/node:toolchain_type"].nodeinfo
node_tool_files = []
if node_tool_info.target_tool_path == "" and not node_tool_info.target_tool:
# If tool_path is empty and tool_target is None then there is no local
# node tool, we will just print a nice error message if the user
# attempts to do bazel run
fail("The node toolchain was not properly configured so %s cannot be executed. Make sure that target_tool_path or target_tool is set." % ctx.attr.name)
else:
node_tool = node_tool_info.target_tool_path
if node_tool_info.target_tool:
node_tool_files += node_tool_info.target_tool.files.to_list()
node_tool = _short_path_to_manifest_path(ctx, node_tool_files[0].short_path)

runfiles = depset([node, ctx.outputs.loader, ctx.file._repository_args], transitive = [sources, node_modules])
substitutions = {
"TEMPLATED_args": " ".join([
expand_location_into_runfiles(ctx, a)
for a in ctx.attr.templated_args
]),
"TEMPLATED_env_vars": env_vars,
"TEMPLATED_expected_exit_code": str(expected_exit_code),
"TEMPLATED_node": node_tool,
"TEMPLATED_repository_args": _short_path_to_manifest_path(ctx, ctx.file._repository_args.short_path),
"TEMPLATED_script_path": script_path,
}
ctx.actions.expand_template(
template = ctx.file._launcher_template,
output = ctx.outputs.script,
substitutions = substitutions,
is_executable = True,
)

runfiles = depset(node_tool_files + [ctx.outputs.loader, ctx.file._repository_args], transitive = [sources, node_modules])

# entry point is only needed in runfiles if it is a .js file
if ctx.file.entry_point.extension == "js":
Expand All @@ -192,8 +204,7 @@ def _nodejs_binary_impl(ctx):
executable = ctx.outputs.script,
runfiles = ctx.runfiles(
transitive_files = runfiles,
files = [
node,
files = node_tool_files + [
ctx.outputs.loader,
] + ctx.files._source_map_support_files +

Expand Down Expand Up @@ -292,11 +303,6 @@ _NODEJS_EXECUTABLE_ATTRS = {
in TypeScript.""",
default = True,
),
"node": attr.label(
doc = """The node entry point target.""",
default = Label("@nodejs//:node_bin"),
allow_single_file = True,
),
"node_modules": attr.label(
doc = """The npm packages which should be available to `require()` during
execution.
Expand Down Expand Up @@ -406,6 +412,7 @@ nodejs_binary = rule(
attrs = _NODEJS_EXECUTABLE_ATTRS,
executable = True,
outputs = _NODEJS_EXECUTABLE_OUTPUTS,
toolchains = ["@build_bazel_rules_nodejs//toolchains/node:toolchain_type"],
)
"""Runs some JavaScript code in NodeJS.
"""
Expand All @@ -420,6 +427,7 @@ nodejs_test = rule(
}),
test = True,
outputs = _NODEJS_EXECUTABLE_OUTPUTS,
toolchains = ["@build_bazel_rules_nodejs//toolchains/node:toolchain_type"],
)
"""
Identical to `nodejs_binary`, except this can be used with `bazel test` as well.
Expand Down
Loading