Defer some preflight checks to @portage generation

Currently our preflight checks run whenever the user runs bazel, but
some checks are relevant only when the user generates @portage. This
patch moves them to @portage generation.

BUG=b:339769898
TEST=bazel build @alchemist//:alchemist
TEST=bazel build @portage//target/virtual/target-os  # fails
TEST=ALCHEMY_EXPERIMENTAL_OUTSIDE_CHROOT=1 bazel build \
     @portage//target/virtual/target-os  # passes

Change-Id: Id25ef752a6470af0443849112f6dc4c4007b2200
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/bazel/+/5526388
Commit-Queue: Raul Rangel <rrangel@chromium.org>
Tested-by: Shuhei Takahashi <nya@chromium.org>
Reviewed-by: Raul Rangel <rrangel@chromium.org>
diff --git a/module_extensions/portage/extension.bzl b/module_extensions/portage/extension.bzl
index 17c273c..7dac72e 100644
--- a/module_extensions/portage/extension.bzl
+++ b/module_extensions/portage/extension.bzl
@@ -16,9 +16,13 @@
 load("//bazel/portage/bin/alchemist/src/bin/alchemist:repo_rule_srcs.bzl", "ALCHEMIST_REPO_RULE_SRCS")
 load("//bazel/portage/repo_defs/chrome:cros_chrome_repository.bzl", _cros_chrome_repository = "cros_chrome_repository")
 load("//bazel/repo_defs:nested_bazel.bzl", "nested_bazel")
+load("//bazel/repo_defs:preflight_checks.bzl", "portage_preflight_checks")
 load("//bazel/repo_defs:repo_repository.bzl", _repo_repository = "repo_repository")
 
 def _portage_impl(module_ctx):
