Skip to content

Commit

Permalink
feat(builtin): add nodejs toolchain support
Browse files Browse the repository at this point in the history
  • Loading branch information
Globegitter authored and alexeagle committed Jul 9, 2019
1 parent 1dccfef commit 9afb8db
Show file tree
Hide file tree
Showing 19 changed files with 541 additions and 83 deletions.
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 @@ -472,6 +472,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_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
17 changes: 14 additions & 3 deletions internal/common/os_name.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@
"""Helper function for repository rules
"""

OS_ARCH_NAMES = [
("darwin", "amd64"),
("windows", "amd64"),
("linux", "amd64"),
]

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

def is_windows(os_name):
return os_name == OS_NAMES[1]

def os_name(repository_ctx):
"""Get the os name for a repository rule
Expand All @@ -26,10 +37,10 @@ def os_name(repository_ctx):
"""
os_name = repository_ctx.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)
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")
6 changes: 3 additions & 3 deletions internal/node/generate_build_file.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const fs = require('fs');
const path = require('path');

const IS_WINDOWS = TEMPLATED_is_windows;
const NODE_DIR = 'TEMPLATED_node_dir';
const IS_VENDORED_NODE = TEMPLATED_vendored_node;
const NODE_ACTUAL = 'TEMPLATED_node_actual';
const NODE_BIN_ACTUAL = 'TEMPLATED_node_bin_actual';
const NPM_ACTUAL = 'TEMPLATED_npm_actual';
Expand Down Expand Up @@ -65,12 +65,12 @@ module.exports = { main };

function generateBuildFile() {
const binaryExt = IS_WINDOWS ? '.cmd' : '';
const exportedNodeBin = IS_VENDORED_NODE ? '' : `\n "${NODE_BIN_ACTUAL}",`;
const buildFile = `# Generated by node_repositories.bzl
package(default_visibility = ["//visibility:public"])
exports_files([
"run_npm.sh.template",
"bin/node_repo_args.sh",
"${NODE_DIR}/bin/node",
"bin/node_repo_args.sh",${exportedNodeBin}
"bin/node${binaryExt}",
"bin/npm${binaryExt}",
"bin/npm_node_repositories${binaryExt}",
Expand Down
60 changes: 34 additions & 26 deletions internal/node/node.bzl
Original file line number Diff line number Diff line change
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
40 changes: 20 additions & 20 deletions internal/node/node_labels.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -17,37 +17,37 @@
Labels are different on windows and linux/OSX.
"""

def get_node_label(repository_ctx):
if repository_ctx.os.name.lower().find("windows") != -1:
label = Label("@nodejs//:bin/node.cmd")
def get_node_label(os_name):
if os_name.find("windows") != -1:
label = Label("@nodejs_%s//:bin/node.cmd" % os_name)
else:
label = Label("@nodejs//:bin/node")
label = Label("@nodejs_%s//:bin/node" % os_name)
return label

def get_npm_label(repository_ctx):
if repository_ctx.os.name.lower().find("windows") != -1:
label = Label("@nodejs//:bin/npm.cmd")
def get_npm_label(os_name):
if os_name.find("windows") != -1:
label = Label("@nodejs_%s//:bin/npm.cmd" % os_name)
else:
label = Label("@nodejs//:bin/npm")
label = Label("@nodejs_%s//:bin/npm" % os_name)
return label

def get_npm_node_repositories_label(repository_ctx):
if repository_ctx.os.name.lower().find("windows") != -1:
label = Label("@nodejs//:bin/npm_node_repositories.cmd")
def get_npm_node_repositories_label(os_name):
if os_name.find("windows") != -1:
label = Label("@nodejs_%s//:bin/npm_node_repositories.cmd" % os_name)
else:
label = Label("@nodejs//:bin/npm_node_repositories")
label = Label("@nodejs_%s//:bin/npm_node_repositories" % os_name)
return label

def get_yarn_label(repository_ctx):
if repository_ctx.os.name.lower().find("windows") != -1:
label = Label("@nodejs//:bin/yarn.cmd")
def get_yarn_label(os_name):
if os_name.find("windows") != -1:
label = Label("@nodejs_%s//:bin/yarn.cmd" % os_name)
else:
label = Label("@nodejs//:bin/yarn")
label = Label("@nodejs_%s//:bin/yarn" % os_name)
return label

def get_yarn_node_repositories_label(repository_ctx):
if repository_ctx.os.name.lower().find("windows") != -1:
label = Label("@nodejs//:bin/yarn_node_repositories.cmd")
def get_yarn_node_repositories_label(os_name):
if os_name.find("windows") != -1:
label = Label("@nodejs%s//:bin/yarn_node_repositories.cmd" % os_name)
else:
label = Label("@nodejs//:bin/yarn_node_repositories")
label = Label("@nodejs_%s//:bin/yarn_node_repositories" % os_name)
return label
Loading

0 comments on commit 9afb8db

Please sign in to comment.