diff --git a/README.md b/README.md index 5c11a52..3fa96a0 100644 --- a/README.md +++ b/README.md @@ -225,7 +225,9 @@ bun_compile( `bun_build` exposes a directory output so Bun can emit HTML, CSS, assets, and split chunks. `bun_compile` produces a single executable artifact and supports -explicit cross-compilation via `compile_executable`. +explicit cross-compilation via `compile_executable`. When `root` is omitted, +`bun_build` derives a stable default from the entry point parent directory so +HTML and asset output stays inside Bazel's declared output tree. ### `bun_dev` for local development diff --git a/docs/rules.md b/docs/rules.md index 61c73b7..e22c7ba 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -102,7 +102,7 @@ may be requested with `metafile` and `metafile_md`. | production | If true, sets `NODE_ENV=production` and enables Bun production mode. | Boolean | optional | `False` | | public_path | Optional public path prefix for emitted imports. | String | optional | `""` | | react_fast_refresh | If true, enables Bun's React fast refresh transform. | Boolean | optional | `False` | -| root | Optional root directory for multiple entry points. | String | optional | `""` | +| root | Optional root directory for multiple entry points. When omitted, `bun_build` derives one from the entry point parent directories to keep emitted files inside the declared output tree. | String | optional | `""` | | sourcemap | Sourcemap emission mode. | String | optional | `"none"` | | splitting | If true, enables code splitting. | Boolean | optional | `False` | | target | Bun build target environment. | String | optional | `"browser"` | @@ -481,4 +481,3 @@ js_test(name, entry_p | srcs |

-

| `None` | | kwargs |

-

| none | - diff --git a/internal/bun_build_support.bzl b/internal/bun_build_support.bzl index 278597c..fb6b2f8 100644 --- a/internal/bun_build_support.bzl +++ b/internal/bun_build_support.bzl @@ -3,6 +3,26 @@ load("//internal:bun_command.bzl", "add_flag", "add_flag_value", "add_flag_values", "add_install_mode", "add_raw_flags") load("//internal:js_library.bzl", "collect_js_sources") +def infer_entry_point_root(entries): + if not entries: + return None + + common_segments = entries[0].path.split("/")[:-1] + for entry in entries[1:]: + entry_segments = entry.path.split("/")[:-1] + common_length = min(len(common_segments), len(entry_segments)) + idx = common_length + for segment_idx in range(common_length): + if common_segments[segment_idx] != entry_segments[segment_idx]: + idx = segment_idx + break + common_segments = common_segments[:idx] + + if not common_segments: + return "." + + return "/".join(common_segments) + def bun_build_transitive_inputs(ctx): transitive_inputs = [] if getattr(ctx.attr, "node_modules", None): @@ -11,13 +31,17 @@ def bun_build_transitive_inputs(ctx): transitive_inputs.append(collect_js_sources(dep)) return transitive_inputs -def add_bun_build_common_flags(args, attr, metafile = None, metafile_md = None): +def add_bun_build_common_flags(args, attr, metafile = None, metafile_md = None, root = None): + build_root = root + if build_root == None: + build_root = getattr(attr, "root", None) + add_install_mode(args, getattr(attr, "install_mode", "disable")) add_flag_value(args, "--target", getattr(attr, "target", None)) add_flag_value(args, "--format", getattr(attr, "format", None)) add_flag(args, "--production", getattr(attr, "production", False)) add_flag(args, "--splitting", getattr(attr, "splitting", False)) - add_flag_value(args, "--root", getattr(attr, "root", None)) + add_flag_value(args, "--root", build_root) sourcemap = getattr(attr, "sourcemap", None) if sourcemap == True: diff --git a/internal/bun_compile.bzl b/internal/bun_compile.bzl index 0fe6a0c..8326274 100644 --- a/internal/bun_compile.bzl +++ b/internal/bun_compile.bzl @@ -1,18 +1,34 @@ """Rules for Bun build outputs and standalone executables.""" -load("//internal:bun_build_support.bzl", "add_bun_build_common_flags", "add_bun_compile_flags", "bun_build_transitive_inputs") +load("//internal:bun_build_support.bzl", "add_bun_build_common_flags", "add_bun_compile_flags", "bun_build_transitive_inputs", "infer_entry_point_root") def _bun_build_impl(ctx): toolchain = ctx.toolchains["//bun:toolchain_type"] bun_bin = toolchain.bun.bun_bin output_dir = ctx.actions.declare_directory(ctx.label.name) - metafile = ctx.actions.declare_file(ctx.label.name + ".meta.json") if ctx.attr.metafile else None - metafile_md = ctx.actions.declare_file(ctx.label.name + ".meta.md") if ctx.attr.metafile_md else None + metafile = None + if ctx.attr.metafile: + metafile = ctx.actions.declare_file(ctx.label.name + ".meta.json") + metafile_md = None + if ctx.attr.metafile_md: + metafile_md = ctx.actions.declare_file(ctx.label.name + ".meta.md") + build_root = ctx.attr.root + if not build_root: + build_root = infer_entry_point_root(ctx.files.entry_points) + transitive_inputs = bun_build_transitive_inputs(ctx) + build_inputs = depset( + direct = ctx.files.entry_points + ctx.files.data, + transitive = transitive_inputs, + ) + input_manifest = ctx.actions.declare_file(ctx.label.name + ".inputs") + runner = ctx.actions.declare_file(ctx.label.name + "_runner.sh") args = ctx.actions.args() + args.add(input_manifest.path) + args.add(bun_bin.path) args.add("--bun") args.add("build") - add_bun_build_common_flags(args, ctx.attr, metafile = metafile, metafile_md = metafile_md) + add_bun_build_common_flags(args, ctx.attr, metafile = metafile, metafile_md = metafile_md, root = build_root) args.add("--outdir") args.add(output_dir.path) args.add_all(ctx.files.entry_points) @@ -23,12 +39,74 @@ def _bun_build_impl(ctx): if metafile_md: outputs.append(metafile_md) + ctx.actions.write( + output = input_manifest, + content = "".join([file.path + "\n" for file in build_inputs.to_list()]), + ) + + ctx.actions.write( + output = runner, + is_executable = True, + content = """#!/usr/bin/env bash +set -euo pipefail + +manifest="$1" +execroot="$(pwd -P)" +bun_bin="$2" +if [[ "${bun_bin}" != /* ]]; then + bun_bin="${execroot}/${bun_bin}" +fi +shift 2 + +stage_dir="$(mktemp -d "${TMPDIR:-/tmp}/rules_bun_build.XXXXXX")" +cleanup() { + rm -rf "${stage_dir}" +} +trap cleanup EXIT + +while IFS= read -r relpath; do + if [[ -z "${relpath}" ]]; then + continue + fi + src="${execroot}/${relpath}" + dest="${stage_dir}/${relpath}" + mkdir -p "$(dirname "${dest}")" + cp -L "${src}" "${dest}" +done < "${manifest}" + +forwarded_args=() +while (($#)); do + case "$1" in + --outdir) + forwarded_args+=("$1" "${execroot}/$2") + shift 2 + ;; + --metafile=*) + forwarded_args+=("--metafile=${execroot}/${1#--metafile=}") + shift + ;; + --metafile-md=*) + forwarded_args+=("--metafile-md=${execroot}/${1#--metafile-md=}") + shift + ;; + *) + forwarded_args+=("$1") + shift + ;; + esac +done + +cd "${stage_dir}" +exec "${bun_bin}" "${forwarded_args[@]}" +""", + ) + ctx.actions.run( - executable = bun_bin, + executable = runner, arguments = [args], inputs = depset( - direct = ctx.files.entry_points + ctx.files.data, - transitive = bun_build_transitive_inputs(ctx), + direct = [input_manifest, bun_bin], + transitive = [build_inputs], ), outputs = outputs, mnemonic = "BunBuild", diff --git a/tests/bundle_test/verify_flag_aquery.sh b/tests/bundle_test/verify_flag_aquery.sh index f9fc033..f0aa63a 100755 --- a/tests/bundle_test/verify_flag_aquery.sh +++ b/tests/bundle_test/verify_flag_aquery.sh @@ -132,6 +132,14 @@ for expected in \ expect_line "${build_output}" "${expected}" done +default_root_output="$(run_aquery "BunBuild" "//tests/bundle_test:site_build_with_meta")" + +for expected in \ + 'arguments: "--root"' \ + 'arguments: "tests/bundle_test/site"'; do + expect_line "${default_root_output}" "${expected}" +done + compile_output="$(run_aquery "BunCompile" "//tests/bundle_test:compiled_cli_with_flags")" for expected in \