From 00af05d840b44e80a8ace9756e49cb86ea19cdd8 Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 4 Mar 2026 03:05:01 +0000 Subject: [PATCH] feat: add phase 2 bun_install repository rule bootstrap --- README.md | 6 ++ bun/defs.bzl | 2 + internal/BUILD.bazel | 1 + internal/bun_install.bzl | 95 +++++++++++++++++++++++++++- tests/install_test/BUILD.bazel | 21 ++++++ tests/install_test/clean_install.sh | 22 +++++++ tests/install_test/stale_lockfile.sh | 41 ++++++++++++ 7 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 internal/BUILD.bazel create mode 100644 tests/install_test/BUILD.bazel create mode 100755 tests/install_test/clean_install.sh create mode 100755 tests/install_test/stale_lockfile.sh diff --git a/README.md b/README.md index 7f3275d..a07dae7 100644 --- a/README.md +++ b/README.md @@ -10,3 +10,9 @@ Phase 1 bootstrap is in place: - Platform-specific Bun repository downloads (`/bun/repositories.bzl`) - Toolchain declarations and registration targets (`/bun/BUILD.bazel`) - Smoke test for `bun --version` (`//tests/toolchain_test:bun_version_test`) + +Phase 2 bootstrap is in place: + +- Repository-rule based `bun_install` (`/internal/bun_install.bzl`) +- Public export via `bun/defs.bzl` +- Focused install behavior tests (`//tests/install_test:all`) diff --git a/bun/defs.bzl b/bun/defs.bzl index bfca96a..220f084 100644 --- a/bun/defs.bzl +++ b/bun/defs.bzl @@ -1,8 +1,10 @@ +load("//internal:bun_install.bzl", "bun_install") load(":repositories.bzl", "bun_register_toolchains", "bun_repositories") load(":toolchain.bzl", "BunToolchainInfo", "bun_toolchain") __all__ = [ "BunToolchainInfo", + "bun_install", "bun_register_toolchains", "bun_repositories", "bun_toolchain", diff --git a/internal/BUILD.bazel b/internal/BUILD.bazel new file mode 100644 index 0000000..ffd0fb0 --- /dev/null +++ b/internal/BUILD.bazel @@ -0,0 +1 @@ +package(default_visibility = ["//visibility:public"]) diff --git a/internal/bun_install.bzl b/internal/bun_install.bzl index b7e5080..9cb4646 100644 --- a/internal/bun_install.bzl +++ b/internal/bun_install.bzl @@ -1,2 +1,93 @@ -def bun_install(**_kwargs): - fail("bun_install is not implemented yet") +"""Repository-rule based bun_install implementation.""" + +def _select_bun_binary(repository_ctx): + os_name = repository_ctx.os.name.lower() + arch = repository_ctx.os.arch.lower() + + if "linux" in os_name: + if arch in ["aarch64", "arm64"]: + return repository_ctx.path(repository_ctx.attr.bun_linux_aarch64) + return repository_ctx.path(repository_ctx.attr.bun_linux_x64) + + if "mac" in os_name or "darwin" in os_name: + if arch in ["aarch64", "arm64"]: + return repository_ctx.path(repository_ctx.attr.bun_darwin_aarch64) + return repository_ctx.path(repository_ctx.attr.bun_darwin_x64) + + if "windows" in os_name: + return repository_ctx.path(repository_ctx.attr.bun_windows_x64) + + fail("Unsupported host platform: os={}, arch={}".format(repository_ctx.os.name, repository_ctx.os.arch)) + + +def _bun_install_repository_impl(repository_ctx): + package_json = repository_ctx.path(repository_ctx.attr.package_json) + bun_lockfile = repository_ctx.path(repository_ctx.attr.bun_lockfile) + + if not package_json.exists: + fail("bun_install: package_json not found: {}".format(repository_ctx.attr.package_json)) + + if not bun_lockfile.exists: + fail("bun_install: bun_lockfile not found: {}".format(repository_ctx.attr.bun_lockfile)) + + bun_bin = _select_bun_binary(repository_ctx) + + repository_ctx.symlink(package_json, "package.json") + repository_ctx.symlink(bun_lockfile, "bun.lockb") + + result = repository_ctx.execute( + [str(bun_bin), "install", "--frozen-lockfile", "--no-progress"], + timeout = 600, + quiet = False, + environment = {"HOME": str(repository_ctx.path("."))}, + ) + + if result.return_code: + fail("""bun_install failed running `bun install --frozen-lockfile`. +stdout: +{} +stderr: +{} +""".format(result.stdout, result.stderr)) + + repository_ctx.file( + "BUILD.bazel", + """filegroup( + name = "node_modules", + srcs = glob(["node_modules/**"], allow_empty = False), + visibility = ["//visibility:public"], +) +""", + ) + + +_bun_install_repository = repository_rule( + implementation = _bun_install_repository_impl, + attrs = { + "package_json": attr.label(mandatory = True, allow_single_file = True), + "bun_lockfile": attr.label(mandatory = True, allow_single_file = True), + "bun_linux_x64": attr.label(default = "@bun_linux_x64//:bun", allow_single_file = True), + "bun_linux_aarch64": attr.label(default = "@bun_linux_aarch64//:bun", allow_single_file = True), + "bun_darwin_x64": attr.label(default = "@bun_darwin_x64//:bun", allow_single_file = True), + "bun_darwin_aarch64": attr.label(default = "@bun_darwin_aarch64//:bun", allow_single_file = True), + "bun_windows_x64": attr.label(default = "@bun_windows_x64//:bun", allow_single_file = True), + }, +) + + +def bun_install(name, package_json, bun_lockfile): + """Create an external repository containing installed node_modules. + + Usage (WORKSPACE): + bun_install( + name = "node_modules", + package_json = "//:package.json", + bun_lockfile = "//:bun.lockb", + ) + """ + + _bun_install_repository( + name = name, + package_json = package_json, + bun_lockfile = bun_lockfile, + ) diff --git a/tests/install_test/BUILD.bazel b/tests/install_test/BUILD.bazel new file mode 100644 index 0000000..f23b708 --- /dev/null +++ b/tests/install_test/BUILD.bazel @@ -0,0 +1,21 @@ +sh_test( + name = "bun_install_clean_install_test", + srcs = ["clean_install.sh"], + args = ["$(location @bun_linux_x64//:bun)"], + data = ["@bun_linux_x64//:bun"], + target_compatible_with = [ + "@platforms//cpu:x86_64", + "@platforms//os:linux", + ], +) + +sh_test( + name = "bun_install_stale_lockfile_test", + srcs = ["stale_lockfile.sh"], + args = ["$(location @bun_linux_x64//:bun)"], + data = ["@bun_linux_x64//:bun"], + target_compatible_with = [ + "@platforms//cpu:x86_64", + "@platforms//os:linux", + ], +) diff --git a/tests/install_test/clean_install.sh b/tests/install_test/clean_install.sh new file mode 100755 index 0000000..93647f9 --- /dev/null +++ b/tests/install_test/clean_install.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail + +bun_path="$1" +workdir="$(mktemp -d)" +trap 'rm -rf "${workdir}"' EXIT + +cat > "${workdir}/package.json" <<'JSON' +{ + "name": "clean-install-test", + "version": "1.0.0" +} +JSON + +"${bun_path}" install --cwd "${workdir}" >/dev/null +rm -rf "${workdir}/node_modules" +"${bun_path}" install --cwd "${workdir}" --frozen-lockfile >/dev/null + +if [[ ! -d "${workdir}/node_modules" ]]; then + echo "Expected node_modules to be created" >&2 + exit 1 +fi diff --git a/tests/install_test/stale_lockfile.sh b/tests/install_test/stale_lockfile.sh new file mode 100755 index 0000000..e21cfd2 --- /dev/null +++ b/tests/install_test/stale_lockfile.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -euo pipefail + +bun_path="$1" +workdir="$(mktemp -d)" +trap 'rm -rf "${workdir}"' EXIT + +cat > "${workdir}/package.json" <<'JSON' +{ + "name": "stale-lockfile-test", + "version": "1.0.0" +} +JSON + +"${bun_path}" install --cwd "${workdir}" >/dev/null + +cat > "${workdir}/package.json" <<'JSON' +{ + "name": "stale-lockfile-test", + "version": "1.0.0", + "dependencies": { + "left-pad": "1.3.0" + } +} +JSON + +set +e +output="$(${bun_path} install --cwd "${workdir}" --frozen-lockfile 2>&1)" +code=$? +set -e + +if [[ ${code} -eq 0 ]]; then + echo "Expected frozen lockfile install to fail when package.json changes" >&2 + exit 1 +fi + +if [[ "${output}" != *"lockfile"* && "${output}" != *"frozen"* ]]; then + echo "Expected lockfile-related error, got:" >&2 + echo "${output}" >&2 + exit 1 +fi