blob: 10b08f9ae8c8e079f8fa7d3662f19c0a1a20366e [file]
"multitool hub implementation"
_HUB_TEMPLATE = "//multitool/private:hub_repo_template/{filename}.template"
_TOOL_TEMPLATE = "//multitool/private:tool_template/{filename}.template"
def _render_hub(rctx, filename, substitutions = None):
rctx.template(
filename,
Label(_HUB_TEMPLATE.format(filename = filename)),
substitutions = substitutions or {},
)
def _render_tool(rctx, tool_name, filename, substitutions = None):
rctx.template(
"tools/{tool_name}/{filename}".format(tool_name = tool_name, filename = filename),
Label(_TOOL_TEMPLATE.format(filename = filename)),
substitutions = {
"{name}": tool_name,
} | (substitutions or {}),
)
def _check(condition, message):
"fails iff condition is False and emits message"
if not condition:
fail(message)
def _multitool_hub_impl(rctx):
tools = {}
for lockfile in rctx.attr.lockfiles:
# TODO: validate no conflicts from multiple hub declarations and/or
# fix toolchains to also declare their versions and enable consumers
# to use constraints to pick the right one.
# (this is also a very naive merge at the tool level)
tools = tools | json.decode(rctx.read(lockfile))
loads = []
defines = []
for tool_name, tool in tools.items():
toolchains = []
for binary in tool["binaries"]:
_check(binary["os"] in ["linux", "macos"], "Unknown os '{os}'".format(os = binary["os"]))
_check(binary["cpu"] in ["x86_64", "arm64"], "Unknown cpu '{cpu}'".format(cpu = binary["cpu"]))
target_executable = "tools/{tool_name}/{os}_{cpu}_executable".format(
tool_name = tool_name,
cpu = binary["cpu"],
os = binary["os"],
)
if binary["kind"] == "file":
rctx.download(
url = binary["url"],
sha256 = binary["sha256"],
output = target_executable,
executable = True,
)
elif binary["kind"] == "archive":
archive_path = "tools/{tool_name}/{os}_{cpu}_archive".format(
tool_name = tool_name,
cpu = binary["cpu"],
os = binary["os"],
)
rctx.download_and_extract(
url = binary["url"],
sha256 = binary["sha256"],
output = archive_path,
)
# link to the executable
rctx.symlink(
"{archive_path}/{file}".format(archive_path = archive_path, file = binary["file"]),
target_executable,
)
elif binary["kind"] == "pkg":
# Check if pkgutil is on the path, and if not fail silently.
# repository rules execute irrespective of platform/OS, so this
# check is required for `pkg_archive` to not fail on Linux.
pkgutil_cmd = rctx.which("pkgutil")
if not pkgutil_cmd:
continue
archive_path = "tools/{tool_name}/{os}_{cpu}_pkg".format(
tool_name = tool_name,
cpu = binary["cpu"],
os = binary["os"],
)
rctx.download(
url = binary["url"],
sha256 = binary["sha256"],
output = archive_path + ".pkg",
)
rctx.execute([pkgutil_cmd, "--expand-full", archive_path + ".pkg", archive_path])
# link to the executable
rctx.symlink(
"{archive_path}/{file}".format(archive_path = archive_path, file = binary["file"]),
target_executable,
)
else:
fail("Unknown 'kind' {kind}".format(kind = binary["kind"]))
toolchains.append('\n _declare_toolchain(name="{name}", os="{os}", cpu="{cpu}")'.format(
name = tool_name,
cpu = binary["cpu"],
os = binary["os"],
))
_render_tool(rctx, tool_name, "BUILD.bazel")
_render_tool(rctx, tool_name, "tool.bzl", {
"{toolchains}": "\n".join(toolchains),
})
clean_name = tool_name.replace("-", "_")
loads.append('load("//tools/{tool_name}:tool.bzl", declare_{clean_name}_toolchains = "declare_toolchains")'.format(
tool_name = tool_name,
clean_name = clean_name,
))
defines.append("declare_{clean_name}_toolchains()".format(clean_name = clean_name))
_render_hub(rctx, "BUILD.bazel")
_render_hub(rctx, "toolchain_info.bzl")
_render_hub(rctx, "tools/BUILD.bazel")
_render_hub(rctx, "toolchains/BUILD.bazel", {
"{loads}": "\n".join(loads),
"{defines}": "\n".join(defines),
})
_multitool_hub = repository_rule(
attrs = {
"lockfiles": attr.label_list(mandatory = True, allow_files = True),
},
implementation = _multitool_hub_impl,
)
def hub(name, lockfiles):
"Create a multitool hub."
_multitool_hub(name = name, lockfiles = lockfiles)
def multitool(name, lockfile):
"(non-bzlmod) Create a multitool hub and register its toolchains."
_multitool_hub(name = name, lockfiles = [lockfile])
native.register_toolchains("@multitool//toolchains:all")