Generate rules/cpp/cc_configure.WORKSPACE from distdir_deps.bzl rather than keeping it in sync by hand

This makes it possible for the versions of repositories used in the distributed Bazel tests to align with the versions used at Bazel build time without updating files scattered throughout the source and test trees.

- Add //distdir_deps.bzl%gen_workspace_stanza rule to generate WORKSPACE rules for libraries in DISTDIR_DEPS.
- Apply the new capability to rules_cc.
- move dist_http_archive from distdir_deps.bzl to distdir.bzl so that all the repository time rules are distinct from the build time rules.

This finalizes the structural changes needed for issue #12081. The remaining part is applying the same pattern for each of the dependencies.

Closes #12743.

PiperOrigin-RevId: 350527359
diff --git a/BUILD b/BUILD
index c3867d5..3bbe99f 100644
--- a/BUILD
+++ b/BUILD
@@ -113,7 +113,10 @@
 # be included in //src:derived_java_sources).
 filegroup(
     name = "generated_resources",
-    srcs = ["//src/main/java/com/google/devtools/build/lib/bazel/rules:builtins_bzl.zip"],
+    srcs = [
+        "//src/main/java/com/google/devtools/build/lib/bazel/rules:builtins_bzl.zip",
+        "//src/main/java/com/google/devtools/build/lib/bazel/rules/cpp:cc_configure.WORKSPACE",
+    ],
 )
 
 pkg_tar(
diff --git a/WORKSPACE b/WORKSPACE
index e04b456..88c85c5 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -1,8 +1,8 @@
 workspace(name = "io_bazel")
 
 load("//tools/build_defs/repo:http.bzl", "http_archive", "http_file")
-load("//:distdir.bzl", "distdir_tar")
-load("//:distdir_deps.bzl", "DIST_DEPS", "dist_http_archive")
+load("//:distdir.bzl", "distdir_tar", "dist_http_archive")
+load("//:distdir_deps.bzl", "DIST_DEPS")
 
 # These can be used as values for the patch_cmds and patch_cmds_win attributes
 # of http_archive, in order to export the WORKSPACE file from the BUILD or
@@ -474,14 +474,10 @@
 )
 
 # This must be kept in sync with src/main/java/com/google/devtools/build/lib/bazel/rules/java/jdk.WORKSPACE.
