bash: Add a toolchain for local Bash.

Bazel automatically detects the local Bash and
creates a custom toolchain rule for it.

Later, rules that use Bash will require this
toolchain and retrieve Bash's path from it instead
of relying on hardcoded paths or the
`--shell_executable` flag.

See https://github.com/bazelbuild/bazel/issues/4319

Change-Id: Idd8242a20d202b1f5a56cddac95b625c6c08ede9

Closes #4980.

Change-Id: Ic2406a4da260b284e15852070d58472ca18340af
PiperOrigin-RevId: 193022708
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRulesModule.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRulesModule.java
index 8594e633..865a521 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRulesModule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelRulesModule.java
@@ -18,6 +18,7 @@
 import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
 import com.google.devtools.build.lib.analysis.config.BuildOptions;
 import com.google.devtools.build.lib.bazel.rules.cpp.BazelCppRuleClasses;
+import com.google.devtools.build.lib.bazel.rules.sh.BazelShRuleClasses;
 import com.google.devtools.build.lib.rules.cpp.FdoSupportFunction;
 import com.google.devtools.build.lib.rules.cpp.FdoSupportValue;
 import com.google.devtools.build.lib.runtime.BlazeModule;
@@ -41,6 +42,8 @@
           ResourceFileLoader.loadResource(BazelCppRuleClasses.class, "cc_configure.WORKSPACE"));
       builder.addWorkspaceFileSuffix(
           ResourceFileLoader.loadResource(BazelRulesModule.class, "xcode_configure.WORKSPACE"));
+      builder.addWorkspaceFileSuffix(
+          ResourceFileLoader.loadResource(BazelShRuleClasses.class, "sh_configure.WORKSPACE"));
     } catch (IOException e) {
       throw new IllegalStateException(e);
     }
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/sh/sh_configure.WORKSPACE b/src/main/java/com/google/devtools/build/lib/bazel/rules/sh/sh_configure.WORKSPACE
new file mode 100644
index 0000000..c41037c
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/sh/sh_configure.WORKSPACE
@@ -0,0 +1,2 @@
+load("@bazel_tools//tools/sh:sh_configure.bzl", "sh_configure")
+sh_configure()
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 9fee906..b31f2ac 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
@@ -70,6 +70,11 @@
         tools.getRelative("tools/osx/xcode_configure.bzl"),
         "def xcode_configure(*args, **kwargs):",
         "    pass");
+    FileSystemUtils.writeIsoLatin1(tools.getRelative("tools/sh/BUILD"), "");
+    FileSystemUtils.writeIsoLatin1(
+        tools.getRelative("tools/sh/sh_configure.bzl"),
+        "def sh_configure(*args, **kwargs):",
+        "    pass");
   }
 
   private void fetchExternalRepo(RepositoryName externalRepo) {
diff --git a/src/test/shell/integration/discard_graph_edges_test.sh b/src/test/shell/integration/discard_graph_edges_test.sh
index b64a62b..d2da4cf 100755
--- a/src/test/shell/integration/discard_graph_edges_test.sh
+++ b/src/test/shell/integration/discard_graph_edges_test.sh
@@ -231,7 +231,7 @@
   package_count="$(extract_histogram_count "$histo_file" \
       'devtools\.build\.lib\..*\.Package$')"
   # A few packages aren't cleared.
-  [[ "$package_count" -le 8 ]] \
+  [[ "$package_count" -le 10 ]] \
       || fail "package count $package_count too high"
   glob_count="$(extract_histogram_count "$histo_file" "GlobValue$")"
   [[ "$glob_count" -le 1 ]] \
diff --git a/tools/BUILD b/tools/BUILD
index 21a95fc..0b5835a 100644
--- a/tools/BUILD
+++ b/tools/BUILD
@@ -31,6 +31,7 @@
         "//tools/test:srcs",
         "//tools/python:srcs",
         "//tools/runfiles:srcs",
+        "//tools/sh:srcs",
         "//tools/whitelists:srcs",
         "//tools/zip:srcs",
     ],
@@ -62,6 +63,7 @@
         "//tools/test:srcs",
         "//tools/osx/crosstool:srcs",
         "//tools/osx:srcs",
+        "//tools/sh:embedded_tools",
         "//tools/whitelists:srcs",
         "//tools/zip:srcs",
     ],
