third_party/linux: use toolchain-bundle for linux kernel build

This contains lots of workarounds and should be migrated to a native
bazel integration.

Change-Id: Ib0a52daf3879df4d5369541475c42fa506a54328
Reviewed-on: https://review.monogon.dev/c/monogon/+/4101
Tested-by: Jenkins CI
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
diff --git a/third_party/linux/BUILD.bazel b/third_party/linux/BUILD.bazel
index cccc300..500a1a1 100644
--- a/third_party/linux/BUILD.bazel
+++ b/third_party/linux/BUILD.bazel
@@ -1,3 +1,4 @@
+load("//build/utils:target_info.bzl", "target_info")
 load("//third_party/linux:def.bzl", "linux_image")
 
 alias(
@@ -12,5 +13,26 @@
 linux_image(
     name = "linux",
     kernel_config = ":kconfig",
+    kernel_src = "@linux//:all",
+    visibility = ["//visibility:public"],
+)
+
+target_info(
+    name = "image_name",
+    value = select({
+        "@platforms//cpu:x86_64": "bzImage",
+        "@platforms//cpu:aarch64": "Image",
+        "@platforms//cpu:riscv64": "Image",
+    }),
+    visibility = ["//visibility:private"],
+)
+
+target_info(
+    name = "target_arch",
+    value = select({
+        "@platforms//cpu:x86_64": "x86",
+        "@platforms//cpu:aarch64": "arm64",
+        "@platforms//cpu:riscv64": "riscv64",
+    }),
     visibility = ["//visibility:public"],
 )
diff --git a/third_party/linux/def.bzl b/third_party/linux/def.bzl
index dd2a70f..5e320bd 100644
--- a/third_party/linux/def.bzl
+++ b/third_party/linux/def.bzl
@@ -1,32 +1,20 @@
-#  Copyright 2020 The Monogon Project Authors.
-#
-#  SPDX-License-Identifier: Apache-2.0
-#
-#  Licensed under the Apache License, Version 2.0 (the "License");
-#  you may not use this file except in compliance with the License.
-#  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-#  Unless required by applicable law or agreed to in writing, software
-#  distributed under the License is distributed on an "AS IS" BASIS,
-#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-#  See the License for the specific language governing permissions and
-#  limitations under the License.
+# Copyright The Monogon Project Authors.
+# SPDX-License-Identifier: Apache-2.0
 
 """
 Rules for building Linux kernel images.
 
-This currently performs the build in a fully unhermetic manner, using
-make/gcc/... from the sandbox sysroot, and is only slightly better than a genrule. This
-should be replaced by a hermetic build that at least uses rules_cc toolchain
-information, or even better, just uses cc_library targets.
+This currently performs the build in a fully hermetic manner, using
+make/gcc/... from the toolchain bundle, and is only slightly better than a genrule. This
+should be replaced by a hermetic build that just uses cc_library targets.
 """
 
-load("@rules_cc//cc:action_names.bzl", "C_COMPILE_ACTION_NAME")
 load("@rules_cc//cc:find_cc_toolchain.bzl", "CC_TOOLCHAIN_ATTRS", "find_cpp_toolchain", "use_cc_toolchain")
 load("@rules_cc//cc/common:cc_common.bzl", "cc_common")
-load("//build/utils:detect_root.bzl", "detect_root")
+load("@rules_cc//cc/common:cc_info.bzl", "CcInfo")
+load("//build/utils:detect_root.bzl", "detect_root", "detect_roots")
+load("//build/utils:foreign_build.bzl", "generate_foreign_build_env", "merge_env")
+load("//build/utils:target_info.bzl", "TargetInfo")
 load("//osbase/build:def.bzl", "ignore_unused_configuration")
 
 def _linux_image_impl_resources(_os, _ninputs):
@@ -48,50 +36,19 @@
         "local_test": 0,
     }
 
