Skip to content

fix: allow js_run_binary to hoist runfiles in the exec configuration#2809

Closed
acozzette wants to merge 7 commits intoaspect-build:mainfrom
acozzette:js-run-binary
Closed

fix: allow js_run_binary to hoist runfiles in the exec configuration#2809
acozzette wants to merge 7 commits intoaspect-build:mainfrom
acozzette:js-run-binary

Conversation

@acozzette
Copy link
Copy Markdown
Contributor

@acozzette acozzette commented Apr 22, 2026

The js_run_binary macro has a use_execroot_entry_point option which is enabled by default and which hoists out the runfiles into the execroot. Currently this hoisting effectively rebuilds the runfiles in the target platform config, but they should really be in the exec config since they consist of code that is going to run during the build action.

This change fixes that problem by making sure we hoist the runfiles in the exec config. I also updated the launcher script, since it needs to be prepared to look for the entry point in a different directory. This is all guarded by the hoist_runfiles_to_exec_cfg option on js_run_binary, which effectively defaults to False for now. The default is actually None but only for technical reasons.

The default is controlled by a new flag //js:_hoist_runfiles_to_exec_cfg, but this is intended only for our own CI purposes, allowing us to run most tests against both behaviors. There are a handful of js_run_binary targets that require one behavior or the other, so I updated those ones to explicitly specify the flag they need.

This addresses #2754, #2121, and #2073, but does not automatically fix them yet since the fix is opt-in for now.

Co-Authored-By: Claude Sonnet 4.6 [email protected]


Changes are visible to end-users: yes

  • Searched for relevant documentation and updated as needed: yes
  • Breaking change (forces users to change their own code or config): no
  • Suggested release notes appear below: yes

The js_run_binary macro now supports a hoist_runfiles_to_exec_cfg option, which will cause runfiles to be hoisted in the exec configuration instead of the target configuration. This is currently disabled by default, but you can opt in to achieve a more correct build setup.

Test plan

  • Covered by existing test cases
  • New test cases added

@aspect-workflows
Copy link
Copy Markdown

aspect-workflows Bot commented Apr 22, 2026

Bazel 7 (Test)

All tests were cache hits

256 tests (100.0%) were fully cached saving 29s.


Bazel 8 (Test)

All tests were cache hits

216 tests (100.0%) were fully cached saving 28s.


Bazel 9 (Test)

All tests were cache hits

216 tests (100.0%) were fully cached saving 32s.


Bazel 9 with exec cfg (Test)

All tests were cache hits

216 tests (100.0%) were fully cached saving 33s.


Bazel 7 (Test)

e2e/bzlmod

All tests were cache hits

7 tests (100.0%) were fully cached saving 554ms.


Bazel 8 (Test)

e2e/bzlmod

All tests were cache hits

7 tests (100.0%) were fully cached saving 667ms.


Bazel 9 (Test)

e2e/bzlmod

All tests were cache hits

7 tests (100.0%) were fully cached saving 552ms.


Bazel 9 with exec cfg (Test)

e2e/bzlmod

All tests were cache hits

7 tests (100.0%) were fully cached saving 552ms.


Bazel 7 (Test)

e2e/git_dep_metadata

All tests were cache hits

1 test (100.0%) was fully cached saving 30ms.


Bazel 8 (Test)

e2e/git_dep_metadata

All tests were cache hits

1 test (100.0%) was fully cached saving 26ms.


Bazel 9 (Test)

e2e/git_dep_metadata

All tests were cache hits

1 test (100.0%) was fully cached saving 30ms.


Bazel 9 with exec cfg (Test)

e2e/git_dep_metadata

All tests were cache hits

1 test (100.0%) was fully cached saving 30ms.


Bazel 7 (Test)

e2e/gyp_no_install_script

All tests were cache hits

2 tests (100.0%) were fully cached saving 133ms.


Bazel 8 (Test)

e2e/gyp_no_install_script

All tests were cache hits

1 test (100.0%) was fully cached saving 82ms.


Bazel 9 (Test)

e2e/gyp_no_install_script

All tests were cache hits

1 test (100.0%) was fully cached saving 58ms.


Bazel 9 with exec cfg (Test)

e2e/gyp_no_install_script

All tests were cache hits

1 test (100.0%) was fully cached saving 58ms.


Bazel 7 (Test)

e2e/js_binary_workspace

All tests were cache hits

