Skip to content

Commit ceddd1d

Browse files
authored
feat(builtin): document how nodejs_binary#entry_point can use a direc… (bazel-contrib#2579)
* feat(builtin): document how nodejs_binary#entry_point can use a directory * refactor: rename to directory_entry_point per discussion * refactor: make the feature more general, suitable to upstream to bazel-skylib
1 parent c9fea94 commit ceddd1d

File tree

11 files changed

+208
-24
lines changed

11 files changed

+208
-24
lines changed

.bazelci/presubmit.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ tasks:
379379
test_flags:
380380
# Firefox not supported on Windows with rules_webtesting (if run it exit with success)
381381
# See https://github.com/bazelbuild/rules_webtesting/blob/0.3.3/browsers/BUILD.bazel#L66.
382-
- "--test_tag_filters=-e2e,-examples,-fix-windows,-manual,-browser:firefox-local,-cypress"
382+
- "--test_tag_filters=-e2e,-examples,-fix-windows,-no-bazelci-windows,-manual,-browser:firefox-local,-cypress"
383383
test_targets:
384384
- "//..."
385385
# //internal/node/test:nodejs_toolchain_windows_amd64_test is a "manual" test that must be run

index.bzl

+5
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ load("//internal/node:npm_package_bin.bzl", _npm_bin = "npm_package_bin")
3434
load("//internal/npm_install:npm_install.bzl", _npm_install = "npm_install", _yarn_install = "yarn_install")
3535
load("//internal/pkg_npm:pkg_npm.bzl", _pkg_npm = "pkg_npm_macro")
3636
load("//internal/pkg_web:pkg_web.bzl", _pkg_web = "pkg_web")
37+
load(
38+
"//internal/providers:tree_artifacts.bzl",
39+
_directory_file_path = "directory_file_path",
40+
)
3741

3842
check_bazel_version = _check_bazel_version
3943
nodejs_binary = _nodejs_binary
@@ -46,6 +50,7 @@ copy_to_bin = _copy_to_bin
4650
params_file = _params_file
4751
generated_file_test = _generated_file_test
4852
js_library = _js_library
53+
directory_file_path = _directory_file_path
4954
# ANY RULES ADDED HERE SHOULD BE DOCUMENTED, see index.for_docs.bzl
5055

5156
# Allows us to avoid a transitive dependency on bazel_skylib from leaking to users

internal/common/assert.bzl

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"helpers for test assertions"
2+
3+
load("@bazel_skylib//rules:diff_test.bzl", "diff_test")
4+
load("@bazel_skylib//rules:write_file.bzl", "write_file")
5+
load("@build_bazel_rules_nodejs//:index.bzl", "npm_package_bin")
6+
7+
def assert_program_produces_stdout(name, tool, stdout, tags = []):
8+
write_file(
9+
name = "_write_expected_" + name,
10+
out = "expected_" + name,
11+
content = stdout,
12+
tags = tags,
13+
)
14+
15+
npm_package_bin(
16+
name = "_write_actual_" + name,
17+
tool = tool,
18+
stdout = "actual_" + name,
19+
tags = tags,
20+
)
21+
22+
diff_test(
23+
name = name,
24+
file1 = "expected_" + name,
25+
file2 = "actual_" + name,
26+
tags = tags,
27+
)

internal/node/launcher.sh

+3
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,9 @@ else
357357
register_source_map_support=$(rlocation build_bazel_rules_nodejs/third_party/github.com/source-map-support/register.js)
358358
LAUNCHER_NODE_OPTIONS+=( "--require" "${register_source_map_support}" )
359359
fi
360+
if [[ -n "TEMPLATED_entry_point_main" ]]; then
361+
MAIN="${MAIN}/"TEMPLATED_entry_point_main
362+
fi
360363

361364
# The EXPECTED_EXIT_CODE lets us write bazel tests which assert that
362365
# a binary fails to run. Otherwise any failure would make such a test

internal/node/node.bzl

+21-10
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ They support module mapping: any targets in the transitive dependencies with
2020
a `module_name` attribute can be `require`d by that name.
2121
"""
2222

23-
load("//:providers.bzl", "ExternalNpmPackageInfo", "JSModuleInfo", "JSNamedModuleInfo", "NodeRuntimeDepsInfo", "node_modules_aspect")
23+
load("//:providers.bzl", "DirectoryFilePathInfo", "ExternalNpmPackageInfo", "JSModuleInfo", "JSNamedModuleInfo", "NodeRuntimeDepsInfo", "node_modules_aspect")
2424
load("//internal/common:expand_into_runfiles.bzl", "expand_location_into_runfiles")
2525
load("//internal/common:module_mappings.bzl", "module_mappings_runtime_aspect")
2626
load("//internal/common:path_utils.bzl", "strip_external")
@@ -106,11 +106,17 @@ def _ts_to_js(entry_point_path):
106106
return entry_point_path[:-4] + ".js"
107107
return entry_point_path
108108

109-
def _write_loader_script(ctx):
110-
if len(ctx.attr.entry_point.files.to_list()) != 1:
109+
def _get_entry_point_file(ctx):
110+
if len(ctx.attr.entry_point.files.to_list()) > 1:
111111
fail("labels in entry_point must contain exactly one file")
112+
if len(ctx.files.entry_point) == 1:
113+
return ctx.files.entry_point[0]
114+
if DirectoryFilePathInfo in ctx.attr.entry_point:
115+
return ctx.attr.entry_point[DirectoryFilePathInfo].directory
116+
fail("entry_point must either be a file, or provide DirectoryFilePathInfo")
112117

113-
entry_point_path = _ts_to_js(_to_manifest_path(ctx, ctx.file.entry_point))
118+
def _write_loader_script(ctx):
119+
entry_point_path = _ts_to_js(_to_manifest_path(ctx, _get_entry_point_file(ctx)))
114120

115121
ctx.actions.expand_template(
116122
template = ctx.file._loader_template,
@@ -310,8 +316,12 @@ fi
310316
#else:
311317
# substitutions["TEMPLATED_script_path"] = "$(rlocation \"%s\")" % _to_manifest_path(ctx, ctx.file.entry_point)
312318
# For now we need to look in both places
313-
substitutions["TEMPLATED_entry_point_execroot_path"] = "\"%s\"" % _ts_to_js(_to_execroot_path(ctx, ctx.file.entry_point))
314-
substitutions["TEMPLATED_entry_point_manifest_path"] = "$(rlocation \"%s\")" % _ts_to_js(_to_manifest_path(ctx, ctx.file.entry_point))
319+
substitutions["TEMPLATED_entry_point_execroot_path"] = "\"%s\"" % _ts_to_js(_to_execroot_path(ctx, _get_entry_point_file(ctx)))
320+
substitutions["TEMPLATED_entry_point_manifest_path"] = "$(rlocation \"%s\")" % _ts_to_js(_to_manifest_path(ctx, _get_entry_point_file(ctx)))
321+
if DirectoryFilePathInfo in ctx.attr.entry_point:
322+
substitutions["TEMPLATED_entry_point_main"] = ctx.attr.entry_point[DirectoryFilePathInfo].path
323+
else:
324+
substitutions["TEMPLATED_entry_point_main"] = ""
315325

316326
ctx.actions.expand_template(
317327
template = ctx.file._launcher_template,
@@ -326,9 +336,10 @@ fi
326336
else:
327337
executable = ctx.outputs.launcher_sh
328338

339+
# syntax sugar: allows you to avoid repeating the entry point in data
329340
# entry point is only needed in runfiles if it is a .js file
330-
if ctx.file.entry_point.extension == "js":
331-
runfiles.append(ctx.file.entry_point)
341+
if len(ctx.files.entry_point) == 1 and ctx.files.entry_point[0].extension == "js":
342+
runfiles.extend(ctx.files.entry_point)
332343

333344
return [
334345
DefaultInfo(
@@ -351,7 +362,7 @@ fi
351362
# TODO(alexeagle): remove sources and node_modules from the runfiles
352363
# when downstream usage is ready to rely on linker
353364
NodeRuntimeDepsInfo(
354-
deps = depset([ctx.file.entry_point], transitive = [node_modules, sources]),
365+
deps = depset(ctx.files.entry_point, transitive = [node_modules, sources]),
355366
pkgs = ctx.attr.data,
356367
),
357368
# indicates that the this binary should be instrumented by coverage
@@ -462,7 +473,7 @@ nodejs_binary(
462473
```
463474
""",
464475
mandatory = True,
465-
allow_single_file = True,
476+
allow_files = True,
466477
),
467478
"env": attr.string_dict(
468479
doc = """Specifies additional environment variables to set when the target is executed, subject to location
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
load("@bazel_skylib//rules:write_file.bzl", "write_file")
2+
load("@build_bazel_rules_nodejs//:index.bzl", "directory_file_path", "nodejs_binary", "pkg_npm")
3+
load("@npm//typescript:index.bzl", "tsc")
4+
load("//internal/common:assert.bzl", "assert_program_produces_stdout")
5+
6+
filegroup(
7+
name = "ts_sources",
8+
srcs = [
9+
"a.ts",
10+
"b.ts",
11+
],
12+
)
13+
14+
# As the test fixture, produce a directory of js files
15+
tsc(
16+
name = "build",
17+
args = [
18+
"$(execpaths :ts_sources)",
19+
"--outDir",
20+
"$(@D)",
21+
],
22+
data = [
23+
":ts_sources",
24+
"@npm//@types/node",
25+
],
26+
output_dir = True,
27+
)
28+
29+
#############
30+
# Test Case 1
31+
# An pkg_npm can be an entry_point, using the standard npm semantics.
32+
# First, it needs to declare a "main" field in the package.json ...
33+
write_file(
34+
name = "write_package_json",
35+
out = "package.json",
36+
content = [json.encode({
37+
"main": "build/a.js",
38+
})],
39+
)
40+
41+
# ... then declare the package ...
42+
pkg_npm(
43+
name = "package",
44+
deps = [
45+
"build",
46+
"package.json",
47+
],
48+
)
49+
50+
# ... and finally run the program with the package as the entry_point.
51+
nodejs_binary(
52+
name = "run_a",
53+
data = ["package"],
54+
entry_point = "package",
55+
)
56+
57+
# Now just assert that running that program produces the expected stdout
58+
assert_program_produces_stdout(
59+
name = "test_a",
60+
stdout = ["running entry point A"],
61+
# This requires runfiles
62+
tags = ["no-bazelci-windows"],
63+
tool = "run_a",
64+
)
65+
66+
#############
67+
# Test Case 2
68+
# Without the pkg_npm, we should still be able to run the program.
69+
# But we need an adapter rule that gives us the equivalent of the "main" field
70+
71+
directory_file_path(
72+
name = "entry_point_b",
73+
directory = "build",
74+
path = "b.js",
75+
)
76+
77+
nodejs_binary(
78+
name = "run_b",
79+
data = ["build"],
80+
entry_point = "entry_point_b",
81+
)
82+
83+
# Now just assert that running that program produces the expected stdout
84+
assert_program_produces_stdout(
85+
name = "test_b",
86+
stdout = ["running entry point B"],
87+
tool = "run_b",
88+
)
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
if (require.main === module) {
2+
process.stdout.write('running entry point A')
3+
}
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
if (require.main === module) {
2+
process.stdout.write('running entry point B')
3+
}

internal/providers/tree_artifacts.bzl

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Copyright 2017 The Bazel Authors. All rights reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""This module contains providers for working with TreeArtifacts.
16+
17+
See https://github.com/bazelbuild/bazel-skylib/issues/300
18+
(this feature could be upstreamed to bazel-skylib in the future)
19+
20+
These are also called output directories, created by `ctx.actions.declare_directory`.
21+
"""
22+
23+
DirectoryFilePathInfo = provider(
24+
doc = "Joins a label pointing to a TreeArtifact with a path nested within that directory.",
25+
fields = {
26+
"directory": "a TreeArtifact (ctx.actions.declare_directory)",
27+
"path": "path relative to the directory",
28+
},
29+
)
30+
31+
def _directory_file_path(ctx):
32+
if not ctx.file.directory.is_directory:
33+
fail("directory attribute must be created with Bazel declare_directory (TreeArtifact)")
34+
return [DirectoryFilePathInfo(path = ctx.attr.path, directory = ctx.file.directory)]
35+
36+
directory_file_path = rule(
37+
doc = """Provide DirectoryFilePathInfo to reference some file within a directory.
38+
39+
Otherwise there is no way to give a Bazel label for it.""",
40+
implementation = _directory_file_path,
41+
attrs = {
42+
"directory": attr.label(
43+
doc = "a directory",
44+
mandatory = True,
45+
allow_single_file = True,
46+
),
47+
"path": attr.string(
48+
doc = "a path within that directory",
49+
mandatory = True,
50+
),
51+
},
52+
)

providers.bzl

+5
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ load(
4646
_NodeRuntimeDepsInfo = "NodeRuntimeDepsInfo",
4747
_run_node = "run_node",
4848
)
49+
load(
50+
"//internal/providers:tree_artifacts.bzl",
51+
_DirectoryFilePathInfo = "DirectoryFilePathInfo",
52+
)
4953

5054
DeclarationInfo = _DeclarationInfo
5155
declaration_info = _declaration_info
@@ -88,3 +92,4 @@ to create a NodeContextInfo.
8892

8993
NodeRuntimeDepsInfo = _NodeRuntimeDepsInfo
9094
run_node = _run_node
95+
DirectoryFilePathInfo = _DirectoryFilePathInfo

stardoc.patch

-13
This file was deleted.

0 commit comments

Comments
 (0)