diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel index f1bcd8396..0d2272e4e 100644 --- a/WORKSPACE.bazel +++ b/WORKSPACE.bazel @@ -18,6 +18,10 @@ nixpkgs_flake_package( package = "mkosi", ) +load("//bazel/mkosi:mkosi_configure.bzl", "find_system_mkosi") + +find_system_mkosi(name="constellation_mkosi") + # Python toolchain load("//bazel/toolchains:python_deps.bzl", "python_deps") diff --git a/bazel/mkosi/BUILD.bazel b/bazel/mkosi/BUILD.bazel new file mode 100644 index 000000000..06ae17422 --- /dev/null +++ b/bazel/mkosi/BUILD.bazel @@ -0,0 +1,70 @@ +"""toolchain to wrap an mkosi binary. + +Type: @constellation//bazel/mkosi:toolchain_type + +Toolchains: +- mkosi_missing_toolchain: provides a fallback toolchain for exec platforms + where mkosi might not be available. + +- mkosi_auto_toolchain: a toolchain that uses the installed mkosi. See + mkosi_configure.bzl%find_system_mkosi for usage. +""" + +load(":toolchain.bzl", "is_mkosi_available", "mkosi_make_variables", "mkosi_toolchain") + +# Expose the availability of an actual mkosi as a config_setting, so we can +# select() on it. +config_setting( + name = "have_mkosi", + flag_values = { + ":is_mkosi_available": "1", + }, + visibility = ["//visibility:public"], +) + +# Expose the availability of an actual mkosi as a feature flag, so we can +# create a config_setting from it. +is_mkosi_available( + name = "is_mkosi_available", + visibility = ["//:__subpackages__"], +) + +toolchain_type( + name = "toolchain_type", + visibility = ["//visibility:public"], +) + +mkosi_make_variables( + name = "make_variables", + visibility = ["//visibility:public"], +) + +# mkosi_missing_toolchain provides a fallback toolchain so that toolchain +# resolution can succeed even on platforms that do not have a working mkosi. +# If this toolchain is selected, the constraint ":have_mkosi" will not be +# satistifed. +mkosi_toolchain( + name = "no_mkosi", +) + +toolchain( + name = "mkosi_missing_toolchain", + toolchain = ":no_mkosi", + toolchain_type = ":toolchain_type", +) + +mkosi_toolchain( + name = "nix_mkosi", + label = "@mkosi//:bin/mkosi", +) + +toolchain( + name = "mkosi_nix_toolchain", + exec_compatible_with = [ + "@rules_nixpkgs_core//constraints:support_nix", + ], + toolchain = ":nix_mkosi", + toolchain_type = "@constellation//bazel/mkosi:toolchain_type", +) + +exports_files(["mkosi_wrapper.sh.in"]) diff --git a/bazel/mkosi/BUILD.tpl b/bazel/mkosi/BUILD.tpl new file mode 100644 index 000000000..a983918db --- /dev/null +++ b/bazel/mkosi/BUILD.tpl @@ -0,0 +1,13 @@ +# This content is generated by {GENERATOR} +load("@constellation//bazel/mkosi:toolchain.bzl", "mkosi_toolchain") + +mkosi_toolchain( + name = "mkosi_auto", + path = "{MKOSI_PATH}", +) + +toolchain( + name = "mkosi_auto_toolchain", + toolchain = ":mkosi_auto", + toolchain_type = "@constellation//bazel/mkosi:toolchain_type", +) diff --git a/bazel/mkosi/mkosi_configure.bzl b/bazel/mkosi/mkosi_configure.bzl new file mode 100644 index 000000000..23a7d9c23 --- /dev/null +++ b/bazel/mkosi/mkosi_configure.bzl @@ -0,0 +1,43 @@ +"""Repository rule to autoconfigure a toolchain using the system mkosi.""" + +def _write_build(rctx, path): + if not path: + path = "" + rctx.template( + "BUILD", + Label("//bazel/mkosi:BUILD.tpl"), + substitutions = { + "{GENERATOR}": "@constellation//bazel/mkosi/mkosi_configure.bzl%find_system_mkosi", + "{MKOSI_PATH}": str(path), + }, + executable = False, + ) + +def _find_system_mkosi_impl(rctx): + mkosi_path = rctx.which("mkosi") + if rctx.attr.verbose: + if mkosi_path: + print("Found mkosi at '%s'" % mkosi_path) # buildifier: disable=print + else: + print("No system mkosi found.") # buildifier: disable=print + _write_build(rctx = rctx, path = mkosi_path) + +_find_system_mkosi = repository_rule( + implementation = _find_system_mkosi_impl, + doc = """Create a repository that defines an mkosi toolchain based on the system mkosi.""", + local = True, + environ = ["PATH"], + attrs = { + "verbose": attr.bool( + doc = "If true, print status messages.", + ), + }, +) + +def find_system_mkosi(name, verbose = False): + _find_system_mkosi(name = name, verbose = verbose) + native.register_toolchains( + "@constellation//bazel/mkosi:mkosi_nix_toolchain", + "@%s//:mkosi_auto_toolchain" % name, + "@constellation//bazel/mkosi:mkosi_missing_toolchain", + ) diff --git a/bazel/mkosi/toolchain.bzl b/bazel/mkosi/toolchain.bzl new file mode 100644 index 000000000..08bd4b0d2 --- /dev/null +++ b/bazel/mkosi/toolchain.bzl @@ -0,0 +1,73 @@ +"""toolchain to provide an mkosi binary.""" + +TOOLCHAIN_TYPE = "@constellation//bazel/mkosi:toolchain_type" +MAKE_VARIABLES = "@constellation//bazel/mkosi:make_variables" + +MkosiInfo = provider( + doc = """Information needed to invoke mkosi.""", + fields = { + "label": "Label of a target providing a mkosi binary", + "name": "The name of the toolchain", + "path": "Path to the mkosi binary", + "valid": "Is this toolchain valid and usable?", + }, +) + +def _mkosi_toolchain_impl(ctx): + if ctx.attr.label and ctx.attr.path: + fail("mkosi_toolchain: label and path are mutually exclusive") + valid = bool(ctx.attr.label) or bool(ctx.attr.path) + + mkosi_info = MkosiInfo( + name = str(ctx.label), + valid = valid, + label = ctx.attr.label, + path = ctx.attr.path, + ) + toolchain_info = platform_common.ToolchainInfo( + mkosi = mkosi_info, + ) + return [toolchain_info] + +mkosi_toolchain = rule( + implementation = _mkosi_toolchain_impl, + attrs = { + "label": attr.label( + doc = "Label of a target providing a mkosi binary. Mutually exclusive with path.", + executable = True, + cfg = "exec", + allow_single_file = True, + default = None, + ), + "path": attr.string( + doc = "Path to the mkosi binary. Mutually exclusive with label.", + ), + }, +) + +def _mkosi_make_variables_impl(ctx): + info = ctx.toolchains[TOOLCHAIN_TYPE].mkosi + variables = {} + if info.valid: + binary_path = info.label[DefaultInfo].files_to_run.executable.path if info.label else info.path + variables["MKOSI"] = binary_path + return [platform_common.TemplateVariableInfo(variables)] + +mkosi_make_variables = rule( + doc = "Make variables for mkosi.", + implementation = _mkosi_make_variables_impl, + toolchains = [TOOLCHAIN_TYPE], +) + +def _is_mkosi_available_impl(ctx): + toolchain = ctx.toolchains[TOOLCHAIN_TYPE] + available = toolchain and toolchain.mkosi.valid + return [config_common.FeatureFlagInfo( + value = ("1" if available else "0"), + )] + +is_mkosi_available = rule( + implementation = _is_mkosi_available_impl, + attrs = {}, + toolchains = [TOOLCHAIN_TYPE], +)