""" Bazel rule for building mkosi images. """

load("@aspect_bazel_lib//lib:paths.bzl", "relative_file")
load("@bazel_skylib//lib:paths.bzl", "paths")

def _mkosi_image_impl(ctx):
    args = ctx.actions.args()
    inputs = []
    outputs = []
    tools = []
    workdir = ctx.file.mkosi_conf.dirname
    config_rel = lambda target: relative_file(target, ctx.file.mkosi_conf.path)
    env = {}
    args.add("-C", workdir)
    if ctx.attr.distribution:
        args.add("--distribution", ctx.attr.distribution)
    if ctx.attr.architecture:
        args.add("--architecture", ctx.attr.architecture)
    args.add_all(ctx.attr.packages, before_each = "--package")
    for package_file in ctx.files.package_files:
        args.add("--package", config_rel(package_file.path))
    if len(ctx.files.local_mirror) > 0:
        env["LOCAL_MIRROR"] = config_rel(ctx.files.local_mirror[0].dirname)
    for tree in ctx.files.base_trees:
        args.add("--base-tree", config_rel(tree.path))
    for tree in ctx.files.skeleton_trees:
        args.add("--skeleton-tree", config_rel(tree.path))
    for tree in ctx.files.package_manager_trees:
        args.add("--package-manager_tree", config_rel(tree.path))
    for tree in ctx.files.extra_trees:
        args.add("--extra-tree", config_rel(tree.path))
    for initrd in ctx.files.initrds:
        inputs.append(initrd)
        args.add("--initrd", config_rel(initrd.path))
    inputs.extend(ctx.files.mkosi_conf)
    inputs.extend(ctx.files.srcs[:])
    inputs.extend(ctx.files.package_files[:])
    inputs.extend(ctx.files.base_trees[:])
    inputs.extend(ctx.files.skeleton_trees[:])
    inputs.extend(ctx.files.package_manager_trees[:])
    inputs.extend(ctx.files.extra_trees[:])
    inputs.extend(ctx.files.initrds[:])
    inputs.extend(ctx.files.local_mirror[:])
    if ctx.attr.source_date_epoch:
        args.add("--source-date-epoch", ctx.attr.source_date_epoch)
    if ctx.attr.seed:
        args.add("--seed", ctx.attr.seed)
    if ctx.file.version_file:
        env["VERSION_FILE"] = ctx.file.version_file.path
        inputs.append(ctx.file.version_file)
    outputs.extend(ctx.outputs.outs)
    if ctx.attr.out_dir:
        out_dir = ctx.actions.declare_directory(ctx.attr.out_dir)
        for output in outputs:
            if output.path.startswith(out_dir.path + "/"):
                fail("output {} is nested within output directory {}; outputs cannot be nested within each other!".format(output.path, out_dir.path))
            if output.is_directory and out_dir.path.startswith(output.path + "/"):
                fail("output directory {} is nested within output directory {}; outputs cannot be nested within each other!".format(out_dir.path, output.path))
        outputs.append(out_dir)
        args.add("--output-dir", config_rel(out_dir.path))
    else:
        args.add("--output-dir", config_rel(paths.join(ctx.bin_dir.path, ctx.label.package)))
    args.add_all(ctx.attr.extra_args)
    for key, value in ctx.attr.env.items():
        args.add("--environment", "{}={}".format(key, value))
    if ctx.attr.kernel_command_line:
        args.add("--kernel-command-line", ctx.attr.kernel_command_line)
    for key, value in ctx.attr.kernel_command_line_dict.items():
        args.add("--kernel-command-line", "{}={}".format(key, value))
    if ctx.attr.autologin:
        args.add("--autologin", "yes")

    info = ctx.toolchains["@constellation//bazel/mkosi:toolchain_type"].mkosi
    if not info.valid:
        fail("mkosi toolchain is not properly configured: {}".format(info.name))
    if info.label:
        executable_files = info.label[DefaultInfo].files_to_run
        tools.append(executable_files)
        mkosi_bin = executable_files.executable.path
    else:
        mkosi_bin = info.path

    wrapper = ctx.actions.declare_file("mkosi_wrapper.sh")
    ctx.actions.expand_template(
        template = ctx.file._mkosi_wrapper_template,
        output = wrapper,
        substitutions = {
            "@@MKOSI@@": mkosi_bin,
        },
        is_executable = True,
    )
    inputs.append(wrapper)
    ctx.actions.run(
        outputs = outputs,
        inputs = inputs,
        executable = wrapper,
        arguments = [args],
        tools = tools,
        execution_requirements = {"no-remote": "1", "no-sandbox": "1"},
        progress_message = "Building mkosi image " + ctx.label.name,
        env = env,
    )
    return DefaultInfo(files = depset(outputs), runfiles = ctx.runfiles(outputs))

mkosi_image = rule(
    implementation = _mkosi_image_impl,
    attrs = {
        "architecture": attr.string(),
        "autologin": attr.bool(),
        "base_trees": attr.label_list(allow_files = True),
        "distribution": attr.string(),
        "env": attr.string_dict(),
        "extra_args": attr.string_list(),
        "extra_trees": attr.label_list(allow_files = True),
        "initrds": attr.label_list(allow_files = True),
        "kernel_command_line": attr.string(),
        "kernel_command_line_dict": attr.string_dict(),
        "local_mirror": attr.label_list(allow_files = True),
        "mkosi_conf": attr.label(
            allow_single_file = True,
            mandatory = True,
            doc = "main mkosi.conf file",
        ),
        "out_dir": attr.string(),
        "outs": attr.output_list(),
        "package_files": attr.label_list(allow_files = True),
        "package_manager_trees": attr.label_list(allow_files = True),
        "packages": attr.string_list(),
        "seed": attr.string(),
        "skeleton_trees": attr.label_list(allow_files = True),
        "source_date_epoch": attr.string(),
        "srcs": attr.label_list(allow_files = True),
        "version_file": attr.label(allow_single_file = True),
        "_mkosi_wrapper_template": attr.label(
            default = Label("@constellation//bazel/mkosi:mkosi_wrapper.sh.in"),
            allow_single_file = True,
        ),
    },
    executable = False,
    toolchains = ["@constellation//bazel/mkosi:toolchain_type"],
)
