feat: proper windows support
This commit is contained in:
@@ -15,6 +15,8 @@ exports_files([
|
||||
"js_compat.bzl",
|
||||
"js_library.bzl",
|
||||
"js_run_devserver.bzl",
|
||||
"runtime_launcher.bzl",
|
||||
"runtime_launcher.js",
|
||||
"workspace.bzl",
|
||||
])
|
||||
|
||||
@@ -34,6 +36,8 @@ filegroup(
|
||||
"js_compat.bzl",
|
||||
"js_library.bzl",
|
||||
"js_run_devserver.bzl",
|
||||
"runtime_launcher.bzl",
|
||||
"runtime_launcher.js",
|
||||
"workspace.bzl",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
@@ -59,6 +63,7 @@ bzl_library(
|
||||
deps = [
|
||||
":bun_command_bzl",
|
||||
":js_library_bzl",
|
||||
":runtime_launcher_bzl",
|
||||
":workspace_bzl",
|
||||
],
|
||||
)
|
||||
@@ -84,6 +89,7 @@ bzl_library(
|
||||
srcs = ["bun_dev.bzl"],
|
||||
deps = [
|
||||
":bun_command_bzl",
|
||||
":runtime_launcher_bzl",
|
||||
":workspace_bzl",
|
||||
],
|
||||
)
|
||||
@@ -98,6 +104,7 @@ bzl_library(
|
||||
srcs = ["bun_script.bzl"],
|
||||
deps = [
|
||||
":bun_command_bzl",
|
||||
":runtime_launcher_bzl",
|
||||
":workspace_bzl",
|
||||
],
|
||||
)
|
||||
@@ -108,6 +115,7 @@ bzl_library(
|
||||
deps = [
|
||||
":bun_command_bzl",
|
||||
":js_library_bzl",
|
||||
":runtime_launcher_bzl",
|
||||
":workspace_bzl",
|
||||
],
|
||||
)
|
||||
@@ -133,10 +141,16 @@ bzl_library(
|
||||
srcs = ["js_run_devserver.bzl"],
|
||||
deps = [
|
||||
":js_library_bzl",
|
||||
":runtime_launcher_bzl",
|
||||
":workspace_bzl",
|
||||
],
|
||||
)
|
||||
|
||||
bzl_library(
|
||||
name = "runtime_launcher_bzl",
|
||||
srcs = ["runtime_launcher.bzl"],
|
||||
)
|
||||
|
||||
bzl_library(
|
||||
name = "workspace_bzl",
|
||||
srcs = ["workspace.bzl"],
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
"""Rule for running JS/TS scripts with Bun."""
|
||||
|
||||
load("//internal:bun_command.bzl", "append_shell_flag", "append_shell_flag_files", "append_shell_flag_values", "append_shell_install_mode", "append_shell_raw_flags", "render_shell_array", "shell_quote")
|
||||
load("//internal:bun_command.bzl", "append_flag", "append_flag_values", "append_install_mode", "append_raw_flags")
|
||||
load("//internal:js_library.bzl", "collect_js_runfiles")
|
||||
load("//internal:workspace.bzl", "create_bun_workspace_info", "render_workspace_setup", "workspace_runfiles")
|
||||
load("//internal:runtime_launcher.bzl", "declare_runtime_wrapper", "runfiles_path", "runtime_launcher_attrs", "write_launcher_spec")
|
||||
load("//internal:workspace.bzl", "create_bun_workspace_info", "workspace_runfiles")
|
||||
|
||||
def _bun_binary_impl(ctx):
|
||||
toolchain = ctx.toolchains["//bun:toolchain_type"]
|
||||
@@ -15,50 +16,107 @@ def _bun_binary_impl(ctx):
|
||||
primary_file = entry_point,
|
||||
)
|
||||
|
||||
launcher_lines = [render_shell_array("bun_args", ["--bun", "run"])]
|
||||
append_shell_install_mode(launcher_lines, "bun_args", ctx.attr.install_mode)
|
||||
append_shell_flag_files(launcher_lines, "bun_args", "--preload", ctx.files.preload)
|
||||
append_shell_flag_files(launcher_lines, "bun_args", "--env-file", ctx.files.env_files)
|
||||
append_shell_flag(launcher_lines, "bun_args", "--no-env-file", ctx.attr.no_env_file)
|
||||
append_shell_flag(launcher_lines, "bun_args", "--smol", ctx.attr.smol)
|
||||
append_shell_flag_values(launcher_lines, "bun_args", "--conditions", ctx.attr.conditions)
|
||||
append_shell_raw_flags(launcher_lines, "bun_args", ctx.attr.run_flags)
|
||||
launcher_lines.append('bun_args+=("${primary_source}")')
|
||||
for arg in ctx.attr.args:
|
||||
launcher_lines.append("bun_args+=(%s)" % shell_quote(arg))
|
||||
argv = ["--bun", "run"]
|
||||
append_install_mode(argv, ctx.attr.install_mode)
|
||||
append_flag(argv, "--no-env-file", ctx.attr.no_env_file)
|
||||
append_flag(argv, "--smol", ctx.attr.smol)
|
||||
append_flag_values(argv, "--conditions", ctx.attr.conditions)
|
||||
append_raw_flags(argv, ctx.attr.run_flags)
|
||||
|
||||
command = """
|
||||
trap cleanup_runtime_workspace EXIT
|
||||
cd "${runtime_exec_dir}"
|
||||
__BUN_ARGS__
|
||||
exec "${bun_bin}" "${bun_args[@]}" "$@"
|
||||
""".replace("__BUN_ARGS__", "\n".join(launcher_lines))
|
||||
|
||||
launcher = ctx.actions.declare_file(ctx.label.name)
|
||||
ctx.actions.write(
|
||||
output = launcher,
|
||||
is_executable = True,
|
||||
content = render_workspace_setup(
|
||||
bun_short_path = bun_bin.short_path,
|
||||
install_metadata_short_path = workspace_info.install_metadata_file.short_path if workspace_info.install_metadata_file else "",
|
||||
primary_source_short_path = entry_point.short_path,
|
||||
working_dir_mode = ctx.attr.working_dir,
|
||||
) + command,
|
||||
)
|
||||
spec_file = write_launcher_spec(ctx, {
|
||||
"version": 1,
|
||||
"kind": "bun_run",
|
||||
"bun_short_path": runfiles_path(bun_bin),
|
||||
"primary_source_short_path": runfiles_path(entry_point),
|
||||
"package_json_short_path": "",
|
||||
"install_metadata_short_path": runfiles_path(workspace_info.install_metadata_file) if workspace_info.install_metadata_file else "",
|
||||
"install_repo_runfiles_path": workspace_info.install_repo_runfiles_path,
|
||||
"node_modules_roots": workspace_info.node_modules_roots,
|
||||
"package_dir_hint": workspace_info.package_dir_hint,
|
||||
"working_dir_mode": ctx.attr.working_dir,
|
||||
"inherit_host_path": ctx.attr.inherit_host_path,
|
||||
"argv": argv,
|
||||
"args": ctx.attr.args,
|
||||
"passthrough_args": True,
|
||||
"tool_short_path": "",
|
||||
"restart_on": [],
|
||||
"watch_mode": "",
|
||||
"reporter": "",
|
||||
"coverage": False,
|
||||
"coverage_reporters": [],
|
||||
"preload_short_paths": [runfiles_path(file) for file in ctx.files.preload],
|
||||
"env_file_short_paths": [runfiles_path(file) for file in ctx.files.env_files],
|
||||
"test_short_paths": [],
|
||||
})
|
||||
launcher = declare_runtime_wrapper(ctx, bun_bin, spec_file)
|
||||
|
||||
return [
|
||||
workspace_info,
|
||||
DefaultInfo(
|
||||
executable = launcher,
|
||||
executable = launcher.executable,
|
||||
runfiles = workspace_runfiles(
|
||||
ctx,
|
||||
workspace_info,
|
||||
direct_files = [launcher],
|
||||
direct_files = [launcher.executable, launcher.runner, spec_file],
|
||||
transitive_files = dep_runfiles,
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
_BUN_BINARY_ATTRS = runtime_launcher_attrs()
|
||||
_BUN_BINARY_ATTRS.update({
|
||||
"entry_point": attr.label(
|
||||
mandatory = True,
|
||||
allow_single_file = [".js", ".ts", ".jsx", ".tsx", ".mjs", ".cjs"],
|
||||
doc = "Path to the main JS/TS file to execute.",
|
||||
),
|
||||
"node_modules": attr.label(
|
||||
doc = "Optional label providing package files from a `node_modules` tree, typically produced by `bun_install`, in runfiles.",
|
||||
),
|
||||
"data": attr.label_list(
|
||||
allow_files = True,
|
||||
doc = "Additional runtime files required by the program.",
|
||||
),
|
||||
"deps": attr.label_list(
|
||||
doc = "Library dependencies required by the program.",
|
||||
),
|
||||
"preload": attr.label_list(
|
||||
allow_files = True,
|
||||
doc = "Modules to preload with `--preload` before running the entry point.",
|
||||
),
|
||||
"env_files": attr.label_list(
|
||||
allow_files = True,
|
||||
doc = "Additional environment files loaded with `--env-file`.",
|
||||
),
|
||||
"no_env_file": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, disables Bun's automatic `.env` loading.",
|
||||
),
|
||||
"smol": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, enables Bun's lower-memory runtime mode.",
|
||||
),
|
||||
"conditions": attr.string_list(
|
||||
doc = "Custom package resolve conditions passed to Bun.",
|
||||
),
|
||||
"install_mode": attr.string(
|
||||
default = "disable",
|
||||
values = ["disable", "auto", "fallback", "force"],
|
||||
doc = "Whether Bun may auto-install missing packages at runtime.",
|
||||
),
|
||||
"run_flags": attr.string_list(
|
||||
doc = "Additional raw flags forwarded to `bun run` before the entry point.",
|
||||
),
|
||||
"working_dir": attr.string(
|
||||
default = "workspace",
|
||||
values = ["workspace", "entry_point"],
|
||||
doc = "Working directory at runtime: `workspace` root or nearest `entry_point` ancestor containing `.env`/`package.json`.",
|
||||
),
|
||||
"inherit_host_path": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, appends the host PATH after staged node_modules/.bin entries at runtime.",
|
||||
),
|
||||
})
|
||||
|
||||
bun_binary = rule(
|
||||
implementation = _bun_binary_impl,
|
||||
@@ -66,55 +124,7 @@ bun_binary = rule(
|
||||
|
||||
Use this rule for non-test scripts and CLIs that should run via `bazel run`.
|
||||
""",
|
||||
attrs = {
|
||||
"entry_point": attr.label(
|
||||
mandatory = True,
|
||||
allow_single_file = [".js", ".ts", ".jsx", ".tsx", ".mjs", ".cjs"],
|
||||
doc = "Path to the main JS/TS file to execute.",
|
||||
),
|
||||
"node_modules": attr.label(
|
||||
doc = "Optional label providing package files from a `node_modules` tree, typically produced by `bun_install`, in runfiles.",
|
||||
),
|
||||
"data": attr.label_list(
|
||||
allow_files = True,
|
||||
doc = "Additional runtime files required by the program.",
|
||||
),
|
||||
"deps": attr.label_list(
|
||||
doc = "Library dependencies required by the program.",
|
||||
),
|
||||
"preload": attr.label_list(
|
||||
allow_files = True,
|
||||
doc = "Modules to preload with `--preload` before running the entry point.",
|
||||
),
|
||||
"env_files": attr.label_list(
|
||||
allow_files = True,
|
||||
doc = "Additional environment files loaded with `--env-file`.",
|
||||
),
|
||||
"no_env_file": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, disables Bun's automatic `.env` loading.",
|
||||
),
|
||||
"smol": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, enables Bun's lower-memory runtime mode.",
|
||||
),
|
||||
"conditions": attr.string_list(
|
||||
doc = "Custom package resolve conditions passed to Bun.",
|
||||
),
|
||||
"install_mode": attr.string(
|
||||
default = "disable",
|
||||
values = ["disable", "auto", "fallback", "force"],
|
||||
doc = "Whether Bun may auto-install missing packages at runtime.",
|
||||
),
|
||||
"run_flags": attr.string_list(
|
||||
doc = "Additional raw flags forwarded to `bun run` before the entry point.",
|
||||
),
|
||||
"working_dir": attr.string(
|
||||
default = "workspace",
|
||||
values = ["workspace", "entry_point"],
|
||||
doc = "Working directory at runtime: `workspace` root or nearest `entry_point` ancestor containing `.env`/`package.json`.",
|
||||
),
|
||||
},
|
||||
attrs = _BUN_BINARY_ATTRS,
|
||||
executable = True,
|
||||
toolchains = ["//bun:toolchain_type"],
|
||||
)
|
||||
|
||||
@@ -3,6 +3,74 @@
|
||||
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")
|
||||
|
||||
_STAGED_BUILD_RUNNER = """import { spawnSync } from "node:child_process";
|
||||
import { cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { dirname, resolve } from "node:path";
|
||||
|
||||
const [, , manifestPath, ...buildArgs] = process.argv;
|
||||
const execroot = process.cwd();
|
||||
const stageDir = mkdtempSync(resolve(tmpdir(), "rules_bun_build-"));
|
||||
|
||||
function rewriteArgPath(flag, value) {
|
||||
return `${flag}=${resolve(execroot, value)}`;
|
||||
}
|
||||
|
||||
try {
|
||||
for (const relpath of readFileSync(manifestPath, "utf8").split(/\\r?\\n/)) {
|
||||
if (!relpath) {
|
||||
continue;
|
||||
}
|
||||
const src = resolve(execroot, relpath);
|
||||
const dest = resolve(stageDir, relpath);
|
||||
mkdirSync(dirname(dest), { recursive: true });
|
||||
cpSync(src, dest, { dereference: true, force: true, recursive: true });
|
||||
}
|
||||
|
||||
const forwardedArgs = [];
|
||||
for (let index = 0; index < buildArgs.length; index += 1) {
|
||||
const arg = buildArgs[index];
|
||||
if ((arg === "--outdir" || arg === "--outfile") && index + 1 < buildArgs.length) {
|
||||
forwardedArgs.push(arg, resolve(execroot, buildArgs[index + 1]));
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
if (arg.startsWith("--metafile=")) {
|
||||
forwardedArgs.push(rewriteArgPath("--metafile", arg.slice("--metafile=".length)));
|
||||
continue;
|
||||
}
|
||||
if (arg.startsWith("--metafile-md=")) {
|
||||
forwardedArgs.push(rewriteArgPath("--metafile-md", arg.slice("--metafile-md=".length)));
|
||||
continue;
|
||||
}
|
||||
forwardedArgs.push(arg);
|
||||
}
|
||||
|
||||
const result = spawnSync(process.execPath, forwardedArgs, {
|
||||
cwd: stageDir,
|
||||
stdio: "inherit",
|
||||
});
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
process.exit(typeof result.status === "number" ? result.status : 1);
|
||||
} finally {
|
||||
rmSync(stageDir, { recursive: true, force: true });
|
||||
}
|
||||
"""
|
||||
|
||||
def sort_files_by_short_path(files):
|
||||
files_by_path = {}
|
||||
short_paths = []
|
||||
for file in files:
|
||||
files_by_path[file.short_path] = file
|
||||
short_paths.append(file.short_path)
|
||||
return [files_by_path[short_path] for short_path in sorted(short_paths)]
|
||||
|
||||
def validate_hermetic_install_mode(attr, rule_name):
|
||||
if getattr(attr, "install_mode", "disable") != "disable":
|
||||
fail("{} requires install_mode = \"disable\" for hermetic execution".format(rule_name))
|
||||
|
||||
def infer_entry_point_root(entries):
|
||||
if not entries:
|
||||
return None
|
||||
@@ -112,3 +180,29 @@ def add_bun_compile_flags(args, attr, compile_executable = None):
|
||||
add_flag_value(args, "--windows-version", getattr(attr, "windows_version", None))
|
||||
add_flag_value(args, "--windows-description", getattr(attr, "windows_description", None))
|
||||
add_flag_value(args, "--windows-copyright", getattr(attr, "windows_copyright", None))
|
||||
|
||||
def declare_staged_bun_build_action(ctx, bun_bin, build_args, build_inputs, outputs, mnemonic, progress_message, name_suffix):
|
||||
sorted_inputs = sort_files_by_short_path(build_inputs.to_list())
|
||||
input_manifest = ctx.actions.declare_file(ctx.label.name + name_suffix + ".inputs")
|
||||
runner = ctx.actions.declare_file(ctx.label.name + name_suffix + "_runner.js")
|
||||
|
||||
ctx.actions.write(
|
||||
output = input_manifest,
|
||||
content = "".join([file.path + "\n" for file in sorted_inputs]),
|
||||
)
|
||||
ctx.actions.write(
|
||||
output = runner,
|
||||
content = _STAGED_BUILD_RUNNER,
|
||||
)
|
||||
|
||||
ctx.actions.run(
|
||||
executable = bun_bin,
|
||||
arguments = ["--bun", runner.path, input_manifest.path, build_args],
|
||||
inputs = depset(
|
||||
direct = [input_manifest, runner],
|
||||
transitive = [build_inputs],
|
||||
),
|
||||
outputs = outputs,
|
||||
mnemonic = mnemonic,
|
||||
progress_message = progress_message,
|
||||
)
|
||||
|
||||
@@ -1,21 +1,30 @@
|
||||
"""Rule for bundling JS/TS sources with Bun."""
|
||||
|
||||
load("//internal:bun_build_support.bzl", "add_bun_build_common_flags", "bun_build_transitive_inputs")
|
||||
load("//internal:bun_build_support.bzl", "add_bun_build_common_flags", "bun_build_transitive_inputs", "declare_staged_bun_build_action", "sort_files_by_short_path", "validate_hermetic_install_mode")
|
||||
|
||||
|
||||
def _output_name(target_name, entry):
|
||||
stem = entry.basename.rsplit(".", 1)[0]
|
||||
return "{}__{}.js".format(target_name, stem)
|
||||
stem = entry.short_path.rsplit(".", 1)[0]
|
||||
sanitized = stem.replace("\\", "_").replace("/", "_").replace("-", "_").replace(".", "_").replace("@", "at_")
|
||||
sanitized = sanitized.replace("__", "_").replace("__", "_").replace("__", "_")
|
||||
sanitized = sanitized.strip("_")
|
||||
if not sanitized:
|
||||
sanitized = entry.basename.rsplit(".", 1)[0]
|
||||
return "{}__{}.js".format(target_name, sanitized)
|
||||
|
||||
|
||||
def _bun_bundle_impl(ctx):
|
||||
validate_hermetic_install_mode(ctx.attr, "bun_bundle")
|
||||
|
||||
toolchain = ctx.toolchains["//bun:toolchain_type"]
|
||||
bun_bin = toolchain.bun.bun_bin
|
||||
entry_points = sort_files_by_short_path(ctx.files.entry_points)
|
||||
data_files = sort_files_by_short_path(ctx.files.data)
|
||||
|
||||
transitive_inputs = bun_build_transitive_inputs(ctx)
|
||||
|
||||
outputs = []
|
||||
for entry in ctx.files.entry_points:
|
||||
for entry in entry_points:
|
||||
output = ctx.actions.declare_file(_output_name(ctx.label.name, entry))
|
||||
outputs.append(output)
|
||||
|
||||
@@ -27,16 +36,18 @@ def _bun_bundle_impl(ctx):
|
||||
args.add(output.path)
|
||||
args.add(entry.path)
|
||||
|
||||
ctx.actions.run(
|
||||
executable = bun_bin,
|
||||
arguments = [args],
|
||||
inputs = depset(
|
||||
direct = [entry] + ctx.files.data,
|
||||
declare_staged_bun_build_action(
|
||||
ctx,
|
||||
bun_bin,
|
||||
args,
|
||||
depset(
|
||||
direct = [entry] + data_files,
|
||||
transitive = transitive_inputs,
|
||||
),
|
||||
outputs = [output],
|
||||
mnemonic = "BunBundle",
|
||||
progress_message = "Bundling {} with Bun".format(entry.short_path),
|
||||
name_suffix = "_bundle_{}".format(output.basename.rsplit(".", 1)[0]),
|
||||
)
|
||||
|
||||
return [DefaultInfo(files = depset(outputs))]
|
||||
@@ -67,7 +78,7 @@ Each entry point produces one output JavaScript artifact.
|
||||
"install_mode": attr.string(
|
||||
default = "disable",
|
||||
values = ["disable", "auto", "fallback", "force"],
|
||||
doc = "Whether Bun may auto-install missing packages during bundling.",
|
||||
doc = "Whether Bun may auto-install missing packages during bundling. Hermetic bundle actions require `disable`; other values are rejected.",
|
||||
),
|
||||
"target": attr.string(
|
||||
default = "browser",
|
||||
|
||||
@@ -82,3 +82,32 @@ def add_install_mode(args, install_mode):
|
||||
args.add("--no-install")
|
||||
elif install_mode in ["fallback", "force"]:
|
||||
add_flag_value(args, "--install", install_mode)
|
||||
|
||||
def append_arg(values, value):
|
||||
values.append(str(value))
|
||||
|
||||
def append_flag(values, flag, enabled):
|
||||
if enabled:
|
||||
append_arg(values, flag)
|
||||
|
||||
def append_flag_value(values, flag, value):
|
||||
if value == None:
|
||||
return
|
||||
if type(value) == type("") and not value:
|
||||
return
|
||||
append_arg(values, flag)
|
||||
append_arg(values, value)
|
||||
|
||||
def append_flag_values(values, flag, items):
|
||||
for item in items:
|
||||
append_flag_value(values, flag, item)
|
||||
|
||||
def append_raw_flags(values, items):
|
||||
for item in items:
|
||||
append_arg(values, item)
|
||||
|
||||
def append_install_mode(values, install_mode):
|
||||
if install_mode == "disable":
|
||||
append_arg(values, "--no-install")
|
||||
elif install_mode in ["fallback", "force"]:
|
||||
append_flag_value(values, "--install", install_mode)
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
"""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", "infer_entry_point_root")
|
||||
load("//internal:bun_build_support.bzl", "add_bun_build_common_flags", "add_bun_compile_flags", "bun_build_transitive_inputs", "declare_staged_bun_build_action", "infer_entry_point_root", "sort_files_by_short_path", "validate_hermetic_install_mode")
|
||||
|
||||
def _bun_build_impl(ctx):
|
||||
validate_hermetic_install_mode(ctx.attr, "bun_build")
|
||||
|
||||
toolchain = ctx.toolchains["//bun:toolchain_type"]
|
||||
bun_bin = toolchain.bun.bun_bin
|
||||
entry_points = sort_files_by_short_path(ctx.files.entry_points)
|
||||
data_files = sort_files_by_short_path(ctx.files.data)
|
||||
output_dir = ctx.actions.declare_directory(ctx.label.name)
|
||||
metafile = None
|
||||
if ctx.attr.metafile:
|
||||
@@ -14,24 +18,20 @@ def _bun_build_impl(ctx):
|
||||
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)
|
||||
build_root = infer_entry_point_root(entry_points)
|
||||
transitive_inputs = bun_build_transitive_inputs(ctx)
|
||||
build_inputs = depset(
|
||||
direct = ctx.files.entry_points + ctx.files.data,
|
||||
direct = entry_points + data_files,
|
||||
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, root = build_root)
|
||||
args.add("--outdir")
|
||||
args.add(output_dir.path)
|
||||
args.add_all(ctx.files.entry_points)
|
||||
build_args = ctx.actions.args()
|
||||
build_args.add("--bun")
|
||||
build_args.add("build")
|
||||
add_bun_build_common_flags(build_args, ctx.attr, metafile = metafile, metafile_md = metafile_md, root = build_root)
|
||||
build_args.add("--outdir")
|
||||
build_args.add(output_dir.path)
|
||||
build_args.add_all(entry_points)
|
||||
|
||||
outputs = [output_dir]
|
||||
if metafile:
|
||||
@@ -39,87 +39,27 @@ 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 = runner,
|
||||
arguments = [args],
|
||||
inputs = depset(
|
||||
direct = [input_manifest, bun_bin],
|
||||
transitive = [build_inputs],
|
||||
),
|
||||
declare_staged_bun_build_action(
|
||||
ctx,
|
||||
bun_bin,
|
||||
build_args,
|
||||
build_inputs,
|
||||
outputs = outputs,
|
||||
mnemonic = "BunBuild",
|
||||
progress_message = "Building {} with Bun".format(ctx.label.name),
|
||||
name_suffix = "_build",
|
||||
)
|
||||
|
||||
return [DefaultInfo(files = depset(outputs))]
|
||||
|
||||
def _bun_compile_impl(ctx):
|
||||
validate_hermetic_install_mode(ctx.attr, "bun_compile")
|
||||
|
||||
toolchain = ctx.toolchains["//bun:toolchain_type"]
|
||||
bun_bin = toolchain.bun.bun_bin
|
||||
output = ctx.actions.declare_file(ctx.label.name)
|
||||
compile_executable = ctx.file.compile_executable
|
||||
data_files = sort_files_by_short_path(ctx.files.data)
|
||||
|
||||
args = ctx.actions.args()
|
||||
args.add("--bun")
|
||||
@@ -130,20 +70,22 @@ def _bun_compile_impl(ctx):
|
||||
args.add(output.path)
|
||||
args.add(ctx.file.entry_point.path)
|
||||
|
||||
direct_inputs = [ctx.file.entry_point] + ctx.files.data
|
||||
direct_inputs = [ctx.file.entry_point] + data_files
|
||||
if compile_executable:
|
||||
direct_inputs.append(compile_executable)
|
||||
|
||||
ctx.actions.run(
|
||||
executable = bun_bin,
|
||||
arguments = [args],
|
||||
inputs = depset(
|
||||
declare_staged_bun_build_action(
|
||||
ctx,
|
||||
bun_bin,
|
||||
args,
|
||||
depset(
|
||||
direct = direct_inputs,
|
||||
transitive = bun_build_transitive_inputs(ctx),
|
||||
),
|
||||
outputs = [output],
|
||||
mnemonic = "BunCompile",
|
||||
progress_message = "Compiling {} with Bun".format(ctx.file.entry_point.short_path),
|
||||
name_suffix = "_compile",
|
||||
)
|
||||
|
||||
return [
|
||||
@@ -167,7 +109,7 @@ _COMMON_BUILD_ATTRS = {
|
||||
"install_mode": attr.string(
|
||||
default = "disable",
|
||||
values = ["disable", "auto", "fallback", "force"],
|
||||
doc = "Whether Bun may auto-install missing packages while executing the build.",
|
||||
doc = "Whether Bun may auto-install missing packages while executing the build. Hermetic build actions require `disable`; other values are rejected.",
|
||||
),
|
||||
"target": attr.string(
|
||||
default = "browser",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
"""Rule for running JS/TS scripts with Bun in watch mode for development."""
|
||||
|
||||
load("//internal:bun_command.bzl", "append_shell_flag", "append_shell_flag_files", "append_shell_flag_values", "append_shell_install_mode", "append_shell_raw_flags", "render_shell_array", "shell_quote")
|
||||
load("//internal:workspace.bzl", "create_bun_workspace_info", "render_workspace_setup", "workspace_runfiles")
|
||||
load("//internal:bun_command.bzl", "append_flag", "append_flag_values", "append_install_mode", "append_raw_flags")
|
||||
load("//internal:runtime_launcher.bzl", "declare_runtime_wrapper", "runfiles_path", "runtime_launcher_attrs", "write_launcher_spec")
|
||||
load("//internal:workspace.bzl", "create_bun_workspace_info", "workspace_runfiles")
|
||||
|
||||
def _bun_dev_impl(ctx):
|
||||
toolchain = ctx.toolchains["//bun:toolchain_type"]
|
||||
@@ -13,200 +14,127 @@ def _bun_dev_impl(ctx):
|
||||
primary_file = entry_point,
|
||||
)
|
||||
|
||||
restart_watch_paths = "\n".join([path.short_path for path in ctx.files.restart_on])
|
||||
launcher_lines = [render_shell_array("bun_args", ["--bun", "run"])]
|
||||
append_shell_install_mode(launcher_lines, "bun_args", ctx.attr.install_mode)
|
||||
append_shell_flag_files(launcher_lines, "bun_args", "--preload", ctx.files.preload)
|
||||
append_shell_flag_files(launcher_lines, "bun_args", "--env-file", ctx.files.env_files)
|
||||
append_shell_flag(launcher_lines, "bun_args", "--no-env-file", ctx.attr.no_env_file)
|
||||
append_shell_flag(launcher_lines, "bun_args", "--smol", ctx.attr.smol)
|
||||
append_shell_flag_values(launcher_lines, "bun_args", "--conditions", ctx.attr.conditions)
|
||||
append_shell_flag(launcher_lines, "bun_args", "--no-clear-screen", ctx.attr.no_clear_screen)
|
||||
append_shell_raw_flags(launcher_lines, "bun_args", ctx.attr.run_flags)
|
||||
launcher_lines.append('bun_args+=("${primary_source}")')
|
||||
for arg in ctx.attr.args:
|
||||
launcher_lines.append("bun_args+=(%s)" % shell_quote(arg))
|
||||
argv = ["--bun", "run"]
|
||||
append_install_mode(argv, ctx.attr.install_mode)
|
||||
append_flag(argv, "--no-env-file", ctx.attr.no_env_file)
|
||||
append_flag(argv, "--smol", ctx.attr.smol)
|
||||
append_flag_values(argv, "--conditions", ctx.attr.conditions)
|
||||
append_flag(argv, "--no-clear-screen", ctx.attr.no_clear_screen)
|
||||
append_raw_flags(argv, ctx.attr.run_flags)
|
||||
|
||||
command = """
|
||||
__BUN_ARGS__
|
||||
watch_mode="__WATCH_MODE__"
|
||||
if [[ "${watch_mode}" == "hot" ]]; then
|
||||
bun_args+=("--hot")
|
||||
else
|
||||
bun_args+=("--watch")
|
||||
fi
|
||||
|
||||
if [[ __RESTART_COUNT__ -eq 0 ]]; then
|
||||
trap cleanup_runtime_workspace EXIT
|
||||
cd "${runtime_exec_dir}"
|
||||
exec "${bun_bin}" "${bun_args[@]}" "$@"
|
||||
fi
|
||||
|
||||
readarray -t restart_paths <<'EOF_RESTART_PATHS'
|
||||
__RESTART_PATHS__
|
||||
EOF_RESTART_PATHS
|
||||
|
||||
file_mtime() {
|
||||
local path="$1"
|
||||
if stat -f '%m' "${path}" >/dev/null 2>&1; then
|
||||
stat -f '%m' "${path}"
|
||||
return 0
|
||||
fi
|
||||
stat -c '%Y' "${path}"
|
||||
}
|
||||
|
||||
declare -A mtimes
|
||||
for rel in "${restart_paths[@]}"; do
|
||||
path="${runfiles_dir}/_main/${rel}"
|
||||
if [[ -e "${path}" ]]; then
|
||||
mtimes["${rel}"]="$(file_mtime "${path}")"
|
||||
else
|
||||
mtimes["${rel}"]="missing"
|
||||
fi
|
||||
done
|
||||
|
||||
child_pid=""
|
||||
restart_child() {
|
||||
if [[ -n "${child_pid}" ]] && kill -0 "${child_pid}" 2>/dev/null; then
|
||||
kill "${child_pid}"
|
||||
wait "${child_pid}" || true
|
||||
fi
|
||||
|
||||
(
|
||||
cd "${runtime_exec_dir}"
|
||||
exec "${bun_bin}" "${bun_args[@]}" "$@"
|
||||
) &
|
||||
child_pid=$!
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
if [[ -n "${child_pid}" ]] && kill -0 "${child_pid}" 2>/dev/null; then
|
||||
kill "${child_pid}"
|
||||
wait "${child_pid}" || true
|
||||
fi
|
||||
cleanup_runtime_workspace
|
||||
}
|
||||
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
restart_child "$@"
|
||||
|
||||
while true; do
|
||||
sleep 1
|
||||
changed=0
|
||||
for rel in "${restart_paths[@]}"; do
|
||||
path="${runfiles_dir}/_main/${rel}"
|
||||
if [[ -e "${path}" ]]; then
|
||||
current="$(file_mtime "${path}")"
|
||||
else
|
||||
current="missing"
|
||||
fi
|
||||
if [[ "${current}" != "${mtimes[${rel}]}" ]]; then
|
||||
mtimes["${rel}"]="${current}"
|
||||
changed=1
|
||||
fi
|
||||
done
|
||||
if [[ "${changed}" -eq 1 ]]; then
|
||||
restart_child "$@"
|
||||
fi
|
||||
done
|
||||
""".replace("__WATCH_MODE__", ctx.attr.watch_mode).replace(
|
||||
"__RESTART_COUNT__",
|
||||
str(len(ctx.files.restart_on)),
|
||||
).replace(
|
||||
"__RESTART_PATHS__",
|
||||
restart_watch_paths,
|
||||
).replace(
|
||||
"__BUN_ARGS__",
|
||||
"\n".join(launcher_lines),
|
||||
)
|
||||
|
||||
launcher = ctx.actions.declare_file(ctx.label.name)
|
||||
ctx.actions.write(
|
||||
output = launcher,
|
||||
is_executable = True,
|
||||
content = render_workspace_setup(
|
||||
bun_short_path = bun_bin.short_path,
|
||||
install_metadata_short_path = workspace_info.install_metadata_file.short_path if workspace_info.install_metadata_file else "",
|
||||
primary_source_short_path = entry_point.short_path,
|
||||
working_dir_mode = ctx.attr.working_dir,
|
||||
) + command,
|
||||
)
|
||||
spec_file = write_launcher_spec(ctx, {
|
||||
"version": 1,
|
||||
"kind": "bun_run",
|
||||
"bun_short_path": runfiles_path(bun_bin),
|
||||
"primary_source_short_path": runfiles_path(entry_point),
|
||||
"package_json_short_path": "",
|
||||
"install_metadata_short_path": runfiles_path(workspace_info.install_metadata_file) if workspace_info.install_metadata_file else "",
|
||||
"install_repo_runfiles_path": workspace_info.install_repo_runfiles_path,
|
||||
"node_modules_roots": workspace_info.node_modules_roots,
|
||||
"package_dir_hint": workspace_info.package_dir_hint,
|
||||
"working_dir_mode": ctx.attr.working_dir,
|
||||
"inherit_host_path": ctx.attr.inherit_host_path,
|
||||
"argv": argv,
|
||||
"args": ctx.attr.args,
|
||||
"passthrough_args": True,
|
||||
"tool_short_path": "",
|
||||
"restart_on": [runfiles_path(file) for file in ctx.files.restart_on],
|
||||
"watch_mode": ctx.attr.watch_mode,
|
||||
"reporter": "",
|
||||
"coverage": False,
|
||||
"coverage_reporters": [],
|
||||
"preload_short_paths": [runfiles_path(file) for file in ctx.files.preload],
|
||||
"env_file_short_paths": [runfiles_path(file) for file in ctx.files.env_files],
|
||||
"test_short_paths": [],
|
||||
})
|
||||
launcher = declare_runtime_wrapper(ctx, bun_bin, spec_file)
|
||||
|
||||
return [
|
||||
workspace_info,
|
||||
DefaultInfo(
|
||||
executable = launcher,
|
||||
runfiles = workspace_runfiles(ctx, workspace_info, direct_files = [launcher]),
|
||||
executable = launcher.executable,
|
||||
runfiles = workspace_runfiles(
|
||||
ctx,
|
||||
workspace_info,
|
||||
direct_files = [launcher.executable, launcher.runner, spec_file],
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
_BUN_DEV_ATTRS = runtime_launcher_attrs()
|
||||
_BUN_DEV_ATTRS.update({
|
||||
"entry_point": attr.label(
|
||||
mandatory = True,
|
||||
allow_single_file = [".js", ".ts", ".jsx", ".tsx", ".mjs", ".cjs"],
|
||||
doc = "Path to the main JS/TS file to execute in dev mode.",
|
||||
),
|
||||
"watch_mode": attr.string(
|
||||
default = "watch",
|
||||
values = ["watch", "hot"],
|
||||
doc = "Bun live-reload mode: `watch` (default) or `hot`.",
|
||||
),
|
||||
"restart_on": attr.label_list(
|
||||
allow_files = True,
|
||||
doc = "Files that trigger a full Bun process restart when they change.",
|
||||
),
|
||||
"node_modules": attr.label(
|
||||
doc = "Optional label providing package files from a `node_modules` tree, typically produced by `bun_install`, in runfiles.",
|
||||
),
|
||||
"data": attr.label_list(
|
||||
allow_files = True,
|
||||
doc = "Additional runtime files required by the dev process.",
|
||||
),
|
||||
"preload": attr.label_list(
|
||||
allow_files = True,
|
||||
doc = "Modules to preload with `--preload` before running the entry point.",
|
||||
),
|
||||
"env_files": attr.label_list(
|
||||
allow_files = True,
|
||||
doc = "Additional environment files loaded with `--env-file`.",
|
||||
),
|
||||
"no_env_file": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, disables Bun's automatic `.env` loading.",
|
||||
),
|
||||
"smol": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, enables Bun's lower-memory runtime mode.",
|
||||
),
|
||||
"conditions": attr.string_list(
|
||||
doc = "Custom package resolve conditions passed to Bun.",
|
||||
),
|
||||
"install_mode": attr.string(
|
||||
default = "disable",
|
||||
values = ["disable", "auto", "fallback", "force"],
|
||||
doc = "Whether Bun may auto-install missing packages in dev mode.",
|
||||
),
|
||||
"no_clear_screen": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, disables terminal clearing on Bun reloads.",
|
||||
),
|
||||
"run_flags": attr.string_list(
|
||||
doc = "Additional raw flags forwarded to `bun run` before the entry point.",
|
||||
),
|
||||
"working_dir": attr.string(
|
||||
default = "workspace",
|
||||
values = ["workspace", "entry_point"],
|
||||
doc = "Working directory at runtime: `workspace` root or nearest `entry_point` ancestor containing `.env`/`package.json`.",
|
||||
),
|
||||
"inherit_host_path": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, appends the host PATH after staged node_modules/.bin entries at runtime.",
|
||||
),
|
||||
})
|
||||
|
||||
bun_dev = rule(
|
||||
implementation = _bun_dev_impl,
|
||||
doc = """Runs a JS/TS entry point in Bun development watch mode.
|
||||
|
||||
This rule is intended for local dev loops (`bazel run`) and supports Bun
|
||||
watch/HMR plus optional full restarts on selected file changes.
|
||||
watch/HMR plus optional full restarts on selected file changes. It is a local
|
||||
workflow helper rather than a hermetic build rule.
|
||||
""",
|
||||
attrs = {
|
||||
"entry_point": attr.label(
|
||||
mandatory = True,
|
||||
allow_single_file = [".js", ".ts", ".jsx", ".tsx", ".mjs", ".cjs"],
|
||||
doc = "Path to the main JS/TS file to execute in dev mode.",
|
||||
),
|
||||
"watch_mode": attr.string(
|
||||
default = "watch",
|
||||
values = ["watch", "hot"],
|
||||
doc = "Bun live-reload mode: `watch` (default) or `hot`.",
|
||||
),
|
||||
"restart_on": attr.label_list(
|
||||
allow_files = True,
|
||||
doc = "Files that trigger a full Bun process restart when they change.",
|
||||
),
|
||||
"node_modules": attr.label(
|
||||
doc = "Optional label providing package files from a `node_modules` tree, typically produced by `bun_install`, in runfiles.",
|
||||
),
|
||||
"data": attr.label_list(
|
||||
allow_files = True,
|
||||
doc = "Additional runtime files required by the dev process.",
|
||||
),
|
||||
"preload": attr.label_list(
|
||||
allow_files = True,
|
||||
doc = "Modules to preload with `--preload` before running the entry point.",
|
||||
),
|
||||
"env_files": attr.label_list(
|
||||
allow_files = True,
|
||||
doc = "Additional environment files loaded with `--env-file`.",
|
||||
),
|
||||
"no_env_file": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, disables Bun's automatic `.env` loading.",
|
||||
),
|
||||
"smol": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, enables Bun's lower-memory runtime mode.",
|
||||
),
|
||||
"conditions": attr.string_list(
|
||||
doc = "Custom package resolve conditions passed to Bun.",
|
||||
),
|
||||
"install_mode": attr.string(
|
||||
default = "disable",
|
||||
values = ["disable", "auto", "fallback", "force"],
|
||||
doc = "Whether Bun may auto-install missing packages in dev mode.",
|
||||
),
|
||||
"no_clear_screen": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, disables terminal clearing on Bun reloads.",
|
||||
),
|
||||
"run_flags": attr.string_list(
|
||||
doc = "Additional raw flags forwarded to `bun run` before the entry point.",
|
||||
),
|
||||
"working_dir": attr.string(
|
||||
default = "workspace",
|
||||
values = ["workspace", "entry_point"],
|
||||
doc = "Working directory at runtime: `workspace` root or nearest `entry_point` ancestor containing `.env`/`package.json`.",
|
||||
),
|
||||
},
|
||||
attrs = _BUN_DEV_ATTRS,
|
||||
executable = True,
|
||||
toolchains = ["//bun:toolchain_type"],
|
||||
)
|
||||
|
||||
@@ -205,8 +205,14 @@ def _materialize_workspace_packages(repository_ctx, package_json):
|
||||
workspace_packages[relative_dir] = package_name if type(package_name) == type("") else ""
|
||||
|
||||
package_dirs = sorted(workspace_packages.keys())
|
||||
package_names_by_dir = {}
|
||||
for package_dir in package_dirs:
|
||||
package_name = workspace_packages[package_dir]
|
||||
if package_name:
|
||||
package_names_by_dir[package_dir] = package_name
|
||||
return struct(
|
||||
package_dirs = package_dirs,
|
||||
package_names_by_dir = package_names_by_dir,
|
||||
package_names = [workspace_packages[package_dir] for package_dir in package_dirs if workspace_packages[package_dir]],
|
||||
)
|
||||
|
||||
@@ -381,8 +387,10 @@ stderr:
|
||||
"node_modules/.rules_bun/install.json",
|
||||
json.encode({
|
||||
"bun_lockfile": lockfile_name,
|
||||
"install_root_rel_dir": ".",
|
||||
"package_json": "package.json",
|
||||
"workspace_package_dirs": workspace_packages.package_dirs,
|
||||
"workspace_package_names_by_dir": workspace_packages.package_names_by_dir,
|
||||
}) + "\n",
|
||||
)
|
||||
|
||||
@@ -409,7 +417,7 @@ bun_install_repository = repository_rule(
|
||||
"omit": attr.string_list(),
|
||||
"linker": attr.string(),
|
||||
"backend": attr.string(),
|
||||
"ignore_scripts": attr.bool(default = False),
|
||||
"ignore_scripts": attr.bool(default = True),
|
||||
"install_flags": attr.string_list(),
|
||||
"visible_repo_name": attr.string(),
|
||||
"bun_linux_x64": attr.label(default = "@bun_linux_x64//:bun-linux-x64/bun", allow_single_file = True),
|
||||
@@ -430,7 +438,7 @@ def bun_install(
|
||||
omit = [],
|
||||
linker = "",
|
||||
backend = "",
|
||||
ignore_scripts = False,
|
||||
ignore_scripts = True,
|
||||
install_flags = []):
|
||||
"""Create an external repository containing installed node_modules.
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""Rule for running package.json scripts with Bun."""
|
||||
|
||||
load("//internal:bun_command.bzl", "append_shell_flag", "append_shell_flag_files", "append_shell_flag_value", "append_shell_flag_values", "append_shell_install_mode", "append_shell_raw_flags", "render_shell_array", "shell_quote")
|
||||
load("//internal:workspace.bzl", "create_bun_workspace_info", "render_workspace_setup", "workspace_runfiles")
|
||||
|
||||
load("//internal:bun_command.bzl", "append_flag", "append_flag_value", "append_flag_values", "append_install_mode", "append_raw_flags")
|
||||
load("//internal:runtime_launcher.bzl", "declare_runtime_wrapper", "runfiles_path", "runtime_launcher_attrs", "write_launcher_spec")
|
||||
load("//internal:workspace.bzl", "create_bun_workspace_info", "workspace_runfiles")
|
||||
|
||||
def _bun_script_impl(ctx):
|
||||
toolchain = ctx.toolchains["//bun:toolchain_type"]
|
||||
@@ -16,56 +16,141 @@ def _bun_script_impl(ctx):
|
||||
primary_file = package_json,
|
||||
)
|
||||
|
||||
launcher_lines = [render_shell_array("bun_args", ["--bun", "run"])]
|
||||
append_shell_install_mode(launcher_lines, "bun_args", ctx.attr.install_mode)
|
||||
append_shell_flag_files(launcher_lines, "bun_args", "--preload", ctx.files.preload)
|
||||
append_shell_flag_files(launcher_lines, "bun_args", "--env-file", ctx.files.env_files)
|
||||
append_shell_flag(launcher_lines, "bun_args", "--no-env-file", ctx.attr.no_env_file)
|
||||
append_shell_flag(launcher_lines, "bun_args", "--smol", ctx.attr.smol)
|
||||
append_shell_flag_values(launcher_lines, "bun_args", "--conditions", ctx.attr.conditions)
|
||||
append_shell_flag(launcher_lines, "bun_args", "--workspaces", ctx.attr.workspaces)
|
||||
append_shell_flag_values(launcher_lines, "bun_args", "--filter", ctx.attr.filters)
|
||||
argv = ["--bun", "run"]
|
||||
append_install_mode(argv, ctx.attr.install_mode)
|
||||
append_flag(argv, "--no-env-file", ctx.attr.no_env_file)
|
||||
append_flag(argv, "--smol", ctx.attr.smol)
|
||||
append_flag_values(argv, "--conditions", ctx.attr.conditions)
|
||||
append_flag(argv, "--workspaces", ctx.attr.workspaces)
|
||||
append_flag_values(argv, "--filter", ctx.attr.filters)
|
||||
if ctx.attr.execution_mode == "parallel":
|
||||
append_shell_flag(launcher_lines, "bun_args", "--parallel", True)
|
||||
append_flag(argv, "--parallel", True)
|
||||
elif ctx.attr.execution_mode == "sequential":
|
||||
append_shell_flag(launcher_lines, "bun_args", "--sequential", True)
|
||||
append_shell_flag(launcher_lines, "bun_args", "--no-exit-on-error", ctx.attr.no_exit_on_error)
|
||||
append_shell_flag_value(launcher_lines, "bun_args", "--shell", ctx.attr.shell)
|
||||
append_shell_flag(launcher_lines, "bun_args", "--silent", ctx.attr.silent)
|
||||
append_shell_raw_flags(launcher_lines, "bun_args", ctx.attr.run_flags)
|
||||
launcher_lines.append('bun_args+=(%s)' % shell_quote(ctx.attr.script))
|
||||
for arg in ctx.attr.args:
|
||||
launcher_lines.append("bun_args+=(%s)" % shell_quote(arg))
|
||||
append_flag(argv, "--sequential", True)
|
||||
append_flag(argv, "--no-exit-on-error", ctx.attr.no_exit_on_error)
|
||||
append_flag_value(argv, "--shell", ctx.attr.shell)
|
||||
append_flag(argv, "--silent", ctx.attr.silent)
|
||||
append_raw_flags(argv, ctx.attr.run_flags)
|
||||
|
||||
command = """
|
||||
trap cleanup_runtime_workspace EXIT
|
||||
cd "${runtime_exec_dir}"
|
||||
__BUN_ARGS__
|
||||
exec "${bun_bin}" "${bun_args[@]}" "$@"
|
||||
""".replace("__BUN_ARGS__", "\n".join(launcher_lines))
|
||||
|
||||
launcher = ctx.actions.declare_file(ctx.label.name)
|
||||
ctx.actions.write(
|
||||
output = launcher,
|
||||
is_executable = True,
|
||||
content = render_workspace_setup(
|
||||
bun_short_path = bun_bin.short_path,
|
||||
package_dir_hint = package_json.dirname or ".",
|
||||
package_json_short_path = package_json.short_path,
|
||||
primary_source_short_path = package_json.short_path,
|
||||
install_metadata_short_path = workspace_info.install_metadata_file.short_path if workspace_info.install_metadata_file else "",
|
||||
working_dir_mode = ctx.attr.working_dir,
|
||||
) + command,
|
||||
)
|
||||
spec_file = write_launcher_spec(ctx, {
|
||||
"version": 1,
|
||||
"kind": "bun_run",
|
||||
"bun_short_path": runfiles_path(bun_bin),
|
||||
"primary_source_short_path": "",
|
||||
"package_json_short_path": runfiles_path(package_json),
|
||||
"install_metadata_short_path": runfiles_path(workspace_info.install_metadata_file) if workspace_info.install_metadata_file else "",
|
||||
"install_repo_runfiles_path": workspace_info.install_repo_runfiles_path,
|
||||
"node_modules_roots": workspace_info.node_modules_roots,
|
||||
"package_dir_hint": package_json.dirname or ".",
|
||||
"working_dir_mode": ctx.attr.working_dir,
|
||||
"inherit_host_path": ctx.attr.inherit_host_path,
|
||||
"argv": argv,
|
||||
"args": [ctx.attr.script] + ctx.attr.args,
|
||||
"passthrough_args": True,
|
||||
"tool_short_path": "",
|
||||
"restart_on": [],
|
||||
"watch_mode": "",
|
||||
"reporter": "",
|
||||
"coverage": False,
|
||||
"coverage_reporters": [],
|
||||
"preload_short_paths": [runfiles_path(file) for file in ctx.files.preload],
|
||||
"env_file_short_paths": [runfiles_path(file) for file in ctx.files.env_files],
|
||||
"test_short_paths": [],
|
||||
})
|
||||
launcher = declare_runtime_wrapper(ctx, bun_bin, spec_file)
|
||||
|
||||
return [
|
||||
workspace_info,
|
||||
DefaultInfo(
|
||||
executable = launcher,
|
||||
runfiles = workspace_runfiles(ctx, workspace_info, direct_files = [launcher]),
|
||||
executable = launcher.executable,
|
||||
runfiles = workspace_runfiles(
|
||||
ctx,
|
||||
workspace_info,
|
||||
direct_files = [launcher.executable, launcher.runner, spec_file],
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
_BUN_SCRIPT_ATTRS = runtime_launcher_attrs()
|
||||
_BUN_SCRIPT_ATTRS.update({
|
||||
"script": attr.string(
|
||||
mandatory = True,
|
||||
doc = "Name of the `package.json` script to execute via `bun run <script>`.",
|
||||
),
|
||||
"package_json": attr.label(
|
||||
mandatory = True,
|
||||
allow_single_file = True,
|
||||
doc = "Label of the `package.json` file containing the named script.",
|
||||
),
|
||||
"node_modules": attr.label(
|
||||
doc = "Optional label providing package files from a `node_modules` tree, typically produced by `bun_install`, in runfiles. Executables from `node_modules/.bin` are added to `PATH`, which is useful for scripts such as `vite`.",
|
||||
),
|
||||
"data": attr.label_list(
|
||||
allow_files = True,
|
||||
doc = "Additional runtime files required by the script.",
|
||||
),
|
||||
"preload": attr.label_list(
|
||||
allow_files = True,
|
||||
doc = "Modules to preload with `--preload` before running the script.",
|
||||
),
|
||||
"env_files": attr.label_list(
|
||||
allow_files = True,
|
||||
doc = "Additional environment files loaded with `--env-file`.",
|
||||
),
|
||||
"no_env_file": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, disables Bun's automatic `.env` loading.",
|
||||
),
|
||||
"smol": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, enables Bun's lower-memory runtime mode.",
|
||||
),
|
||||
"conditions": attr.string_list(
|
||||
doc = "Custom package resolve conditions passed to Bun.",
|
||||
),
|
||||
"install_mode": attr.string(
|
||||
default = "disable",
|
||||
values = ["disable", "auto", "fallback", "force"],
|
||||
doc = "Whether Bun may auto-install missing packages while running the script.",
|
||||
),
|
||||
"filters": attr.string_list(
|
||||
doc = "Workspace package filters passed via repeated `--filter` flags.",
|
||||
),
|
||||
"workspaces": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, runs the script in all workspace packages.",
|
||||
),
|
||||
"execution_mode": attr.string(
|
||||
default = "single",
|
||||
values = ["single", "parallel", "sequential"],
|
||||
doc = "How Bun should execute matching workspace scripts.",
|
||||
),
|
||||
"no_exit_on_error": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, Bun keeps running other workspace scripts when one fails.",
|
||||
),
|
||||
"shell": attr.string(
|
||||
default = "",
|
||||
values = ["", "bun", "system"],
|
||||
doc = "Optional shell implementation for package scripts.",
|
||||
),
|
||||
"silent": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, suppresses Bun's command echo for package scripts.",
|
||||
),
|
||||
"run_flags": attr.string_list(
|
||||
doc = "Additional raw flags forwarded to `bun run` before the script name.",
|
||||
),
|
||||
"working_dir": attr.string(
|
||||
default = "package",
|
||||
values = ["workspace", "package"],
|
||||
doc = "Working directory at runtime: Bazel runfiles `workspace` root or the directory containing `package.json`. The default `package` mode matches tools such as Vite that resolve config and assets relative to the package directory.",
|
||||
),
|
||||
"inherit_host_path": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, appends the host PATH after staged node_modules/.bin entries at runtime.",
|
||||
),
|
||||
})
|
||||
|
||||
bun_script = rule(
|
||||
implementation = _bun_script_impl,
|
||||
@@ -74,84 +159,10 @@ bun_script = rule(
|
||||
Use this rule to expose existing package scripts such as `dev`, `build`, or
|
||||
`check` via `bazel run` without adding wrapper shell scripts. This is a good fit
|
||||
for Vite-style workflows, where scripts like `vite dev` or `vite build` are
|
||||
declared in `package.json` and expect to run from the package directory with
|
||||
`node_modules/.bin` available on `PATH`.
|
||||
declared in `package.json` and expect to run from the package directory. This
|
||||
is a local workflow helper rather than a hermetic build rule.
|
||||
""",
|
||||
attrs = {
|
||||
"script": attr.string(
|
||||
mandatory = True,
|
||||
doc = "Name of the `package.json` script to execute via `bun run <script>`.",
|
||||
),
|
||||
"package_json": attr.label(
|
||||
mandatory = True,
|
||||
allow_single_file = True,
|
||||
doc = "Label of the `package.json` file containing the named script.",
|
||||
),
|
||||
"node_modules": attr.label(
|
||||
doc = "Optional label providing package files from a `node_modules` tree, typically produced by `bun_install`, in runfiles. Executables from `node_modules/.bin` are added to `PATH`, which is useful for scripts such as `vite`.",
|
||||
),
|
||||
"data": attr.label_list(
|
||||
allow_files = True,
|
||||
doc = "Additional runtime files required by the script.",
|
||||
),
|
||||
"preload": attr.label_list(
|
||||
allow_files = True,
|
||||
doc = "Modules to preload with `--preload` before running the script.",
|
||||
),
|
||||
"env_files": attr.label_list(
|
||||
allow_files = True,
|
||||
doc = "Additional environment files loaded with `--env-file`.",
|
||||
),
|
||||
"no_env_file": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, disables Bun's automatic `.env` loading.",
|
||||
),
|
||||
"smol": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, enables Bun's lower-memory runtime mode.",
|
||||
),
|
||||
"conditions": attr.string_list(
|
||||
doc = "Custom package resolve conditions passed to Bun.",
|
||||
),
|
||||
"install_mode": attr.string(
|
||||
default = "disable",
|
||||
values = ["disable", "auto", "fallback", "force"],
|
||||
doc = "Whether Bun may auto-install missing packages while running the script.",
|
||||
),
|
||||
"filters": attr.string_list(
|
||||
doc = "Workspace package filters passed via repeated `--filter` flags.",
|
||||
),
|
||||
"workspaces": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, runs the script in all workspace packages.",
|
||||
),
|
||||
"execution_mode": attr.string(
|
||||
default = "single",
|
||||
values = ["single", "parallel", "sequential"],
|
||||
doc = "How Bun should execute matching workspace scripts.",
|
||||
),
|
||||
"no_exit_on_error": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, Bun keeps running other workspace scripts when one fails.",
|
||||
),
|
||||
"shell": attr.string(
|
||||
default = "",
|
||||
values = ["", "bun", "system"],
|
||||
doc = "Optional shell implementation for package scripts.",
|
||||
),
|
||||
"silent": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, suppresses Bun's command echo for package scripts.",
|
||||
),
|
||||
"run_flags": attr.string_list(
|
||||
doc = "Additional raw flags forwarded to `bun run` before the script name.",
|
||||
),
|
||||
"working_dir": attr.string(
|
||||
default = "package",
|
||||
values = ["workspace", "package"],
|
||||
doc = "Working directory at runtime: Bazel runfiles `workspace` root or the directory containing `package.json`. The default `package` mode matches tools such as Vite that resolve config and assets relative to the package directory.",
|
||||
),
|
||||
},
|
||||
attrs = _BUN_SCRIPT_ATTRS,
|
||||
executable = True,
|
||||
toolchains = ["//bun:toolchain_type"],
|
||||
)
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
"""Rule for running test suites with Bun."""
|
||||
|
||||
load("//internal:bun_command.bzl", "append_shell_flag", "append_shell_flag_files", "append_shell_flag_value", "append_shell_flag_values", "append_shell_install_mode", "append_shell_raw_flags", "render_shell_array", "shell_quote")
|
||||
load("//internal:bun_command.bzl", "append_flag", "append_flag_value", "append_install_mode", "append_raw_flags")
|
||||
load("//internal:js_library.bzl", "collect_js_runfiles")
|
||||
load("//internal:workspace.bzl", "create_bun_workspace_info", "render_workspace_setup", "workspace_runfiles")
|
||||
|
||||
load("//internal:runtime_launcher.bzl", "declare_runtime_wrapper", "runfiles_path", "runtime_launcher_attrs", "write_launcher_spec")
|
||||
load("//internal:workspace.bzl", "create_bun_workspace_info", "workspace_runfiles")
|
||||
|
||||
def _bun_test_impl(ctx):
|
||||
if ctx.attr.install_mode != "disable":
|
||||
fail("bun_test requires install_mode = \"disable\" for hermetic test execution")
|
||||
|
||||
toolchain = ctx.toolchains["//bun:toolchain_type"]
|
||||
bun_bin = toolchain.bun.bun_bin
|
||||
primary_file = ctx.files.srcs[0]
|
||||
@@ -16,197 +19,180 @@ def _bun_test_impl(ctx):
|
||||
primary_file = primary_file,
|
||||
)
|
||||
|
||||
launcher_lines = [render_shell_array("bun_args", ["--bun", "test"])]
|
||||
append_shell_install_mode(launcher_lines, "bun_args", ctx.attr.install_mode)
|
||||
append_shell_flag_files(launcher_lines, "bun_args", "--preload", ctx.files.preload)
|
||||
append_shell_flag_files(launcher_lines, "bun_args", "--env-file", ctx.files.env_files)
|
||||
append_shell_flag(launcher_lines, "bun_args", "--no-env-file", ctx.attr.no_env_file)
|
||||
append_shell_flag(launcher_lines, "bun_args", "--smol", ctx.attr.smol)
|
||||
append_shell_flag_value(launcher_lines, "bun_args", "--timeout", str(ctx.attr.timeout_ms) if ctx.attr.timeout_ms > 0 else None)
|
||||
append_shell_flag(launcher_lines, "bun_args", "--update-snapshots", ctx.attr.update_snapshots)
|
||||
append_shell_flag_value(launcher_lines, "bun_args", "--rerun-each", str(ctx.attr.rerun_each) if ctx.attr.rerun_each > 0 else None)
|
||||
append_shell_flag_value(launcher_lines, "bun_args", "--retry", str(ctx.attr.retry) if ctx.attr.retry > 0 else None)
|
||||
append_shell_flag(launcher_lines, "bun_args", "--todo", ctx.attr.todo)
|
||||
append_shell_flag(launcher_lines, "bun_args", "--only", ctx.attr.only)
|
||||
append_shell_flag(launcher_lines, "bun_args", "--pass-with-no-tests", ctx.attr.pass_with_no_tests)
|
||||
append_shell_flag(launcher_lines, "bun_args", "--concurrent", ctx.attr.concurrent)
|
||||
append_shell_flag(launcher_lines, "bun_args", "--randomize", ctx.attr.randomize)
|
||||
append_shell_flag_value(launcher_lines, "bun_args", "--seed", str(ctx.attr.seed) if ctx.attr.seed > 0 else None)
|
||||
append_shell_flag_value(launcher_lines, "bun_args", "--bail", str(ctx.attr.bail) if ctx.attr.bail > 0 else None)
|
||||
append_shell_flag_value(launcher_lines, "bun_args", "--max-concurrency", str(ctx.attr.max_concurrency) if ctx.attr.max_concurrency > 0 else None)
|
||||
append_shell_raw_flags(launcher_lines, "bun_args", ctx.attr.test_flags)
|
||||
launcher_lines.append('coverage_requested="0"')
|
||||
launcher_lines.append('coverage_dir=""')
|
||||
launcher_lines.append('if [[ "${COVERAGE_DIR:-}" != "" ]]; then')
|
||||
launcher_lines.append(' coverage_requested="1"')
|
||||
launcher_lines.append(' coverage_dir="${COVERAGE_DIR}"')
|
||||
launcher_lines.append('elif [[ "%s" == "1" ]]; then' % ("1" if ctx.attr.coverage else "0"))
|
||||
launcher_lines.append(' coverage_requested="1"')
|
||||
launcher_lines.append(' coverage_dir="${TEST_UNDECLARED_OUTPUTS_DIR:-${runtime_workspace}/coverage}"')
|
||||
launcher_lines.append('fi')
|
||||
launcher_lines.append('if [[ "${coverage_requested}" == "1" ]]; then')
|
||||
launcher_lines.append(' bun_args+=("--coverage")')
|
||||
launcher_lines.append(' bun_args+=("--coverage-dir" "${coverage_dir}")')
|
||||
if ctx.attr.coverage_reporters:
|
||||
for reporter in ctx.attr.coverage_reporters:
|
||||
launcher_lines.append(' bun_args+=("--coverage-reporter" %s)' % shell_quote(reporter))
|
||||
else:
|
||||
launcher_lines.append(' if [[ "${COVERAGE_DIR:-}" != "" ]]; then')
|
||||
launcher_lines.append(' bun_args+=("--coverage-reporter" "lcov")')
|
||||
launcher_lines.append(' fi')
|
||||
launcher_lines.append('fi')
|
||||
launcher_lines.append('if [[ -n "${TESTBRIDGE_TEST_ONLY:-}" ]]; then')
|
||||
launcher_lines.append(' bun_args+=("--test-name-pattern" "${TESTBRIDGE_TEST_ONLY}")')
|
||||
launcher_lines.append('fi')
|
||||
if ctx.attr.reporter == "junit":
|
||||
launcher_lines.append('reporter_out="${XML_OUTPUT_FILE:-${runtime_workspace}/junit.xml}"')
|
||||
launcher_lines.append('bun_args+=("--reporter" "junit" "--reporter-outfile" "${reporter_out}")')
|
||||
elif ctx.attr.reporter == "dots":
|
||||
launcher_lines.append('bun_args+=("--reporter" "dots")')
|
||||
for src in ctx.files.srcs:
|
||||
launcher_lines.append("bun_args+=(%s)" % shell_quote(src.short_path))
|
||||
for arg in ctx.attr.args:
|
||||
launcher_lines.append("bun_args+=(%s)" % shell_quote(arg))
|
||||
argv = ["--bun", "test"]
|
||||
append_install_mode(argv, ctx.attr.install_mode)
|
||||
append_flag(argv, "--no-env-file", ctx.attr.no_env_file)
|
||||
append_flag(argv, "--smol", ctx.attr.smol)
|
||||
append_flag_value(argv, "--timeout", str(ctx.attr.timeout_ms) if ctx.attr.timeout_ms > 0 else None)
|
||||
append_flag(argv, "--update-snapshots", ctx.attr.update_snapshots)
|
||||
append_flag_value(argv, "--rerun-each", str(ctx.attr.rerun_each) if ctx.attr.rerun_each > 0 else None)
|
||||
append_flag_value(argv, "--retry", str(ctx.attr.retry) if ctx.attr.retry > 0 else None)
|
||||
append_flag(argv, "--todo", ctx.attr.todo)
|
||||
append_flag(argv, "--only", ctx.attr.only)
|
||||
append_flag(argv, "--pass-with-no-tests", ctx.attr.pass_with_no_tests)
|
||||
append_flag(argv, "--concurrent", ctx.attr.concurrent)
|
||||
append_flag(argv, "--randomize", ctx.attr.randomize)
|
||||
append_flag_value(argv, "--seed", str(ctx.attr.seed) if ctx.attr.seed > 0 else None)
|
||||
append_flag_value(argv, "--bail", str(ctx.attr.bail) if ctx.attr.bail > 0 else None)
|
||||
append_flag_value(argv, "--max-concurrency", str(ctx.attr.max_concurrency) if ctx.attr.max_concurrency > 0 else None)
|
||||
append_raw_flags(argv, ctx.attr.test_flags)
|
||||
|
||||
command = """
|
||||
trap cleanup_runtime_workspace EXIT
|
||||
cd "${runtime_workspace}"
|
||||
__BUN_ARGS__
|
||||
exec "${bun_bin}" "${bun_args[@]}" "$@"
|
||||
""".replace("__BUN_ARGS__", "\n".join(launcher_lines))
|
||||
spec_file = write_launcher_spec(ctx, {
|
||||
"version": 1,
|
||||
"kind": "bun_test",
|
||||
"bun_short_path": runfiles_path(bun_bin),
|
||||
"primary_source_short_path": runfiles_path(primary_file),
|
||||
"package_json_short_path": "",
|
||||
"install_metadata_short_path": runfiles_path(workspace_info.install_metadata_file) if workspace_info.install_metadata_file else "",
|
||||
"install_repo_runfiles_path": workspace_info.install_repo_runfiles_path,
|
||||
"node_modules_roots": workspace_info.node_modules_roots,
|
||||
"package_dir_hint": workspace_info.package_dir_hint,
|
||||
"working_dir_mode": "workspace",
|
||||
"inherit_host_path": ctx.attr.inherit_host_path,
|
||||
"argv": argv,
|
||||
"args": ctx.attr.args,
|
||||
"passthrough_args": True,
|
||||
"tool_short_path": "",
|
||||
"restart_on": [],
|
||||
"watch_mode": "",
|
||||
"reporter": ctx.attr.reporter,
|
||||
"coverage": ctx.attr.coverage,
|
||||
"coverage_reporters": ctx.attr.coverage_reporters,
|
||||
"preload_short_paths": [runfiles_path(file) for file in ctx.files.preload],
|
||||
"env_file_short_paths": [runfiles_path(file) for file in ctx.files.env_files],
|
||||
"test_short_paths": [runfiles_path(file) for file in ctx.files.srcs],
|
||||
})
|
||||
launcher = declare_runtime_wrapper(ctx, bun_bin, spec_file)
|
||||
|
||||
launcher = ctx.actions.declare_file(ctx.label.name)
|
||||
ctx.actions.write(
|
||||
output = launcher,
|
||||
is_executable = True,
|
||||
content = render_workspace_setup(
|
||||
bun_short_path = bun_bin.short_path,
|
||||
install_metadata_short_path = workspace_info.install_metadata_file.short_path if workspace_info.install_metadata_file else "",
|
||||
primary_source_short_path = primary_file.short_path,
|
||||
working_dir_mode = "workspace",
|
||||
) + command,
|
||||
)
|
||||
return [
|
||||
workspace_info,
|
||||
DefaultInfo(
|
||||
executable = launcher,
|
||||
executable = launcher.executable,
|
||||
runfiles = workspace_runfiles(
|
||||
ctx,
|
||||
workspace_info,
|
||||
direct_files = [launcher],
|
||||
direct_files = [launcher.executable, launcher.runner, spec_file],
|
||||
transitive_files = dep_runfiles,
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
_BUN_TEST_ATTRS = runtime_launcher_attrs()
|
||||
_BUN_TEST_ATTRS.update({
|
||||
"srcs": attr.label_list(
|
||||
mandatory = True,
|
||||
allow_files = [".js", ".ts", ".jsx", ".tsx", ".mjs", ".cjs"],
|
||||
doc = "Test source files passed to `bun test`.",
|
||||
),
|
||||
"node_modules": attr.label(
|
||||
doc = "Optional label providing package files from a `node_modules` tree, typically produced by `bun_install`, in runfiles.",
|
||||
),
|
||||
"deps": attr.label_list(
|
||||
doc = "Library dependencies required by test sources.",
|
||||
),
|
||||
"data": attr.label_list(
|
||||
allow_files = True,
|
||||
doc = "Additional runtime files needed by tests.",
|
||||
),
|
||||
"preload": attr.label_list(
|
||||
allow_files = True,
|
||||
doc = "Modules to preload with `--preload` before running tests.",
|
||||
),
|
||||
"env_files": attr.label_list(
|
||||
allow_files = True,
|
||||
doc = "Additional environment files loaded with `--env-file`.",
|
||||
),
|
||||
"no_env_file": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, disables Bun's automatic `.env` loading.",
|
||||
),
|
||||
"smol": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, enables Bun's lower-memory runtime mode.",
|
||||
),
|
||||
"install_mode": attr.string(
|
||||
default = "disable",
|
||||
values = ["disable", "auto", "fallback", "force"],
|
||||
doc = "Whether Bun may auto-install missing packages while testing.",
|
||||
),
|
||||
"timeout_ms": attr.int(
|
||||
default = 0,
|
||||
doc = "Optional per-test timeout in milliseconds.",
|
||||
),
|
||||
"update_snapshots": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, updates Bun snapshot files.",
|
||||
),
|
||||
"rerun_each": attr.int(
|
||||
default = 0,
|
||||
doc = "Optional number of times to rerun each test file.",
|
||||
),
|
||||
"retry": attr.int(
|
||||
default = 0,
|
||||
doc = "Optional default retry count for all tests.",
|
||||
),
|
||||
"todo": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, includes tests marked with `test.todo()`.",
|
||||
),
|
||||
"only": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, runs only tests marked with `test.only()` or `describe.only()`.",
|
||||
),
|
||||
"pass_with_no_tests": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, exits successfully when no tests are found.",
|
||||
),
|
||||
"concurrent": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, treats all tests as concurrent tests.",
|
||||
),
|
||||
"randomize": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, runs tests in random order.",
|
||||
),
|
||||
"seed": attr.int(
|
||||
default = 0,
|
||||
doc = "Optional randomization seed.",
|
||||
),
|
||||
"bail": attr.int(
|
||||
default = 0,
|
||||
doc = "Optional failure count after which Bun exits the test run.",
|
||||
),
|
||||
"reporter": attr.string(
|
||||
default = "console",
|
||||
values = ["console", "dots", "junit"],
|
||||
doc = "Test reporter format.",
|
||||
),
|
||||
"max_concurrency": attr.int(
|
||||
default = 0,
|
||||
doc = "Optional maximum number of concurrent tests.",
|
||||
),
|
||||
"coverage": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, always enables Bun coverage output.",
|
||||
),
|
||||
"coverage_reporters": attr.string_list(
|
||||
doc = "Repeated Bun coverage reporters such as `text` or `lcov`.",
|
||||
),
|
||||
"test_flags": attr.string_list(
|
||||
doc = "Additional raw flags forwarded to `bun test` before the test source list.",
|
||||
),
|
||||
"inherit_host_path": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, appends the host PATH after staged node_modules/.bin entries at runtime.",
|
||||
),
|
||||
})
|
||||
|
||||
bun_test = rule(
|
||||
implementation = _bun_test_impl,
|
||||
doc = """Runs Bun tests as a Bazel test target.
|
||||
|
||||
Supports Bazel test filtering (`--test_filter`) and coverage integration.
|
||||
Supports Bazel test filtering (`--test_filter`) and coverage integration. Tests
|
||||
run with strict install-mode semantics and do not inherit the host PATH unless
|
||||
explicitly requested.
|
||||
""",
|
||||
attrs = {
|
||||
"srcs": attr.label_list(
|
||||
mandatory = True,
|
||||
allow_files = [".js", ".ts", ".jsx", ".tsx", ".mjs", ".cjs"],
|
||||
doc = "Test source files passed to `bun test`.",
|
||||
),
|
||||
"node_modules": attr.label(
|
||||
doc = "Optional label providing package files from a `node_modules` tree, typically produced by `bun_install`, in runfiles.",
|
||||
),
|
||||
"deps": attr.label_list(
|
||||
doc = "Library dependencies required by test sources.",
|
||||
),
|
||||
"data": attr.label_list(
|
||||
allow_files = True,
|
||||
doc = "Additional runtime files needed by tests.",
|
||||
),
|
||||
"preload": attr.label_list(
|
||||
allow_files = True,
|
||||
doc = "Modules to preload with `--preload` before running tests.",
|
||||
),
|
||||
"env_files": attr.label_list(
|
||||
allow_files = True,
|
||||
doc = "Additional environment files loaded with `--env-file`.",
|
||||
),
|
||||
"no_env_file": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, disables Bun's automatic `.env` loading.",
|
||||
),
|
||||
"smol": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, enables Bun's lower-memory runtime mode.",
|
||||
),
|
||||
"install_mode": attr.string(
|
||||
default = "disable",
|
||||
values = ["disable", "auto", "fallback", "force"],
|
||||
doc = "Whether Bun may auto-install missing packages while testing.",
|
||||
),
|
||||
"timeout_ms": attr.int(
|
||||
default = 0,
|
||||
doc = "Optional per-test timeout in milliseconds.",
|
||||
),
|
||||
"update_snapshots": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, updates Bun snapshot files.",
|
||||
),
|
||||
"rerun_each": attr.int(
|
||||
default = 0,
|
||||
doc = "Optional number of times to rerun each test file.",
|
||||
),
|
||||
"retry": attr.int(
|
||||
default = 0,
|
||||
doc = "Optional default retry count for all tests.",
|
||||
),
|
||||
"todo": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, includes tests marked with `test.todo()`.",
|
||||
),
|
||||
"only": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, runs only tests marked with `test.only()` or `describe.only()`.",
|
||||
),
|
||||
"pass_with_no_tests": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, exits successfully when no tests are found.",
|
||||
),
|
||||
"concurrent": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, treats all tests as concurrent tests.",
|
||||
),
|
||||
"randomize": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, runs tests in random order.",
|
||||
),
|
||||
"seed": attr.int(
|
||||
default = 0,
|
||||
doc = "Optional randomization seed.",
|
||||
),
|
||||
"bail": attr.int(
|
||||
default = 0,
|
||||
doc = "Optional failure count after which Bun exits the test run.",
|
||||
),
|
||||
"reporter": attr.string(
|
||||
default = "console",
|
||||
values = ["console", "dots", "junit"],
|
||||
doc = "Test reporter format.",
|
||||
),
|
||||
"max_concurrency": attr.int(
|
||||
default = 0,
|
||||
doc = "Optional maximum number of concurrent tests.",
|
||||
),
|
||||
"coverage": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, always enables Bun coverage output.",
|
||||
),
|
||||
"coverage_reporters": attr.string_list(
|
||||
doc = "Repeated Bun coverage reporters such as `text` or `lcov`.",
|
||||
),
|
||||
"test_flags": attr.string_list(
|
||||
doc = "Additional raw flags forwarded to `bun test` before the test source list.",
|
||||
),
|
||||
},
|
||||
attrs = _BUN_TEST_ATTRS,
|
||||
test = True,
|
||||
toolchains = ["//bun:toolchain_type"],
|
||||
)
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
"""Compatibility rule for running an executable target as a dev server."""
|
||||
|
||||
load("//internal:js_library.bzl", "collect_js_runfiles")
|
||||
load("//internal:workspace.bzl", "create_bun_workspace_info", "render_workspace_setup", "workspace_runfiles")
|
||||
|
||||
def _shell_quote(value):
|
||||
return "'" + value.replace("'", "'\"'\"'") + "'"
|
||||
load("//internal:runtime_launcher.bzl", "declare_runtime_wrapper", "runfiles_path", "runtime_launcher_attrs", "write_launcher_spec")
|
||||
load("//internal:workspace.bzl", "create_bun_workspace_info", "workspace_runfiles")
|
||||
|
||||
def _js_run_devserver_impl(ctx):
|
||||
toolchain = ctx.toolchains["//bun:toolchain_type"]
|
||||
@@ -21,81 +19,93 @@ def _js_run_devserver_impl(ctx):
|
||||
extra_files = ctx.files.data + [bun_bin, tool_default_info.files_to_run.executable],
|
||||
)
|
||||
|
||||
tool_workspace = ctx.attr.tool.label.workspace_name or "_main"
|
||||
tool_path = "{}/{}".format(tool_workspace, tool_default_info.files_to_run.executable.short_path)
|
||||
default_args = " ".join([_shell_quote(arg) for arg in ctx.attr.args])
|
||||
|
||||
launcher = ctx.actions.declare_file(ctx.label.name)
|
||||
ctx.actions.write(
|
||||
output = launcher,
|
||||
is_executable = True,
|
||||
content = render_workspace_setup(
|
||||
bun_short_path = bun_bin.short_path,
|
||||
install_metadata_short_path = workspace_info.install_metadata_file.short_path if workspace_info.install_metadata_file else "",
|
||||
primary_source_short_path = package_json.short_path if package_json else tool_default_info.files_to_run.executable.short_path,
|
||||
package_json_short_path = package_json.short_path if package_json else "",
|
||||
package_dir_hint = ctx.attr.package_dir_hint,
|
||||
working_dir_mode = ctx.attr.working_dir,
|
||||
) + """
|
||||
trap cleanup_runtime_workspace EXIT
|
||||
cd "${runtime_exec_dir}"
|
||||
tool="${runfiles_dir}/__TOOL_SHORT_PATH__"
|
||||
exec "${tool}" __DEFAULT_ARGS__ "$@"
|
||||
""".replace("__TOOL_SHORT_PATH__", tool_path).replace("__DEFAULT_ARGS__", default_args),
|
||||
)
|
||||
spec_file = write_launcher_spec(ctx, {
|
||||
"version": 1,
|
||||
"kind": "tool_exec",
|
||||
"bun_short_path": runfiles_path(bun_bin),
|
||||
"primary_source_short_path": runfiles_path(package_json) if package_json else runfiles_path(tool_default_info.files_to_run.executable),
|
||||
"package_json_short_path": runfiles_path(package_json) if package_json else "",
|
||||
"install_metadata_short_path": runfiles_path(workspace_info.install_metadata_file) if workspace_info.install_metadata_file else "",
|
||||
"install_repo_runfiles_path": workspace_info.install_repo_runfiles_path,
|
||||
"node_modules_roots": workspace_info.node_modules_roots,
|
||||
"package_dir_hint": ctx.attr.package_dir_hint,
|
||||
"working_dir_mode": ctx.attr.working_dir,
|
||||
"inherit_host_path": ctx.attr.inherit_host_path,
|
||||
"argv": [],
|
||||
"args": ctx.attr.args,
|
||||
"passthrough_args": True,
|
||||
"tool_short_path": runfiles_path(tool_default_info.files_to_run.executable),
|
||||
"restart_on": [],
|
||||
"watch_mode": "",
|
||||
"reporter": "",
|
||||
"coverage": False,
|
||||
"coverage_reporters": [],
|
||||
"preload_short_paths": [],
|
||||
"env_file_short_paths": [],
|
||||
"test_short_paths": [],
|
||||
})
|
||||
launcher = declare_runtime_wrapper(ctx, bun_bin, spec_file)
|
||||
|
||||
return [
|
||||
workspace_info,
|
||||
DefaultInfo(
|
||||
executable = launcher,
|
||||
executable = launcher.executable,
|
||||
runfiles = workspace_runfiles(
|
||||
ctx,
|
||||
workspace_info,
|
||||
direct_files = [launcher, tool_default_info.files_to_run.executable],
|
||||
direct_files = [launcher.executable, launcher.runner, spec_file, tool_default_info.files_to_run.executable],
|
||||
transitive_files = dep_runfiles,
|
||||
).merge(tool_default_info.default_runfiles),
|
||||
),
|
||||
]
|
||||
|
||||
_JS_RUN_DEVSERVER_ATTRS = runtime_launcher_attrs()
|
||||
_JS_RUN_DEVSERVER_ATTRS.update({
|
||||
"tool": attr.label(
|
||||
mandatory = True,
|
||||
executable = True,
|
||||
cfg = "target",
|
||||
doc = "Executable target to launch as the dev server.",
|
||||
),
|
||||
"package_json": attr.label(
|
||||
allow_single_file = True,
|
||||
doc = "Optional package.json used to resolve the package working directory.",
|
||||
),
|
||||
"package_dir_hint": attr.string(
|
||||
default = ".",
|
||||
doc = "Optional package-relative directory hint when package_json is not supplied.",
|
||||
),
|
||||
"node_modules": attr.label(
|
||||
doc = "Optional label providing package files from a node_modules tree, typically produced by bun_install or npm_translate_lock, in runfiles.",
|
||||
),
|
||||
"deps": attr.label_list(
|
||||
doc = "Library dependencies required by the dev server.",
|
||||
),
|
||||
"data": attr.label_list(
|
||||
allow_files = True,
|
||||
doc = "Additional runtime files required by the dev server.",
|
||||
),
|
||||
"working_dir": attr.string(
|
||||
default = "workspace",
|
||||
values = ["workspace", "package"],
|
||||
doc = "Working directory at runtime: Bazel runfiles workspace root or the resolved package directory.",
|
||||
),
|
||||
"inherit_host_path": attr.bool(
|
||||
default = False,
|
||||
doc = "If true, appends the host PATH after staged node_modules/.bin entries at runtime.",
|
||||
),
|
||||
})
|
||||
|
||||
js_run_devserver = rule(
|
||||
implementation = _js_run_devserver_impl,
|
||||
doc = """Runs an executable target from a staged JS workspace.
|
||||
|
||||
This is a Bun-backed compatibility adapter for `rules_js`-style devserver
|
||||
targets. It stages the same runtime workspace as the Bun rules, then executes
|
||||
the provided tool with any default arguments.
|
||||
the provided tool with any default arguments. It is intended for local
|
||||
development workflows rather than hermetic build execution.
|
||||
""",
|
||||
attrs = {
|
||||
"tool": attr.label(
|
||||
mandatory = True,
|
||||
executable = True,
|
||||
cfg = "target",
|
||||
doc = "Executable target to launch as the dev server.",
|
||||
),
|
||||
"package_json": attr.label(
|
||||
allow_single_file = True,
|
||||
doc = "Optional package.json used to resolve the package working directory.",
|
||||
),
|
||||
"package_dir_hint": attr.string(
|
||||
default = ".",
|
||||
doc = "Optional package-relative directory hint when package_json is not supplied.",
|
||||
),
|
||||
"node_modules": attr.label(
|
||||
doc = "Optional label providing package files from a node_modules tree, typically produced by bun_install or npm_translate_lock, in runfiles.",
|
||||
),
|
||||
"deps": attr.label_list(
|
||||
doc = "Library dependencies required by the dev server.",
|
||||
),
|
||||
"data": attr.label_list(
|
||||
allow_files = True,
|
||||
doc = "Additional runtime files required by the dev server.",
|
||||
),
|
||||
"working_dir": attr.string(
|
||||
default = "workspace",
|
||||
values = ["workspace", "package"],
|
||||
doc = "Working directory at runtime: Bazel runfiles workspace root or the resolved package directory.",
|
||||
),
|
||||
},
|
||||
attrs = _JS_RUN_DEVSERVER_ATTRS,
|
||||
executable = True,
|
||||
toolchains = ["//bun:toolchain_type"],
|
||||
)
|
||||
|
||||
173
internal/runtime_launcher.bzl
Normal file
173
internal/runtime_launcher.bzl
Normal file
@@ -0,0 +1,173 @@
|
||||
"""Shared launcher spec and OS-native wrapper helpers for runtime rules."""
|
||||
|
||||
_RUNTIME_LAUNCHER = Label("//internal:runtime_launcher.js")
|
||||
_WINDOWS_CONSTRAINT = Label("@platforms//os:windows")
|
||||
|
||||
_POSIX_WRAPPER_TEMPLATE = """#!/bin/sh
|
||||
set -eu
|
||||
|
||||
self="$0"
|
||||
runfiles_dir="${RUNFILES_DIR:-}"
|
||||
manifest="${RUNFILES_MANIFEST_FILE:-}"
|
||||
|
||||
if [ -n "${runfiles_dir}" ] && [ -d "${runfiles_dir}" ]; then
|
||||
:
|
||||
elif [ -n "${manifest}" ] && [ -f "${manifest}" ]; then
|
||||
:
|
||||
elif [ -d "${self}.runfiles" ]; then
|
||||
runfiles_dir="${self}.runfiles"
|
||||
elif [ -f "${self}.runfiles_manifest" ]; then
|
||||
manifest="${self}.runfiles_manifest"
|
||||
elif [ -f "${self}.exe.runfiles_manifest" ]; then
|
||||
manifest="${self}.exe.runfiles_manifest"
|
||||
else
|
||||
echo "rules_bun: unable to locate runfiles for ${self}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rlocation() {
|
||||
path="$1"
|
||||
if [ -n "${runfiles_dir}" ]; then
|
||||
printf '%s\\n' "${runfiles_dir}/${path}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
result=""
|
||||
while IFS= read -r line; do
|
||||
case "${line}" in
|
||||
"${path} "*)
|
||||
result="${line#${path} }"
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done < "${manifest}"
|
||||
if [ -z "${result}" ]; then
|
||||
echo "rules_bun: missing runfile ${path}" >&2
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\\n' "${result}"
|
||||
}
|
||||
|
||||
bun_bin="$(rlocation "__BUN_RUNFILES_PATH__")"
|
||||
runner="$(rlocation "__RUNNER_RUNFILES_PATH__")"
|
||||
spec="$(rlocation "__SPEC_RUNFILES_PATH__")"
|
||||
|
||||
export RULES_BUN_LAUNCHER_PATH="${self}"
|
||||
if [ -n "${runfiles_dir}" ]; then
|
||||
export RULES_BUN_RUNFILES_DIR="${runfiles_dir}"
|
||||
fi
|
||||
if [ -n "${manifest}" ]; then
|
||||
export RULES_BUN_RUNFILES_MANIFEST="${manifest}"
|
||||
fi
|
||||
|
||||
exec "${bun_bin}" --bun "${runner}" "${spec}" "$@"
|
||||
"""
|
||||
|
||||
_CMD_WRAPPER_TEMPLATE = """@echo off
|
||||
setlocal
|
||||
|
||||
set "SELF=%~f0"
|
||||
set "RUNFILES_DIR_VALUE=%RUNFILES_DIR%"
|
||||
set "RUNFILES_MANIFEST_VALUE=%RUNFILES_MANIFEST_FILE%"
|
||||
|
||||
if defined RUNFILES_DIR_VALUE if exist "%RUNFILES_DIR_VALUE%" goto have_runfiles
|
||||
if defined RUNFILES_MANIFEST_VALUE if exist "%RUNFILES_MANIFEST_VALUE%" goto have_runfiles
|
||||
if exist "%SELF%.runfiles" (
|
||||
set "RUNFILES_DIR_VALUE=%SELF%.runfiles"
|
||||
goto have_runfiles
|
||||
)
|
||||
if exist "%SELF%.runfiles_manifest" (
|
||||
set "RUNFILES_MANIFEST_VALUE=%SELF%.runfiles_manifest"
|
||||
goto have_runfiles
|
||||
)
|
||||
if exist "%~dpn0.runfiles_manifest" (
|
||||
set "RUNFILES_MANIFEST_VALUE=%~dpn0.runfiles_manifest"
|
||||
goto have_runfiles
|
||||
)
|
||||
|
||||
echo rules_bun: unable to locate runfiles for "%SELF%" 1>&2
|
||||
exit /b 1
|
||||
|
||||
:have_runfiles
|
||||
call :rlocation "__BUN_RUNFILES_PATH__" BUN_BIN || exit /b 1
|
||||
call :rlocation "__RUNNER_RUNFILES_PATH__" RUNNER || exit /b 1
|
||||
call :rlocation "__SPEC_RUNFILES_PATH__" SPEC || exit /b 1
|
||||
|
||||
set "RULES_BUN_LAUNCHER_PATH=%SELF%"
|
||||
if defined RUNFILES_DIR_VALUE (
|
||||
set "RULES_BUN_RUNFILES_DIR=%RUNFILES_DIR_VALUE%"
|
||||
) else (
|
||||
set "RULES_BUN_RUNFILES_DIR="
|
||||
)
|
||||
if defined RUNFILES_MANIFEST_VALUE (
|
||||
set "RULES_BUN_RUNFILES_MANIFEST=%RUNFILES_MANIFEST_VALUE%"
|
||||
) else (
|
||||
set "RULES_BUN_RUNFILES_MANIFEST="
|
||||
)
|
||||
|
||||
"%BUN_BIN%" --bun "%RUNNER%" "%SPEC%" %*
|
||||
exit /b %ERRORLEVEL%
|
||||
|
||||
:rlocation
|
||||
set "LOOKUP=%~1"
|
||||
set "OUTPUT_VAR=%~2"
|
||||
if defined RUNFILES_DIR_VALUE (
|
||||
set "%OUTPUT_VAR%=%RUNFILES_DIR_VALUE%\\%LOOKUP:/=\\%"
|
||||
exit /b 0
|
||||
)
|
||||
for /f "tokens=1,* delims= " %%A in ('findstr /b /c:"%LOOKUP% " "%RUNFILES_MANIFEST_VALUE%"') do (
|
||||
set "%OUTPUT_VAR%=%%B"
|
||||
exit /b 0
|
||||
)
|
||||
echo rules_bun: missing runfile %LOOKUP% 1>&2
|
||||
exit /b 1
|
||||
"""
|
||||
|
||||
def runfiles_path(file):
|
||||
workspace_name = file.owner.workspace_name
|
||||
if workspace_name:
|
||||
return "{}/{}".format(workspace_name, file.short_path)
|
||||
return "_main/{}".format(file.short_path)
|
||||
|
||||
def runtime_launcher_attrs():
|
||||
return {
|
||||
"_runtime_launcher": attr.label(
|
||||
default = _RUNTIME_LAUNCHER,
|
||||
allow_single_file = True,
|
||||
),
|
||||
"_windows_constraint": attr.label(
|
||||
default = _WINDOWS_CONSTRAINT,
|
||||
),
|
||||
}
|
||||
|
||||
def is_windows_target(ctx):
|
||||
return ctx.target_platform_has_constraint(ctx.attr._windows_constraint[platform_common.ConstraintValueInfo])
|
||||
|
||||
def write_launcher_spec(ctx, spec):
|
||||
spec_file = ctx.actions.declare_file(ctx.label.name + ".launcher.json")
|
||||
ctx.actions.write(
|
||||
output = spec_file,
|
||||
content = json.encode(spec) + "\n",
|
||||
)
|
||||
return spec_file
|
||||
|
||||
def declare_runtime_wrapper(ctx, bun_bin, spec_file):
|
||||
runner = ctx.file._runtime_launcher
|
||||
wrapper = ctx.actions.declare_file(ctx.label.name + (".cmd" if is_windows_target(ctx) else ""))
|
||||
content = _CMD_WRAPPER_TEMPLATE if is_windows_target(ctx) else _POSIX_WRAPPER_TEMPLATE
|
||||
content = content.replace("__BUN_RUNFILES_PATH__", runfiles_path(bun_bin)).replace(
|
||||
"__RUNNER_RUNFILES_PATH__",
|
||||
runfiles_path(runner),
|
||||
).replace(
|
||||
"__SPEC_RUNFILES_PATH__",
|
||||
runfiles_path(spec_file),
|
||||
)
|
||||
ctx.actions.write(
|
||||
output = wrapper,
|
||||
content = content,
|
||||
is_executable = True,
|
||||
)
|
||||
return struct(
|
||||
executable = wrapper,
|
||||
runner = runner,
|
||||
)
|
||||
1199
internal/runtime_launcher.js
Normal file
1199
internal/runtime_launcher.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,13 @@
|
||||
"""Shared Bun workspace metadata and launcher helpers."""
|
||||
"""Shared Bun workspace metadata helpers."""
|
||||
|
||||
BunWorkspaceInfo = provider(
|
||||
doc = "Workspace/runtime metadata shared by Bun rules and adapters.",
|
||||
fields = {
|
||||
"install_metadata_file": "Optional install metadata file from bun_install.",
|
||||
"install_repo_runfiles_path": "Runfiles root for the node_modules repository when present.",
|
||||
"metadata_file": "Rule-local metadata file describing the staged workspace inputs.",
|
||||
"node_modules_files": "Depset of node_modules files from bun_install.",
|
||||
"node_modules_roots": "Sorted repo-relative node_modules roots available in runfiles.",
|
||||
"package_dir_hint": "Package-relative directory when known at analysis time.",
|
||||
"package_json": "Package manifest file when explicitly provided.",
|
||||
"primary_file": "Primary source file used to resolve the runtime package context.",
|
||||
@@ -13,690 +15,41 @@ BunWorkspaceInfo = provider(
|
||||
},
|
||||
)
|
||||
|
||||
_WORKSPACE_SETUP_TEMPLATE = """#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
runfiles_dir="${RUNFILES_DIR:-$0.runfiles}"
|
||||
workspace_root="${runfiles_dir}/_main"
|
||||
workspace_root="$(cd "${workspace_root}" && pwd -P)"
|
||||
bun_bin="${runfiles_dir}/_main/__BUN_SHORT_PATH__"
|
||||
primary_source=""
|
||||
if [[ -n "__PRIMARY_SOURCE_SHORT_PATH__" ]]; then
|
||||
primary_source="${runfiles_dir}/_main/__PRIMARY_SOURCE_SHORT_PATH__"
|
||||
fi
|
||||
package_json=""
|
||||
if [[ -n "__PACKAGE_JSON_SHORT_PATH__" ]]; then
|
||||
package_json="${runfiles_dir}/_main/__PACKAGE_JSON_SHORT_PATH__"
|
||||
fi
|
||||
package_rel_dir_hint="__PACKAGE_DIR_HINT__"
|
||||
install_root_rel_dir_hint="__INSTALL_ROOT_REL_DIR__"
|
||||
install_metadata=""
|
||||
if [[ -n "__INSTALL_METADATA_SHORT_PATH__" ]]; then
|
||||
install_metadata="${runfiles_dir}/_main/__INSTALL_METADATA_SHORT_PATH__"
|
||||
fi
|
||||
working_dir_mode="__WORKING_DIR_MODE__"
|
||||
|
||||
normalize_rel_dir() {
|
||||
local value="$1"
|
||||
if [[ -z "${value}" || "${value}" == "." ]]; then
|
||||
echo "."
|
||||
else
|
||||
echo "${value#./}"
|
||||
fi
|
||||
}
|
||||
|
||||
dirname_rel_dir() {
|
||||
local value
|
||||
value="$(normalize_rel_dir "$1")"
|
||||
if [[ "${value}" == "." || "${value}" != */* ]]; then
|
||||
echo "."
|
||||
return 0
|
||||
fi
|
||||
echo "${value%/*}"
|
||||
}
|
||||
|
||||
first_path_component() {
|
||||
local value
|
||||
value="$(normalize_rel_dir "$1")"
|
||||
if [[ "${value}" == "." ]]; then
|
||||
echo ""
|
||||
return 0
|
||||
fi
|
||||
echo "${value%%/*}"
|
||||
}
|
||||
|
||||
rel_dir_from_abs_path() {
|
||||
local absolute_path="$1"
|
||||
if [[ "${absolute_path}" == "${workspace_root}" ]]; then
|
||||
echo "."
|
||||
return 0
|
||||
fi
|
||||
echo "${absolute_path#"${workspace_root}/"}"
|
||||
}
|
||||
|
||||
find_package_rel_dir_for_path() {
|
||||
local path="$1"
|
||||
local dir="$1"
|
||||
if [[ -f "${dir}" ]]; then
|
||||
dir="$(dirname "${dir}")"
|
||||
fi
|
||||
|
||||
while [[ "${dir}" == "${workspace_root}"* ]]; do
|
||||
if [[ -f "${dir}/package.json" ]]; then
|
||||
rel_dir_from_abs_path "${dir}"
|
||||
return 0
|
||||
fi
|
||||
if [[ "${dir}" == "${workspace_root}" ]]; then
|
||||
break
|
||||
fi
|
||||
dir="$(dirname "${dir}")"
|
||||
done
|
||||
|
||||
rel_dir_from_abs_path "$(dirname "${path}")"
|
||||
}
|
||||
|
||||
find_working_rel_dir_for_path() {
|
||||
local path="$1"
|
||||
local dir="$1"
|
||||
if [[ -f "${dir}" ]]; then
|
||||
dir="$(dirname "${dir}")"
|
||||
fi
|
||||
|
||||
while [[ "${dir}" == "${workspace_root}"* ]]; do
|
||||
if [[ -f "${dir}/.env" || -f "${dir}/package.json" ]]; then
|
||||
rel_dir_from_abs_path "${dir}"
|
||||
return 0
|
||||
fi
|
||||
if [[ "${dir}" == "${workspace_root}" ]]; then
|
||||
break
|
||||
fi
|
||||
dir="$(dirname "${dir}")"
|
||||
done
|
||||
|
||||
rel_dir_from_abs_path "$(dirname "${path}")"
|
||||
}
|
||||
|
||||
strip_rel_prefix() {
|
||||
local child
|
||||
child="$(normalize_rel_dir "$1")"
|
||||
local parent
|
||||
parent="$(normalize_rel_dir "$2")"
|
||||
|
||||
if [[ "${parent}" == "." ]]; then
|
||||
echo "${child}"
|
||||
return 0
|
||||
fi
|
||||
if [[ "${child}" == "${parent}" ]]; then
|
||||
echo "."
|
||||
return 0
|
||||
fi
|
||||
if [[ "${child}" == "${parent}/"* ]]; then
|
||||
echo "${child#"${parent}/"}"
|
||||
return 0
|
||||
fi
|
||||
echo "${child}"
|
||||
}
|
||||
|
||||
select_primary_node_modules() {
|
||||
local selected=""
|
||||
local fallback=""
|
||||
while IFS= read -r node_modules_dir; do
|
||||
if [[ -z "${fallback}" ]]; then
|
||||
fallback="${node_modules_dir}"
|
||||
fi
|
||||
|
||||
if [[ ! -d "${node_modules_dir}/.bun" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ "${node_modules_dir}" != *"/runfiles/_main/"* ]]; then
|
||||
selected="${node_modules_dir}"
|
||||
break
|
||||
fi
|
||||
|
||||
if [[ -z "${selected}" ]]; then
|
||||
selected="${node_modules_dir}"
|
||||
fi
|
||||
done < <(find -L "${runfiles_dir}" -type d -name node_modules 2>/dev/null | sort)
|
||||
|
||||
if [[ -n "${selected}" ]]; then
|
||||
echo "${selected}"
|
||||
else
|
||||
echo "${fallback}"
|
||||
fi
|
||||
}
|
||||
|
||||
link_top_level_entries() {
|
||||
local source_root="$1"
|
||||
local destination_root="$2"
|
||||
local skipped_entry="$3"
|
||||
local entry=""
|
||||
local entry_name=""
|
||||
|
||||
shopt -s dotglob nullglob
|
||||
for entry in "${source_root}"/* "${source_root}"/.[!.]* "${source_root}"/..?*; do
|
||||
entry_name="$(basename "${entry}")"
|
||||
if [[ "${entry_name}" == "." || "${entry_name}" == ".." ]]; then
|
||||
continue
|
||||
fi
|
||||
if [[ -n "${skipped_entry}" && "${entry_name}" == "${skipped_entry}" ]]; then
|
||||
continue
|
||||
fi
|
||||
ln -s "${entry}" "${destination_root}/${entry_name}"
|
||||
done
|
||||
shopt -u dotglob nullglob
|
||||
}
|
||||
|
||||
materialize_package_path() {
|
||||
local source_root="$1"
|
||||
local destination_root="$2"
|
||||
local package_rel_dir
|
||||
package_rel_dir="$(normalize_rel_dir "$3")"
|
||||
|
||||
if [[ "${package_rel_dir}" == "." ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local source_cursor="${source_root}"
|
||||
local destination_cursor="${destination_root}"
|
||||
local parts=()
|
||||
local current="${package_rel_dir}"
|
||||
|
||||
while [[ -n "${current}" ]]; do
|
||||
if [[ "${current}" == */* ]]; then
|
||||
parts+=("${current%%/*}")
|
||||
current="${current#*/}"
|
||||
else
|
||||
parts+=("${current}")
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
local index=0
|
||||
while [[ ${index} -lt $((${#parts[@]} - 1)) ]]; do
|
||||
local part="${parts[${index}]}"
|
||||
local next_part="${parts[$((index + 1))]}"
|
||||
source_cursor="${source_cursor}/${part}"
|
||||
destination_cursor="${destination_cursor}/${part}"
|
||||
mkdir -p "${destination_cursor}"
|
||||
|
||||
local sibling=""
|
||||
local sibling_name=""
|
||||
shopt -s dotglob nullglob
|
||||
for sibling in "${source_cursor}"/* "${source_cursor}"/.[!.]* "${source_cursor}"/..?*; do
|
||||
sibling_name="$(basename "${sibling}")"
|
||||
if [[ "${sibling_name}" == "." || "${sibling_name}" == ".." || "${sibling_name}" == "${next_part}" ]]; then
|
||||
continue
|
||||
fi
|
||||
if [[ ! -e "${destination_cursor}/${sibling_name}" ]]; then
|
||||
ln -s "${sibling}" "${destination_cursor}/${sibling_name}"
|
||||
fi
|
||||
done
|
||||
shopt -u dotglob nullglob
|
||||
index=$((index + 1))
|
||||
done
|
||||
|
||||
mkdir -p "${destination_root}/${package_rel_dir}"
|
||||
}
|
||||
|
||||
materialize_directory_entries() {
|
||||
local source_root="$1"
|
||||
local destination_root="$2"
|
||||
local entry=""
|
||||
local entry_name=""
|
||||
|
||||
mkdir -p "${destination_root}"
|
||||
shopt -s dotglob nullglob
|
||||
for entry in "${source_root}"/* "${source_root}"/.[!.]* "${source_root}"/..?*; do
|
||||
entry_name="$(basename "${entry}")"
|
||||
if [[ "${entry_name}" == "." || "${entry_name}" == ".." ]]; then
|
||||
continue
|
||||
fi
|
||||
rm -rf "${destination_root}/${entry_name}"
|
||||
ln -s "${entry}" "${destination_root}/${entry_name}"
|
||||
done
|
||||
shopt -u dotglob nullglob
|
||||
}
|
||||
|
||||
stage_workspace_view() {
|
||||
local source_root="$1"
|
||||
local destination_root="$2"
|
||||
local package_rel_dir
|
||||
package_rel_dir="$(normalize_rel_dir "$3")"
|
||||
local skipped_entry
|
||||
skipped_entry="$(first_path_component "${package_rel_dir}")"
|
||||
|
||||
link_top_level_entries "${source_root}" "${destination_root}" "${skipped_entry}"
|
||||
|
||||
if [[ "${package_rel_dir}" == "." ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
materialize_package_path "${source_root}" "${destination_root}" "${package_rel_dir}"
|
||||
materialize_directory_entries "${source_root}/${package_rel_dir}" "${destination_root}/${package_rel_dir}"
|
||||
}
|
||||
|
||||
materialize_tree_contents() {
|
||||
local source_root="$1"
|
||||
local destination_root="$2"
|
||||
|
||||
rm -rf "${destination_root}"
|
||||
mkdir -p "${destination_root}"
|
||||
cp -RL "${source_root}/." "${destination_root}"
|
||||
}
|
||||
|
||||
build_workspace_package_map() {
|
||||
local root="$1"
|
||||
local out="$2"
|
||||
|
||||
python3 - "${root}" >"${out}" <<'PY'
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
root = os.path.abspath(sys.argv[1])
|
||||
|
||||
for dirpath, dirnames, filenames in os.walk(root):
|
||||
dirnames[:] = [name for name in dirnames if name != "node_modules"]
|
||||
if "package.json" not in filenames:
|
||||
continue
|
||||
|
||||
manifest_path = os.path.join(dirpath, "package.json")
|
||||
try:
|
||||
with open(manifest_path, "r", encoding="utf-8") as manifest_file:
|
||||
package_name = json.load(manifest_file).get("name")
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
if not isinstance(package_name, str):
|
||||
continue
|
||||
|
||||
rel_dir = os.path.relpath(dirpath, root)
|
||||
if rel_dir == ".":
|
||||
rel_dir = "."
|
||||
print(f"{package_name}\t{rel_dir}")
|
||||
PY
|
||||
}
|
||||
|
||||
workspace_package_rel_dir_for_source() {
|
||||
local source="$1"
|
||||
local manifest_path="${source}/package.json"
|
||||
local package_name=""
|
||||
|
||||
if [[ ! -f "${manifest_path}" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
package_name="$(python3 - "${manifest_path}" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
|
||||
try:
|
||||
with open(sys.argv[1], "r", encoding="utf-8") as manifest_file:
|
||||
package_name = json.load(manifest_file).get("name", "")
|
||||
except Exception:
|
||||
package_name = ""
|
||||
|
||||
if isinstance(package_name, str):
|
||||
print(package_name)
|
||||
PY
|
||||
)"
|
||||
|
||||
if [[ -z "${package_name}" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
awk -F '\t' -v name="${package_name}" '$1 == name { print $2; exit }' "${workspace_package_map}"
|
||||
}
|
||||
|
||||
link_node_modules_entry() {
|
||||
local source="$1"
|
||||
local destination="$2"
|
||||
local workspace_rel_dir=""
|
||||
|
||||
rm -rf "${destination}"
|
||||
workspace_rel_dir="$(workspace_package_rel_dir_for_source "${source}" || true)"
|
||||
if [[ -n "${workspace_rel_dir}" ]]; then
|
||||
ln -s "${runtime_workspace}/${workspace_rel_dir}" "${destination}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -L "${source}" ]]; then
|
||||
ln -s "$(readlink "${source}")" "${destination}"
|
||||
else
|
||||
ln -s "${source}" "${destination}"
|
||||
fi
|
||||
}
|
||||
|
||||
mirror_node_modules_dir() {
|
||||
local source_dir="$1"
|
||||
local destination_dir="$2"
|
||||
local entry=""
|
||||
local entry_name=""
|
||||
local scoped_entry=""
|
||||
local scoped_name=""
|
||||
|
||||
rm -rf "${destination_dir}"
|
||||
mkdir -p "${destination_dir}"
|
||||
|
||||
shopt -s dotglob nullglob
|
||||
for entry in "${source_dir}"/* "${source_dir}"/.[!.]* "${source_dir}"/..?*; do
|
||||
entry_name="$(basename "${entry}")"
|
||||
if [[ "${entry_name}" == "." || "${entry_name}" == ".." || "${entry_name}" == ".rules_bun" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ -d "${entry}" && ! -L "${entry}" && "${entry_name}" == @* ]]; then
|
||||
mkdir -p "${destination_dir}/${entry_name}"
|
||||
for scoped_entry in "${entry}"/* "${entry}"/.[!.]* "${entry}"/..?*; do
|
||||
scoped_name="$(basename "${scoped_entry}")"
|
||||
if [[ "${scoped_name}" == "." || "${scoped_name}" == ".." ]]; then
|
||||
continue
|
||||
fi
|
||||
link_node_modules_entry "${scoped_entry}" "${destination_dir}/${entry_name}/${scoped_name}"
|
||||
done
|
||||
continue
|
||||
fi
|
||||
|
||||
link_node_modules_entry "${entry}" "${destination_dir}/${entry_name}"
|
||||
done
|
||||
shopt -u dotglob nullglob
|
||||
}
|
||||
|
||||
find_install_repo_node_modules() {
|
||||
local repo_root="$1"
|
||||
local package_rel_dir
|
||||
package_rel_dir="$(normalize_rel_dir "$2")"
|
||||
|
||||
if [[ "${package_rel_dir}" != "." ]]; then
|
||||
local candidate="${package_rel_dir}"
|
||||
while true; do
|
||||
if [[ -d "${repo_root}/${candidate}/node_modules" ]]; then
|
||||
echo "${repo_root}/${candidate}/node_modules"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "${candidate}" != */* ]]; then
|
||||
break
|
||||
fi
|
||||
candidate="${candidate%/*}"
|
||||
done
|
||||
fi
|
||||
|
||||
if [[ -d "${repo_root}/node_modules" ]]; then
|
||||
echo "${repo_root}/node_modules"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
mirror_install_repo_workspace_node_modules() {
|
||||
local repo_root="$1"
|
||||
local destination_root="$2"
|
||||
|
||||
while IFS= read -r install_node_modules; do
|
||||
local rel_path="${install_node_modules#${repo_root}/}"
|
||||
local destination="${destination_root}/${rel_path}"
|
||||
|
||||
mkdir -p "$(dirname "${destination}")"
|
||||
mirror_node_modules_dir "${install_node_modules}" "${destination}"
|
||||
done < <(find "${repo_root}" \
|
||||
-path "${repo_root}/node_modules" -prune -o \
|
||||
-type d -name node_modules -print 2>/dev/null | sort)
|
||||
}
|
||||
|
||||
build_runtime_path() {
|
||||
local workspace_dir="$1"
|
||||
local package_dir="$2"
|
||||
local install_root_dir="$3"
|
||||
local entries=()
|
||||
|
||||
if [[ -d "${install_root_dir}/node_modules/.bin" ]]; then
|
||||
entries+=("${install_root_dir}/node_modules/.bin")
|
||||
fi
|
||||
if [[ -d "${package_dir}/node_modules/.bin" ]]; then
|
||||
if [[ "${package_dir}/node_modules/.bin" != "${install_root_dir}/node_modules/.bin" ]]; then
|
||||
entries+=("${package_dir}/node_modules/.bin")
|
||||
fi
|
||||
fi
|
||||
if [[ -d "${workspace_dir}/node_modules/.bin" && "${workspace_dir}/node_modules/.bin" != "${package_dir}/node_modules/.bin" && "${workspace_dir}/node_modules/.bin" != "${install_root_dir}/node_modules/.bin" ]]; then
|
||||
entries+=("${workspace_dir}/node_modules/.bin")
|
||||
fi
|
||||
if [[ -n "${PATH:-}" ]]; then
|
||||
entries+=("${PATH}")
|
||||
fi
|
||||
|
||||
if [[ ${#entries[@]} -eq 0 ]]; then
|
||||
echo ""
|
||||
return 0
|
||||
fi
|
||||
|
||||
local path_value=""
|
||||
local entry=""
|
||||
for entry in "${entries[@]}"; do
|
||||
if [[ -z "${path_value}" ]]; then
|
||||
path_value="${entry}"
|
||||
else
|
||||
path_value="${path_value}:${entry}"
|
||||
fi
|
||||
done
|
||||
echo "${path_value}"
|
||||
}
|
||||
|
||||
resolve_package_rel_dir() {
|
||||
if [[ -n "${package_rel_dir_hint}" && "${package_rel_dir_hint}" != "." ]]; then
|
||||
normalize_rel_dir "${package_rel_dir_hint}"
|
||||
return 0
|
||||
fi
|
||||
if [[ -n "${package_json}" ]]; then
|
||||
find_package_rel_dir_for_path "${package_json}"
|
||||
return 0
|
||||
fi
|
||||
if [[ -n "${primary_source}" ]]; then
|
||||
find_package_rel_dir_for_path "${primary_source}"
|
||||
return 0
|
||||
fi
|
||||
echo "."
|
||||
}
|
||||
|
||||
resolve_execution_rel_dir() {
|
||||
local package_rel_dir="$1"
|
||||
case "${working_dir_mode}" in
|
||||
workspace)
|
||||
echo "."
|
||||
;;
|
||||
package)
|
||||
echo "${package_rel_dir}"
|
||||
;;
|
||||
entry_point)
|
||||
if [[ -n "${primary_source}" ]]; then
|
||||
find_working_rel_dir_for_path "${primary_source}"
|
||||
else
|
||||
echo "${package_rel_dir}"
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "${package_rel_dir}"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
resolve_install_root_rel_dir() {
|
||||
if [[ -n "${install_metadata}" && -f "${install_metadata}" ]]; then
|
||||
local resolved_from_metadata=""
|
||||
resolved_from_metadata="$(
|
||||
python3 - "${install_metadata}" "${package_rel_dir}" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
|
||||
install_metadata_path = sys.argv[1]
|
||||
package_rel_dir = sys.argv[2]
|
||||
|
||||
try:
|
||||
with open(install_metadata_path, "r", encoding="utf-8") as install_metadata_file:
|
||||
workspace_package_dirs = json.load(install_metadata_file).get("workspace_package_dirs", [])
|
||||
except Exception:
|
||||
workspace_package_dirs = []
|
||||
|
||||
normalized_package_rel_dir = package_rel_dir.strip("./") or "."
|
||||
matches = []
|
||||
for workspace_package_dir in workspace_package_dirs:
|
||||
normalized_workspace_package_dir = workspace_package_dir.strip("./")
|
||||
if not normalized_workspace_package_dir:
|
||||
continue
|
||||
if normalized_package_rel_dir == normalized_workspace_package_dir:
|
||||
matches.append((len(normalized_workspace_package_dir), "."))
|
||||
continue
|
||||
suffix = "/" + normalized_workspace_package_dir
|
||||
if normalized_package_rel_dir.endswith(suffix):
|
||||
prefix = normalized_package_rel_dir[:-len(suffix)].strip("/") or "."
|
||||
matches.append((len(normalized_workspace_package_dir), prefix))
|
||||
|
||||
if matches:
|
||||
matches.sort(reverse = True)
|
||||
print(matches[0][1])
|
||||
PY
|
||||
)"
|
||||
if [[ -n "${resolved_from_metadata}" ]]; then
|
||||
echo "${resolved_from_metadata}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
if [[ -n "${install_root_rel_dir_hint}" && "${install_root_rel_dir_hint}" != "." ]]; then
|
||||
normalize_rel_dir "${install_root_rel_dir_hint}"
|
||||
return 0
|
||||
fi
|
||||
if [[ -n "${package_json}" ]]; then
|
||||
find_package_rel_dir_for_path "${package_json}"
|
||||
return 0
|
||||
fi
|
||||
if [[ -n "${primary_source}" ]]; then
|
||||
find_package_rel_dir_for_path "${primary_source}"
|
||||
return 0
|
||||
fi
|
||||
echo "."
|
||||
}
|
||||
|
||||
package_rel_dir="$(resolve_package_rel_dir)"
|
||||
execution_rel_dir="$(resolve_execution_rel_dir "${package_rel_dir}")"
|
||||
install_root_rel_dir="$(resolve_install_root_rel_dir)"
|
||||
package_rel_dir_in_install_root="$(strip_rel_prefix "${package_rel_dir}" "${install_root_rel_dir}")"
|
||||
|
||||
runtime_workspace="$(mktemp -d)"
|
||||
cleanup_runtime_workspace() {
|
||||
rm -rf "${runtime_workspace}"
|
||||
}
|
||||
|
||||
stage_workspace_view "${workspace_root}" "${runtime_workspace}" "${package_rel_dir}"
|
||||
runtime_package_dir="${runtime_workspace}"
|
||||
if [[ "${package_rel_dir}" != "." ]]; then
|
||||
runtime_package_dir="${runtime_workspace}/${package_rel_dir}"
|
||||
fi
|
||||
runtime_install_root="${runtime_workspace}"
|
||||
if [[ "${install_root_rel_dir}" != "." ]]; then
|
||||
runtime_install_root="${runtime_workspace}/${install_root_rel_dir}"
|
||||
fi
|
||||
runtime_exec_dir="${runtime_workspace}"
|
||||
if [[ "${execution_rel_dir}" != "." ]]; then
|
||||
runtime_exec_dir="${runtime_workspace}/${execution_rel_dir}"
|
||||
fi
|
||||
|
||||
if [[ -n "${primary_source}" ]]; then
|
||||
materialize_tree_contents "${workspace_root}/${package_rel_dir}" "${runtime_package_dir}"
|
||||
fi
|
||||
|
||||
if [[ -n "${package_json}" ]]; then
|
||||
materialize_tree_contents "${workspace_root}/${install_root_rel_dir}" "${runtime_install_root}"
|
||||
fi
|
||||
|
||||
if [[ -n "${primary_source}" && "${primary_source}" == "${workspace_root}"* ]]; then
|
||||
primary_source="${runtime_workspace}/$(rel_dir_from_abs_path "${primary_source}")"
|
||||
fi
|
||||
|
||||
if [[ -n "${package_json}" && "${package_json}" == "${workspace_root}"* ]]; then
|
||||
package_json="${runtime_workspace}/$(rel_dir_from_abs_path "${package_json}")"
|
||||
fi
|
||||
|
||||
workspace_package_map="${runtime_workspace}/.rules_bun_workspace_packages.tsv"
|
||||
build_workspace_package_map "${runtime_workspace}" "${workspace_package_map}"
|
||||
|
||||
primary_node_modules="$(select_primary_node_modules)"
|
||||
install_repo_root=""
|
||||
if [[ -n "${primary_node_modules}" ]]; then
|
||||
install_repo_root="$(dirname "${primary_node_modules}")"
|
||||
mkdir -p "${runtime_install_root}"
|
||||
mirror_node_modules_dir "${primary_node_modules}" "${runtime_install_root}/node_modules"
|
||||
fi
|
||||
|
||||
if [[ -n "${install_repo_root}" ]]; then
|
||||
resolved_install_node_modules="$(find_install_repo_node_modules "${install_repo_root}" "${package_rel_dir_in_install_root}" || true)"
|
||||
if [[ -n "${resolved_install_node_modules}" && "${resolved_install_node_modules}" != "${install_repo_root}/node_modules" ]]; then
|
||||
mirror_node_modules_dir "${resolved_install_node_modules}" "${runtime_package_dir}/node_modules"
|
||||
fi
|
||||
mirror_install_repo_workspace_node_modules "${install_repo_root}" "${runtime_install_root}"
|
||||
fi
|
||||
|
||||
if [[ ! -e "${runtime_package_dir}/node_modules" && -e "${runtime_install_root}/node_modules" && "${runtime_package_dir}" != "${runtime_install_root}" ]]; then
|
||||
ln -s "${runtime_install_root}/node_modules" "${runtime_package_dir}/node_modules"
|
||||
fi
|
||||
|
||||
runtime_path="$(build_runtime_path "${runtime_workspace}" "${runtime_package_dir}" "${runtime_install_root}")"
|
||||
if [[ -n "${runtime_path}" ]]; then
|
||||
export PATH="${runtime_path}"
|
||||
fi
|
||||
"""
|
||||
|
||||
def _shell_quote(value):
|
||||
return "'" + value.replace("'", "'\"'\"'") + "'"
|
||||
|
||||
def _dirname(path):
|
||||
if not path or path == ".":
|
||||
return "."
|
||||
|
||||
index = path.rfind("/")
|
||||
if index < 0:
|
||||
return "."
|
||||
if index == 0:
|
||||
return "/"
|
||||
return path[:index]
|
||||
|
||||
def find_install_metadata_file(files):
|
||||
for file in files:
|
||||
if file.short_path.endswith("node_modules/.rules_bun/install.json"):
|
||||
return file
|
||||
return None
|
||||
|
||||
def resolve_node_modules_roots(files, workspace_dir = ""):
|
||||
install_metadata_file = find_install_metadata_file(files)
|
||||
shared_node_modules_root = None
|
||||
workspace_node_modules_root = None
|
||||
def _runfiles_workspace(file):
|
||||
workspace_name = file.owner.workspace_name
|
||||
if workspace_name:
|
||||
return workspace_name
|
||||
return "_main"
|
||||
|
||||
if install_metadata_file:
|
||||
shared_node_modules_root = _dirname(_dirname(install_metadata_file.path))
|
||||
def _repo_relative_short_path(file):
|
||||
short_path = file.short_path.replace("\\", "/")
|
||||
workspace_name = _runfiles_workspace(file)
|
||||
external_prefix = "../{}/".format(workspace_name)
|
||||
if short_path.startswith(external_prefix):
|
||||
return short_path[len(external_prefix):]
|
||||
if short_path == "../{}".format(workspace_name):
|
||||
return "."
|
||||
return short_path
|
||||
|
||||
workspace_marker = ""
|
||||
if workspace_dir:
|
||||
workspace_marker = "/%s/node_modules/" % workspace_dir.strip("/")
|
||||
def resolve_node_modules_roots(files):
|
||||
roots = {}
|
||||
marker = "/node_modules/"
|
||||
for file in files:
|
||||
short_path = _repo_relative_short_path(file)
|
||||
if short_path == "node_modules" or short_path.startswith("node_modules/"):
|
||||
roots["node_modules"] = True
|
||||
|
||||
shortest_path = None
|
||||
for src in files:
|
||||
if workspace_marker and workspace_marker in src.path and workspace_node_modules_root == None:
|
||||
workspace_node_modules_root = src.path[:src.path.find(workspace_marker) + len(workspace_marker) - 1]
|
||||
if shortest_path == None or len(src.path) < len(shortest_path):
|
||||
shortest_path = src.path
|
||||
|
||||
if shared_node_modules_root == None and shortest_path:
|
||||
marker = "/node_modules/"
|
||||
marker_index = shortest_path.find(marker)
|
||||
marker_index = short_path.find(marker)
|
||||
if marker_index >= 0:
|
||||
shared_node_modules_root = shortest_path[:marker_index + len("/node_modules")]
|
||||
roots[short_path[:marker_index + len("/node_modules")]] = True
|
||||
|
||||
return struct(
|
||||
install_metadata_file = install_metadata_file,
|
||||
node_modules_root = workspace_node_modules_root or shared_node_modules_root,
|
||||
shared_node_modules_root = shared_node_modules_root,
|
||||
)
|
||||
return sorted(roots.keys())
|
||||
|
||||
def create_bun_workspace_info(ctx, primary_file = None, package_json = None, package_dir_hint = ".", extra_files = None):
|
||||
direct_runtime_files = []
|
||||
@@ -708,15 +61,25 @@ def create_bun_workspace_info(ctx, primary_file = None, package_json = None, pac
|
||||
|
||||
node_modules_files = depset()
|
||||
install_metadata_file = None
|
||||
install_repo_runfiles_path = ""
|
||||
node_modules_roots = []
|
||||
if getattr(ctx.attr, "node_modules", None):
|
||||
node_modules_files = ctx.attr.node_modules[DefaultInfo].files
|
||||
install_metadata_file = find_install_metadata_file(node_modules_files.to_list())
|
||||
node_modules_file_list = node_modules_files.to_list()
|
||||
install_metadata_file = find_install_metadata_file(node_modules_file_list)
|
||||
node_modules_roots = resolve_node_modules_roots(node_modules_file_list)
|
||||
if install_metadata_file:
|
||||
install_repo_runfiles_path = _runfiles_workspace(install_metadata_file)
|
||||
elif node_modules_file_list:
|
||||
install_repo_runfiles_path = _runfiles_workspace(node_modules_file_list[0])
|
||||
|
||||
metadata_file = ctx.actions.declare_file(ctx.label.name + ".bun_workspace.json")
|
||||
ctx.actions.write(
|
||||
output = metadata_file,
|
||||
content = json.encode({
|
||||
"install_metadata": install_metadata_file.short_path if install_metadata_file else "",
|
||||
"install_repo_runfiles_path": install_repo_runfiles_path,
|
||||
"node_modules_roots": node_modules_roots,
|
||||
"package_dir_hint": package_dir_hint or ".",
|
||||
"package_json": package_json.short_path if package_json else "",
|
||||
"primary_file": primary_file.short_path if primary_file else "",
|
||||
@@ -731,8 +94,10 @@ def create_bun_workspace_info(ctx, primary_file = None, package_json = None, pac
|
||||
|
||||
return BunWorkspaceInfo(
|
||||
install_metadata_file = install_metadata_file,
|
||||
install_repo_runfiles_path = install_repo_runfiles_path,
|
||||
metadata_file = metadata_file,
|
||||
node_modules_files = node_modules_files,
|
||||
node_modules_roots = node_modules_roots,
|
||||
package_dir_hint = package_dir_hint or ".",
|
||||
package_json = package_json,
|
||||
primary_file = primary_file,
|
||||
@@ -746,31 +111,3 @@ def workspace_runfiles(ctx, workspace_info, direct_files = None, transitive_file
|
||||
transitive = [workspace_info.runtime_files] + (transitive_files or []),
|
||||
),
|
||||
)
|
||||
|
||||
def render_workspace_setup(
|
||||
bun_short_path,
|
||||
working_dir_mode,
|
||||
primary_source_short_path = "",
|
||||
package_json_short_path = "",
|
||||
package_dir_hint = ".",
|
||||
install_root_rel_dir = ".",
|
||||
install_metadata_short_path = ""):
|
||||
return _WORKSPACE_SETUP_TEMPLATE.replace("__BUN_SHORT_PATH__", bun_short_path).replace(
|
||||
"__PRIMARY_SOURCE_SHORT_PATH__",
|
||||
primary_source_short_path,
|
||||
).replace(
|
||||
"__PACKAGE_JSON_SHORT_PATH__",
|
||||
package_json_short_path,
|
||||
).replace(
|
||||
"__PACKAGE_DIR_HINT__",
|
||||
package_dir_hint or ".",
|
||||
).replace(
|
||||
"__INSTALL_ROOT_REL_DIR__",
|
||||
install_root_rel_dir or ".",
|
||||
).replace(
|
||||
"__INSTALL_METADATA_SHORT_PATH__",
|
||||
install_metadata_short_path,
|
||||
).replace(
|
||||
"__WORKING_DIR_MODE__",
|
||||
working_dir_mode,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user