Embed @platforms into the Bazel binary

This PR implements the change discussed at https://docs.google.com/document/d/1EArrWYUDugqJzBcb0-OxY5BH1FFPYV3jxLIXbJpD9RY/edit?pli=1#heading=h.5mcn15i0e1ch.

Closes https://github.com/bazelbuild/bazel/pull/8600.
https://github.com/bazelbuild/bazel/issues/6516

This is encore of https://github.com/bazelbuild/bazel/commit/324dc44e6bafb487331724ae83d67bc18ed8a8aa with these changes:

* removing WORKSPACE file from the platforms_archive - repository rules always create new WORKSPACE file and that messes up the timestamps after re-archiving. This fixes the determinism test.
* after @aehlig kindly explained the semantics of external repositories, I discovered that overriding @platforms doesn't work when there is a load statement somewhere above the override, which is the common case. Therefore I had to move bundled platforms to the workspace suffix and had to use maybe pattern.
* because of maybe pattern I have to mock the bzl file in Bazel unit tests.

RELNOTES:
PiperOrigin-RevId: 253193463
diff --git a/BUILD b/BUILD
index 4aef01e..d68f3ca 100644
--- a/BUILD
+++ b/BUILD
@@ -78,6 +78,14 @@
     visibility = ["//:__subpackages__"],
 )
 
+pkg_tar(
+    name = "platforms-srcs",
+    srcs = ["@platforms//:srcs"],
+    package_dir = "platforms",
+    strip_prefix = ".",
+    visibility = ["//:__subpackages__"],
+)
+
 py_binary(
     name = "combine_distfiles",
     srcs = ["combine_distfiles.py"],
@@ -89,6 +97,7 @@
     name = "bazel-distfile",
     srcs = [
         ":bazel-srcs",
+        ":platforms-srcs",
         "//src:derived_java_srcs",
         "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec:bootstrap_autocodec.tar",
         "@additional_distfiles//:archives.tar",
@@ -104,6 +113,7 @@
     name = "bazel-distfile-tar",
     srcs = [
         ":bazel-srcs",
+        ":platforms-srcs",
         "//src:derived_java_srcs",
         "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec:bootstrap_autocodec.tar",
         "@additional_distfiles//:archives.tar",
diff --git a/WORKSPACE b/WORKSPACE
index 549e95d..d4766bc 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -117,6 +117,8 @@
         "8ccf4f1c351928b55d5dddf3672e3667f6978d60.tar.gz",
         "0.16.2.zip",
         "android_tools_pkg-0.6.tar.gz",
+        # bazelbuild/platforms
+        "441afe1bfdadd6236988e9cac159df6b5a9f5a98.zip",
     ],
     dirname = "derived/distdir",
     sha256 = {
@@ -130,6 +132,8 @@
         "8ccf4f1c351928b55d5dddf3672e3667f6978d60.tar.gz": "d868ce50d592ef4aad7dec4dd32ae68d2151261913450fac8390b3fd474bb898",
         "0.16.2.zip": "9b72bb0aea72d7cbcfc82a01b1e25bf3d85f791e790ddec16c65e2d906382ee0",
         "android_tools_pkg-0.6.tar.gz": "8231a3305ba5f75daed851930af3d7c2fb0f00cd5dfd397e26e44fdf295b237e", # built at ae148c75dd0e122b01ef5ce9dd90259e616d3b76
+        # bazelbuild/platforms
+        "441afe1bfdadd6236988e9cac159df6b5a9f5a98.zip": "a07fe5e75964361885db725039c2ba673f0ee0313d971ae4f50c9b18cd28b0b5",
     },
     urls = {
         "e0b0291b2c51fbe5a7cfa14473a1ae850f94f021.zip": [
@@ -167,6 +171,11 @@
         "android_tools_pkg-0.6.tar.gz": [
             "https://mirror.bazel.build/bazel_android_tools/android_tools_pkg-0.6.tar.gz",
         ],
+        # bazelbuild/platforms
+        "441afe1bfdadd6236988e9cac159df6b5a9f5a98.zip": [
+            "https://mirror.bazel.build/github.com/bazelbuild/platforms/archive/441afe1bfdadd6236988e9cac159df6b5a9f5a98.zip",
+            "https://github.com/bazelbuild/platforms/archive/441afe1bfdadd6236988e9cac159df6b5a9f5a98.zip",
+        ],
     },
 )
 
@@ -391,6 +400,8 @@
         "zulu11.29.3-ca-jdk11.0.2-macosx_x64.zip",
         "zulu11.29.3-ca-jdk11.0.2-win_x64.zip",
         "android_tools_pkg-0.6.tar.gz",
+        # bazelbuild/platforms
+        "441afe1bfdadd6236988e9cac159df6b5a9f5a98.zip",
     ],
     dirname = "test_WORKSPACE/distdir",
     sha256 = {
@@ -413,6 +424,8 @@
         "zulu11.29.3-ca-jdk11.0.2-macosx_x64.zip": "059f8e3484bf07b63a8f2820d5f528f473eff1befdb1896ee4f8ff06be3b8d8f",
         "zulu11.29.3-ca-jdk11.0.2-win_x64.zip": "e1f5b4ce1b9148140fae2fcfb8a96d1c9b7eac5b8df0e13fbcad9b8561284880",
         "android_tools_pkg-0.6.tar.gz": "8231a3305ba5f75daed851930af3d7c2fb0f00cd5dfd397e26e44fdf295b237e", # built at ae148c75dd0e122b01ef5ce9dd90259e616d3b76
+        # bazelbuild/platforms
+        "441afe1bfdadd6236988e9cac159df6b5a9f5a98.zip": "a07fe5e75964361885db725039c2ba673f0ee0313d971ae4f50c9b18cd28b0b5",
     },
     urls = {
         "zulu9.0.7.1-jdk9.0.7-linux_x64-allmodules.tar.gz": ["https://mirror.bazel.build/openjdk/azul-zulu-9.0.7.1-jdk9.0.7/zulu9.0.7.1-jdk9.0.7-linux_x64-allmodules.tar.gz"],
@@ -436,6 +449,11 @@
         "android_tools_pkg-0.6.tar.gz": [
             "https://mirror.bazel.build/bazel_android_tools/android_tools_pkg-0.6.tar.gz",
         ],
+        # bazelbuild/platforms
+        "441afe1bfdadd6236988e9cac159df6b5a9f5a98.zip": [
+            "https://mirror.bazel.build/github.com/bazelbuild/platforms/archive/441afe1bfdadd6236988e9cac159df6b5a9f5a98.zip",
+            "https://github.com/bazelbuild/platforms/archive/441afe1bfdadd6236988e9cac159df6b5a9f5a98.zip",
+        ],
     },
 )
 