4 tests (100.0%) were fully cached saving 225ms.


Bazel 8 (Test)

e2e/js_binary_workspace

All tests were cache hits

4 tests (100.0%) were fully cached saving 275ms.


Bazel 9 (Test)

e2e/js_binary_workspace

All tests were cache hits

4 tests (100.0%) were fully cached saving 232ms.


Bazel 9 with exec cfg (Test)

e2e/js_binary_workspace

All tests were cache hits

4 tests (100.0%) were fully cached saving 246ms.


Bazel 7 (Test)

e2e/js_image_oci

Buildkite build #12614 is running...


Bazel 7 (Test)

e2e/nextjs

All tests were cache hits

3 tests (100.0%) were fully cached saving 165ms.


Bazel 8 (Test)

e2e/nextjs

All tests were cache hits

3 tests (100.0%) were fully cached saving 94ms.


Bazel 9 (Test)

e2e/nextjs

All tests were cache hits

3 tests (100.0%) were fully cached saving 112ms.


Bazel 9 with exec cfg (Test)

e2e/nextjs

All tests were cache hits

3 tests (100.0%) were fully cached saving 112ms.


Bazel 7 (Test)

e2e/npm_link_package

All tests were cache hits

4 tests (100.0%) were fully cached saving 418ms.


Bazel 8 (Test)

e2e/npm_link_package

All tests were cache hits

4 tests (100.0%) were fully cached saving 537ms.


Bazel 9 (Test)

e2e/npm_link_package

All tests were cache hits

4 tests (100.0%) were fully cached saving 431ms.


Bazel 9 with exec cfg (Test)

e2e/npm_link_package

All tests were cache hits

4 tests (100.0%) were fully cached saving 431ms.


Bazel 7 (Test)

e2e/npm_link_package-rerooted

Buildkite build #12614 is running...


Bazel 8 (Test)

e2e/npm_link_package-rerooted

All tests were cache hits

2 tests (100.0%) were fully cached saving 165ms.


Bazel 9 (Test)

e2e/npm_link_package-rerooted

All tests were cache hits

2 tests (100.0%) were fully cached saving 220ms.


Bazel 9 with exec cfg (Test)

e2e/npm_link_package-rerooted

All tests were cache hits

2 tests (100.0%) were fully cached saving 220ms.


Bazel 7 (Test)

e2e/npm_translate_lock

All tests were cache hits

3 tests (100.0%) were fully cached saving 392ms.


Bazel 8 (Test)

e2e/npm_translate_lock

All tests were cache hits

3 tests (100.0%) were fully cached saving 369ms.


Bazel 9 (Test)

e2e/npm_translate_lock

All tests were cache hits

3 tests (100.0%) were fully cached saving 540ms.


Bazel 9 with exec cfg (Test)

e2e/npm_translate_lock

All tests were cache hits

3 tests (100.0%) were fully cached saving 540ms.


Bazel 7 (Test)

e2e/npm_translate_lock_disable_hooks

Buildkite build #12614 is running...


Bazel 8 (Test)

e2e/npm_translate_lock_disable_hooks

Buildkite build #12614 is running...


Bazel 9 (Test)

e2e/npm_translate_lock_disable_hooks

All tests were cache hits

1 test (100.0%) was fully cached saving 32ms.


Bazel 9 with exec cfg (Test)

e2e/npm_translate_lock_disable_hooks

Buildkite build #12614 is running...


Bazel 7 (Test)

e2e/npm_translate_lock_empty

All tests were cache hits

2 tests (100.0%) were fully cached saving 132ms.


Bazel 8 (Test)

e2e/npm_translate_lock_empty

All tests were cache hits

2 tests (100.0%) were fully cached saving 114ms.


Bazel 9 (Test)

e2e/npm_translate_lock_empty

All tests were cache hits

2 tests (100.0%) were fully cached saving 105ms.


Bazel 9 with exec cfg (Test)

e2e/npm_translate_lock_empty

All tests were cache hits

2 tests (100.0%) were fully cached saving 105ms.


Bazel 7 (Test)

e2e/npm_translate_lock_exclude_package_contents

Waiting for runner...


Bazel 8 (Test)

e2e/npm_translate_lock_exclude_package_contents

All tests were cache hits

1 test (100.0%) was fully cached saving 21ms.


Bazel 9 (Test)

e2e/npm_translate_lock_exclude_package_contents