diff --git a/tools/sh/BUILD b/tools/sh/BUILD
new file mode 100644
index 0000000..66f710a
--- /dev/null
+++ b/tools/sh/BUILD
@@ -0,0 +1,23 @@
+package(default_visibility = ["//visibility:private"])
+
+filegroup(
+    name = "srcs",
+    srcs = glob(
+        ["**"],
+        exclude = [
+            "*~",
+            ".*",
+        ],
+    ),
+    visibility = ["//tools:__pkg__"],
+)
+
+filegroup(
+    name = "embedded_tools",
+    srcs = [
+        "BUILD.tools",
+        "sh_configure.bzl",
+        "sh_toolchain.bzl",
+    ],
+    visibility = ["//tools:__pkg__"],
+)
diff --git a/tools/sh/BUILD.tools b/tools/sh/BUILD.tools
new file mode 100644
index 0000000..d5d9d48
--- /dev/null
+++ b/tools/sh/BUILD.tools
@@ -0,0 +1,6 @@
+exports_files(["sh_toolchain.bzl", "sh_configure.bzl"])
+
+toolchain_type(
+    name = "toolchain_type",
+    visibility = ["//visibility:public"],
+)
diff --git a/tools/sh/sh_configure.bzl b/tools/sh/sh_configure.bzl
new file mode 100644
index 0000000..7ba056c
--- /dev/null
+++ b/tools/sh/sh_configure.bzl
@@ -0,0 +1,94 @@
+# Copyright 2018 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.
+"""Configure the shell toolchain on the local machine."""
+
+def _is_windows(repository_ctx):
+  """Returns true if the host OS is Windows."""
+  return repository_ctx.os.name.startswith("windows")
+
+def _sh_config_impl(repository_ctx):
+  """sh_config rule implementation.
+
+  Detects the path of the shell interpreter on the local machine and
+  stores it in a sh_toolchain rule.
+
+  Args:
+    repository_ctx: the repository rule context object
+  """
+  sh_path = repository_ctx.os.environ.get("BAZEL_SH")
+  if not sh_path:
+    if _is_windows(repository_ctx):
+      sh_path = repository_ctx.which("bash.exe")
+      if sh_path:
+        # When the Windows Subsystem for Linux is installed there's a
+        # bash.exe under %WINDIR%\system32\bash.exe that launches Ubuntu
+        # Bash which cannot run native Windows programs so it's not what
+        # we want.
+        windir = repository_ctx.os.environ.get("WINDIR")
+        if windir and sh_path.startswith(windir):
+          sh_path = None
+    else:
+      sh_path = repository_ctx.which("bash")
+      if not sh_path:
+        sh_path = repository_ctx.which("sh")
+
+  if not sh_path:
+    sh_path = ""
+
+  if sh_path and _is_windows(repository_ctx):
+    sh_path = sh_path.replace("\\", "/")
+
+  os_label = None
+  if _is_windows(repository_ctx):
+    os_label = "@bazel_tools//platforms:windows"
+  elif repository_ctx.os.name.startswith("linux"):
+    os_label = "@bazel_tools//platforms:linux"
+  elif repository_ctx.os.name.startswith("mac"):
+    os_label = "@bazel_tools//platforms:osx"
+  else:
+    fail("Unknown OS")
+
+  repository_ctx.file("BUILD", """
+load("@bazel_tools//tools/sh:sh_toolchain.bzl", "sh_toolchain")
+
+sh_toolchain(
+    name = "local_sh",
+    path = "{sh_path}",
+    visibility = ["//visibility:public"],
+)
+
+toolchain(
+    name = "local_sh_toolchain",
+    exec_compatible_with = [
+        "@bazel_tools//platforms:x86_64",
+        "{os_label}",
+    ],
+    toolchain = ":local_sh",
+    toolchain_type = "@bazel_tools//tools/sh:toolchain_type",
+)
+""".format(sh_path = sh_path, os_label = os_label))
+
+sh_config = repository_rule(
+    environ = [
+        "WINDIR",
+        "PATH",
+    ],
+    local = True,
+    implementation = _sh_config_impl,
+)
+
+def sh_configure():
+  """Detect the local shell interpreter and register its toolchain."""
+  sh_config(name = "local_config_sh")
+  native.register_toolchains("@local_config_sh//:local_sh_toolchain")
diff --git a/tools/sh/sh_toolchain.bzl b/tools/sh/sh_toolchain.bzl
new file mode 100644
index 0000000..ddd7de7
--- /dev/null
+++ b/tools/sh/sh_toolchain.bzl
@@ -0,0 +1,26 @@
+# Copyright 2018 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.
+"""Define a toolchain rule for the shell."""
+
+def _sh_toolchain_impl(ctx):
+  """sh_toolchain rule implementation."""
+  return [platform_common.ToolchainInfo(path = ctx.attr.path)]
+
+sh_toolchain = rule(
+    attrs = {
+        # Absolute path to the shell interpreter.
+        "path": attr.string(),
+    },
+    implementation = _sh_toolchain_impl,
+)