-DISABLED_FEATURES = []
-
-# NOTE: Multicall tool is called as path/to/llvm clang to workaround bug in out-of-process execution where tool name is repeated and parsing breaks.
+TOOLCHAINS = [
+    "//build/toolchain/toolchain-bundle:make_toolchain",
+    "//build/toolchain/toolchain-bundle:flex_toolchain",
+    "//build/toolchain/toolchain-bundle:bison_toolchain",
+    "//build/toolchain/toolchain-bundle:m4_toolchain",
+    "//build/toolchain/toolchain-bundle:busybox_toolchain",
+    "//build/toolchain/toolchain-bundle:bc_toolchain",
+    "//build/toolchain/toolchain-bundle:diff_toolchain",
+    "//build/toolchain/toolchain-bundle:perl_toolchain",
+    "//build/toolchain/toolchain-bundle:lz4_toolchain",
+]
 
 def _linux_image_impl(ctx):
-    kernel_config = ctx.file.kernel_config
-    kernel_src = ctx.files.kernel_src
-    image_format = ctx.attr.image_format
-
-    # Root of the given Linux sources.
-    root = detect_root(ctx.attr.kernel_src.files.to_list())
-
-    # Figure out target CC toolchain
-    cc_toolchain = find_cpp_toolchain(ctx)
-    feature_configuration = cc_common.configure_features(
-        ctx = ctx,
-        cc_toolchain = cc_toolchain,
-        requested_features = ctx.features,
-        unsupported_features = DISABLED_FEATURES + ctx.disabled_features,
-    )
-    c_compiler_path = cc_common.get_tool_for_action(
-        feature_configuration = feature_configuration,
-        action_name = C_COMPILE_ACTION_NAME,
-    )
-
-    # Figure out Kbuild ARCH option
-    target_arch = None
-    compressed_image_name = None
-
-    if ctx.target_platform_has_constraint(ctx.attr._constraint_x86_64[platform_common.ConstraintValueInfo]):
-        target_arch = "x86"
-        compressed_image_name = "bzImage"
-
-    if ctx.target_platform_has_constraint(ctx.attr._constraint_aarch64[platform_common.ConstraintValueInfo]):
-        target_arch = "arm64"
-        compressed_image_name = "Image"
-
-    if ctx.target_platform_has_constraint(ctx.attr._constraint_riscv64[platform_common.ConstraintValueInfo]):
-        target_arch = "riscv"
-        compressed_image_name = "Image"
-
-    if not target_arch:
-        fail("Target platform does not match expected constraints: @platforms//cpu:x86_64, @platforms//cpu:aarch64, or @platforms//cpu:riscv64.")
-
     # Tuple containing information about how to build and access the resulting
     # image.
     # The first element (target) is the make target to build, the second