@@ -497,6 +515,16 @@
 )
 
 http_archive(
+    name = "platforms",
+    sha256 = "a07fe5e75964361885db725039c2ba673f0ee0313d971ae4f50c9b18cd28b0b5",
+    urls = [
+        "https://mirror.bazel.build/github.com/bazelbuild/platforms/archive/441afe1bfdadd6236988e9cac159df6b5a9f5a98.zip",
+        "https://github.com/bazelbuild/platforms/archive/441afe1bfdadd6236988e9cac159df6b5a9f5a98.zip",
+    ],
+    strip_prefix = "platforms-441afe1bfdadd6236988e9cac159df6b5a9f5a98"
+)
+
+http_archive(
     name = "java_tools_langtools_javac12",
     sha256 = "99b107105165a91df82cd7cf82a8efb930d803fb7de1663cf7f780142104cd14",
     urls = [
diff --git a/scripts/bootstrap/compile.sh b/scripts/bootstrap/compile.sh
index 069ee91..90782f9 100755
--- a/scripts/bootstrap/compile.sh
+++ b/scripts/bootstrap/compile.sh
@@ -289,6 +289,9 @@
 ARCHIVE_DIR=${OUTPUT_DIR}/archive
 mkdir -p ${ARCHIVE_DIR}/_embedded_binaries
 
+# Prepare @platforms local repository
+link_dir ${PWD}/platforms ${ARCHIVE_DIR}/_embedded_binaries/platforms
+
 # Dummy build-runfiles (we can't compile C++ yet, so we can't have the real one)
 if [ "${PLATFORM}" = "windows" ]; then
   # We don't rely on runfiles trees on Windows
diff --git a/src/BUILD b/src/BUILD
index c0e70b8..e7c6827 100644
--- a/src/BUILD
+++ b/src/BUILD
@@ -328,10 +328,11 @@
     name = "package-zip" + suffix,
     srcs = ([":embedded_tools" + suffix + ".zip"] if embed else []) + [
         # The script assumes that the embedded tools zip (if exists) is the
-        # first item here, the deploy jar the second and install base key is the
-        # third
+        # first item here, the deploy jar the second, install base key is the
+        # third, and platforms archive is the fourth.
         "//src/main/java/com/google/devtools/build/lib:bazel/BazelServer_deploy.jar",
         "install_base_key" + suffix,
+        ":platforms_archive",
         ":libunix",
         "//src/main/tools:build-runfiles",
         "//src/main/tools:process-wrapper",
@@ -355,6 +356,15 @@
     ("_nojdk", True),
 ]]
 
+genrule(
+    name = "platforms_archive",
+    srcs = ["@platforms//:srcs"],
+    outs = ["platforms.zip"],
+    # Removing the WORKSPACE file since local_repository creates it no matter what and leaving
+    # it there would make the zip nondeterministic.
+    cmd = "zip -qX $@ $$(echo $(SRCS) | xargs -n 1 | grep -v '.*/WORKSPACE$$' | sort | xargs)",
+)
+
 [genrule(
     name = "bazel-bin" + suffix,
     srcs = [
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/local_config_platform.WORKSPACE b/src/main/java/com/google/devtools/build/lib/bazel/repository/local_config_platform.WORKSPACE
index d0ce5b7..6793501 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/local_config_platform.WORKSPACE
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/local_config_platform.WORKSPACE
@@ -1 +1,12 @@
-local_config_platform(name = "local_config_platform")
+load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
+
+maybe(
+    local_repository,
+    "platforms",
+    path = __embedded_dir__ + "/platforms",
+)
+
+maybe(
+    local_config_platform,
+    "local_config_platform",
+)
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java
index d1de9db..1d5a322 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProvider.java
@@ -406,7 +406,7 @@
           builder.addRuleDefinition(new LocalConfigPlatformRule());
 
           try {
-            builder.addWorkspaceFilePrefix(
+            builder.addWorkspaceFileSuffix(
                 ResourceFileLoader.loadResource(
                     LocalConfigPlatformRule.class, "local_config_platform.WORKSPACE"));
           } catch (IOException e) {
diff --git a/src/package-bazel.sh b/src/package-bazel.sh
index 632b133..0ebefed 100755
--- a/src/package-bazel.sh
+++ b/src/package-bazel.sh
@@ -16,16 +16,15 @@
 
 set -euo pipefail
 
-# This script bootstraps building a Bazel binary without Bazel then
-# use this compiled Bazel to bootstrap Bazel itself. It can also
-# be provided with a previous version of Bazel to bootstrap Bazel
-# itself.
+# This script creates the Bazel archive that Bazel client unpacks and then
+# starts the server from.
 
 WORKDIR=$(pwd)
 OUT=$1
 EMBEDDED_TOOLS=$2
 DEPLOY_JAR=$3
 INSTALL_BASE_KEY=$4
+PLATFORMS_ARCHIVE=$5
 shift 4
 
 TMP_DIR=${TMPDIR:-/tmp}
@@ -66,4 +65,16 @@
   (cd ${PACKAGE_DIR}/embedded_tools && unzip -q "${WORKDIR}/${EMBEDDED_TOOLS}")
 fi
 
+# Unzip platforms.zip into platforms/, move files up from external/platforms
+# subdirectory, and cleanup after itself.
+( \
+  cd ${PACKAGE_DIR} && \
+    unzip -q -d platforms platforms.zip && \
+    rm platforms.zip && \
+    cd platforms && \
+    mv external/platforms/* . && \
+    rmdir -p external/platforms \
+)
+touch -t 198001010000.00 ${PACKAGE_DIR}/platforms/WORKSPACE
+
 (cd ${PACKAGE_DIR} && find . -type f | sort | zip -q9DX@ "${WORKDIR}/${OUT}")
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java b/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java
index f6cae4e..edf34a1 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/mock/BazelAnalysisMock.java
@@ -383,6 +383,11 @@
     config.create("/bazel_tools_workspace/WORKSPACE", "workspace(name = 'bazel_tools')");
     config.create("/bazel_tools_workspace/tools/build_defs/repo/BUILD");
     config.create(
+        "/bazel_tools_workspace/tools/build_defs/repo/utils.bzl",
+        "def maybe(repo_rule, name, **kwargs):",
+        "  if name not in native.existing_rules():",
+        "    repo_rule(name = name, **kwargs)");
+    config.create(
         "/bazel_tools_workspace/tools/build_defs/repo/http.bzl",
         "def http_archive(**kwargs):",
         "  pass",
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
index 834d8ff..aa7d269 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/BuildViewTestCase.java
@@ -238,6 +238,11 @@
     mockToolsConfig.create("/bazel_tools_workspace/WORKSPACE", "workspace(name = 'bazel_tools')");
     mockToolsConfig.create("/bazel_tools_workspace/tools/build_defs/repo/BUILD");
     mockToolsConfig.create(
+        "/bazel_tools_workspace/tools/build_defs/repo/utils.bzl",
+        "def maybe(repo_rule, name, **kwargs):",
+        "  if name not in native.existing_rules():",
+        "    repo_rule(name = name, **kwargs)");
+    mockToolsConfig.create(
         "/bazel_tools_workspace/tools/build_defs/repo/http.bzl",
         "def http_archive(**kwargs):",
         "  pass",
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/ConfigurationTestCase.java b/src/test/java/com/google/devtools/build/lib/analysis/util/ConfigurationTestCase.java
index 8122469..ea96da0 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/ConfigurationTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/ConfigurationTestCase.java
@@ -114,6 +114,11 @@
     mockToolsConfig.create("/bazel_tools_workspace/WORKSPACE", "workspace(name = 'bazel_tools')");
     mockToolsConfig.create("/bazel_tools_workspace/tools/build_defs/repo/BUILD");
     mockToolsConfig.create(
+        "/bazel_tools_workspace/tools/build_defs/repo/utils.bzl",
+        "def maybe(repo_rule, name, **kwargs):",
+        "  if name not in native.existing_rules():",
+        "    repo_rule(name = name, **kwargs)");
+    mockToolsConfig.create(
         "/bazel_tools_workspace/tools/build_defs/repo/http.bzl",
         "def http_archive(**kwargs):",
         "  pass",
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/packages/BazelPackageLoaderTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/packages/BazelPackageLoaderTest.java
index a3370c1..49b4148 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/packages/BazelPackageLoaderTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/packages/BazelPackageLoaderTest.java
@@ -86,6 +86,11 @@
         "",
         "def http_file(**kwargs):",
         "  pass");
+    FileSystemUtils.writeIsoLatin1(
+        tools.getRelative("tools/build_defs/repo/utils.bzl"),
+        "def maybe(repo_rule, name, **kwargs):",
+        "  if name not in native.existing_rules():",
+        "    repo_rule(name = name, **kwargs)");
   }
 
   private void fetchExternalRepo(RepositoryName externalRepo) {
diff --git a/src/test/shell/bazel/BUILD b/src/test/shell/bazel/BUILD
index 1a773ce..e085eee 100644
--- a/src/test/shell/bazel/BUILD
+++ b/src/test/shell/bazel/BUILD
@@ -1104,6 +1104,13 @@
 )
 
 sh_test(
+    name = "platforms_test",
+    srcs = ["platforms_test.sh"],
+    data = [":test-deps"],
+    deps = ["@bazel_tools//tools/bash/runfiles"],
+)
+
+sh_test(
     name = "platform_mapping_test",
     srcs = ["platform_mapping_test.sh"],
     data = [":test-deps"],
diff --git a/src/test/shell/bazel/platforms_test.sh b/src/test/shell/bazel/platforms_test.sh
new file mode 100755
index 0000000..6db625a
--- /dev/null
+++ b/src/test/shell/bazel/platforms_test.sh
@@ -0,0 +1,75 @@
+#!/bin/bash
+#
+# Copyright 2019 The Bazel Authors. All rights reserved.
+#
+# 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.
+#
+# Test related to @platforms embedded repository
+#
+
+# --- begin runfiles.bash initialization ---
+set -euo pipefail
+if [[ ! -d "${RUNFILES_DIR:-/dev/null}" && ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
+  if [[ -f "$0.runfiles_manifest" ]]; then
+    export RUNFILES_MANIFEST_FILE="$0.runfiles_manifest"
+  elif [[ -f "$0.runfiles/MANIFEST" ]]; then
+    export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST"
+  elif [[ -f "$0.runfiles/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
+    export RUNFILES_DIR="$0.runfiles"
+  fi
+fi
+if [[ -f "${RUNFILES_DIR:-/dev/null}/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then
+  source "${RUNFILES_DIR}/bazel_tools/tools/bash/runfiles/runfiles.bash"
+elif [[ -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then
+  source "$(grep -m1 "^bazel_tools/tools/bash/runfiles/runfiles.bash " \
+            "$RUNFILES_MANIFEST_FILE" | cut -d ' ' -f 2-)"
+else
+  echo >&2 "ERROR: cannot find @bazel_tools//tools/bash/runfiles:runfiles.bash"
+  exit 1
+fi
+# --- end runfiles.bash initialization ---
+
+source "$(rlocation "io_bazel/src/test/shell/integration_test_setup.sh")" \
+  || { echo "integration_test_setup.sh not found!" >&2; exit 1; }
+
+function test_platforms_repository_builds_itself() {
+  # We test that a built-in @platforms repository is buildable.
+  bazel build @platforms//:all &> $TEST_log \
+      || fail "Build failed unexpectedly"
+}
+
+function test_platforms_can_be_overridden() {
+  # We test that a custom repository can override @platforms in their
+  # WORKSPACE file.
+  mkdir -p platforms_can_be_overridden || fail "couldn't create directory"
+  touch platforms_can_be_overridden/BUILD || \ fail "couldn't touch BUILD file"
+  cat > platforms_can_be_overridden/WORKSPACE <<EOF
+local_repository(
+  name = 'platforms',
+  path = '../override',
+)
+EOF
+
+  mkdir -p override || fail "couldn't create override directory"
+  touch override/WORKSPACE || fail "couldn't touch override/WORKSPACE"
+  cat > override/BUILD <<EOF
+filegroup(name = 'yolo')
+EOF
+
+  cd platforms_can_be_overridden || fail "couldn't cd into workspace"
+  bazel build @platforms//:yolo &> $TEST_log || \
+    fail "Bazel failed to build @platforms"
+}
+
+run_suite "platform mapping test"
+
diff --git a/src/test/shell/integration/discard_graph_edges_test.sh b/src/test/shell/integration/discard_graph_edges_test.sh
index aab82d9..926dc79 100755
--- a/src/test/shell/integration/discard_graph_edges_test.sh
+++ b/src/test/shell/integration/discard_graph_edges_test.sh
@@ -271,7 +271,7 @@
   package_count="$(extract_histogram_count "$histo_file" \
       'devtools\.build\.lib\..*\.Package$')"
   # A few packages aren't cleared.
-  [[ "$package_count" -le 16 ]] \
+  [[ "$package_count" -le 17 ]] \
       || fail "package count $package_count too high"
   glob_count="$(extract_histogram_count "$histo_file" "GlobValue$")"
   [[ "$glob_count" -le 1 ]] \
diff --git a/tools/build_defs/repo/utils.bzl b/tools/build_defs/repo/utils.bzl
index 5521137..c38b045 100644
--- a/tools/build_defs/repo/utils.bzl
+++ b/tools/build_defs/repo/utils.bzl
@@ -117,3 +117,20 @@
     result["name"] = orig.name
     result.update(override)
     return result
+
+def maybe(repo_rule, name, **kwargs):
+    """Utility function for only adding a repository if it's not already present.
+
+    This is to implement safe repositories.bzl macro documented in
+    https://docs.bazel.build/versions/master/skylark/deploying.html#dependencies.
+
+    Args:
+        repo_rule: repository rule function.
+        name: name of the repository to create.
+        **kwargs: remaining arguments that are passed to the repo_rule function.
+
+    Returns:
+        Nothing, defines the repository when needed as a side-effect.
+    """
+    if name not in native.existing_rules():
+        repo_rule(name = name, **kwargs)