Waiting for runner...


Bazel 9 with exec cfg (Test)

e2e/npm_translate_lock_exclude_package_contents

All tests were cache hits

1 test (100.0%) was fully cached saving 86ms.


Bazel 7 (Test)

e2e/npm_translate_lock_multi

Buildkite build #12614 is running...


Bazel 8 (Test)

e2e/npm_translate_lock_multi

All tests were cache hits

2 tests (100.0%) were fully cached saving 54ms.


Bazel 9 (Test)

e2e/npm_translate_lock_multi

All tests were cache hits

2 tests (100.0%) were fully cached saving 113ms.


Bazel 9 with exec cfg (Test)

e2e/npm_translate_lock_multi

All tests were cache hits

2 tests (100.0%) were fully cached saving 113ms.


Bazel 7 (Test)

e2e/npm_translate_lock_partial_clone

All tests were cache hits

1 test (100.0%) was fully cached saving 26ms.


Bazel 8 (Test)

e2e/npm_translate_lock_partial_clone

All tests were cache hits

1 test (100.0%) was fully cached saving 30ms.


Bazel 9 (Test)

e2e/npm_translate_lock_partial_clone

All tests were cache hits

1 test (100.0%) was fully cached saving 38ms.


Bazel 9 with exec cfg (Test)

e2e/npm_translate_lock_partial_clone

All tests were cache hits

1 test (100.0%) was fully cached saving 38ms.


Bazel 7 (Test)

e2e/npm_translate_lock_replace_packages

All tests were cache hits

4 tests (100.0%) were fully cached saving 402ms.


Bazel 8 (Test)

e2e/npm_translate_lock_replace_packages

All tests were cache hits

4 tests (100.0%) were fully cached saving 265ms.


Bazel 9 (Test)

e2e/npm_translate_lock_replace_packages

All tests were cache hits

4 tests (100.0%) were fully cached saving 368ms.


Bazel 9 with exec cfg (Test)

e2e/npm_translate_lock_replace_packages

Buildkite build #12614 is running...


Bazel 7 (Test)

e2e/npm_translate_lock_subdir_patch

All tests were cache hits

1 test (100.0%) was fully cached saving 68ms.


Bazel 8 (Test)

e2e/npm_translate_lock_subdir_patch

All tests were cache hits

1 test (100.0%) was fully cached saving 53ms.


Bazel 9 (Test)

e2e/npm_translate_lock_subdir_patch

All tests were cache hits

1 test (100.0%) was fully cached saving 54ms.


Bazel 9 with exec cfg (Test)

e2e/npm_translate_lock_subdir_patch

Buildkite build #12614 is running...


Bazel 7 (Test)

e2e/npm_translate_package_lock

Buildkite build #12614 is running...


Bazel 8 (Test)

e2e/npm_translate_package_lock

Buildkite build #12614 is running...


Bazel 9 (Test)

e2e/npm_translate_package_lock

Buildkite build #12614 is running...


Bazel 9 with exec cfg (Test)

e2e/npm_translate_package_lock

All tests were cache hits

1 test (100.0%) was fully cached saving 32ms.


Bazel 7 (Test)

e2e/npm_translate_yarn_lock

All tests were cache hits

1 test (100.0%) was fully cached saving 25ms.


Bazel 8 (Test)

e2e/npm_translate_yarn_lock

Buildkite build #12614 is running...


Bazel 9 (Test)

e2e/npm_translate_yarn_lock

All tests were cache hits

1 test (100.0%) was fully cached saving 32ms.


Bazel 9 with exec cfg (Test)

e2e/npm_translate_yarn_lock

All tests were cache hits

1 test (100.0%) was fully cached saving 32ms.


Bazel 7 (Test)

e2e/output_paths

Waiting for runner...


Bazel 8 (Test)

e2e/output_paths

Waiting for runner...


Bazel 9 (Test)

e2e/output_paths

Buildkite build #12614 is running...


Bazel 9 with exec cfg (Test)

e2e/output_paths

Buildkite build #12614 is running...


Bazel 7 (Test)

e2e/patch_from_repo

All tests were cache hits

1 test (100.0%) was fully cached saving 25ms.


Bazel 9 with exec cfg (Test)

e2e/patch_from_repo

⚠️ Buildkite build #12614 failed.


Bazel 7 (Test)

e2e/pnpm_lockfiles