@@ -100,51 +57,106 @@
     # rule.
     (target, image_source, image_name) = {
         "vmlinux": ("vmlinux modules", "vmlinux", "vmlinux"),
-        "Image": ("all modules", "arch/" + target_arch + "/boot/" + compressed_image_name, "Image"),
-    }[image_format]
+        "Image": ("all modules", "arch/" + ctx.attr._target_arch[TargetInfo].value + "/boot/" + ctx.attr._image_name[TargetInfo].value, "Image"),
+    }[ctx.attr.image_format]
+
+    ssl_src, ssl_gen = detect_roots(ctx.attr._ssl[CcInfo].compilation_context.direct_public_headers)
+    crypto_src, crypto_gen = detect_roots(ctx.attr._crypto[CcInfo].compilation_context.direct_public_headers)
+    extra_env = {
+        "HOSTLDFLAGS": " -L ".join(
+            [
+                "",  # First element empty, for force a the join prefix
+                detect_root(ctx.attr._zstd.files.to_list()).rsplit("/", 1)[0],
+                detect_root(ctx.attr._zlib.files.to_list()).rsplit("/", 1)[0],
+                detect_root(ctx.attr._libelf.files.to_list()).rsplit("/", 1)[0],
+                detect_root(ctx.attr._ssl.files.to_list()).rsplit("/", 1)[0],
+                detect_root(ctx.attr._crypto.files.to_list()).rsplit("/", 1)[0],
+            ],
+        ),
+        "HOSTCFLAGS": " -I ".join(
+            [
+                "",  # First element empty, for force a the join prefix
+                detect_root(ctx.attr._libelf[CcInfo].compilation_context.direct_public_headers),
+                ssl_src + "/../",
+                ssl_gen + "/../include/",
+                crypto_src + "/../",
+                crypto_gen + "/../include/",
+            ],
+        ),
+    }
+
+    inputs = depset(
+        ctx.files.kernel_config +
+        ctx.files.kernel_src +
+        ctx.files._libelf +
+        ctx.attr._libelf[CcInfo].compilation_context.direct_public_headers +
+        ctx.files._zlib +
+        ctx.files._ssl +
+        ctx.attr._ssl[CcInfo].compilation_context.direct_public_headers +
+        ctx.files._crypto +
+        ctx.attr._crypto[CcInfo].compilation_context.direct_public_headers,
+    )
+
+    # Setup the environment for the foreign build.
+    toolchain_env, toolchain_inputs, toolchain_cmd = generate_foreign_build_env(
+        ctx = ctx,
+        target_toolchain = find_cpp_toolchain(ctx),
+        exec_toolchain = ctx.attr._exec_toolchain[cc_common.CcToolchainInfo],
+        toolchain_bundle_tools = TOOLCHAINS,
+    )
 
     image = ctx.actions.declare_file(image_name)
     modinfo = ctx.actions.declare_file("modules.builtin.modinfo")
     modules = ctx.actions.declare_directory("modules")
     ctx.actions.run_shell(
         outputs = [image, modinfo, modules],
-        inputs = depset([kernel_config] + kernel_src, transitive = [cc_toolchain.all_files]),
+        inputs = depset(transitive = [inputs, toolchain_inputs]),
         resource_set = _linux_image_impl_resources,
-        command = '''
-            kconfig=$1
-            target=$2
-            image_source=$3
-            image=$4
-            root=$5
-            modinfo=$6
-            modules=$7
-            arch=$8
-            cc=$PWD/$9
-
+        env = merge_env(toolchain_env, extra_env),
+        progress_message = "Building Linux Kernel: {}".format(ctx.label.name),
+        mnemonic = "BuildLinux",
+        command = toolchain_cmd + """
+            export BISON_PKGDATADIR=$(realpath $(dirname $BISON))/../share/bison
             builddir=$(mktemp -d)
 
-            mkdir ${root}/.bin
-            cp ${kconfig} ${builddir}/.config
-            (cd ${root} && make -j 16 KBUILD_OUTPUT="${builddir}" ARCH="${arch}" CC="${cc//clang/llvm} clang" LD="${cc//clang/ld.lld}" OBJCOPY="${cc//clang/llvm-objcopy}" OBJDUMP="${cc//clang/llvm-objdump}" AR="${cc//clang/llvm-ar}" NM="${cc//clang/llvm-nm}" STRIP="${cc//clang/llvm-strip}" READELF="${cc//clang/llvm-readelf}" olddefconfig ${target} >/dev/null)
-            cp "${builddir}"/${image_source} ${image}
-            cp "${builddir}"/modules.builtin.modinfo ${modinfo}
+            mkdir {kernel_src}/.bin
+            cp {kconfig} $builddir/.config
+            (
+                cd {kernel_src} &&
+                make -j 16 \
+                \
+                CC="$CC" CXX="$CXX" LD="$LD" AR="$AR" NM="$NM" STRIP="$STRIP" \
+                OBJCOPY="$OBJCOPY" OBJDUMP="$OBJDUMP" READELF="$READELF" \
+                CFLAGS="$CFLAGS" LDFLAGS="$LDFLAGS" \
+                \
+                HOSTCC="$HOSTCC" HOSTCXX="$HOSTCXX" HOSTLD="$HOSTLD" \
+                HOSTAR="$HOSTAR" HOSTNM="$HOSTNM" HOSTSTRIP="$HOSTSTRIP" \
+                HOSTOBJCOPY="$HOSTOBJCOPY" HOSTOBJDUMP="$HOSTOBJDUMP" \
+                HOSTREADELF="$HOSTREADELF" HOSTCFLAGS="$HOSTCFLAGS" \
+                HOSTLDFLAGS="$HOSTLDFLAGS" \
+                \
+                KBUILD_OUTPUT="$builddir" \
+                ARCH="{target_arch}" \
+                olddefconfig {target}
+            ) > /dev/null
+
+            cp "$builddir"/{image_source} {image}
+            cp "$builddir"/modules.builtin.modinfo {modinfo}
             # Not using modules_install as it tries to run depmod and friends
-            for f in $(find "${builddir}" -name '*.ko' -type f -printf "%P\n" ); do
-                install -D "${builddir}/$f" "${modules}/$f"
+            for f in $(find "$builddir" -name '*.ko' -type f -printf "%P\n" ); do
+                install -D "$builddir/$f" "{modules}/$f"
             done
             rm -Rf "$builddir"
-        ''',
-        arguments = [
-            kernel_config.path,
-            target,
-            image_source,
-            image.path,
-            root,
-            modinfo.path,
-            modules.path,
-            target_arch,
-            c_compiler_path,
-        ],
+            """.format(
+            kconfig = ctx.file.kernel_config.path,
+            target = target,
+            image_source = image_source,
+            kernel_src = detect_root(ctx.attr.kernel_src.files.to_list()),
+            image = image.path,
+            modinfo = modinfo.path,
+            modules = modules.path,
+            target_arch = ctx.attr._target_arch[TargetInfo].value,
+        ),
         use_default_shell_env = True,
     )
 
@@ -161,7 +173,7 @@
 
 linux_image = rule(
     doc = """
-        Build Linux kernel image unhermetically in a given format.
+        Build Linux kernel image hermetically in a given format.
     """,
     implementation = _linux_image_impl,
     cfg = ignore_unused_configuration,
@@ -176,7 +188,6 @@
             doc = """
                 Filegroup containing Linux kernel sources.
             """,
-            default = "@linux//:all",
         ),
         "image_format": attr.string(
             doc = """
@@ -188,20 +199,40 @@
             ],
             default = "Image",
         ),
