Files
rules_bun/internal/bun_install.bzl
eric 7139aa3ba2
Some checks failed
CI / test (macos-14, darwin-arm64) (push) Has been cancelled
CI / test (windows-latest, windows) (push) Has been cancelled
CI / test (ubuntu-latest, linux-x64) (push) Has been cancelled
Docs Pages / deploy (push) Failing after 42s
fix: bun_install adds node_modules to each directory
2026-03-06 21:44:15 +01:00

198 lines
6.6 KiB
Python

"""Repository-rule based bun_install implementation."""
def _segment_matches(name, pattern):
if pattern == "*":
return True
if "*" not in pattern:
return name == pattern
parts = pattern.split("*")
if len(parts) == 1:
return name == pattern
pos = 0
anchored_start = not pattern.startswith("*")
anchored_end = not pattern.endswith("*")
for i, part in enumerate(parts):
if not part:
continue
match_index = name.find(part, pos)
if match_index < 0:
return False
if i == 0 and anchored_start and match_index != 0:
return False
pos = match_index + len(part)
if anchored_end and parts[-1] and not name.endswith(parts[-1]):
return False
return True
def _walk_workspace_dirs(root, segments):
matches = [root]
for segment in segments:
if segment == "**":
fail("bun_install: `**` is not supported in workspace patterns; use explicit segments or `*`")
next_matches = []
for parent in matches:
for child in parent.readdir():
if child.is_dir and _segment_matches(child.basename, segment):
next_matches.append(child)
matches = next_matches
return matches
def _workspace_patterns(repository_ctx, package_json):
manifest = json.decode(repository_ctx.read(package_json))
workspaces = manifest.get("workspaces", [])
if type(workspaces) == type({}):
workspaces = workspaces.get("packages", [])
if type(workspaces) != type([]):
fail("bun_install: `workspaces` must be a list or an object with a `packages` list")
patterns = []
for pattern in workspaces:
if type(pattern) != type(""):
fail("bun_install: workspace pattern must be a string, got {}".format(type(pattern)))
normalized = "/".join([segment for segment in pattern.split("/") if segment and segment != "."])
if normalized:
patterns.append(normalized)
return patterns
def _materialize_workspace_packages(repository_ctx, package_json):
package_root = package_json.dirname
package_root_str = str(package_root)
written = {}
for pattern in _workspace_patterns(repository_ctx, package_json):
segments = pattern.split("/")
for workspace_dir in _walk_workspace_dirs(package_root, segments):
workspace_package_json = repository_ctx.path(str(workspace_dir) + "/package.json")
if not workspace_package_json.exists:
continue
workspace_dir_str = str(workspace_dir)
if workspace_dir_str == package_root_str:
continue
relative_dir = workspace_dir_str[len(package_root_str) + 1:]
if relative_dir in written:
continue
repository_ctx.file(
relative_dir + "/package.json",
repository_ctx.read(workspace_package_json),
)
written[relative_dir] = True
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)
lockfile_name = bun_lockfile.basename
if lockfile_name not in ["bun.lock", "bun.lockb"]:
lockfile_name = "bun.lock"
repository_ctx.file("package.json", repository_ctx.read(package_json))
repository_ctx.symlink(bun_lockfile, lockfile_name)
_materialize_workspace_packages(repository_ctx, package_json)
result = repository_ctx.execute(
[str(bun_bin), "--bun", "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 --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-linux-x64/bun", allow_single_file = True),
"bun_linux_aarch64": attr.label(default = "@bun_linux_aarch64//:bun-linux-aarch64/bun", allow_single_file = True),
"bun_darwin_x64": attr.label(default = "@bun_darwin_x64//:bun-darwin-x64/bun", allow_single_file = True),
"bun_darwin_aarch64": attr.label(default = "@bun_darwin_aarch64//:bun-darwin-aarch64/bun", allow_single_file = True),
"bun_windows_x64": attr.label(default = "@bun_windows_x64//:bun-windows-x64/bun.exe", allow_single_file = True),
},
)
def bun_install(name, package_json, bun_lockfile):
"""Create an external repository containing installed node_modules.
Args:
name: Repository name to create.
package_json: Label to a package.json file.
bun_lockfile: Label to a bun.lockb file.
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,
)