Buildkite build #12614 is running...


Bazel 8 (Test)

e2e/pnpm_lockfiles

All tests were cache hits

17 tests (100.0%) were fully cached saving 1s.


Bazel 9 (Test)

e2e/pnpm_lockfiles

Buildkite build #12614 is running...


Bazel 9 with exec cfg (Test)

e2e/pnpm_lockfiles

Buildkite build #12614 is running...


Bazel 7 (Test)

e2e/pnpm_repo_install

All tests were cache hits

1 test (100.0%) was fully cached saving 1s.


Bazel 8 (Test)

e2e/pnpm_repo_install

Buildkite build #12614 is running...


Bazel 9 (Test)

e2e/pnpm_repo_install

Buildkite build #12614 is running...


Bazel 9 with exec cfg (Test)

e2e/pnpm_repo_install

Buildkite build #12614 is running...


Bazel 7 (Test)

e2e/pnpm_version

Waiting for runner...


Bazel 8 (Test)

e2e/pnpm_version

All tests were cache hits

1 test (100.0%) was fully cached saving 47ms.


Bazel 9 (Test)

e2e/pnpm_version

All tests were cache hits

1 test (100.0%) was fully cached saving 64ms.


Bazel 9 with exec cfg (Test)

e2e/pnpm_version

All tests were cache hits

1 test (100.0%) was fully cached saving 64ms.


Bazel 7 (Test)

e2e/pnpm_workspace

Buildkite build #12614 is running...


Bazel 8 (Test)

e2e/pnpm_workspace

All tests were cache hits

14 tests (100.0%) were fully cached saving 2s.


Bazel 9 (Test)

e2e/pnpm_workspace

All tests were cache hits

14 tests (100.0%) were fully cached saving 2s.


Bazel 9 with exec cfg (Test)

e2e/pnpm_workspace

Buildkite build #12614 is running...


Bazel 7 (Test)

e2e/pnpm_workspace_deps

Waiting for runner...


Bazel 8 (Test)

e2e/pnpm_workspace_deps

Buildkite build #12614 is running...


Bazel 9 (Test)

e2e/pnpm_workspace_deps

Buildkite build #12614 is running...


Bazel 9 with exec cfg (Test)

e2e/pnpm_workspace_deps

All tests were cache hits

3 tests (100.0%) were fully cached saving 277ms.


Bazel 7 (Test)

e2e/pnpm_workspace_rerooted

Buildkite build #12614 is running...


Bazel 8 (Test)

e2e/pnpm_workspace_rerooted

Waiting for runner...


Bazel 9 (Test)

e2e/pnpm_workspace_rerooted

Waiting for runner...


Bazel 9 with exec cfg (Test)

e2e/pnpm_workspace_rerooted

Buildkite build #12614 is running...


Bazel 7 (Test)

e2e/protobuf-es

Waiting for runner...


Bazel 8 (Test)

e2e/protobuf-es

Buildkite build #12614 is running...


Bazel 9 (Test)

e2e/protobuf-es

Buildkite build #12614 is running...


Bazel 9 with exec cfg (Test)

e2e/protobuf-es

Buildkite build #12614 is running...


Bazel 7 (Test)

e2e/protobuf-google

Buildkite build #12614 is running...


Bazel 8 (Test)

e2e/protobuf-google

Buildkite build #12614 is running...


Bazel 9 (Test)

e2e/protobuf-google

Buildkite build #12614 is running...


Bazel 9 with exec cfg (Test)

e2e/protobuf-google

All tests were cache hits

2 tests (100.0%) were fully cached saving 305ms.


Bazel 7 (Test)

e2e/repo_mapping

Buildkite build #12614 is running...


Bazel 8 (Test)

e2e/repo_mapping

Buildkite build #12614 is running...


Bazel 9 (Test)

e2e/repo_mapping

All tests were cache hits

3 tests (100.0%) were fully cached saving 304ms.


Bazel 7 (Test)

e2e/vendored_node

Buildkite build #12614 is running...


Bazel 8 (Test)

e2e/vendored_node

Waiting for runner...


Bazel 9 (Test)

e2e/vendored_node

All tests were cache hits

1 test (100.0%) was fully cached saving 63ms.


Bazel 9 with exec cfg (Test)

e2e/vendored_node

Waiting for runner...


Bazel 7 (Test)

e2e/vendored_tarfile

Buildkite build #12614 is running...


