Skip to content

Commit 10d136a

Browse files
committed
refactor(builtin): improve node toolchain
- Support the toolchains attribute of rules like genrule, so they can get the NODE_PATH - Expose files and a path to node, rather than whether the node binary is locally installed or came from Bazel fetching it
1 parent df37fca commit 10d136a

File tree

3 files changed

+111
-71
lines changed

3 files changed

+111
-71
lines changed

internal/node/node.bzl

Lines changed: 52 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,12 @@ def _write_loader_script(ctx):
119119
is_executable = True,
120120
)
121121

122-
def _short_path_to_manifest_path(ctx, short_path):
123-
if short_path.startswith("../"):
124-
return short_path[3:]
122+
# Avoid writing non-normalized paths (workspace/../other_workspace/path)
123+
def _to_manifest_path(ctx, file):
124+
if file.short_path.startswith("../"):
125+
return file.short_path[3:]
125126
else:
126-
return ctx.workspace_name + "/" + short_path
127+
return ctx.workspace_name + "/" + file.short_path
127128

128129
def _nodejs_binary_impl(ctx):
129130
node_modules = depset(ctx.files.node_modules)
@@ -147,14 +148,8 @@ def _nodejs_binary_impl(ctx):
147148

148149
_write_loader_script(ctx)
149150

150-
# Avoid writing non-normalized paths (workspace/../other_workspace/path)
151-
if ctx.outputs.loader.short_path.startswith("../"):
152-
script_path = ctx.outputs.loader.short_path[len("../"):]
153-
else:
154-
script_path = "/".join([
155-
ctx.workspace_name,
156-
ctx.outputs.loader.short_path,
157-
])
151+
script_path = _to_manifest_path(ctx, ctx.outputs.loader)
152+
158153
env_vars = "export BAZEL_TARGET=%s\n" % ctx.label
159154
for k in ctx.attr.configuration_env_vars + ctx.attr.default_env_vars:
160155
if k in ctx.var.keys():
@@ -165,61 +160,58 @@ def _nodejs_binary_impl(ctx):
165160
expected_exit_code = ctx.attr.expected_exit_code
166161

167162
node_tool_info = ctx.toolchains["@build_bazel_rules_nodejs//toolchains/node:toolchain_type"].nodeinfo
168-
node_tool_files = []
169-
if node_tool_info.target_tool_path == "" and not node_tool_info.target_tool:
163+
164+
# Make a copy so we don't try to mutate a frozen object
165+
node_tool_files = node_tool_info.tool_files[:]
166+
if node_tool_info.target_tool_path == "":
170167
# If tool_path is empty and tool_target is None then there is no local
171168
# node tool, we will just print a nice error message if the user
172169
# attempts to do bazel run
173170
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)
174-
else:
175-
node_tool = node_tool_info.target_tool_path
176-
if node_tool_info.target_tool:
177-
node_tool_files += node_tool_info.target_tool.files.to_list()
178-
node_tool = _short_path_to_manifest_path(ctx, node_tool_files[0].short_path)
179-
180-
if not ctx.outputs.templated_args_file:
181-
templated_args = ctx.attr.templated_args
182-
else:
183-
# Distribute the templated_args between the params file and the node options
184-
params = []
185-
templated_args = []
186-
for a in ctx.attr.templated_args:
187-
if a.startswith("--node_options="):
188-
templated_args.append(a)
189-
else:
190-
params.append(a)
191-
192-
# Put the params into the params file
193-
ctx.actions.write(
194-
output = ctx.outputs.templated_args_file,
195-
content = "\n".join([expand_location_into_runfiles(ctx, p) for p in params]),
196-
is_executable = False,
197-
)
198-
199-
# after the node_options args, pass the params file arg
200-
templated_args.append(ctx.outputs.templated_args_file.short_path)
201-
202-
# also be sure to include the params file in the program inputs
203-
node_tool_files += [ctx.outputs.templated_args_file]
204171

