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 committed Jun 13, 2019
1 parent c8e61c5 commit ac12658
Show file tree
Hide file tree
Showing 10 changed files with 353 additions and 60 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 @@ -65,6 +66,7 @@ npm_package(
"//internal/npm_install:package_contents",
"//internal/npm_package:package_contents",
"//internal/web_package:package_contents",
"//toolchains/node:package_contents",
],
)

Expand Down
14 changes: 11 additions & 3 deletions internal/common/os_name.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@
"""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 os_name(repository_ctx):
"""Get the os name for a repository rule
Expand All @@ -26,10 +34,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)
63 changes: 37 additions & 26 deletions internal/node/node.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,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 @@ -161,25 +160,41 @@ 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
ctx.actions.write(
content = ("echo node toolchain was not properly configured so %s cannot be executed." % ctx.attr.name),
output = ctx.outputs.script,
)
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 @@ -189,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 @@ -289,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 @@ -403,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 @@ -417,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
42 changes: 38 additions & 4 deletions internal/node/node_repositories.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ 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_name")
load("//internal/common:os_name.bzl", "OS_ARCH_NAMES", "OS_NAMES", "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_configure.bzl", node_toolchain_configure = "node_configure")
load(":node_labels.bzl", "get_yarn_node_repositories_label")

# Callers that don't specify a particular version will get these.
Expand Down Expand Up @@ -130,8 +131,10 @@ def _download_node(repository_ctx):
"""
if repository_ctx.attr.vendored_node:
return

host = os_name(repository_ctx)
if repository_ctx.name == "nodejs":
host = os_name(repository_ctx)
else:
host = 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
Expand Down Expand Up @@ -441,7 +444,13 @@ if %errorlevel% neq 0 exit /b %errorlevel%
"TEMPLATED_yarn_actual": yarn_node_repositories_entry,
},
)
result = repository_ctx.execute([node_entry, "generate_build_file.js"])
host_os = os_name(repository_ctx)
if host_os in repository_ctx.attr.name or repository_ctx.attr.name == "nodejs":
# We have to use the relative path here otherwise bazel reports a cycle
result = repository_ctx.execute([node_entry, "generate_build_file.js"])
else:
fail("The repository name has to be either 'nodejs' or include one of the following OS identifiers in its name: %s" % ", ".join([os for os, _ in OS_ARCH_NAMES]))

if result.return_code:
fail("generate_build_file.js failed: \nSTDOUT:\n%s\nSTDERR:\n%s" % (result.stdout, result.stderr))

Expand Down Expand Up @@ -567,6 +576,9 @@ def node_repositories(
minimum_bazel_version = "0.21.0",
)

# This "nodejs" repo is just for convinience so one does not have to target @nodejs_<os_name>//...
# At macro time we can unfortunately not figure out the host os so that means there will be a
# duplicate set of external repositories
_maybe(
_nodejs_repo,
name = "nodejs",
Expand All @@ -582,6 +594,28 @@ def node_repositories(
preserve_symlinks = preserve_symlinks,
)

# This needs to be setup so toolchains can access nodejs for all different versions
node_repository_names = []
for os_name in OS_NAMES:
node_repository_name = "nodejs_%s" % os_name
_maybe(
_nodejs_repo,
name = node_repository_name,
package_json = package_json,
node_version = node_version,
yarn_version = yarn_version,
vendored_node = vendored_node,
vendored_yarn = vendored_yarn,
node_repositories = node_repositories,
yarn_repositories = yarn_repositories,
node_urls = node_urls,
yarn_urls = yarn_urls,
preserve_symlinks = preserve_symlinks,
)
node_repository_names.append(node_repository_name)

node_toolchain_configure(node_repository_names)

_maybe(
_yarn_repo,
name = "yarn",
Expand Down
15 changes: 8 additions & 7 deletions internal/npm_install/npm_install.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def _create_build_files(repository_ctx, node, lock_file):
repository_ctx.attr.name,
str(lock_file),
",".join(repository_ctx.attr.included_files),
])
], quiet = False)
if result.return_code:
fail("generate_build_file.js failed: \nSTDOUT:\n%s\nSTDERR:\n%s" % (result.stdout, result.stderr))

Expand Down Expand Up @@ -166,9 +166,10 @@ def _npm_install_impl(repository_ctx):

_check_min_bazel_version("npm_install", repository_ctx)

is_windows = os_name(repository_ctx).find("windows") != -1
node = repository_ctx.path(get_node_label(repository_ctx))
npm = get_npm_label(repository_ctx)
os = os_name(repository_ctx)
is_windows = os.find("windows") != -1
node = repository_ctx.path(get_node_label(os))
npm = get_npm_label(os)
npm_args = ["install"]

if repository_ctx.attr.prod_only:
Expand Down Expand Up @@ -278,8 +279,9 @@ def _yarn_install_impl(repository_ctx):

_check_min_bazel_version("yarn_install", repository_ctx)

node = repository_ctx.path(get_node_label(repository_ctx))
yarn = get_yarn_label(repository_ctx)
os = os_name(repository_ctx)
node = repository_ctx.path(get_node_label(os))
yarn = get_yarn_label(os)

# If symlink_node_modules is true then run the package manager
# in the package.json folder; otherwise, run it in the root of
Expand Down Expand Up @@ -333,7 +335,6 @@ def _yarn_install_impl(repository_ctx):
timeout = repository_ctx.attr.timeout,
quiet = repository_ctx.attr.quiet,
)

if result.return_code:
fail("yarn_install failed: %s (%s)" % (result.stdout, result.stderr))

Expand Down
Loading

0 comments on commit ac12658

Please sign in to comment.