Bazel 8 (Test)

e2e/vendored_tarfile

Waiting for runner...


Bazel 9 (Test)

e2e/vendored_tarfile

Waiting for runner...


Bazel 9 with exec cfg (Test)

e2e/vendored_tarfile

Waiting for runner...


Bazel 7 (Test)

e2e/verify_patches

Buildkite build #12614 is running...


Bazel 8 (Test)

e2e/verify_patches

Waiting for runner...


Bazel 9 (Test)

e2e/verify_patches

Buildkite build #12614 is running...


Bazel 9 with exec cfg (Test)

e2e/verify_patches

All tests were cache hits

2 tests (100.0%) were fully cached saving 120ms.


Bazel 7 (Test)

examples

All tests were cache hits

83 tests (100.0%) were fully cached saving 13s.


Bazel 8 (Test)

examples

All tests were cache hits

81 tests (100.0%) were fully cached saving 13s.


Bazel 9 (Test)

examples

Buildkite build #12614 is running...


Bazel 9 with exec cfg (Test)

examples

Buildkite build #12614 is running...


Buildifier      Format

@acozzette acozzette force-pushed the js-run-binary branch 10 times, most recently from a69a993 to 82a7e1d Compare April 29, 2026 22:39
The `js_run_binary` macro has a `use_execroot_entry_point` option which is
enabled by default and which hoists out the runfiles into the execroot.
Currently this hoisting effectively rebuilds the runfiles in the target
platform config, but they should really be in the exec config since they
consist of code that is going to run during the build action.

This change fixes that problem by making sure we hoist the runfiles in the exec
config. I also updated the launcher script, since it needs to be prepared to
look for the entry point in a different directory. This is all guarded by the
`hoist_runfiles_to_exec_cfg` option on `js_run_binary`, which effectively
defaults to False for now. The default is actually `None` but only for
technical reasons.

The default is controlled by a new flag `//js:_hoist_runfiles_to_exec_cfg`, but
this is intended only for our own CI purposes, allowing us to run most tests
against both behaviors. There are a handful of `js_run_binary` targets that
require one behavior or the other, so I updated those ones to explicitly
specify the flag they need.

This addresses aspect-build#2754, aspect-build#2121, and aspect-build#2073, but does not automatically fix them yet
since the fix is opt-in for now.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@acozzette acozzette changed the title fix: make js_run_binary respect distinction between exec and target platforms fix: allow js_run_binary to hoist runfiles in the exec configuration Apr 30, 2026
@acozzette acozzette marked this pull request as ready for review April 30, 2026 17:24
@acozzette acozzette requested a review from jbedard April 30, 2026 17:24
Comment thread .aspect/workflows/config.yaml Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bb975a4474

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread js/private/js_run_binary.bzl
Comment thread js/private/js_run_binary.bzl Outdated
Comment thread js/private/js_run_binary.bzl Outdated
Comment thread js/private/test/data/BUILD.bazel
Comment thread .aspect/workflows/config.yaml Outdated
Comment thread MODULE.bazel
@@ -19,7 +19,7 @@ bazel_dep(name = "rules_nodejs", version = "6.7.3")

# Changes ensured by rules_js:
# 3.2.2: https://github.com/bazel-contrib/bazel-lib/commit/cac2d7855949d1b222fa26888892fbbe1d31015d
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment just like this for the reason we need 3.3.0?

# TEST: js_test(data = js_run_binary(srcs)) ---------------
js_run_binary(
name = "run-write-srcs",
srcs = ["data.json"],
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was essentially an undeclared input before, but magically worked because the tool also declared the input? Now it correctly fails with the new option enabled?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's right. With the new option enabled and without this line adding the dep via srcs, this would fail since data.json would no longer be in the same place.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sounds correct to me 👍

local suffix
if [[ "$short_path" == ../* ]]; then
echo "$JS_BINARY__EXECROOT/${BAZEL_BINDIR:-$JS_BINARY__BINDIR}/external/${short_path:3}"
suffix="external/${short_path:3}"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just modify short_parth instead of introducing the extra suffix var? Any disadvantage to that?

Comment thread js/BUILD.bazel
# This flag controls the default value of the use_execroot_exec_cfg flag on
# js_run_binary.
bool_flag(
name = "use_execroot_exec_cfg",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT about prefixing this with js_run_binary_ since that is the one and only usage?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants