From 78651d4a407fd6114d7d1129a26d5500fdeb21f2 Mon Sep 17 00:00:00 2001 From: lberki Date: Fri, 6 Apr 2018 01:52:58 -0700 Subject: [PATCH] Move shell executable to its own configuration fragment. RELNOTES: None. PiperOrigin-RevId: 191861074 --- .../build/lib/analysis/BuildView.java | 2 +- .../build/lib/analysis/CommandHelper.java | 3 +- .../analysis/ConfiguredRuleClassProvider.java | 23 +- .../lib/analysis/ConfiguredTargetFactory.java | 4 +- .../build/lib/analysis/RuleContext.java | 17 +- .../lib/analysis/ShellConfiguration.java | 213 ++++++++++++++++++ .../lib/analysis/actions/SpawnAction.java | 5 +- .../analysis/config/BuildConfiguration.java | 38 ---- .../lib/analysis/test/TestRunnerAction.java | 3 +- .../lib/bazel/rules/BazelConfiguration.java | 200 ---------------- .../bazel/rules/BazelRuleClassProvider.java | 118 +++++++++- .../bazel/rules/java/BazelJavaSemantics.java | 6 +- .../build/lib/bazel/rules/sh/ShBinary.java | 6 +- .../devtools/build/lib/exec/TestStrategy.java | 4 +- .../lib/runtime/commands/RunCommand.java | 16 +- .../skyframe/TransitiveTargetFunction.java | 6 +- .../build/lib/analysis/BuildViewTest.java | 2 +- .../lib/analysis/ShellConfigurationTest.java | 66 ++++++ .../lib/analysis/mock/BazelAnalysisMock.java | 10 +- .../bazel/rules/BazelConfigurationTest.java | 90 -------- .../rules/BazelRuleClassProviderTest.java | 52 +++++ .../genrule/GenRuleConfiguredTargetTest.java | 5 +- 22 files changed, 512 insertions(+), 377 deletions(-) create mode 100644 src/main/java/com/google/devtools/build/lib/analysis/ShellConfiguration.java delete mode 100644 src/main/java/com/google/devtools/build/lib/bazel/rules/BazelConfiguration.java create mode 100644 src/test/java/com/google/devtools/build/lib/analysis/ShellConfigurationTest.java delete mode 100644 src/test/java/com/google/devtools/build/lib/bazel/rules/BazelConfigurationTest.java diff --git a/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java b/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java index 0e4307716c9eb7..6d6d8a011b8a64 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/BuildView.java @@ -1270,7 +1270,7 @@ public RuleContext getRuleContextForTesting( getPrerequisiteMapForTesting( eventHandler, configuredTarget, configurations, toolchainContext)) .setConfigConditions(ImmutableMap.of()) - .setUniversalFragment(ruleClassProvider.getUniversalFragment()) + .setUniversalFragments(ruleClassProvider.getUniversalFragments()) .setToolchainContext(toolchainContext) .build(); } diff --git a/src/main/java/com/google/devtools/build/lib/analysis/CommandHelper.java b/src/main/java/com/google/devtools/build/lib/analysis/CommandHelper.java index 85dfc7a043dac7..f364257c685d83 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/CommandHelper.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/CommandHelper.java @@ -306,6 +306,7 @@ public List buildCommandLine( private PathFragment shellPath(Map executionInfo) { // Use vanilla /bin/bash for actions running on mac machines. return executionInfo.containsKey(ExecutionRequirements.REQUIRES_DARWIN) - ? PathFragment.create("/bin/bash") : ruleContext.getConfiguration().getShellExecutable(); + ? PathFragment.create("/bin/bash") + : ruleContext.getConfiguration().getFragment(ShellConfiguration.class).getShellExecutable(); } } diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java index 34836ab6bd3f6d..e55b44aba293ac 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredRuleClassProvider.java @@ -225,7 +225,8 @@ public static class Builder implements RuleDefinitionEnvironment { private final Digraph> dependencyGraph = new Digraph<>(); private PatchTransition lipoDataTransition; - private Class universalFragment; + private List> universalFragments = + new ArrayList<>(); private PrerequisiteValidator prerequisiteValidator; private ImmutableMap.Builder skylarkAccessibleTopLevels = ImmutableMap.builder(); @@ -340,9 +341,9 @@ public Builder addConfigurationFragment(ConfigurationFragmentFactory factory) { return this; } - public Builder setUniversalConfigurationFragment( + public Builder addUniversalConfigurationFragment( Class fragment) { - this.universalFragment = fragment; + this.universalFragments.add(fragment); return this; } @@ -456,7 +457,7 @@ public ConfiguredRuleClassProvider build() { ImmutableList.copyOf(configurationOptions), ImmutableList.copyOf(configurationFragmentFactories), lipoDataTransition, - universalFragment, + ImmutableList.copyOf(universalFragments), prerequisiteValidator, skylarkAccessibleTopLevels.build(), skylarkModules.build(), @@ -555,10 +556,10 @@ public Label load(String from) { private final PatchTransition lipoDataTransition; /** - * A configuration fragment that should be available to all rules even when they don't + * Configuration fragments that should be available to all rules even when they don't * explicitly require it. */ - private final Class universalFragment; + private final ImmutableList> universalFragments; private final ImmutableList buildInfoFactories; @@ -583,7 +584,7 @@ private ConfiguredRuleClassProvider( ImmutableList> configurationOptions, ImmutableList configurationFragments, PatchTransition lipoDataTransition, - Class universalFragment, + ImmutableList> universalFragments, PrerequisiteValidator prerequisiteValidator, ImmutableMap skylarkAccessibleJavaClasses, ImmutableList> skylarkModules, @@ -600,7 +601,7 @@ private ConfiguredRuleClassProvider( this.configurationOptions = configurationOptions; this.configurationFragmentFactories = configurationFragments; this.lipoDataTransition = lipoDataTransition; - this.universalFragment = universalFragment; + this.universalFragments = universalFragments; this.prerequisiteValidator = prerequisiteValidator; this.globals = createGlobals(skylarkAccessibleJavaClasses, skylarkModules); this.nativeProviders = nativeProviders; @@ -685,8 +686,8 @@ public RuleDefinition getRuleClassDefinition(String ruleClassName) { * Returns the configuration fragment that should be available to all rules even when they * don't explicitly require it. */ - public Class getUniversalFragment() { - return universalFragment; + public ImmutableList> getUniversalFragments() { + return universalFragments; } /** @@ -808,7 +809,7 @@ public ImmutableSortedSet> getAllFr for (ConfigurationFragmentFactory factory : getConfigurationFragments()) { fragmentsBuilder.add(factory.creates()); } - fragmentsBuilder.add(getUniversalFragment()); + fragmentsBuilder.addAll(getUniversalFragments()); return fragmentsBuilder.build(); } diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredTargetFactory.java b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredTargetFactory.java index 1edf561a507fb9..7a1d4ec38d502d 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredTargetFactory.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/ConfiguredTargetFactory.java @@ -339,7 +339,7 @@ private ConfiguredTarget createRule( .setVisibility(convertVisibility(prerequisiteMap, env.getEventHandler(), rule, null)) .setPrerequisites(prerequisiteMap) .setConfigConditions(configConditions) - .setUniversalFragment(ruleClassProvider.getUniversalFragment()) + .setUniversalFragments(ruleClassProvider.getUniversalFragments()) .setToolchainContext(toolchainContext) .build(); if (ruleContext.hasErrors()) { @@ -461,7 +461,7 @@ public ConfiguredAspect createAspect( .setPrerequisites(prerequisiteMap) .setAspectAttributes(aspectAttributes) .setConfigConditions(configConditions) - .setUniversalFragment(ruleClassProvider.getUniversalFragment()) + .setUniversalFragments(ruleClassProvider.getUniversalFragments()) .setToolchainContext(toolchainContext) .build(); if (ruleContext.hasErrors()) { diff --git a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java index c8c477524ffd54..043bee78a1b7a3 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/RuleContext.java @@ -184,7 +184,7 @@ public ImmutableList getFiles() { private final BuildConfiguration hostConfiguration; private final PatchTransition disableLipoTransition; private final ConfigurationFragmentPolicy configurationFragmentPolicy; - private final Class universalFragment; + private final ImmutableList> universalFragments; private final ErrorReporter reporter; @Nullable private final ToolchainContext toolchainContext; @@ -199,7 +199,7 @@ private RuleContext( ListMultimap targetMap, ListMultimap filesetEntryMap, ImmutableMap configConditions, - Class universalFragment, + ImmutableList> universalFragments, String ruleClassNameForLogging, ImmutableMap aspectAttributes, @Nullable ToolchainContext toolchainContext) { @@ -214,7 +214,7 @@ private RuleContext( .map(a -> a.getDescriptor()) .collect(ImmutableList.toImmutableList()); this.configurationFragmentPolicy = builder.configurationFragmentPolicy; - this.universalFragment = universalFragment; + this.universalFragments = universalFragments; this.targetMap = targetMap; this.filesetEntryMap = filesetEntryMap; this.configConditions = configConditions; @@ -452,7 +452,7 @@ public ImmutableCollection getSkylarkFragmentNames(ConfigurationTransiti public boolean isLegalFragment( Class fragment, ConfigurationTransition transition) { - return fragment == universalFragment + return universalFragments.contains(fragment) || fragment == PlatformConfiguration.class || configurationFragmentPolicy.isLegalConfigurationFragment(fragment, transition); } @@ -1405,7 +1405,7 @@ public static final class Builder implements RuleErrorConsumer { private final AnalysisEnvironment env; private final Rule rule; private final ConfigurationFragmentPolicy configurationFragmentPolicy; - private Class universalFragment; + private ImmutableList> universalFragments; private final BuildConfiguration configuration; private final BuildConfiguration hostConfiguration; private PatchTransition disableLipoTransition; @@ -1453,7 +1453,7 @@ RuleContext build() { targetMap, filesetEntryMap, configConditions, - universalFragment, + universalFragments, getRuleClassNameForLogging(), aspectAttributes != null ? aspectAttributes : ImmutableMap.of(), toolchainContext); @@ -1498,12 +1498,13 @@ Builder setConfigConditions(ImmutableMap configCo /** * Sets the fragment that can be legally accessed even when not explicitly declared. */ - Builder setUniversalFragment(Class fragment) { + Builder setUniversalFragments( + ImmutableList> fragments) { // TODO(bazel-team): Add this directly to ConfigurationFragmentPolicy, so we // don't need separate logic specifically for checking this fragment. The challenge is // that we need RuleClassProvider to figure out what this fragment is, and not every // call state that creates ConfigurationFragmentPolicy has access to that. - this.universalFragment = fragment; + this.universalFragments = fragments; return this; } diff --git a/src/main/java/com/google/devtools/build/lib/analysis/ShellConfiguration.java b/src/main/java/com/google/devtools/build/lib/analysis/ShellConfiguration.java new file mode 100644 index 00000000000000..2e4769e506ebaa --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/analysis/ShellConfiguration.java @@ -0,0 +1,213 @@ +// 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.ConfigurationEnvironment; +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.DeserializationContext; +import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec; +import com.google.devtools.build.lib.skyframe.serialization.SerializationContext; +import com.google.devtools.build.lib.skyframe.serialization.SerializationException; +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.protobuf.CodedInputStream; +import com.google.protobuf.CodedOutputStream; +import java.io.IOException; +import java.util.Map; +import javax.annotation.Nullable; + +/** A configuration fragment that tells where the shell is. */ +public class ShellConfiguration extends BuildConfiguration.Fragment { + + /** + * A codec for {@link ShellConfiguration}. + * + *

It does not handle the Bazel version, but that's fine, because we don't want to serialize + * anything in Bazel. + * + *

We cannot use {@code AutoCodec} because the field {@link #actionEnvironment} is a lambda. + * That does not necessarily need to be the case, but it's there in support for + * {@link BuildConfiguration.Fragment#setupActionEnvironment()}, which is slated to be removed. + */ + public static final class Codec implements ObjectCodec { + @Override + public Class getEncodedClass() { + return ShellConfiguration.class; + } + + @Override + public void serialize(SerializationContext context, ShellConfiguration obj, + CodedOutputStream codedOut) throws SerializationException, IOException { + context.serialize(obj.shellExecutable, codedOut); + } + + @Override + public ShellConfiguration deserialize(DeserializationContext context, CodedInputStream codedIn) + throws SerializationException, IOException { + PathFragment shellExecutable = context.deserialize(codedIn); + return new ShellConfiguration(shellExecutable, NO_ACTION_ENV.fromOptions(null)); + } + } + + 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 ShellActionEnvironment actionEnvironment; + + public ShellConfiguration(PathFragment shellExecutable, + ShellActionEnvironment actionEnvironment) { + this.shellExecutable = shellExecutable; + this.actionEnvironment = actionEnvironment; + } + + public PathFragment getShellExecutable() { + return shellExecutable; + } + + @Override + public void setupActionEnvironment(Map builder) { + actionEnvironment.setupActionEnvironment(this, builder); + } + + /** An option that tells Bazel where the shell is. */ + @AutoCodec(strategy = AutoCodec.Strategy.PUBLIC_FIELDS) + 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; + + @Override + public Options getHost() { + Options host = (Options) getDefault(); + host.shellExecutable = shellExecutable; + return host; + } + } + + /** + * Encapsulates the contributions of {@link ShellConfiguration} to the action environment. + * + *

This is done this way because we need the shell environment to be different between Bazel + * and Blaze, but configuration fragments are handed out based on their classes, thus, + * doing this with inheritance would be difficult. The good old "has-a instead of this-a" pattern. + */ + public interface ShellActionEnvironment { + void setupActionEnvironment(ShellConfiguration configuration, Map builder); + } + + /** + * A factory for shell action environments. + * + *

This is necessary because the Bazel shell action environment depends on whether we use + * strict action environments or not, but we cannot simply hardcode the dependency on that bit + * here because it doesn't exist in Blaze. Thus, during configuration creation time, we call this + * factory which returns an object, which, when called, updates the actual action environment. + */ + public interface ShellActionEnvironmentFactory { + ShellActionEnvironment fromOptions(BuildOptions options); + } + + /** A {@link ShellConfiguration} that contributes nothing to the action environment. */ + public static final ShellActionEnvironmentFactory NO_ACTION_ENV = + (BuildOptions options) -> (ShellConfiguration config, Map builder) -> {}; + + /** 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 (BuildOptions options) -> PathFragment.create(shell); + } + + /** The loader for {@link ShellConfiguration}. */ + public static class Loader implements ConfigurationFragmentFactory { + private final ShellExecutableProvider shellExecutableProvider; + private final ShellActionEnvironmentFactory actionEnvironmentFactory; + private final ImmutableSet> requiredOptions; + + public Loader(ShellExecutableProvider shellExecutableProvider, + ShellActionEnvironmentFactory actionEnvironmentFactory, + Class... requiredOptions) { + this.shellExecutableProvider = shellExecutableProvider; + this.actionEnvironmentFactory = actionEnvironmentFactory; + this.requiredOptions = ImmutableSet.copyOf(requiredOptions); + } + + @Nullable + @Override + public Fragment create(ConfigurationEnvironment env, BuildOptions buildOptions) { + return new ShellConfiguration( + shellExecutableProvider.getShellExecutable(buildOptions), + actionEnvironmentFactory.fromOptions(buildOptions)); + } + + 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; + } + } +} diff --git a/src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnAction.java b/src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnAction.java index 3d352c499228e5..a89651c680bd76 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnAction.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/actions/SpawnAction.java @@ -55,6 +55,7 @@ import com.google.devtools.build.lib.actions.extra.SpawnInfo; import com.google.devtools.build.lib.analysis.AnalysisEnvironment; import com.google.devtools.build.lib.analysis.FilesToRunProvider; +import com.google.devtools.build.lib.analysis.ShellConfiguration; import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.collect.nestedset.NestedSet; @@ -678,8 +679,8 @@ private CommandLine buildCommandLine( AnalysisEnvironment analysisEnvironment, BuildConfiguration configuration, List paramFileActions) { - ImmutableList executableArgs = - buildExecutableArgs(configuration.getShellExecutable()); + ImmutableList executableArgs = buildExecutableArgs( + configuration.getFragment(ShellConfiguration.class).getShellExecutable()); boolean hasConditionalParamFile = commandLines.stream().anyMatch(c -> c.paramFileInfo != null && !c.paramFileInfo.always()); boolean spillToParamFiles = false; diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java index 51cbfbc261f4fe..a068e51b72d284 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BuildConfiguration.java @@ -164,16 +164,6 @@ public String getOutputDirectoryName() { public void setupActionEnvironment(Map builder) { } - /** - * Returns the shell to be used. - * - *

Each configuration instance must have at most one fragment that returns non-null. - */ - @SuppressWarnings("unused") - public PathFragment getShellExecutable() { - return null; - } - /** * Returns { 'option name': 'alternative default' } entries for options where the * "real default" should be something besides the default specified in the {@link Option} @@ -1115,9 +1105,6 @@ ArtifactRoot getRoot( // is mutable, so a cached value there could fall out of date when it's updated. private final boolean actionsEnabled; - // TODO(bazel-team): Move this to a configuration fragment. - private final PathFragment shellExecutable; - /** * The global "make variables" such as "$(TARGET_CPU)"; these get applied to all rules analyzed in * this configuration. @@ -1335,8 +1322,6 @@ public BuildConfiguration( OutputDirectory.MIDDLEMAN.getRoot( RepositoryName.MAIN, outputDirName, directories, mainRepositoryName); - this.shellExecutable = computeShellExecutable(); - this.actionEnv = setupActionEnvironment(); this.testEnv = setupTestEnvironment(); @@ -1641,13 +1626,6 @@ public ImmutableSet getVariableShellEnvironment() { return actionEnv.getInheritedEnv(); } - /** - * Returns the path to sh. - */ - public PathFragment getShellExecutable() { - return shellExecutable; - } - /** * Returns a regex-based instrumentation filter instance that used to match label * names to identify targets to be instrumented in the coverage mode. @@ -1929,22 +1907,6 @@ public boolean enableWindowsExeLauncher() { return options.windowsExeLauncher; } - /** - * Collects executables defined by fragments. - */ - private PathFragment computeShellExecutable() { - PathFragment result = null; - - for (Fragment fragment : fragments.values()) { - if (fragment.getShellExecutable() != null) { - Verify.verify(result == null); - result = fragment.getShellExecutable(); - } - } - - return result; - } - /** * Returns the transition that produces the "artifact owner" for this configuration, or null * if this configuration is its own owner. diff --git a/src/main/java/com/google/devtools/build/lib/analysis/test/TestRunnerAction.java b/src/main/java/com/google/devtools/build/lib/analysis/test/TestRunnerAction.java index 336ca69140bc67..500a4f54896105 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/test/TestRunnerAction.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/test/TestRunnerAction.java @@ -33,6 +33,7 @@ import com.google.devtools.build.lib.actions.ExecException; import com.google.devtools.build.lib.actions.NotifyOnActionCacheHit; import com.google.devtools.build.lib.analysis.RunfilesSupplierImpl; +import com.google.devtools.build.lib.analysis.ShellConfiguration; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.analysis.config.RunUnder; import com.google.devtools.build.lib.buildeventstream.TestFileNameConstants; @@ -754,7 +755,7 @@ public Artifact getTestSetupScript() { } public PathFragment getShExecutable() { - return configuration.getShellExecutable(); + return configuration.getFragment(ShellConfiguration.class).getShellExecutable(); } public ImmutableMap getLocalShellEnvironment() { diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelConfiguration.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelConfiguration.java deleted file mode 100644 index bde1dcce8cc161..00000000000000 --- a/src/main/java/com/google/devtools/build/lib/bazel/rules/BazelConfiguration.java +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright 2014 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.bazel.rules; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; -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.ConfigurationEnvironment; -import com.google.devtools.build.lib.analysis.config.ConfigurationFragmentFactory; -import com.google.devtools.build.lib.analysis.config.FragmentOptions; -import com.google.devtools.build.lib.analysis.config.InvalidConfigurationException; -import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; -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 java.util.Map; -import javax.annotation.Nullable; - -/** Bazel-specific configuration fragment. */ -@AutoCodec -@Immutable -public class BazelConfiguration extends Fragment { - /** Command-line options. */ - @AutoCodec(strategy = AutoCodec.Strategy.PUBLIC_FIELDS) - public static class Options extends FragmentOptions { - @Option( - name = "experimental_strict_action_env", - defaultValue = "false", - documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, - effectTags = {OptionEffectTag.LOADING_AND_ANALYSIS}, - help = - "If true, Bazel uses an environment with a static value for PATH and does not " - + "inherit LD_LIBRARY_PATH or TMPDIR. Use --action_env=ENV_VARIABLE if you want to " - + "inherit specific environment variables from the client, but note that doing so " - + "can prevent cross-user caching if a shared cache is used." - ) - public boolean useStrictActionEnv; - - @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; - - @Override - public Options getHost() { - Options host = (Options) getDefault(); - host.useStrictActionEnv = useStrictActionEnv; - host.shellExecutable = shellExecutable; - return host; - } - } - - /** - * Loader for Bazel-specific settings. - */ - public static class Loader implements ConfigurationFragmentFactory { - @Override - public Fragment create(ConfigurationEnvironment env, BuildOptions buildOptions) - throws InvalidConfigurationException { - return new BazelConfiguration(OS.getCurrent(), buildOptions.get(Options.class)); - } - - @Override - public Class creates() { - return BazelConfiguration.class; - } - - @Override - public ImmutableSet> requiredOptions() { - return ImmutableSet.of(Options.class); - } - } - - 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 static final PathFragment FALLBACK_SHELL = PathFragment.create("/bin/bash"); - - private final OS os; - private final boolean useStrictActionEnv; - private final PathFragment shellExecutable; - - public BazelConfiguration(OS os, Options options) { - this(os, options.useStrictActionEnv, determineShellExecutable(os, options.shellExecutable)); - } - - @AutoCodec.Instantiator - BazelConfiguration(OS os, boolean useStrictActionEnv, PathFragment shellExecutable) { - this.os = os; - this.useStrictActionEnv = useStrictActionEnv; - this.shellExecutable = shellExecutable; - } - - @Override - public PathFragment getShellExecutable() { - return shellExecutable; - } - - @Override - public void setupActionEnvironment(Map builder) { - // All entries in the builder that have a value of null inherit the value from the client - // environment, which is only known at execution time - we don't want to bake the client env - // into the configuration since any change to the configuration requires rerunning the full - // analysis phase. - if (useStrictActionEnv) { - builder.put("PATH", pathOrDefault(os, null, getShellExecutable())); - } else if (os == OS.WINDOWS) { - // TODO(ulfjack): We want to add the MSYS root to the PATH, but that prevents us from - // inheriting PATH from the client environment. For now we use System.getenv even though - // that is incorrect. We should enable strict_action_env by default and then remove this - // code, but that change may break Windows users who are relying on the MSYS root being in - // the PATH. - builder.put("PATH", pathOrDefault(os, System.getenv("PATH"), getShellExecutable())); - builder.put("LD_LIBRARY_PATH", null); - } else { - // The previous implementation used System.getenv (which uses the server's environment), and - // fell back to a hard-coded "/bin:/usr/bin" if PATH was not set. - builder.put("PATH", null); - builder.put("LD_LIBRARY_PATH", null); - } - } - - private static PathFragment determineShellExecutable(OS os, PathFragment fromOption) { - if (fromOption != null) { - return fromOption; - } - // 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 : FALLBACK_SHELL; - } - - @VisibleForTesting - static String pathOrDefault(OS os, @Nullable String path, @Nullable PathFragment sh) { - // TODO(ulfjack): The default PATH should be set from the exec platform, which may be different - // from the local machine. For now, this can be overridden with --action_env=PATH=, so - // at least there's a workaround. - if (os != OS.WINDOWS) { - return "/bin:/usr/bin"; - } - - // Attempt to compute the MSYS root (the real Windows path of "/") from `sh`. - if (sh != null && sh.getParentDirectory() != null) { - String newPath = sh.getParentDirectory().getPathString(); - if (sh.getParentDirectory().endsWith(PathFragment.create("usr/bin"))) { - newPath += - ";" + sh.getParentDirectory().getParentDirectory().replaceName("bin").getPathString(); - } else if (sh.getParentDirectory().endsWith(PathFragment.create("bin"))) { - newPath += - ";" + sh.getParentDirectory().replaceName("usr").getRelative("bin").getPathString(); - } - newPath = newPath.replace('/', '\\'); - - if (path != null) { - newPath += ";" + path; - } - return newPath; - } else { - return null; - } - } -} 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 f3b5110fa936a7..483fa23b9e218e 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 @@ -16,12 +16,18 @@ import static com.google.common.base.Preconditions.checkNotNull; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider.Builder; import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider.RuleSet; +import com.google.devtools.build.lib.analysis.ShellConfiguration; +import com.google.devtools.build.lib.analysis.ShellConfiguration.ShellActionEnvironmentFactory; +import com.google.devtools.build.lib.analysis.ShellConfiguration.ShellExecutableProvider; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +import com.google.devtools.build.lib.analysis.config.BuildOptions; +import com.google.devtools.build.lib.analysis.config.FragmentOptions; import com.google.devtools.build.lib.bazel.rules.android.AndroidNdkRepositoryRule; import com.google.devtools.build.lib.bazel.rules.android.AndroidSdkRepositoryRule; import com.google.devtools.build.lib.bazel.rules.android.BazelAarImportRule; @@ -86,13 +92,76 @@ import com.google.devtools.build.lib.rules.repository.CoreWorkspaceRules; import com.google.devtools.build.lib.rules.repository.NewLocalRepositoryRule; import com.google.devtools.build.lib.rules.test.TestingSupportRules; +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.ResourceFileLoader; +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 java.io.IOException; +import java.util.Map; +import javax.annotation.Nullable; /** A rule class provider implementing the rules Bazel knows. */ public class BazelRuleClassProvider { public static final String TOOLS_REPOSITORY = "@bazel_tools"; + /** Command-line options. */ + @AutoCodec(strategy = AutoCodec.Strategy.PUBLIC_FIELDS) + public static class StrictActionEnvOptions extends FragmentOptions { + @Option( + name = "experimental_strict_action_env", + defaultValue = "false", + documentationCategory = OptionDocumentationCategory.UNCATEGORIZED, + effectTags = {OptionEffectTag.LOADING_AND_ANALYSIS}, + help = + "If true, Bazel uses an environment with a static value for PATH and does not " + + "inherit LD_LIBRARY_PATH or TMPDIR. Use --action_env=ENV_VARIABLE if you want to " + + "inherit specific environment variables from the client, but note that doing so " + + "can prevent cross-user caching if a shared cache is used." + ) + public boolean useStrictActionEnv; + + @Override + public StrictActionEnvOptions getHost() { + StrictActionEnvOptions host = (StrictActionEnvOptions) getDefault(); + host.useStrictActionEnv = useStrictActionEnv; + return host; + } + } + + public static final ShellActionEnvironmentFactory SHELL_ACTION_ENV = (BuildOptions options) -> { + boolean strictActionEnv = options.get( + StrictActionEnvOptions.class).useStrictActionEnv; + return (ShellConfiguration configuration, Map builder) -> { + OS os = OS.getCurrent(); + // All entries in the builder that have a value of null inherit the value from the client + // environment, which is only known at execution time - we don't want to bake the client env + // into the configuration since any change to the configuration requires rerunning the full + // analysis phase. + if (!strictActionEnv) { + builder.put("LD_LIBRARY_PATH", null); + } + + if (strictActionEnv) { + builder.put("PATH", pathOrDefault(os, null, configuration.getShellExecutable())); + } else if (os == OS.WINDOWS) { + // TODO(ulfjack): We want to add the MSYS root to the PATH, but that prevents us from + // inheriting PATH from the client environment. For now we use System.getenv even though + // that is incorrect. We should enable strict_action_env by default and then remove this + // code, but that change may break Windows users who are relying on the MSYS root being in + // the PATH. + builder.put("PATH", pathOrDefault( + os, System.getenv("PATH"), configuration.getShellExecutable())); + } else { + // The previous implementation used System.getenv (which uses the server's environment), and + // fell back to a hard-coded "/bin:/usr/bin" if PATH was not set. + builder.put("PATH", null); + } + }; + }; + /** Used by the build encyclopedia generator. */ public static ConfiguredRuleClassProvider create() { ConfiguredRuleClassProvider.Builder builder = new ConfiguredRuleClassProvider.Builder(); @@ -107,6 +176,14 @@ public static void setup(ConfiguredRuleClassProvider.Builder builder) { } } + private static final PathFragment FALLBACK_SHELL = PathFragment.create("/bin/bash"); + + public static final ShellExecutableProvider SHELL_EXECUTABLE = (BuildOptions options) -> + ShellConfiguration.Loader.determineShellExecutable( + OS.getCurrent(), + options.get(ShellConfiguration.Options.class), + FALLBACK_SHELL); + public static final RuleSet BAZEL_SETUP = new RuleSet() { @Override @@ -117,9 +194,14 @@ public void init(Builder builder) { .setRunfilesPrefix(Label.DEFAULT_REPOSITORY_DIRECTORY) .setPrerequisiteValidator(new BazelPrerequisiteValidator()); - builder.setUniversalConfigurationFragment(BazelConfiguration.class); - builder.addConfigurationFragment(new BazelConfiguration.Loader()); - builder.addConfigurationOptions(BazelConfiguration.Options.class); + builder.addConfigurationOptions(ShellConfiguration.Options.class); + builder.addConfigurationFragment(new ShellConfiguration.Loader( + SHELL_EXECUTABLE, + SHELL_ACTION_ENV, + ShellConfiguration.Options.class, + StrictActionEnvOptions.class)); + builder.addUniversalConfigurationFragment(ShellConfiguration.class); + builder.addConfigurationOptions(StrictActionEnvOptions.class); builder.addConfigurationOptions(BuildConfiguration.Options.class); } @@ -315,4 +397,34 @@ public ImmutableList requires() { // This rule set is a little special: it needs to depend on every configuration fragment // that has Make variables, so we put it last. ToolchainRules.INSTANCE); + + @VisibleForTesting + public static String pathOrDefault(OS os, @Nullable String path, @Nullable PathFragment sh) { + // TODO(ulfjack): The default PATH should be set from the exec platform, which may be different + // from the local machine. For now, this can be overridden with --action_env=PATH=, so + // at least there's a workaround. + if (os != OS.WINDOWS) { + return "/bin:/usr/bin"; + } + + // Attempt to compute the MSYS root (the real Windows path of "/") from `sh`. + if (sh != null && sh.getParentDirectory() != null) { + String newPath = sh.getParentDirectory().getPathString(); + if (sh.getParentDirectory().endsWith(PathFragment.create("usr/bin"))) { + newPath += + ";" + sh.getParentDirectory().getParentDirectory().replaceName("bin").getPathString(); + } else if (sh.getParentDirectory().endsWith(PathFragment.create("bin"))) { + newPath += + ";" + sh.getParentDirectory().replaceName("usr").getRelative("bin").getPathString(); + } + newPath = newPath.replace('/', '\\'); + + if (path != null) { + newPath += ";" + path; + } + return newPath; + } else { + return null; + } + } } diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java index 561ce8e641f002..cb793323d94ecc 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/java/BazelJavaSemantics.java @@ -26,6 +26,7 @@ import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder; import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.analysis.Runfiles; +import com.google.devtools.build.lib.analysis.ShellConfiguration; import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; import com.google.devtools.build.lib.analysis.actions.CustomCommandLine; import com.google.devtools.build.lib.analysis.actions.LauncherFileWriteAction; @@ -37,7 +38,6 @@ import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Template; import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode; import com.google.devtools.build.lib.analysis.test.TestConfiguration; -import com.google.devtools.build.lib.bazel.rules.BazelConfiguration; import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.collect.nestedset.Order; @@ -405,13 +405,13 @@ public Artifact createStubAction( Substitution.of( "%bash_exe_path%", ruleContext - .getFragment(BazelConfiguration.class) + .getFragment(ShellConfiguration.class) .getShellExecutable() .getPathString()), Substitution.of( "%cygpath_exe_path%", ruleContext - .getFragment(BazelConfiguration.class) + .getFragment(ShellConfiguration.class) .getShellExecutable() .replaceName("cygpath.exe") .getPathString())), diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/sh/ShBinary.java b/src/main/java/com/google/devtools/build/lib/bazel/rules/sh/ShBinary.java index 85de9e98d2ff1b..19de0229103324 100644 --- a/src/main/java/com/google/devtools/build/lib/bazel/rules/sh/ShBinary.java +++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/sh/ShBinary.java @@ -23,6 +23,7 @@ import com.google.devtools.build.lib.analysis.Runfiles; import com.google.devtools.build.lib.analysis.RunfilesProvider; import com.google.devtools.build.lib.analysis.RunfilesSupport; +import com.google.devtools.build.lib.analysis.ShellConfiguration; import com.google.devtools.build.lib.analysis.actions.ExecutableSymlinkAction; import com.google.devtools.build.lib.analysis.actions.LauncherFileWriteAction; import com.google.devtools.build.lib.analysis.actions.LauncherFileWriteAction.LaunchInfo; @@ -30,7 +31,6 @@ import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Substitution; import com.google.devtools.build.lib.analysis.actions.TemplateExpansionAction.Template; import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget.Mode; -import com.google.devtools.build.lib.bazel.rules.BazelConfiguration; import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.util.OS; @@ -113,7 +113,7 @@ private static Artifact createWindowsExeLauncher(RuleContext ruleContext) .addKeyValuePair( "bash_bin_path", ruleContext - .getFragment(BazelConfiguration.class) + .getFragment(ShellConfiguration.class) .getShellExecutable() .getPathString()) .build(); @@ -153,7 +153,7 @@ private static Artifact launcherForWindows( Substitution.of( "%bash_exe_path%", ruleContext - .getFragment(BazelConfiguration.class) + .getFragment(ShellConfiguration.class) .getShellExecutable() .getPathString())), true)); diff --git a/src/main/java/com/google/devtools/build/lib/exec/TestStrategy.java b/src/main/java/com/google/devtools/build/lib/exec/TestStrategy.java index b457eba6c4de4d..07a1942a68c901 100644 --- a/src/main/java/com/google/devtools/build/lib/exec/TestStrategy.java +++ b/src/main/java/com/google/devtools/build/lib/exec/TestStrategy.java @@ -28,6 +28,7 @@ import com.google.devtools.build.lib.actions.SpawnResult; import com.google.devtools.build.lib.actions.TestExecException; import com.google.devtools.build.lib.actions.UserExecException; +import com.google.devtools.build.lib.analysis.ShellConfiguration; import com.google.devtools.build.lib.analysis.config.PerLabelOptions; import com.google.devtools.build.lib.analysis.test.TestActionContext; import com.google.devtools.build.lib.analysis.test.TestResult; @@ -183,7 +184,8 @@ private static void addRunUnderArgs(TestRunnerAction testAction, List ar // the --run_under parameter and getCommand only returns the first such token. boolean needsShell = !command.contains("/"); if (needsShell) { - args.add(testAction.getConfiguration().getShellExecutable().getPathString()); + args.add(testAction.getConfiguration().getFragment(ShellConfiguration.class) + .getShellExecutable().getPathString()); args.add("-c"); args.add("\"$@\""); args.add("/bin/sh"); // Sets $0. diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/RunCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/RunCommand.java index 66b4e676e0dd12..37ed1f8d9e268c 100644 --- a/src/main/java/com/google/devtools/build/lib/runtime/commands/RunCommand.java +++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/RunCommand.java @@ -26,6 +26,7 @@ import com.google.devtools.build.lib.analysis.ConfiguredTarget; import com.google.devtools.build.lib.analysis.FilesToRunProvider; import com.google.devtools.build.lib.analysis.RunfilesSupport; +import com.google.devtools.build.lib.analysis.ShellConfiguration; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; import com.google.devtools.build.lib.analysis.config.RunUnder; import com.google.devtools.build.lib.analysis.test.TestProvider; @@ -192,7 +193,7 @@ private List computeArgs(CommandEnvironment env, ConfiguredTarget target } private BlazeCommandResult runTargetUnderServer(CommandEnvironment env, - ConfiguredTarget targetToRun, BuildConfiguration configuration, + ConfiguredTarget targetToRun, PathFragment shellExecutable, ConfiguredTarget runUnderTarget, Path workingDir, List commandLineArgs) { RunOptions runOptions = env.getOptions().getOptions(RunOptions.class); List args = computeArgs(env, targetToRun, commandLineArgs); @@ -215,7 +216,7 @@ private BlazeCommandResult runTargetUnderServer(CommandEnvironment env, cmdLine.add(ProcessWrapperUtil.getProcessWrapper(env).getPathString()); } List prettyCmdLine = new ArrayList<>(); - constructCommandLine(cmdLine, prettyCmdLine, env, configuration.getShellExecutable(), + constructCommandLine(cmdLine, prettyCmdLine, env, shellExecutable, targetToRun, runUnderTarget, args); // Add a newline between the blaze output and the binary's output. @@ -420,9 +421,12 @@ public BlazeCommandResult exec(CommandEnvironment env, OptionsProvider options) } + PathFragment shellExecutable = + configuration.getFragment(ShellConfiguration.class).getShellExecutable(); + if (!runOptions.direct) { - return runTargetUnderServer(env, targetToRun, configuration, runUnderTarget, - runfilesDir, commandLineArgs); + return runTargetUnderServer( + env, targetToRun, shellExecutable, runUnderTarget, runfilesDir, commandLineArgs); } Map runEnvironment = new TreeMap<>(); @@ -473,7 +477,7 @@ public BlazeCommandResult exec(CommandEnvironment env, OptionsProvider options) List prettyCmdLine = new ArrayList<>(); List args = computeArgs(env, targetToRun, commandLineArgs); constructCommandLine(cmdLine, prettyCmdLine, env, - configuration.getShellExecutable(), targetToRun, runUnderTarget, args); + shellExecutable, targetToRun, runUnderTarget, args); } if (runOptions.scriptPath != null) { @@ -492,7 +496,7 @@ public BlazeCommandResult exec(CommandEnvironment env, OptionsProvider options) ByteString.copyFrom(workingDir.getPathString(), StandardCharsets.ISO_8859_1)); ImmutableList shellCmdLine = ImmutableList.of( - configuration.getShellExecutable().getPathString(), + shellExecutable.getPathString(), "-c", ShellEscaper.escapeJoinAll(cmdLine)); diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TransitiveTargetFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/TransitiveTargetFunction.java index 86146668a12e04..2d820e76241c83 100644 --- a/src/main/java/com/google/devtools/build/lib/skyframe/TransitiveTargetFunction.java +++ b/src/main/java/com/google/devtools/build/lib/skyframe/TransitiveTargetFunction.java @@ -218,8 +218,10 @@ public SkyValue computeSkyValue(TargetAndErrorIfAny targetAndErrorIfAny, addFragmentsIfNew(builder, getFragmentsFromRequiredOptions(rule)); // Fragments to unconditionally include: - addFragmentIfNew(builder, - ruleClassProvider.getUniversalFragment().asSubclass(BuildConfiguration.Fragment.class)); + for (Class universalFragment : + ruleClassProvider.getUniversalFragments()) { + addFragmentIfNew(builder, universalFragment); + } } return builder.build(errorLoadingTarget); diff --git a/src/test/java/com/google/devtools/build/lib/analysis/BuildViewTest.java b/src/test/java/com/google/devtools/build/lib/analysis/BuildViewTest.java index 61076f9111f994..90bc03bb9f2c97 100644 --- a/src/test/java/com/google/devtools/build/lib/analysis/BuildViewTest.java +++ b/src/test/java/com/google/devtools/build/lib/analysis/BuildViewTest.java @@ -1241,7 +1241,7 @@ public void testTopLevelTargetsAreTrimmedWithTrimmedConfigurations() throws Exce AnalysisResult res = update("//foo:x"); ConfiguredTarget topLevelTarget = Iterables.getOnlyElement(res.getTargetsToBuild()); assertThat(getConfiguration(topLevelTarget).getFragmentsMap().keySet()) - .containsExactly(ruleClassProvider.getUniversalFragment()); + .containsExactlyElementsIn(ruleClassProvider.getUniversalFragments()); } /** diff --git a/src/test/java/com/google/devtools/build/lib/analysis/ShellConfigurationTest.java b/src/test/java/com/google/devtools/build/lib/analysis/ShellConfigurationTest.java new file mode 100644 index 00000000000000..8d077add2d2af6 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/analysis/ShellConfigurationTest.java @@ -0,0 +1,66 @@ +// 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 static com.google.common.truth.Truth.assertThat; + +import com.google.devtools.build.lib.analysis.util.BuildViewTestCase; +import com.google.devtools.build.lib.util.OS; +import com.google.devtools.build.lib.vfs.PathFragment; +import com.google.devtools.common.options.Options; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +/** + * Unit tests for {@link ShellConfiguration}. + */ +@RunWith(JUnit4.class) +public class ShellConfigurationTest extends BuildViewTestCase { + @Test + public void getShellExecutableUnset() { + assertThat(determineShellExecutable(OS.LINUX, null)) + .isEqualTo(PathFragment.create("/bin/bash")); + assertThat(determineShellExecutable(OS.FREEBSD, null)) + .isEqualTo(PathFragment.create("/usr/local/bin/bash")); + assertThat(determineShellExecutable(OS.WINDOWS, null)) + .isEqualTo(PathFragment.create("c:/tools/msys64/usr/bin/bash.exe")); + } + + @Test + public void getShellExecutableIfSet() { + PathFragment binBash = PathFragment.create("/bin/bash"); + assertThat(determineShellExecutable(OS.LINUX, binBash)) + .isEqualTo(PathFragment.create("/bin/bash")); + assertThat(determineShellExecutable(OS.FREEBSD, binBash)) + .isEqualTo(PathFragment.create("/bin/bash")); + assertThat(determineShellExecutable(OS.WINDOWS, binBash)) + .isEqualTo(PathFragment.create("/bin/bash")); + } + + @Test + public void optionsAlsoApplyToHost() { + ShellConfiguration.Options o = Options.getDefaults(ShellConfiguration.Options.class); + o.shellExecutable = PathFragment.create("/my/shell/binary"); + ShellConfiguration.Options h = o.getHost(); + assertThat(h.shellExecutable).isEqualTo(PathFragment.create("/my/shell/binary")); + } + + private static PathFragment determineShellExecutable(OS os, PathFragment executableOption) { + ShellConfiguration.Options options = Options.getDefaults(ShellConfiguration.Options.class); + options.shellExecutable = executableOption; + return ShellConfiguration.Loader.determineShellExecutable( + os, options, PathFragment.create("/bin/bash")); + } +} 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 34450da04c9fac..eb082244a60bcf 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 @@ -17,9 +17,11 @@ import com.google.common.collect.Iterables; import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; import com.google.devtools.build.lib.analysis.PlatformConfigurationLoader; +import com.google.devtools.build.lib.analysis.ShellConfiguration; import com.google.devtools.build.lib.analysis.config.ConfigurationFragmentFactory; import com.google.devtools.build.lib.analysis.util.AnalysisMock; -import com.google.devtools.build.lib.bazel.rules.BazelConfiguration; +import com.google.devtools.build.lib.bazel.rules.BazelRuleClassProvider; +import com.google.devtools.build.lib.bazel.rules.BazelRuleClassProvider.StrictActionEnvOptions; import com.google.devtools.build.lib.bazel.rules.python.BazelPythonConfiguration; import com.google.devtools.build.lib.packages.util.BazelMockCcSupport; import com.google.devtools.build.lib.packages.util.MockCcSupport; @@ -280,8 +282,12 @@ public void setupMockWorkspaceFiles(Path embeddedBinariesRoot) throws IOExceptio @Override public List getDefaultConfigurationFragmentFactories() { return ImmutableList.of( - new BazelConfiguration.Loader(), new CppConfigurationLoader(CpuTransformer.IDENTITY), + new ShellConfiguration.Loader( + BazelRuleClassProvider.SHELL_EXECUTABLE, + BazelRuleClassProvider.SHELL_ACTION_ENV, + ShellConfiguration.Options.class, + StrictActionEnvOptions.class), new PythonConfigurationLoader(), new BazelPythonConfiguration.Loader(), new JavaConfigurationLoader(), diff --git a/src/test/java/com/google/devtools/build/lib/bazel/rules/BazelConfigurationTest.java b/src/test/java/com/google/devtools/build/lib/bazel/rules/BazelConfigurationTest.java deleted file mode 100644 index f4ce424f9eb2fe..00000000000000 --- a/src/test/java/com/google/devtools/build/lib/bazel/rules/BazelConfigurationTest.java +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2017 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.bazel.rules; - -import static com.google.common.truth.Truth.assertThat; -import static com.google.devtools.build.lib.bazel.rules.BazelConfiguration.pathOrDefault; - -import com.google.devtools.build.lib.util.OS; -import com.google.devtools.build.lib.vfs.PathFragment; -import com.google.devtools.common.options.Options; -import java.util.HashMap; -import java.util.Map; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -/** - * Tests for {@link BazelConfiguration}. - */ -@RunWith(JUnit4.class) -public class BazelConfigurationTest { - @Test - public void getShellExecutableUnset() { - BazelConfiguration.Options o = Options.getDefaults(BazelConfiguration.Options.class); - assertThat(new BazelConfiguration(OS.LINUX, o).getShellExecutable()) - .isEqualTo(PathFragment.create("/bin/bash")); - assertThat(new BazelConfiguration(OS.FREEBSD, o).getShellExecutable()) - .isEqualTo(PathFragment.create("/usr/local/bin/bash")); - assertThat(new BazelConfiguration(OS.WINDOWS, o).getShellExecutable()) - .isEqualTo(PathFragment.create("c:/tools/msys64/usr/bin/bash.exe")); - } - - @Test - public void getShellExecutableIfSet() { - BazelConfiguration.Options o = Options.getDefaults(BazelConfiguration.Options.class); - o.shellExecutable = PathFragment.create("/bin/bash"); - assertThat(new BazelConfiguration(OS.LINUX, o).getShellExecutable()) - .isEqualTo(PathFragment.create("/bin/bash")); - assertThat(new BazelConfiguration(OS.FREEBSD, o).getShellExecutable()) - .isEqualTo(PathFragment.create("/bin/bash")); - assertThat(new BazelConfiguration(OS.WINDOWS, o).getShellExecutable()) - .isEqualTo(PathFragment.create("/bin/bash")); - } - - @Test - public void strictActionEnv() { - BazelConfiguration.Options options = Options.getDefaults(BazelConfiguration.Options.class); - options.useStrictActionEnv = true; - BazelConfiguration config = new BazelConfiguration(OS.LINUX, options); - Map env = new HashMap<>(); - config.setupActionEnvironment(env); - assertThat(env).containsEntry("PATH", "/bin:/usr/bin"); - } - - @Test - public void pathOrDefaultOnLinux() { - assertThat(pathOrDefault(OS.LINUX, null, null)).isEqualTo("/bin:/usr/bin"); - assertThat(pathOrDefault(OS.LINUX, "/not/bin", null)).isEqualTo("/bin:/usr/bin"); - } - - @Test - public void pathOrDefaultOnWindows() { - assertThat(pathOrDefault(OS.WINDOWS, null, null)).isNull(); - assertThat(pathOrDefault(OS.WINDOWS, "C:/mypath", null)).isNull(); - assertThat(pathOrDefault(OS.WINDOWS, "C:/mypath", PathFragment.create("D:/foo/shell"))) - .isEqualTo("D:\\foo;C:/mypath"); - } - - @Test - public void optionsAlsoApplyToHost() { - BazelConfiguration.Options o = Options.getDefaults(BazelConfiguration.Options.class); - o.shellExecutable = PathFragment.create("/my/shell/binary"); - o.useStrictActionEnv = true; - BazelConfiguration.Options h = o.getHost(); - assertThat(h.shellExecutable).isEqualTo(PathFragment.create("/my/shell/binary")); - assertThat(h.useStrictActionEnv).isTrue(); - } -} diff --git a/src/test/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProviderTest.java b/src/test/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProviderTest.java index d7bd55f395cbd5..1c1fe3a7affd28 100644 --- a/src/test/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProviderTest.java +++ b/src/test/java/com/google/devtools/build/lib/bazel/rules/BazelRuleClassProviderTest.java @@ -16,19 +16,30 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; +import static com.google.devtools.build.lib.bazel.rules.BazelRuleClassProvider.SHELL_ACTION_ENV; +import static com.google.devtools.build.lib.bazel.rules.BazelRuleClassProvider.pathOrDefault; +import com.google.common.collect.ImmutableList; import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider.RuleSet; +import com.google.devtools.build.lib.analysis.ShellConfiguration; import com.google.devtools.build.lib.analysis.config.BuildConfiguration; +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.bazel.rules.BazelRuleClassProvider.StrictActionEnvOptions; import com.google.devtools.build.lib.packages.RuleClass; import com.google.devtools.build.lib.rules.config.ConfigRules; import com.google.devtools.build.lib.rules.core.CoreRules; import com.google.devtools.build.lib.rules.cpp.transitions.LipoDataTransitionRuleSet; import com.google.devtools.build.lib.rules.repository.CoreWorkspaceRules; +import com.google.devtools.build.lib.util.OS; +import com.google.devtools.build.lib.vfs.PathFragment; +import com.google.devtools.common.options.Options; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import org.junit.Test; import org.junit.runner.RunWith; @@ -153,4 +164,45 @@ public void variousWorkspaceConsistency() { public void toolchainConsistency() { checkModule(ToolchainRules.INSTANCE); } + + @Test + public void strictActionEnv() throws Exception { + if (OS.getCurrent() == OS.WINDOWS) { + return; + } + + BuildOptions options = BuildOptions.of( + ImmutableList.of(StrictActionEnvOptions.class), + "--experimental_strict_action_env"); + + ShellConfiguration configuration = new ShellConfiguration( + PathFragment.create("/bin/bash"), + SHELL_ACTION_ENV.fromOptions(options)); + Map env = new HashMap<>(); + configuration.setupActionEnvironment(env); + assertThat(env).containsEntry("PATH", "/bin:/usr/bin"); + } + + @Test + public void pathOrDefaultOnLinux() { + assertThat(pathOrDefault(OS.LINUX, null, null)).isEqualTo("/bin:/usr/bin"); + assertThat(pathOrDefault(OS.LINUX, "/not/bin", null)).isEqualTo("/bin:/usr/bin"); + } + + @Test + public void pathOrDefaultOnWindows() { + assertThat(pathOrDefault(OS.WINDOWS, null, null)).isNull(); + assertThat(pathOrDefault(OS.WINDOWS, "C:/mypath", null)).isNull(); + assertThat(pathOrDefault(OS.WINDOWS, "C:/mypath", PathFragment.create("D:/foo/shell"))) + .isEqualTo("D:\\foo;C:/mypath"); + } + + @Test + public void optionsAlsoApplyToHost() { + StrictActionEnvOptions o = Options.getDefaults( + StrictActionEnvOptions.class); + o.useStrictActionEnv = true; + StrictActionEnvOptions h = o.getHost(); + assertThat(h.useStrictActionEnv).isTrue(); + } } diff --git a/src/test/java/com/google/devtools/build/lib/bazel/rules/genrule/GenRuleConfiguredTargetTest.java b/src/test/java/com/google/devtools/build/lib/bazel/rules/genrule/GenRuleConfiguredTargetTest.java index e0f16892e08821..a70e6946e996d1 100644 --- a/src/test/java/com/google/devtools/build/lib/bazel/rules/genrule/GenRuleConfiguredTargetTest.java +++ b/src/test/java/com/google/devtools/build/lib/bazel/rules/genrule/GenRuleConfiguredTargetTest.java @@ -27,6 +27,7 @@ import com.google.devtools.build.lib.actions.util.ActionsTestUtil; import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider; import com.google.devtools.build.lib.analysis.ConfiguredTarget; +import com.google.devtools.build.lib.analysis.ShellConfiguration; import com.google.devtools.build.lib.analysis.actions.SpawnAction; import com.google.devtools.build.lib.analysis.configuredtargets.FileConfiguredTarget; import com.google.devtools.build.lib.analysis.util.AnalysisMock; @@ -188,8 +189,8 @@ public void testActionIsShellCommand() throws Exception { assertThat(shellAction.getOutputs()).containsExactly(messageArtifact); String expected = "echo \"Hello, world.\" >" + messageArtifact.getExecPathString(); - assertThat(shellAction.getArguments().get(0)) - .isEqualTo(targetConfig.getShellExecutable().getPathString()); + assertThat(shellAction.getArguments().get(0)).isEqualTo( + targetConfig.getFragment(ShellConfiguration.class).getShellExecutable().getPathString()); assertThat(shellAction.getArguments().get(1)).isEqualTo("-c"); assertCommandEquals(expected, shellAction.getArguments().get(2)); }