205-
substitutions = {
206-
"TEMPLATED_args": " ".join([
207-
expand_location_into_runfiles(ctx, a)
208-
for a in templated_args
209-
]),
210-
"TEMPLATED_env_vars": env_vars,
211-
"TEMPLATED_expected_exit_code": str(expected_exit_code),
212-
"TEMPLATED_node": node_tool,
213-
"TEMPLATED_repository_args": _short_path_to_manifest_path(ctx, ctx.file._repository_args.short_path),
214-
"TEMPLATED_script_path": script_path,
215-
}
216-
ctx.actions.expand_template(
217-
template = ctx.file._launcher_template,
218-
output = ctx.outputs.script,
219-
substitutions = substitutions,
220-
is_executable = True,
172+
if not ctx.outputs.templated_args_file:
173+
templated_args = ctx.attr.templated_args
174+
else:
175+
# Distribute the templated_args between the params file and the node options
176+
params = []
177+
templated_args = []
178+
for a in ctx.attr.templated_args:
179+
if a.startswith("--node_options="):
180+
templated_args.append(a)
181+
else:
182+
params.append(a)
183+
184+
# Put the params into the params file
185+
ctx.actions.write(
186+
output = ctx.outputs.templated_args_file,
187+
content = "\n".join([expand_location_into_runfiles(ctx, p) for p in params]),
188+
is_executable = False,
221189
)
222190

191+
# after the node_options args, pass the params file arg
192+
templated_args.append(ctx.outputs.templated_args_file.short_path)
193+
194+
# also be sure to include the params file in the program inputs
195+
node_tool_files.append(ctx.outputs.templated_args_file)
196+
197+
substitutions = {
198+
"TEMPLATED_args": " ".join([
199+
expand_location_into_runfiles(ctx, a)
200+
for a in templated_args
201+
]),
202+
"TEMPLATED_env_vars": env_vars,
203+
"TEMPLATED_expected_exit_code": str(expected_exit_code),
204+
"TEMPLATED_node": node_tool_info.target_tool_path,
205+
"TEMPLATED_repository_args": _to_manifest_path(ctx, ctx.file._repository_args),
206+
"TEMPLATED_script_path": script_path,
207+
}
208+
ctx.actions.expand_template(
209+
template = ctx.file._launcher_template,
210+
output = ctx.outputs.script,
211+
substitutions = substitutions,
212+
is_executable = True,
213+
)
214+
223215
runfiles = depset(node_tool_files + [ctx.outputs.loader, ctx.file._repository_args], transitive = [sources, node_modules])
224216

225217
if is_windows(ctx):

toolchains/node/BUILD.bazel

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,33 @@ filegroup(
5656
# node toolchain type
5757
toolchain_type(name = "toolchain_type")
5858

59+
# Allow targets to use a toolchains attribute, such as sh_binary and genrule
60+
# This way they can reference the NODE_PATH make variable.
61+
alias(
62+
name = "toolchain",
63+
actual = select({
64+
"@bazel_tools//src/conditions:darwin": "@nodejs_darwin_amd64_config//:toolchain",
65+
"@bazel_tools//src/conditions:darwin_x86_64": "@nodejs_darwin_amd64_config//:toolchain",
66+
"@bazel_tools//src/conditions:linux_x86_64": "@nodejs_linux_amd64_config//:toolchain",
67+
"@bazel_tools//src/conditions:windows": "@nodejs_windows_amd64_config//:toolchain",
68+
"//conditions:default": "@nodejs_linux_amd64_config//:toolchain",
69+
}),
70+
visibility = ["//visibility:public"],
71+
)
72+
73+
# Allow targets to declare a dependency on the node binary for the current host platform
74+
alias(
75+
name = "node_bin",
76+
actual = select({
77+
"@bazel_tools//src/conditions:darwin": "@nodejs_darwin_amd64//:node_bin",
78+
"@bazel_tools//src/conditions:darwin_x86_64": "@nodejs_darwin_amd64//:node_bin",
79+
"@bazel_tools//src/conditions:linux_x86_64": "@nodejs_linux_amd64//:node_bin",
80+
"@bazel_tools//src/conditions:windows": "@nodejs_windows_amd64//:node_bin",
81+
"//conditions:default": "@nodejs_linux_amd64//:node_bin",
82+
}),
83+
visibility = ["//visibility:public"],
84+
)
85+
5986
toolchain(
6087
name = "node_linux_toolchain",
6188
target_compatible_with = [

toolchains/node/node_toolchain.bzl

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,35 +18,56 @@ This module implements the node toolchain rule.
1818
NodeInfo = provider(
1919
doc = "Information about how to invoke the node executable.",
2020
fields = {
21-
"target_tool": "A hermetically downloaded nodejs executable target for the target platform.",
22-
"target_tool_path": "Path to an existing nodejs executable for the target platform..",
21+
"target_tool_path": "Path to the nodejs executable for the target platform.",
22+
"tool_files": """Files required in runfiles to make the nodejs executable available.
23+
24+
May be empty if the target_tool_path points to a locally installed node binary.""",
2325
},
2426
)
2527

28+
def _to_manifest_path(ctx, file):
29+
if file.short_path.startswith("../"):
30+
return file.short_path[3:]
31+
else:
32+
return ctx.workspace_name + "/" + file.short_path
33+
2634
def _node_toolchain_impl(ctx):
2735
if ctx.attr.target_tool and ctx.attr.target_tool_path:
28-
fail("Can only set one of target_tool or target_tool_path but both where set.")
36+
fail("Can only set one of target_tool or target_tool_path but both were set.")
2937
if not ctx.attr.target_tool and not ctx.attr.target_tool_path:
3038
fail("Must set one of target_tool or target_tool_path.")
3139

32-
toolchain_info = platform_common.ToolchainInfo(
33-
nodeinfo = NodeInfo(
34-
target_tool_path = ctx.attr.target_tool_path,
35-
target_tool = ctx.attr.target_tool,
40+
tool_files = []
41+
target_tool_path = ctx.attr.target_tool_path
42+
43+
if ctx.attr.target_tool:
44+
tool_files = ctx.attr.target_tool.files.to_list()
45+
target_tool_path = _to_manifest_path(ctx, tool_files[0])
46+
47+
return [
48+
platform_common.ToolchainInfo(
49+
nodeinfo = NodeInfo(
50+
target_tool_path = target_tool_path,
51+
tool_files = tool_files,
52+
),
3653
),
37-
)
38-
return [toolchain_info]
54+
# Make the $(NODE_PATH) variable available in places like genrules.
55+
# See https://docs.bazel.build/versions/master/be/make-variables.html#custom_variables
56+
platform_common.TemplateVariableInfo({
57+
"NODE_PATH": target_tool_path,
58+
}),
59+
]
3960

4061
node_toolchain = rule(
4162
implementation = _node_toolchain_impl,
4263
attrs = {
4364
"target_tool": attr.label(
44-
doc = "A hermetically downloaded nodejs executable target for the target platform..",
65+
doc = "A hermetically downloaded nodejs executable target for the target platform.",
4566
mandatory = False,
4667
allow_single_file = True,
4768
),
4869
"target_tool_path": attr.string(
49-
doc = "Path to an existing nodejs executable for the target platform..",
70+
doc = "Path to an existing nodejs executable for the target platform.",
5071
mandatory = False,
5172
),
5273
},

0 commit comments

Comments
 (0)