+    portage_preflight_checks(
+        name = "portage_preflight_checks",
+    )
     nested_bazel(
         name = "alchemist",
         target = "//bazel/portage/bin/alchemist/src/bin/alchemist",
@@ -27,6 +31,7 @@
     portage_digest(
         name = "portage_digest",
         alchemist = "@alchemist//:alchemist",
+        preflight_checks_ok = "@portage_preflight_checks//:ok.bzl",
     )
     remoteexec_info(
         name = "remoteexec_info",
diff --git a/module_extensions/portage/portage_digest.bzl b/module_extensions/portage/portage_digest.bzl
index a500fa3..c689f53 100644
--- a/module_extensions/portage/portage_digest.bzl
+++ b/module_extensions/portage/portage_digest.bzl
@@ -8,9 +8,10 @@
 
 def _portage_digest_repository_impl(repo_ctx):
     """Repository rule to generate a digest of the boards overlays."""
-    repo_ctx.path(repo_ctx.attr._cache_bust)
 
     # Keep all the ctx.path calls first to avoid expensive restarts
+    repo_ctx.path(repo_ctx.attr._cache_bust)
+    repo_ctx.path(repo_ctx.attr.preflight_checks_ok)
     alchemist = repo_ctx.path(repo_ctx.attr.alchemist)
 
     # --source-dir needs the repo root, not just the `src` directory
@@ -58,6 +59,7 @@
     ],
     attrs = dict(
         alchemist = attr.label(allow_single_file = True),
+        preflight_checks_ok = attr.label(allow_single_file = True),
         _cache_bust = attr.label(
             # We need to point to the actual file, since we can't use a file
             # group from a repository rule.
diff --git a/repo_defs/preflight_checks.bzl b/repo_defs/preflight_checks.bzl
index 63ea616..5776c6a 100644
--- a/repo_defs/preflight_checks.bzl
+++ b/repo_defs/preflight_checks.bzl
@@ -19,28 +19,15 @@
         "https://chromium.googlesource.com/chromiumos/bazel/+/HEAD/README.md",
     )
 
-def _do_preflight_checks(repo_ctx):
+def _do_basic_preflight_checks(repo_ctx):
     # Skip all preflight checks for nested bazel.
     if repo_ctx.os.environ.get("IS_NESTED_BAZEL") == "1":
         return
 
-    # Ensure we're inside the CrOS chroot.
-    if repo_ctx.os.environ.get("ALCHEMY_EXPERIMENTAL_OUTSIDE_CHROOT") != "1":
-        if not repo_ctx.path("/etc/cros_chroot_version").exists:
-            _misconfiguration_fail("Bazel was run outside CrOS chroot.")
-
     # Ensure we're invoked via the wrapper script.
     if repo_ctx.os.environ.get("CHROMITE_BAZEL_WRAPPER") != "1":
         _misconfiguration_fail("Bazel was run without the proper wrapper.")
 
-    # Ensure third_party/llvm-project is checked out.
-    llvm_path = repo_ctx.workspace_root.get_child("third_party/llvm-project")
-    if not llvm_path.exists:
-        _misconfiguration_fail(
-            "third_party/llvm-project is not checked out.\n" +
-            "Did you run `repo init` with `-g default,bazel`?",
-        )
-
     # Ensure the execroot is not overlayfs.
     result = repo_ctx.execute(["stat", "--format=%T", "--file-system", "."], timeout = 10)
     if result.return_code != 0:
@@ -50,31 +37,68 @@
     if result.stdout.strip() == "overlayfs":
         _misconfiguration_fail("The execroot must not be overlayfs.")
 
-def _preflight_checks_impl(repo_ctx):
-    """Performs preflight checks."""
+def _basic_preflight_checks_impl(repo_ctx):
+    """Performs basic preflight checks."""
 
-    _do_preflight_checks(repo_ctx)
+    _do_basic_preflight_checks(repo_ctx)
 
     # Create an empty repository.
     repo_ctx.file("BUILD.bazel", "")
     repo_ctx.file("ok.bzl", "ok = True")
 
-preflight_checks = repository_rule(
-    implementation = _preflight_checks_impl,
+basic_preflight_checks = repository_rule(
+    implementation = _basic_preflight_checks_impl,
     attrs = {},
     environ = [
-        "ALCHEMY_EXPERIMENTAL_OUTSIDE_CHROOT",
         "CHROMITE_BAZEL_WRAPPER",
         "IS_NESTED_BAZEL",
     ],
     local = True,
     doc = """
-    Performs preflight checks to provide friendly diagnostics to users.
+    Performs basic preflight checks to provide friendly diagnostics to users.
 
     This rule should be called at the very beginning of WORKSPACE.bazel.
     """,
 )
 
+def _do_portage_preflight_checks(repo_ctx):
+    # Ensure we're inside the CrOS chroot.
+    if repo_ctx.os.environ.get("ALCHEMY_EXPERIMENTAL_OUTSIDE_CHROOT") != "1":
+        if not repo_ctx.path("/etc/cros_chroot_version").exists:
+            _misconfiguration_fail("Bazel was run outside CrOS chroot.")
+
+    # Ensure third_party/llvm-project is checked out.
+    llvm_path = repo_ctx.workspace_root.get_child("third_party/llvm-project")
+    if not llvm_path.exists:
+        _misconfiguration_fail(
+            "third_party/llvm-project is not checked out.\n" +
+            "Did you run `repo init` with `-g default,bazel`?",
+        )
+
+def _portage_preflight_checks_impl(repo_ctx):
+    """Performs preflight checks before generating @portage."""
+
+    _do_portage_preflight_checks(repo_ctx)
+
+    # Create an empty repository.
+    repo_ctx.file("BUILD.bazel", "")
+    repo_ctx.file("ok.bzl", "ok = True")
+
+portage_preflight_checks = repository_rule(
+    implementation = _portage_preflight_checks_impl,
+    attrs = {},
+    environ = [
+        "ALCHEMY_EXPERIMENTAL_OUTSIDE_CHROOT",
+    ],
+    local = True,
+    doc = """
+    Performs preflight checks before generating @portage to provide friendly
+    diagnostics to users.
+
+    This rule should be called in the module extension generating @portage.
+    """,
+)
+
 def _symlinks_unavailable_impl(_repo_ctx):
     _misconfiguration_fail("Bazel was run without the proper wrapper.")
 
diff --git a/workspace_root/alchemy/WORKSPACE.bazel b/workspace_root/alchemy/WORKSPACE.bazel
index 921df67..175f847 100644
--- a/workspace_root/alchemy/WORKSPACE.bazel
+++ b/workspace_root/alchemy/WORKSPACE.bazel
@@ -6,8 +6,8 @@
 # Instead, modify src/MODULE.bazel and src/bazel/module_extensions/...
 # Preflight checks are the only exception because we want to run them eagerly.
 
-load("//bazel/repo_defs:preflight_checks.bzl", "preflight_checks")
+load("//bazel/repo_defs:preflight_checks.bzl", "basic_preflight_checks")
 
-preflight_checks(name = "preflight_checks")
+basic_preflight_checks(name = "basic_preflight_checks")
 
-load("@preflight_checks//:ok.bzl", "ok")  # @unused
+load("@basic_preflight_checks//:ok.bzl", "ok")  # @unused
diff --git a/workspace_root/general/MODULE.bazel b/workspace_root/general/MODULE.bazel
index 4eddf12..2c0220d 100644
--- a/workspace_root/general/MODULE.bazel
+++ b/workspace_root/general/MODULE.bazel
@@ -176,7 +176,7 @@
 use_repo(cros_deps, "alpine-minirootfs", "chromite", "depot_tools", "zstd")
 
 portage = use_extension("//bazel/module_extensions/portage:extension.bzl", "portage")
-use_repo(portage, "alchemist", "portage", "portage_digest", "remoteexec_info")
+use_repo(portage, "alchemist", "portage", "portage_digest", "portage_preflight_checks", "remoteexec_info")
 
 portage_deps = use_extension("//bazel/module_extensions/portage:extension.bzl", "portage_deps")
 use_repo(portage_deps, "portage_deps")