+        "_libelf": attr.label(
+            default = "//third_party:libelf_elf",
+            cfg = "exec",
+        ),
+        "_zstd": attr.label(
+            default = "//third_party:zstd_zstd",
+            cfg = "exec",
+        ),
+        "_zlib": attr.label(
+            default = "//third_party:zlib_z",
+            cfg = "exec",
+        ),
+        "_ssl": attr.label(
+            default = "//third_party:openssl_ssl",
+            cfg = "exec",
+        ),
+        "_crypto": attr.label(
+            default = "//third_party:openssl_crypto",
+            cfg = "exec",
+        ),
         "_allowlist_function_transition": attr.label(
             default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
         ),
-        # Bazel doesn't let you access the target platform directly, use these
-        "_constraint_x86_64": attr.label(
-            default = "@platforms//cpu:x86_64",
+        "_exec_toolchain": attr.label(
+            default = "@rules_cc//cc:current_cc_toolchain",
+            cfg = "exec",
         ),
-        "_constraint_aarch64": attr.label(
-            default = "@platforms//cpu:aarch64",
+        "_image_name": attr.label(
+            default = "//third_party/linux:image_name",
         ),
-        "_constraint_riscv64": attr.label(
-            default = "@platforms//cpu:riscv64",
+        "_target_arch": attr.label(
+            default = "//third_party/linux:target_arch",
         ),
     } | CC_TOOLCHAIN_ATTRS,
-    toolchains = use_cc_toolchain(),
     fragments = ["cpp"],
+    toolchains = TOOLCHAINS + use_cc_toolchain(),
 )