diff --git a/.bazelrc b/.bazelrc index 0c36346..694f59b 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1 +1,3 @@ -common --enable_bzlmod \ No newline at end of file +common --enable_bzlmod + +common --lockfile_mode=off diff --git a/.bazelversion b/.bazelversion index 19b860c..66ce77b 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -6.4.0 +7.0.0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1811e52..4f169fe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,13 +15,15 @@ jobs: folders: | [ ".", + "examples/module", "examples/target-determinator" ] - # we only support Bazel 6, and only with bzlmod enabled + # we only support Bazel 7, and only with bzlmod enabled exclude: | [ {"bzlmodEnabled": false}, {"bazelversion": "5.4.0"}, + {"bazelversion": "6.4.0"}, ] # this ruleset only supports linux and macos exclude_windows: true diff --git a/BUILD.bazel b/BUILD.bazel index 695684b..f5bf95d 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,7 +1,7 @@ load("@buildifier_prebuilt//:rules.bzl", "buildifier", "buildifier_test") exports_files([ - "WORKSPACE.bazel", # used by buildifier to locate the root of the sandbox + "MODULE.bazel", ]) buildifier( @@ -18,5 +18,5 @@ buildifier_test( lint_mode = "warn", mode = "diff", no_sandbox = True, - workspace = "//:WORKSPACE.bazel", + workspace = "//:MODULE.bazel", ) diff --git a/MODULE.bazel b/MODULE.bazel index 2f911cf..46427b6 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -8,3 +8,10 @@ module( bazel_dep(name = "bazel_skylib", version = "1.4.1") bazel_dep(name = "buildifier_prebuilt", version = "6.1.2") +bazel_dep(name = "platforms", version = "0.0.8") + +# ensure toolchains get registered +multitool = use_extension("//multitool:extension.bzl", "multitool") +use_repo(multitool, "multitool") + +register_toolchains("@multitool//toolchains:all") diff --git a/examples/module/.bazelrc b/examples/module/.bazelrc new file mode 100644 index 0000000..694f59b --- /dev/null +++ b/examples/module/.bazelrc @@ -0,0 +1,3 @@ +common --enable_bzlmod + +common --lockfile_mode=off diff --git a/examples/module/.bazelversion b/examples/module/.bazelversion new file mode 100644 index 0000000..66ce77b --- /dev/null +++ b/examples/module/.bazelversion @@ -0,0 +1 @@ +7.0.0 diff --git a/examples/module/BUILD.bazel b/examples/module/BUILD.bazel new file mode 100644 index 0000000..755f3bb --- /dev/null +++ b/examples/module/BUILD.bazel @@ -0,0 +1,12 @@ +exports_files( + ["multitool.lock.json"], +) + +sh_test( + name = "integration_test", + srcs = ["integration_test.sh"], + args = [ + "$(location @multitool//tools/target-determinator)", + ], + data = ["@multitool//tools/target-determinator"], +) diff --git a/examples/module/MODULE.bazel b/examples/module/MODULE.bazel new file mode 100644 index 0000000..6625e33 --- /dev/null +++ b/examples/module/MODULE.bazel @@ -0,0 +1,18 @@ +"multitool example using target-determinator" + +module( + name = "multitool_examples__target_determinator", + version = "0.0.0", + compatibility_level = 1, +) + +bazel_dep(name = "platforms", version = "0.0.8") +bazel_dep(name = "rules_multitool", version = "0.0.0") +local_path_override( + module_name = "rules_multitool", + path = "../..", +) + +multitool = use_extension("@rules_multitool//multitool:extension.bzl", "multitool") +multitool.hub(lockfile = "//:multitool.lock.json") +use_repo(multitool, "multitool") diff --git a/multitool/private/external_repo_template/BUILD.bazel.template b/examples/module/WORKSPACE.bazel similarity index 100% rename from multitool/private/external_repo_template/BUILD.bazel.template rename to examples/module/WORKSPACE.bazel diff --git a/examples/module/integration_test.sh b/examples/module/integration_test.sh new file mode 100755 index 0000000..7ac2187 --- /dev/null +++ b/examples/module/integration_test.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -eu + +$1 -version diff --git a/examples/module/multitool.lock.json b/examples/module/multitool.lock.json new file mode 100644 index 0000000..110e440 --- /dev/null +++ b/examples/module/multitool.lock.json @@ -0,0 +1,84 @@ +{ + "target-determinator": { + "binaries": [ + { + "kind": "file", + "url": "https://github.com/bazel-contrib/target-determinator/releases/download/v0.25.0/target-determinator.darwin.amd64", + "sha256": "8c7245603dede429b978e214ca327c3f3d686a1bc712c1298fca0396a0f25f23", + "os": "macos", + "cpu": "x86_64" + }, + { + "kind": "file", + "url": "https://github.com/bazel-contrib/target-determinator/releases/download/v0.25.0/target-determinator.darwin.arm64", + "sha256": "8f975b471c4a51d32781b757e1ece9700221bfd4c0ea507c18fa382360d1111f", + "os": "macos", + "cpu": "arm64" + }, + { + "kind": "file", + "url": "https://github.com/bazel-contrib/target-determinator/releases/download/v0.25.0/target-determinator.linux.amd64", + "sha256": "c8a09143e9fe6eccc4b27a6be92c5929e5a78034a8d0b4c43dbed4ee539ec903", + "os": "linux", + "cpu": "x86_64" + } + ] + }, + "gh": { + "binaries": [ + { + "kind": "archive", + "url": "https://github.com/cli/cli/releases/download/v2.44.1/gh_2.44.1_macOS_amd64.zip", + "sha256": "1c545505b5b88feaffeba00b7284ccac3f2002b67461b1246eaec827eb07c31b", + "file": "gh_2.44.1_macOS_amd64/bin/gh", + "os": "macos", + "cpu": "x86_64" + }, + { + "kind": "archive", + "url": "https://github.com/cli/cli/releases/download/v2.44.1/gh_2.44.1_macOS_amd64.zip", + "sha256": "1c545505b5b88feaffeba00b7284ccac3f2002b67461b1246eaec827eb07c31b", + "file": "gh_2.44.1_macOS_amd64/bin/gh", + "os": "macos", + "cpu": "arm64" + }, + { + "kind": "archive", + "url": "https://github.com/cli/cli/releases/download/v2.44.1/gh_2.44.1_linux_amd64.tar.gz", + "sha256": "f11eefb646768e3f53e2185f6d3b01b4cb02112c2c60e65a4b5875150287ff97", + "file": "gh_2.44.1_linux_amd64/bin", + "os": "linux", + "cpu": "x86_64" + } + ] + }, + "aws": { + "binaries": [ + { + "kind": "pkg", + "url": "https://awscli.amazonaws.com/AWSCLIV2-2.8.4.pkg", + "sha256": "df0df526521a5b6c38b6954ec08c16453916daacca83582d37582654e9ca05a3", + "file": "aws-cli.pkg/Payload/aws-cli/aws", + "os": "macos", + "cpu": "x86_64" + }, + { + "kind": "pkg", + "url": "https://awscli.amazonaws.com/AWSCLIV2-2.8.4.pkg", + "sha256": "df0df526521a5b6c38b6954ec08c16453916daacca83582d37582654e9ca05a3", + "file": "aws-cli.pkg/Payload/aws-cli/aws", + "os": "macos", + "cpu": "arm64" + }, + { + "kind": "archive", + "url": "https://awscli.amazonaws.com/awscli-exe-linux-x86_64-2.8.4.zip", + "sha256": "d23b59d08c129daeda7535051eaa082e139cc14d243995aa754d4b250b9c9329", + "file": "aws/dist/aws", + "os": "linux", + "cpu": "x86_64" + } + + ] + } +} diff --git a/examples/target-determinator/.bazelrc b/examples/target-determinator/.bazelrc index 0c36346..694f59b 100644 --- a/examples/target-determinator/.bazelrc +++ b/examples/target-determinator/.bazelrc @@ -1 +1,3 @@ -common --enable_bzlmod \ No newline at end of file +common --enable_bzlmod + +common --lockfile_mode=off diff --git a/examples/target-determinator/BUILD.bazel b/examples/target-determinator/BUILD.bazel index 2b3f93b..38a0bfd 100644 --- a/examples/target-determinator/BUILD.bazel +++ b/examples/target-determinator/BUILD.bazel @@ -2,7 +2,7 @@ sh_test( name = "integration_test", srcs = ["integration_test.sh"], args = [ - "$(location @target-determinator//tool)", + "$(location @multitool//tools/target-determinator)", ], - data = ["@target-determinator//tool"], + data = ["@multitool//tools/target-determinator"], ) diff --git a/examples/target-determinator/WORKSPACE.bazel b/examples/target-determinator/WORKSPACE.bazel index d5c6527..6146eae 100644 --- a/examples/target-determinator/WORKSPACE.bazel +++ b/examples/target-determinator/WORKSPACE.bazel @@ -1,30 +1,6 @@ -load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_file") load("@rules_multitool//multitool:multitool.bzl", "multitool") -http_file( - name = "target_determinator_linux_x86_64", - executable = True, - sha256 = "c8a09143e9fe6eccc4b27a6be92c5929e5a78034a8d0b4c43dbed4ee539ec903", - urls = ["https://github.com/bazel-contrib/target-determinator/releases/download/v0.25.0/target-determinator.linux.amd64"], -) - -http_file( - name = "target_determinator_macos_arm64", - executable = True, - sha256 = "8f975b471c4a51d32781b757e1ece9700221bfd4c0ea507c18fa382360d1111f", - urls = ["https://github.com/bazel-contrib/target-determinator/releases/download/v0.25.0/target-determinator.darwin.arm64"], -) - -http_file( - name = "target_determinator_macos_x86_64", - executable = True, - sha256 = "8c7245603dede429b978e214ca327c3f3d686a1bc712c1298fca0396a0f25f23", - urls = ["https://github.com/bazel-contrib/target-determinator/releases/download/v0.25.0/target-determinator.darwin.amd64"], -) - multitool( - name = "target-determinator", - linux_x86_64_binary = "@target_determinator_linux_x86_64//file", - macos_arm64_binary = "@target_determinator_macos_arm64//file", - macos_x86_64_binary = "@target_determinator_macos_x86_64//file", + name = "multitool", + lockfile = "//:multitool.lock.json", ) diff --git a/examples/target-determinator/multitool.lock.json b/examples/target-determinator/multitool.lock.json new file mode 100644 index 0000000..436d090 --- /dev/null +++ b/examples/target-determinator/multitool.lock.json @@ -0,0 +1,27 @@ +{ + "target-determinator": { + "binaries": [ + { + "kind": "file", + "url": "https://github.com/bazel-contrib/target-determinator/releases/download/v0.25.0/target-determinator.darwin.amd64", + "sha256": "8c7245603dede429b978e214ca327c3f3d686a1bc712c1298fca0396a0f25f23", + "os": "macos", + "cpu": "x86_64" + }, + { + "kind": "file", + "url": "https://github.com/bazel-contrib/target-determinator/releases/download/v0.25.0/target-determinator.darwin.arm64", + "sha256": "8f975b471c4a51d32781b757e1ece9700221bfd4c0ea507c18fa382360d1111f", + "os": "macos", + "cpu": "arm64" + }, + { + "kind": "file", + "url": "https://github.com/bazel-contrib/target-determinator/releases/download/v0.25.0/target-determinator.linux.amd64", + "sha256": "c8a09143e9fe6eccc4b27a6be92c5929e5a78034a8d0b4c43dbed4ee539ec903", + "os": "linux", + "cpu": "x86_64" + } + ] + } +} diff --git a/multitool/BUILD.bazel b/multitool/BUILD.bazel index 812b7d5..51fd426 100644 --- a/multitool/BUILD.bazel +++ b/multitool/BUILD.bazel @@ -1,5 +1,11 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") +bzl_library( + name = "extension", + srcs = ["extension.bzl"], + visibility = ["//visibility:public"], +) + bzl_library( name = "multitool", srcs = ["multitool.bzl"], diff --git a/multitool/extension.bzl b/multitool/extension.bzl new file mode 100644 index 0000000..3ef40ee --- /dev/null +++ b/multitool/extension.bzl @@ -0,0 +1,28 @@ +"multitool" + +load("//multitool/private:multitool.bzl", _hub = "hub") + +hub = tag_class( + attrs = { + "lockfile": attr.label(mandatory = True, allow_single_file = True), + }, +) + +def _extension(module_ctx): + lockfiles = [] + for mod in module_ctx.modules: + for h in mod.tags.hub: + lockfiles.append(h.lockfile) + + # TODO: we should be able to support multiple hubs + _hub( + name = "multitool", + lockfiles = lockfiles, + ) + +multitool = module_extension( + implementation = _extension, + tag_classes = { + "hub": hub, + }, +) diff --git a/multitool/private/external_repo_template/MODULE.bazel.template b/multitool/private/external_repo_template/MODULE.bazel.template deleted file mode 100644 index b905e01..0000000 --- a/multitool/private/external_repo_template/MODULE.bazel.template +++ /dev/null @@ -1,3 +0,0 @@ -"Module for toolchain {name}" - -module(name = "{name}") diff --git a/multitool/private/external_repo_template/WORKSPACE.bazel.template b/multitool/private/external_repo_template/WORKSPACE.bazel.template deleted file mode 100644 index ed947c4..0000000 --- a/multitool/private/external_repo_template/WORKSPACE.bazel.template +++ /dev/null @@ -1 +0,0 @@ -workspace(name = "{name}") diff --git a/multitool/private/external_repo_template/linux/arm64/BUILD.bazel.template b/multitool/private/external_repo_template/linux/arm64/BUILD.bazel.template deleted file mode 100644 index 4254ab0..0000000 --- a/multitool/private/external_repo_template/linux/arm64/BUILD.bazel.template +++ /dev/null @@ -1,22 +0,0 @@ -load("//toolchain_type:toolchain_type.bzl", "TOOLCHAIN_TYPE") -load("//:toolchain_info.bzl", "toolchain_info") - -PLATFORMS = [ - "@platforms//cpu:arm64", - "@platforms//os:linux", -] - -toolchain_info( - name = "toolchain", - cpu = "arm64", - {linux_arm64_binary} - os = "linux", -) - -toolchain( - name = "arm64", - exec_compatible_with = PLATFORMS, - target_compatible_with = PLATFORMS, - toolchain = ":toolchain", - toolchain_type = TOOLCHAIN_TYPE, -) diff --git a/multitool/private/external_repo_template/linux/x86_64/BUILD.bazel.template b/multitool/private/external_repo_template/linux/x86_64/BUILD.bazel.template deleted file mode 100644 index 3878f03..0000000 --- a/multitool/private/external_repo_template/linux/x86_64/BUILD.bazel.template +++ /dev/null @@ -1,22 +0,0 @@ -load("//toolchain_type:toolchain_type.bzl", "TOOLCHAIN_TYPE") -load("//:toolchain_info.bzl", "toolchain_info") - -PLATFORMS = [ - "@platforms//cpu:x86_64", - "@platforms//os:linux", -] - -toolchain_info( - name = "toolchain", - cpu = "x86_64", - {linux_x86_64_binary} - os = "linux", -) - -toolchain( - name = "x86_64", - exec_compatible_with = PLATFORMS, - target_compatible_with = PLATFORMS, - toolchain = ":toolchain", - toolchain_type = TOOLCHAIN_TYPE, -) diff --git a/multitool/private/external_repo_template/macos/arm64/BUILD.bazel.template b/multitool/private/external_repo_template/macos/arm64/BUILD.bazel.template deleted file mode 100644 index 4b0121e..0000000 --- a/multitool/private/external_repo_template/macos/arm64/BUILD.bazel.template +++ /dev/null @@ -1,22 +0,0 @@ -load("//toolchain_type:toolchain_type.bzl", "TOOLCHAIN_TYPE") -load("//:toolchain_info.bzl", "toolchain_info") - -PLATFORMS = [ - "@platforms//cpu:arm64", - "@platforms//os:macos", -] - -toolchain_info( - name = "toolchain", - cpu = "arm64", - {macos_arm64_binary} - os = "macos", -) - -toolchain( - name = "arm64", - exec_compatible_with = PLATFORMS, - target_compatible_with = PLATFORMS, - toolchain = ":toolchain", - toolchain_type = TOOLCHAIN_TYPE, -) diff --git a/multitool/private/external_repo_template/macos/x86_64/BUILD.bazel.template b/multitool/private/external_repo_template/macos/x86_64/BUILD.bazel.template deleted file mode 100644 index dd517c7..0000000 --- a/multitool/private/external_repo_template/macos/x86_64/BUILD.bazel.template +++ /dev/null @@ -1,22 +0,0 @@ -load("//toolchain_type:toolchain_type.bzl", "TOOLCHAIN_TYPE") -load("//:toolchain_info.bzl", "toolchain_info") - -PLATFORMS = [ - "@platforms//cpu:x86_64", - "@platforms//os:macos", -] - -toolchain_info( - name = "toolchain", - cpu = "x86_64", - {macos_x86_64_binary} - os = "macos", -) - -toolchain( - name = "x86_64", - exec_compatible_with = PLATFORMS, - target_compatible_with = PLATFORMS, - toolchain = ":toolchain", - toolchain_type = TOOLCHAIN_TYPE, -) diff --git a/multitool/private/external_repo_template/tool/BUILD.bazel.template b/multitool/private/external_repo_template/tool/BUILD.bazel.template deleted file mode 100644 index 9a96b39..0000000 --- a/multitool/private/external_repo_template/tool/BUILD.bazel.template +++ /dev/null @@ -1,6 +0,0 @@ -load(":tool.bzl", "tool") - -tool( - name = "tool", - visibility = ["//visibility:public"], -) diff --git a/multitool/private/external_repo_template/tool/tool.bzl.template b/multitool/private/external_repo_template/tool/tool.bzl.template deleted file mode 100644 index 5579bc8..0000000 --- a/multitool/private/external_repo_template/tool/tool.bzl.template +++ /dev/null @@ -1,9 +0,0 @@ -load("//toolchain_type:toolchain_type.bzl", "TOOLCHAIN_TYPE") - -def _tool_impl(ctx): - toolchain = ctx.toolchains[TOOLCHAIN_TYPE] - output = ctx.actions.declare_file(ctx.label.name) - ctx.actions.symlink(output = output, target_file = toolchain.executable) - return [DefaultInfo(executable = output)] - -tool = rule(executable = True, implementation = _tool_impl, toolchains = [TOOLCHAIN_TYPE]) diff --git a/multitool/private/external_repo_template/toolchain_type/BUILD.bazel.template b/multitool/private/external_repo_template/toolchain_type/BUILD.bazel.template deleted file mode 100644 index 19b1b56..0000000 --- a/multitool/private/external_repo_template/toolchain_type/BUILD.bazel.template +++ /dev/null @@ -1,4 +0,0 @@ -toolchain_type( - name = "toolchain_type", - visibility = ["//:__subpackages__"], -) diff --git a/multitool/private/external_repo_template/toolchain_type/toolchain_type.bzl.template b/multitool/private/external_repo_template/toolchain_type/toolchain_type.bzl.template deleted file mode 100644 index 08854cd..0000000 --- a/multitool/private/external_repo_template/toolchain_type/toolchain_type.bzl.template +++ /dev/null @@ -1 +0,0 @@ -TOOLCHAIN_TYPE = "@{name}//toolchain_type" diff --git a/multitool/private/hub_repo_template/BUILD.bazel.template b/multitool/private/hub_repo_template/BUILD.bazel.template new file mode 100644 index 0000000..e69de29 diff --git a/multitool/private/external_repo_template/toolchain_info.bzl.template b/multitool/private/hub_repo_template/toolchain_info.bzl.template similarity index 95% rename from multitool/private/external_repo_template/toolchain_info.bzl.template rename to multitool/private/hub_repo_template/toolchain_info.bzl.template index 778d9a5..36775af 100644 --- a/multitool/private/external_repo_template/toolchain_info.bzl.template +++ b/multitool/private/hub_repo_template/toolchain_info.bzl.template @@ -1,16 +1,18 @@ +# generated by multitool + def _toolchain_info_impl(ctx): return [ platform_common.ToolchainInfo( - cpu = ctx.attr.cpu, executable = ctx.file.executable, + cpu = ctx.attr.cpu, os = ctx.attr.os, ), ] toolchain_info = rule( attrs = dict( - cpu = attr.string(mandatory = True, values = ["arm64", "x86_64"]), executable = attr.label(allow_single_file = True), + cpu = attr.string(mandatory = True, values = ["arm64", "x86_64"]), os = attr.string(mandatory = True, values = ["linux", "macos"]), ), implementation = _toolchain_info_impl, diff --git a/multitool/private/hub_repo_template/toolchains/BUILD.bazel.template b/multitool/private/hub_repo_template/toolchains/BUILD.bazel.template new file mode 100644 index 0000000..a20d481 --- /dev/null +++ b/multitool/private/hub_repo_template/toolchains/BUILD.bazel.template @@ -0,0 +1,5 @@ +# generated by multitool + +{loads} + +{defines} diff --git a/multitool/private/hub_repo_template/tools/BUILD.bazel.template b/multitool/private/hub_repo_template/tools/BUILD.bazel.template new file mode 100644 index 0000000..14f9e5b --- /dev/null +++ b/multitool/private/hub_repo_template/tools/BUILD.bazel.template @@ -0,0 +1 @@ +# generated by multitool diff --git a/multitool/private/multitool.bzl b/multitool/private/multitool.bzl index 031491c..10b08f9 100644 --- a/multitool/private/multitool.bzl +++ b/multitool/private/multitool.bzl @@ -1,65 +1,149 @@ -"multitool" - -_COMMON_FILES_TO_GENERATE = [ - "BUILD.bazel", - "MODULE.bazel", - "tool/BUILD.bazel", - "tool/tool.bzl", - "toolchain_info.bzl", - "toolchain_type/BUILD.bazel", - "toolchain_type/toolchain_type.bzl", - "WORKSPACE.bazel", -] - -_PLATFORMS = [ - ("linux", "arm64"), - ("linux", "x86_64"), - ("macos", "arm64"), - ("macos", "x86_64"), -] -_TEMPLATE = "//multitool/private:external_repo_template/{filename}.template" - -def _binary(os, cpu): - return "{os}_{cpu}_binary".format(os = os, cpu = cpu) - -def _template(ctx, filename, substitutions): - ctx.template( +"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(_TEMPLATE.format(filename = filename)), - substitutions = substitutions, + 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 _multitool_impl(ctx): - substitutions = { - "{%s}" % substitution: value - for substitution, value in dict( - { - attrname: """ executable = "%s",""" % str(attrval) if attrval else "" - for os, cpu in _PLATFORMS - for attrname in [_binary(os, cpu)] - for attrval in [getattr(ctx.attr, attrname)] - }, - name = ctx.name, - ).items() - } - - for filename in _COMMON_FILES_TO_GENERATE: - _template(ctx, filename, substitutions) - - attr_keys = dir(ctx.attr) - for os, cpu in _PLATFORMS: - if _binary(os, cpu) in attr_keys and getattr(ctx.attr, _binary(os, cpu)) != None: - _template(ctx, "%s/%s/BUILD.bazel" % (os, cpu), substitutions) - -_multitool = repository_rule( - attrs = {_binary(os, cpu): attr.label() for os, cpu in _PLATFORMS}, - implementation = _multitool_impl, +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 multitool(name, **kwargs): - _multitool(name = name, **kwargs) - native.register_toolchains(*[ - "@{name}//{os}/{cpu}".format(name = name, os = os, cpu = cpu) - for os, cpu in _PLATFORMS - if kwargs.get("{os}_{cpu}_binary".format(os = os, cpu = cpu)) != None - ]) +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") diff --git a/multitool/private/tool_template/BUILD.bazel.template b/multitool/private/tool_template/BUILD.bazel.template new file mode 100644 index 0000000..0165c6d --- /dev/null +++ b/multitool/private/tool_template/BUILD.bazel.template @@ -0,0 +1,15 @@ +# generated by multitool + +load(":tool.bzl", "tool") + +exports_files(glob(include=["*_executable"])) + +toolchain_type( + name = "toolchain_type", + visibility = ["//:__subpackages__"], +) + +tool( + name = "{name}", + visibility = ["//visibility:public"], +) diff --git a/multitool/private/tool_template/tool.bzl.template b/multitool/private/tool_template/tool.bzl.template new file mode 100644 index 0000000..5ab6394 --- /dev/null +++ b/multitool/private/tool_template/tool.bzl.template @@ -0,0 +1,39 @@ +# generated by multitool + +load("//:toolchain_info.bzl", "toolchain_info") + +_TOOLCHAIN_TYPE = "//tools/{name}:toolchain_type" + +def _tool_impl(ctx): + toolchain = ctx.toolchains[_TOOLCHAIN_TYPE] + output = ctx.actions.declare_file(ctx.label.name) + ctx.actions.symlink(output = output, target_file = toolchain.executable) + return [DefaultInfo(executable = output)] + +tool = rule(executable = True, implementation = _tool_impl, toolchains = [_TOOLCHAIN_TYPE]) + +def _declare_toolchain(name, os, cpu): + toolchain_info( + name = "{name}_{os}_{cpu}_toolchain_info".format(name=name, os=os, cpu=cpu), + executable = "//tools/{name}:{os}_{cpu}_executable".format(name=name, os=os, cpu=cpu), + os = os, + cpu = cpu, + ) + + native.toolchain( + name = "{name}_{os}_{cpu}_toolchain".format(name=name, os=os, cpu=cpu), + toolchain = ":{name}_{os}_{cpu}_toolchain_info".format(name=name, os=os, cpu=cpu), + toolchain_type = _TOOLCHAIN_TYPE, + exec_compatible_with = [ + "@platforms//cpu:{cpu}".format(cpu=cpu), + "@platforms//os:{os}".format(os=os), + ], + target_compatible_with = [ + "@platforms//cpu:{cpu}".format(cpu=cpu), + "@platforms//os:{os}".format(os=os), + ], + ) + +def declare_toolchains(): + "toolchain targets" + {toolchains} diff --git a/readme.md b/readme.md index e27b4de..c71480f 100644 --- a/readme.md +++ b/readme.md @@ -4,41 +4,52 @@ An ergonomic approach to defining a single tool target that resolves to a matchi ## Usage -For a quickstart, see the [target-determinator example](examples/target-determinator/). +For a quickstart, see the [bzlmod example](examples/module/). + +Define a lockfile that references the tools to load: + +```json +{ + "tool-name": { + "binaries": [ + { + "kind": "file", + "url": "https://...", + "sha256": "sha256 of the file", + "os": "linux|macos", + "cpu": "x86_64|arm64" + } + ] + } +} +``` -Load the ruleset in your **MODULE.bazel**: +The lockfile supports the following binary kinds: -```python -bazel_dep(name = "rules_multitool", version = "0.0.0") -``` +- **file**: the URL refers to a file to download + + - `sha256`: the sha256 of the downloaded file + +- **archive**: the URL referes to an archive to download, specify additional options: + + - `file`: executable file within the archive + - `sha256`: the sha256 of the downloaded archive -Define tools in your **WORKSPACE.bazel**: +- **pkg**: the URL refers to a MacOS pkg archive to download, specify additional options: + + - `file`: executable file within the archive + - `sha256`: the sha256 of the downloaded pkg archive + +Save your lockfile and ensure the file is exported using `export_files` so that it's available to Bazel. + +Once your lockfile is defined, load the ruleset in your **MODULE.bazel** and create a hub that refers to your lockfile: ```python -load("@rules_multitool//multitool:multitool.bzl", "multitool") - -# Load OS and architecture-specific binaries -http_file( - name = "target_determinator_linux_x86_64", - executable = True, - sha256 = "c8a09143e9fe6eccc4b27a6be92c5929e5a78034a8d0b4c43dbed4ee539ec903", - urls = ["https://github.com/bazel-contrib/target-determinator/releases/download/v0.25.0/target-determinator.linux.amd64"], -) - -http_file( - name = "target_determinator_macos_x86_64", - executable = True, - sha256 = "8c7245603dede429b978e214ca327c3f3d686a1bc712c1298fca0396a0f25f23", - urls = ["https://github.com/bazel-contrib/target-determinator/releases/download/v0.25.0/target-determinator.darwin.amd64"], -) - -# Declare a combined tool, runnable as `bazel run @[name]//tool` -multitool( - name = "target-determinator", - linux_x86_64_binary = "@target_determinator_linux_x86_64//file", - macos_x86_64_binary = "@target_determinator_macos_x86_64//file", - # also valid to specify: - # linux_arm64_binary - # macos_arm64_binary -) +bazel_dep(name = "rules_multitool", version = "0.0.0") + +multitool = use_extension("@rules_multitool//multitool:extension.bzl", "multitool") +multitool.hub(lockfile = "//:multitool.lock.json") +use_repo(multitool, "multitool") ``` + +Tools may then be accessed using `@multitool//tools/tool-name`.