-# This must be kept in sync with src/main/java/com/google/devtools/build/lib/bazel/rules/cpp/cc_configure.WORKSPACE.
-http_archive(
+dist_http_archive(
     name = "rules_cc",
     patch_cmds = EXPORT_WORKSPACE_IN_BUILD_FILE,
     patch_cmds_win = EXPORT_WORKSPACE_IN_BUILD_FILE_WIN,
-    sha256 = DIST_DEPS["rules_cc"]["sha256"],
-    strip_prefix = DIST_DEPS["rules_cc"]["strip_prefix"],
-    urls = DIST_DEPS["rules_cc"]["urls"],
 )
 
 http_archive(
diff --git a/distdir.bzl b/distdir.bzl
index 60b8678..3178180 100644
--- a/distdir.bzl
+++ b/distdir.bzl
@@ -13,6 +13,9 @@
 # limitations under the License.
 """Defines a repository rule that generates an archive consisting of the specified files to fetch"""
 
+load("//:distdir_deps.bzl", "DIST_DEPS")
+load("//tools/build_defs/repo:http.bzl", "http_archive")
+
 _BUILD = """
 load("@rules_pkg//:pkg.bzl", "pkg_tar")
 
@@ -70,3 +73,28 @@
         urls = urls,
         dirname = dirname,
     )
+
+def dist_http_archive(name, **kwargs):
+    """Wraps http_archive but takes sha and urls from DIST_DEPS.
+
+    dist_http_archive wraps an http_archive invocation, but looks up relevant
+    information from DIST_DEPS so the user does not have to specify it. It
+    always strips sha256 and urls from kwargs.
+
+    Args:
+      name: repo name
+      **kwargs: see http_archive for allowed args.
+    """
+    info = DIST_DEPS[name]
+    if "patch_args" not in kwargs:
+        kwargs["patch_args"] = info.get("patch_args")
+    if "patches" not in kwargs:
+        kwargs["patches"] = info.get("patches")
+    if "strip_prefix" not in kwargs:
+        kwargs["strip_prefix"] = info.get("strip_prefix")
+    http_archive(
+        name = name,
+        sha256 = info["sha256"],
+        urls = info["urls"],
+        **kwargs
+    )
diff --git a/distdir_deps.bzl b/distdir_deps.bzl
index b0a4dc6..1c3d899 100644
--- a/distdir_deps.bzl
+++ b/distdir_deps.bzl
@@ -13,31 +13,6 @@
 # limitations under the License.
 """List the distribution dependencies we need to build Bazel."""
 
-load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
-
-def dist_http_archive(name, **kwargs):
-    """Wraps http_archive but takes sha and urls from DIST_DEPS.
-
-    dist_http_archive wraps an http_archive invocation, but looks up relevant
-    information from DIST_DEPS so the user does not have to specify it. It
-    always strips sha256 and urls from kwargs.
-
-    Args:
-      name: repo name
-      **kwargs: see http_archive for allowed args.
-    """
-    info = DIST_DEPS[name]
-    if "patches" not in kwargs:
-        kwargs["patches"] = info.get("patches")
-    if "strip_prefix" not in kwargs:
-        kwargs["strip_prefix"] = info.get("strip_prefix")
-    http_archive(
-        name = name,
-        sha256 = info["sha256"],
-        urls = info["urls"],
-        **kwargs
-    )
-
 DIST_DEPS = {
     ########################################
     #
@@ -91,3 +66,66 @@
         ],
     },
 }
+
+def _gen_workspace_stanza_impl(ctx):
+    if ctx.attr.template and (ctx.attr.preamble or ctx.attr.postamble):
+        fail("Can not use template with either preamble or postamble")
+
+    repo_clause = """
+maybe(
+    http_archive,
+    "{repo}",
+    sha256 = "{sha256}",
+    strip_prefix = {strip_prefix},
+    urls = {urls},
+)
+"""
+    repo_stanzas = {}
+    for repo in ctx.attr.repos:
+        info = DIST_DEPS[repo]
+        strip_prefix = info.get("strip_prefix")
+        if strip_prefix:
+            strip_prefix = "\"%s\"" % strip_prefix
+        else:
+            strip_prefix = "None"
+
+        repo_stanzas["{%s}" % repo] = repo_clause.format(
+            repo = repo,
+            archive = info["archive"],
+            sha256 = str(info["sha256"]),
+            strip_prefix = strip_prefix,
+            urls = info["urls"],
+        )
+
+    if ctx.attr.template:
+        ctx.actions.expand_template(
+            output = ctx.outputs.out,
+            template = ctx.file.template,
+            substitutions = repo_stanzas,
+        )
+    else:
+        content = "\n".join([p.strip() for p in ctx.attr.preamble.strip().split("\n")])
+        content += "\n"
+        content += "".join(repo_stanzas.values())
+        content += "\n"
+        content += "\n".join([p.strip() for p in ctx.attr.postamble.strip().split("\n")])
+        content += "\n"
+        ctx.actions.write(ctx.outputs.out, content)
+
+    return [DefaultInfo(files = depset([ctx.outputs.out]))]
+
+gen_workspace_stanza = rule(
+    implementation = _gen_workspace_stanza_impl,
+    attrs = {
+        "repos": attr.string_list(doc = "Set of repos to inlcude"),
+        "out": attr.output(mandatory = True),
+        "preamble": attr.string(doc = "Preamble."),
+        "postamble": attr.string(doc = "setup rules to follow repos."),
+        "template": attr.label(
+            doc = "Template WORKSPACE file. May not be used with preable or postamble." +
+                  "Repo stanzas can be include with the syntax '{repo name}'.",
+            allow_single_file = True,
+            mandatory = False,
+        ),
+    },
+)
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/cpp/BUILD b/src/main/java/com/google/devtools/build/lib/bazel/rules/cpp/BUILD
index c51f851..9f4683e 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/cpp/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/cpp/BUILD
@@ -1,4 +1,5 @@
 load("@rules_java//java:defs.bzl", "java_library")
+load("//:distdir_deps.bzl", "gen_workspace_stanza")
 
 package(
     default_visibility = ["//src:__subpackages__"],
@@ -18,7 +19,7 @@
         ["*.java"],
         exclude = ["BazelCppSemantics.java"],
     ),
-    resources = glob(["*.WORKSPACE"]),
+    resources = [":cc_configure.WORKSPACE"],
     deps = [
         ":bazel_cpp_semantics",
         "//src/main/java/com/google/devtools/build/lib/actions:artifacts",
@@ -52,3 +53,18 @@
         "//third_party:guava",
     ],
 )
+
+gen_workspace_stanza(
+    name = "workspace_with_rules_cc",
+    out = "cc_configure.WORKSPACE",
+    postamble = """
+        cc_configure()
+    """,
+    preamble = """
+        load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+        load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
+        load("@bazel_tools//tools/cpp:cc_configure.bzl", "cc_configure")
+    """,
+    repos = ["rules_cc"],
+    visibility = ["//:__pkg__"],
+)
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/cpp/cc_configure.WORKSPACE b/src/main/java/com/google/devtools/build/lib/bazel/rules/cpp/cc_configure.WORKSPACE
deleted file mode 100644
index 6257a4c..0000000
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/cpp/cc_configure.WORKSPACE
+++ /dev/null
@@ -1,17 +0,0 @@
-load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
-load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
-load("@bazel_tools//tools/cpp:cc_configure.bzl", "cc_configure")
-
-# rules_cc is used in @bazel_tools//tools/cpp, so must be loaded here.
-maybe(
-    http_archive,
-    "rules_cc",
-    sha256 = "d0c573b94a6ef20ef6ff20154a23d0efcb409fb0e1ff0979cec318dfe42f0cdd",
-    strip_prefix = "rules_cc-b1c40e1de81913a3c40e5948f78719c28152486d",
-    urls = [
-        "https://mirror.bazel.build/github.com/bazelbuild/rules_cc/archive/b1c40e1de81913a3c40e5948f78719c28152486d.zip",
-        "https://github.com/bazelbuild/rules_cc/archive/b1c40e1de81913a3c40e5948f78719c28152486d.zip",
-    ],
-)
-
-cc_configure()