Skip to content

Commit 3f182f0

Browse files
mattemalexeagle
authored andcommitted
feat: add stdout capture to npm_package_bin
1 parent 080b460 commit 3f182f0

8 files changed

Lines changed: 113 additions & 5 deletions

internal/node/launcher.sh

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,9 +194,14 @@ ARGS=()
194194
LAUNCHER_NODE_OPTIONS=( "--require" "$require_patch_script" )
195195
USER_NODE_OPTIONS=()
196196
ALL_ARGS=(TEMPLATED_args $NODE_REPOSITORY_ARGS "$@")
197+
STDOUT_CAPTURE=""
198+
STDERR_CAPTURE=""
199+
197200
for ARG in "${ALL_ARGS[@]:-}"; do
198201
case "$ARG" in
199202
--bazel_node_modules_manifest=*) MODULES_MANIFEST="${ARG#--bazel_node_modules_manifest=}" ;;
203+
--bazel_capture_stdout=*) STDOUT_CAPTURE="${ARG#--bazel_capture_stdout=}" ;;
204+
--bazel_capture_stderr=*) STDERR_CAPTURE="${ARG#--bazel_capture_stderr=}" ;;
200205
--nobazel_patch_module_resolver)
201206
MAIN=TEMPLATED_entry_point_execroot_path
202207
LAUNCHER_NODE_OPTIONS=( "--require" "$node_patches_script" )
@@ -283,7 +288,17 @@ _int() {
283288

284289
# Execute the main program
285290
set +e
286-
"${node}" "${LAUNCHER_NODE_OPTIONS[@]:-}" "${USER_NODE_OPTIONS[@]:-}" "${MAIN}" ${ARGS[@]+"${ARGS[@]}"} <&0 &
291+
292+
if [[ -n "${STDOUT_CAPTURE}" ]] && [[ -n "${STDERR_CAPTURE}" ]]; then
293+
"${node}" "${LAUNCHER_NODE_OPTIONS[@]:-}" "${USER_NODE_OPTIONS[@]:-}" "${MAIN}" ${ARGS[@]+"${ARGS[@]}"} <&0 >$STDOUT_CAPTURE 2>$STDERR_CAPTURE &
294+
elif [[ -n "${STDOUT_CAPTURE}" ]]; then
295+
"${node}" "${LAUNCHER_NODE_OPTIONS[@]:-}" "${USER_NODE_OPTIONS[@]:-}" "${MAIN}" ${ARGS[@]+"${ARGS[@]}"} <&0 >$STDOUT_CAPTURE &
296+
elif [[ -n "${STDERR_CAPTURE}" ]]; then
297+
"${node}" "${LAUNCHER_NODE_OPTIONS[@]:-}" "${USER_NODE_OPTIONS[@]:-}" "${MAIN}" ${ARGS[@]+"${ARGS[@]}"} <&0 2>$STDERR_CAPTURE &
298+
else
299+
"${node}" "${LAUNCHER_NODE_OPTIONS[@]:-}" "${USER_NODE_OPTIONS[@]:-}" "${MAIN}" ${ARGS[@]+"${ARGS[@]}"} <&0 &
300+
fi
301+
287302
readonly child=$!
288303
trap _term SIGTERM
289304
trap _int SIGINT

internal/node/npm_package_bin.bzl

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ _ATTRS = {
1212
"data": attr.label_list(allow_files = True, aspects = [module_mappings_aspect, node_modules_aspect]),
1313
"output_dir": attr.bool(),
1414
"outs": attr.output_list(),
15+
"stderr": attr.output(),
16+
"stdout": attr.output(),
1517
"tool": attr.label(
1618
executable = True,
1719
cfg = "host",
@@ -38,12 +40,13 @@ def _inputs(ctx):
3840
def _impl(ctx):
3941
if ctx.attr.output_dir and ctx.outputs.outs:
4042
fail("Only one of output_dir and outs may be specified")
41-
if not ctx.attr.output_dir and not ctx.outputs.outs:
42-
fail("One of output_dir and outs must be specified")
43+
if not ctx.attr.output_dir and not ctx.outputs.outs and not ctx.attr.stdout:
44+
fail("One of output_dir, outs or stdout must be specified")
4345

4446
args = ctx.actions.args()
4547
inputs = _inputs(ctx)
4648
outputs = []
49+
4750
if ctx.attr.output_dir:
4851
outputs = [ctx.actions.declare_directory(ctx.attr.name)]
4952
else:
@@ -52,15 +55,25 @@ def _impl(ctx):
5255
for a in ctx.attr.args:
5356
args.add_all([expand_variables(ctx, e, outs = ctx.outputs.outs, output_dir = ctx.attr.output_dir) for e in _expand_locations(ctx, a)])
5457

58+
tool_outputs = []
59+
if ctx.outputs.stdout:
60+
tool_outputs.append(ctx.outputs.stdout)
61+
62+
if ctx.outputs.stderr:
63+
tool_outputs.append(ctx.outputs.stderr)
64+
5565
run_node(
5666
ctx,
5767
executable = "tool",
5868
inputs = inputs,
5969
outputs = outputs,
6070
arguments = [args],
6171
configuration_env_vars = ctx.attr.configuration_env_vars,
72+
stdout = ctx.outputs.stdout,
73+
stderr = ctx.outputs.stderr,
6274
)
63-
return [DefaultInfo(files = depset(outputs))]
75+
76+
return [DefaultInfo(files = depset(outputs + tool_outputs))]
6477

6578
_npm_package_bin = rule(
6679
_impl,
@@ -85,6 +98,10 @@ def npm_package_bin(tool = None, package = None, package_bin = None, data = [],
8598
output_dir: set to True if you want the output to be a directory
8699
Exactly one of `outs`, `output_dir` may be used.
87100
If you output a directory, there can only be one output, which will be a directory named the same as the target.
101+
stderr: set to capture the stderr of the binary to a file, which can later be used as an input to another target
102+
subject to the same semantics as `outs`
103+
stdout: set to capture the stdout of the binary to a file, which can later be used as an input to another target
104+
subject to the same semantics as `outs`
88105
89106
args: Command-line arguments to the tool.
90107

internal/node/test/BUILD.bazel

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,54 @@ npm_package_bin(
241241
package = "terser",
242242
)
243243

244+
# captures stdout of terser_input.js to minified.stdout.js (note args do not include --output flag)
245+
npm_package_bin(
246+
name = "run_terser_stdout",
247+
args = [
248+
"$(execpath terser_input.js)",
249+
],
250+
data = ["terser_input.js"],
251+
package = "terser",
252+
stdout = "minified.stdout.js",
253+
)
254+
255+
generated_file_test(
256+
name = "stdout_output_test",
257+
src = "stdout_output_test.golden",
258+
generated = ":minified.stdout.js",
259+
)
260+
261+
# capture stderr to diagnostics.out, then analyze it in terser_diagnostics_stats printing some stats
262+
# stdout is captured into greeter.min.js (again, without the --output $@ flags)
263+
npm_package_bin(
264+
name = "diagnostics_producing_bin",
265+
args = [
266+
"$(execpath terser_input_with_diagnostics.js)",
267+
"--compress",
268+
"--verbose",
269+
"--warn",
270+
],
271+
data = ["terser_input_with_diagnostics.js"],
272+
package = "terser",
273+
stderr = "diagnostics.out",
274+
stdout = "greeter.min.js",
275+
)
276+
277+
nodejs_binary(
278+
name = "terser_diagnostics_stats",
279+
data = [
280+
"diagnostics_producing_bin",
281+
"terser_diagnostics_stats.js",
282+
],
283+
entry_point = "terser_diagnostics_stats.js",
284+
)
285+
286+
generated_file_test(
287+
name = "stderr_output_test",
288+
src = "stderr_output_test.golden",
289+
generated = ":diagnostics.out",
290+
)
291+
244292
nodejs_binary(
245293
name = "copy_to_directory",
246294
entry_point = "copy_to_directory.js",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
WARN: Dropping unreachable code [internal/node/test/terser_input_with_diagnostics.js:6,4]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
class A{doThing(){console.error("thing")}}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
const fs = require('fs');
2+
3+
const diagnostics = fs.readFileSync('internal/node/test/diagnostics.out', {encoding: 'utf8'});
4+
const count = diagnostics.trim().split('\n').length;
5+
6+
console.log(`Terser produced ${count} diagnostic${count > 1 ? 's' : ''}`);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
class Greeter {
2+
greet(name) {
3+
return name;
4+
// return statement above is intentional to force terser to produce a diagnostic
5+
// about dropping now unreachable code below
6+
console.log(`Hello ${name}`);
7+
}
8+
}

internal/providers/node_runtime_deps_info.bzl

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ def run_node(ctx, inputs, arguments, executable, **kwargs):
6666
exec_attr = getattr(ctx.attr, executable)
6767
exec_exec = getattr(ctx.executable, executable)
6868

69+
outputs = kwargs.pop("outputs", [])
6970
extra_inputs = []
7071
link_data = []
7172
if (NodeRuntimeDepsInfo in exec_attr):
@@ -75,6 +76,16 @@ def run_node(ctx, inputs, arguments, executable, **kwargs):
7576
modules_manifest = write_node_modules_manifest(ctx, link_data)
7677
add_arg(arguments, "--bazel_node_modules_manifest=%s" % modules_manifest.path)
7778

79+
stdout_file = kwargs.pop("stdout", None)
80+
if stdout_file:
81+
add_arg(arguments, "--bazel_capture_stdout=%s" % stdout_file.path)
82+
outputs = outputs + [stdout_file]
83+
84+
stderr_file = kwargs.pop("stderr", None)
85+
if stderr_file:
86+
add_arg(arguments, "--bazel_capture_stderr=%s" % stderr_file.path)
87+
outputs = outputs + [stderr_file]
88+
7889
# By using the run_node helper, you suggest that your program
7990
# doesn't implicitly use runfiles to require() things
8091
# To access runfiles, you must use a runfiles helper in the program instead
@@ -86,7 +97,7 @@ def run_node(ctx, inputs, arguments, executable, **kwargs):
8697
configuration_env_vars = kwargs.pop("configuration_env_vars", []) + ["COMPILATION_MODE"]
8798
for var in configuration_env_vars:
8899
if var not in env.keys():
89-
# If env is not explicitely specified, check ctx.var first & if env var not in there
100+
# If env is not explicitly specified, check ctx.var first & if env var not in there
90101
# then check ctx.configuration.default_shell_env. The former will contain values from
91102
# --define=FOO=BAR and latter will contain values from --action_env=FOO=BAR
92103
# (but not from --action_env=FOO).
@@ -97,6 +108,7 @@ def run_node(ctx, inputs, arguments, executable, **kwargs):
97108
env["BAZEL_NODE_MODULES_ROOT"] = _compute_node_modules_root(ctx)
98109

99110
ctx.actions.run(
111+
outputs = outputs,
100112
inputs = inputs + extra_inputs + [modules_manifest],
101113
arguments = arguments,
102114
executable = exec_exec,

0 commit comments

Comments
 (0)