// 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. package com.google.devtools.build.lib.analysis; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.analysis.config.BuildConfiguration.Fragment; import com.google.devtools.build.lib.analysis.config.BuildOptions; import com.google.devtools.build.lib.analysis.config.ConfigurationFragmentFactory; import com.google.devtools.build.lib.analysis.config.FragmentOptions; import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; import com.google.devtools.build.lib.util.OS; import com.google.devtools.build.lib.util.OptionsUtils.PathFragmentConverter; import com.google.devtools.build.lib.vfs.PathFragment; import com.google.devtools.common.options.Option; import com.google.devtools.common.options.OptionDocumentationCategory; import com.google.devtools.common.options.OptionEffectTag; import com.google.devtools.common.options.OptionMetadataTag; import java.io.Serializable; import javax.annotation.Nullable; /** A configuration fragment that tells where the shell is. */ @AutoCodec public class ShellConfiguration extends BuildConfiguration.Fragment { private static final ImmutableMap OS_SPECIFIC_SHELL = ImmutableMap.builder() .put(OS.WINDOWS, PathFragment.create("c:/tools/msys64/usr/bin/bash.exe")) .put(OS.FREEBSD, PathFragment.create("/usr/local/bin/bash")) .build(); private final PathFragment shellExecutable; private final boolean useShBinaryStubScript; public ShellConfiguration(PathFragment shellExecutable, boolean useShBinaryStubScript) { this.shellExecutable = shellExecutable; this.useShBinaryStubScript = useShBinaryStubScript; } public PathFragment getShellExecutable() { return shellExecutable; } public boolean useShBinaryStubScript() { return useShBinaryStubScript; } /** An option that tells Bazel where the shell is. */ public static class Options extends FragmentOptions { @Option( name = "shell_executable", converter = PathFragmentConverter.class, defaultValue = "null", documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, effectTags = {OptionEffectTag.LOADING_AND_ANALYSIS}, help = "Absolute path to the shell executable for Bazel to use. If this is unset, but the " + "BAZEL_SH environment variable is set on the first Bazel invocation (that starts " + "up a Bazel server), Bazel uses that. If neither is set, Bazel uses a hard-coded " + "default path depending on the operating system it runs on (Windows: " + "c:/tools/msys64/usr/bin/bash.exe, FreeBSD: /usr/local/bin/bash, all others: " + "/bin/bash). Note that using a shell that is not compatible with bash may lead " + "to build failures or runtime failures of the generated binaries." ) public PathFragment shellExecutable; @Option( name = "experimental_use_sh_binary_stub_script", documentationCategory = OptionDocumentationCategory.UNDOCUMENTED, effectTags = {OptionEffectTag.AFFECTS_OUTPUTS}, metadataTags = {OptionMetadataTag.EXPERIMENTAL}, defaultValue = "false", help = "If enabled, use a stub script for sh_binary targets.") public boolean useShBinaryStubScript; @Override public Options getHost() { Options host = (Options) getDefault(); host.shellExecutable = shellExecutable; host.useShBinaryStubScript = useShBinaryStubScript; return host; } } /** the part of {@link ShellConfiguration} that determines where the shell is. */ public interface ShellExecutableProvider { PathFragment getShellExecutable(BuildOptions options); } /** A shell executable whose path is hard-coded. */ public static ShellExecutableProvider hardcodedShellExecutable(String shell) { return (ShellExecutableProvider & Serializable) (options) -> PathFragment.create(shell); } /** The loader for {@link ShellConfiguration}. */ public static class Loader implements ConfigurationFragmentFactory { private final ShellExecutableProvider shellExecutableProvider; private final ImmutableSet> requiredOptions; public Loader(ShellExecutableProvider shellExecutableProvider, Class... requiredOptions) { this.shellExecutableProvider = shellExecutableProvider; this.requiredOptions = ImmutableSet.copyOf(requiredOptions); } @Nullable @Override public Fragment create(BuildOptions buildOptions) { Options options = buildOptions.get(Options.class); return new ShellConfiguration( shellExecutableProvider.getShellExecutable(buildOptions), options != null && options.useShBinaryStubScript); } public static PathFragment determineShellExecutable( OS os, Options options, PathFragment defaultShell) { if (options.shellExecutable != null) { return options.shellExecutable; } // Honor BAZEL_SH env variable for backwards compatibility. String path = System.getenv("BAZEL_SH"); if (path != null) { return PathFragment.create(path); } // TODO(ulfjack): instead of using the OS Bazel runs on, we need to use the exec platform, // which may be different for remote execution. For now, this can be overridden with // --shell_executable, so at least there's a workaround. PathFragment result = OS_SPECIFIC_SHELL.get(os); return result != null ? result : defaultShell; } @Override public Class creates() { return ShellConfiguration.class; } @Override public ImmutableSet> requiredOptions() { return requiredOptions; } } }