| // 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.analysis.actions; |
| |
| import static com.google.common.base.Preconditions.checkArgument; |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static com.google.common.collect.ImmutableSet.toImmutableSet; |
| import static com.google.devtools.build.lib.actions.ActionAnalysisMetadata.mergeMaps; |
| import static com.google.devtools.build.lib.packages.ExecGroup.DEFAULT_EXEC_GROUP_NAME; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.CharMatcher; |
| import com.google.common.base.Strings; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.ImmutableSortedMap; |
| import com.google.common.collect.Interner; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Sets; |
| import com.google.devtools.build.lib.actions.AbstractAction; |
| import com.google.devtools.build.lib.actions.ActionEnvironment; |
| import com.google.devtools.build.lib.actions.ActionExecutionContext; |
| import com.google.devtools.build.lib.actions.ActionExecutionException; |
| import com.google.devtools.build.lib.actions.ActionInput; |
| import com.google.devtools.build.lib.actions.ActionInputHelper; |
| import com.google.devtools.build.lib.actions.ActionKeyContext; |
| import com.google.devtools.build.lib.actions.ActionOwner; |
| import com.google.devtools.build.lib.actions.ActionResult; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander; |
| import com.google.devtools.build.lib.actions.BaseSpawn; |
| import com.google.devtools.build.lib.actions.CommandAction; |
| import com.google.devtools.build.lib.actions.CommandLine; |
| import com.google.devtools.build.lib.actions.CommandLineExpansionException; |
| import com.google.devtools.build.lib.actions.CommandLineItem; |
| import com.google.devtools.build.lib.actions.CommandLineLimits; |
| import com.google.devtools.build.lib.actions.CommandLines; |
| import com.google.devtools.build.lib.actions.CommandLines.CommandLineAndParamFileInfo; |
| import com.google.devtools.build.lib.actions.CommandLines.ExpandedCommandLines; |
| import com.google.devtools.build.lib.actions.CompositeRunfilesSupplier; |
| import com.google.devtools.build.lib.actions.ExecException; |
| import com.google.devtools.build.lib.actions.FilesetOutputSymlink; |
| import com.google.devtools.build.lib.actions.ParamFileInfo; |
| import com.google.devtools.build.lib.actions.PathMapper; |
| import com.google.devtools.build.lib.actions.ResourceSetOrBuilder; |
| import com.google.devtools.build.lib.actions.RunfilesSupplier; |
| import com.google.devtools.build.lib.actions.Spawn; |
| import com.google.devtools.build.lib.actions.SpawnResult; |
| import com.google.devtools.build.lib.actions.extra.EnvironmentVariable; |
| import com.google.devtools.build.lib.actions.extra.ExtraActionInfo; |
| import com.google.devtools.build.lib.actions.extra.SpawnInfo; |
| import com.google.devtools.build.lib.analysis.FilesToRunProvider; |
| import com.google.devtools.build.lib.analysis.TransitiveInfoCollection; |
| import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue; |
| import com.google.devtools.build.lib.analysis.config.CoreOptions.OutputPathsMode; |
| import com.google.devtools.build.lib.analysis.starlark.Args; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSet; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; |
| import com.google.devtools.build.lib.concurrent.BlazeInterners; |
| import com.google.devtools.build.lib.exec.SpawnExecException; |
| import com.google.devtools.build.lib.exec.SpawnStrategyResolver; |
| import com.google.devtools.build.lib.server.FailureDetails; |
| import com.google.devtools.build.lib.server.FailureDetails.FailureDetail; |
| import com.google.devtools.build.lib.server.FailureDetails.Spawn.Code; |
| import com.google.devtools.build.lib.starlarkbuildapi.CommandLineArgsApi; |
| import com.google.devtools.build.lib.util.DetailedExitCode; |
| import com.google.devtools.build.lib.util.Fingerprint; |
| import com.google.devtools.build.lib.util.OnDemandString; |
| import com.google.devtools.build.lib.util.ShellEscaper; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import com.google.errorprone.annotations.CanIgnoreReturnValue; |
| import com.google.errorprone.annotations.CheckReturnValue; |
| import com.google.errorprone.annotations.CompileTimeConstant; |
| import com.google.errorprone.annotations.ForOverride; |
| import com.google.errorprone.annotations.FormatMethod; |
| import com.google.errorprone.annotations.FormatString; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Map; |
| import javax.annotation.Nullable; |
| import net.starlark.java.eval.EvalException; |
| import net.starlark.java.eval.Sequence; |
| import net.starlark.java.eval.StarlarkList; |
| |
| /** An Action representing an arbitrary subprocess to be forked and exec'd. */ |
| public class SpawnAction extends AbstractAction implements CommandAction { |
| |
| private static final String GUID = "ebd6fce3-093e-45ee-adb6-bf513b602f0d"; |
| |
| private static final Interner<ImmutableSortedMap<String, String>> executionInfoInterner = |
| BlazeInterners.newWeakInterner(); |
| |
| private final NestedSet<Artifact> tools; |
| private final RunfilesSupplier runfilesSupplier; |
| private final CommandLines commandLines; |
| private final ActionEnvironment env; |
| |
| private final CharSequence progressMessage; |
| private final String mnemonic; |
| |
| private final ResourceSetOrBuilder resourceSetOrBuilder; |
| private final ImmutableMap<String, String> executionInfo; |
| private final OutputPathsMode outputPathsMode; |
| |
| /** |
| * Constructs a SpawnAction using direct initialization arguments. |
| * |
| * <p>All collections provided must not be subsequently modified. |
| * |
| * @param owner the owner of the Action |
| * @param tools the set of files comprising the tool that does the work (e.g. compiler). This is a |
| * subset of "inputs" and is only used by the WorkerSpawnStrategy |
| * @param inputs the set of all files potentially read by this action; must not be subsequently |
| * modified |
| * @param outputs the set of all files written by this action; must not be subsequently modified. |
| * @param resourceSetOrBuilder the resources consumed by executing this Action. |
| * @param env the action's environment |
| * @param executionInfo out-of-band information for scheduling the spawn |
| * @param commandLines the command lines to execute. This includes the main argv vector and any |
| * param file-backed command lines. |
| * @param progressMessage the message printed during the progression of the build |
| * @param runfilesSupplier {@link RunfilesSupplier}s describing the runfiles for the action |
| * @param mnemonic the mnemonic that is reported in the master log |
| */ |
| public SpawnAction( |
| ActionOwner owner, |
| NestedSet<Artifact> tools, |
| NestedSet<Artifact> inputs, |
| Iterable<? extends Artifact> outputs, |
| ResourceSetOrBuilder resourceSetOrBuilder, |
| CommandLines commandLines, |
| ActionEnvironment env, |
| ImmutableMap<String, String> executionInfo, |
| CharSequence progressMessage, |
| RunfilesSupplier runfilesSupplier, |
| String mnemonic, |
| OutputPathsMode outputPathsMode) { |
| super(owner, inputs, outputs); |
| this.tools = tools; |
| this.runfilesSupplier = runfilesSupplier; |
| this.resourceSetOrBuilder = resourceSetOrBuilder; |
| this.executionInfo = |
| executionInfo.isEmpty() |
| ? ImmutableSortedMap.of() |
| : executionInfoInterner.intern(ImmutableSortedMap.copyOf(executionInfo)); |
| this.commandLines = commandLines; |
| this.env = env; |
| this.progressMessage = progressMessage; |
| this.mnemonic = mnemonic; |
| this.outputPathsMode = outputPathsMode; |
| } |
| |
| @Override |
| public final NestedSet<Artifact> getTools() { |
| return tools; |
| } |
| |
| @Override |
| public final RunfilesSupplier getRunfilesSupplier() { |
| return runfilesSupplier; |
| } |
| |
| @Override |
| public final ActionEnvironment getEnvironment() { |
| return env; |
| } |
| |
| @VisibleForTesting |
| public CommandLines getCommandLines() { |
| return commandLines; |
| } |
| |
| @Override |
| public List<String> getArguments() throws CommandLineExpansionException, InterruptedException { |
| return commandLines.allArguments(PathMappers.create(this, outputPathsMode)); |
| } |
| |
| @Override |
| public Sequence<CommandLineArgsApi> getStarlarkArgs() { |
| ImmutableList.Builder<CommandLineArgsApi> result = ImmutableList.builder(); |
| ImmutableSet<Artifact> directoryInputs = |
| getInputs().toList().stream().filter(Artifact::isDirectory).collect(toImmutableSet()); |
| |
| for (CommandLineAndParamFileInfo commandLine : commandLines.unpack()) { |
| result.add(Args.forRegisteredAction(commandLine, directoryInputs)); |
| } |
| return StarlarkList.immutableCopyOf(result.build()); |
| } |
| |
| @Override |
| public Sequence<String> getStarlarkArgv() throws EvalException, InterruptedException { |
| try { |
| return StarlarkList.immutableCopyOf(getArguments()); |
| } catch (CommandLineExpansionException ex) { |
| throw new EvalException(ex); |
| } |
| } |
| |
| @Override |
| @VisibleForTesting |
| public NestedSet<Artifact> getPossibleInputsForTesting() { |
| return getInputs(); |
| } |
| |
| /** Returns command argument, argv[0]. */ |
| @VisibleForTesting |
| public String getCommandFilename() throws CommandLineExpansionException, InterruptedException { |
| return Iterables.getFirst(getArguments(), null); |
| } |
| |
| /** Returns the (immutable) list of arguments, excluding the command name, argv[0]. */ |
| @VisibleForTesting |
| public List<String> getRemainingArguments() |
| throws CommandLineExpansionException, InterruptedException { |
| return ImmutableList.copyOf(Iterables.skip(getArguments(), 1)); |
| } |
| |
| @Override |
| public final boolean isVolatile() { |
| return executeUnconditionally(); |
| } |
| |
| /** Hook for subclasses to perform work before the spawn is executed. */ |
| protected void beforeExecute(ActionExecutionContext actionExecutionContext) |
| throws ExecException {} |
| |
| /** |
| * Hook for subclasses to perform work after the spawn is executed. This method is only executed |
| * if the subprocess execution returns normally, not in case of errors (non-zero exit, |
| * setup/network failures, etc.). |
| */ |
| @ForOverride |
| protected void afterExecute( |
| ActionExecutionContext actionExecutionContext, |
| List<SpawnResult> spawnResults, |
| PathMapper pathMapper) |
| throws ExecException, InterruptedException {} |
| |
| @Override |
| public final ActionResult execute(ActionExecutionContext actionExecutionContext) |
| throws ActionExecutionException, InterruptedException { |
| try { |
| beforeExecute(actionExecutionContext); |
| Spawn spawn = getSpawn(actionExecutionContext); |
| ImmutableList<SpawnResult> result = |
| actionExecutionContext |
| .getContext(SpawnStrategyResolver.class) |
| .exec(spawn, actionExecutionContext); |
| afterExecute(actionExecutionContext, result, spawn.getPathMapper()); |
| return ActionResult.create(result); |
| } catch (CommandLineExpansionException e) { |
| throw createCommandLineException(e); |
| } catch (ExecException e) { |
| if (e instanceof SpawnExecException) { |
| throw ((SpawnExecException) e).toActionExecutionException(this); |
| } |
| throw ActionExecutionException.fromExecException(e, this); |
| } |
| } |
| |
| private ActionExecutionException createCommandLineException(CommandLineExpansionException e) { |
| DetailedExitCode detailedExitCode = |
| DetailedExitCode.of( |
| FailureDetail.newBuilder() |
| .setMessage(Strings.nullToEmpty(e.getMessage())) |
| .setSpawn( |
| FailureDetails.Spawn.newBuilder().setCode(Code.COMMAND_LINE_EXPANSION_FAILURE)) |
| .build()); |
| return new ActionExecutionException(e, this, /*catastrophe=*/ false, detailedExitCode); |
| } |
| |
| @VisibleForTesting |
| public ResourceSetOrBuilder getResourceSetOrBuilder() { |
| return resourceSetOrBuilder; |
| } |
| |
| /** |
| * Returns a Spawn that is representative of the command that this Action will execute. This |
| * function must not modify any state. |
| * |
| * <p>This method is final, as it is merely a shorthand use of the generic way to obtain a spawn, |
| * which also depends on the client environment. Subclasses that wish to override the way to get a |
| * spawn should override the other getSpawn() methods instead. |
| */ |
| @VisibleForTesting |
| public final Spawn getSpawn() throws CommandLineExpansionException, InterruptedException { |
| return getSpawn(getInputs()); |
| } |
| |
| final Spawn getSpawn(NestedSet<Artifact> inputs) |
| throws CommandLineExpansionException, InterruptedException { |
| return new ActionSpawn( |
| commandLines.allArguments(), |
| this, |
| /* env= */ ImmutableMap.of(), |
| /* envResolved= */ false, |
| inputs, |
| /* additionalInputs= */ ImmutableList.of(), |
| /* filesetMappings= */ ImmutableMap.of(), |
| /* reportOutputs= */ true, |
| PathMapper.NOOP); |
| } |
| |
| /** |
| * Returns a spawn that is representative of the command that this Action will execute in the |
| * given client environment. |
| */ |
| public Spawn getSpawn(ActionExecutionContext actionExecutionContext) |
| throws CommandLineExpansionException, InterruptedException { |
| return getSpawn( |
| actionExecutionContext.getArtifactExpander(), |
| actionExecutionContext.getClientEnv(), |
| /*envResolved=*/ false, |
| actionExecutionContext.getTopLevelFilesets(), |
| /*reportOutputs=*/ true); |
| } |
| |
| /** |
| * Return a spawn that is representative of the command that this Action will execute in the given |
| * environment. |
| * |
| * @param envResolved If set to true, the passed environment variables will be used as the Spawn |
| * effective environment. Otherwise they will be used as client environment to resolve the |
| * action env. |
| */ |
| protected Spawn getSpawn( |
| ArtifactExpander artifactExpander, |
| Map<String, String> env, |
| boolean envResolved, |
| Map<Artifact, ImmutableList<FilesetOutputSymlink>> filesetMappings, |
| boolean reportOutputs) |
| throws CommandLineExpansionException, InterruptedException { |
| PathMapper pathMapper = PathMappers.create(this, outputPathsMode); |
| ExpandedCommandLines expandedCommandLines = |
| commandLines.expand( |
| artifactExpander, getPrimaryOutput().getExecPath(), pathMapper, getCommandLineLimits()); |
| return new ActionSpawn( |
| ImmutableList.copyOf(expandedCommandLines.arguments()), |
| this, |
| env, |
| envResolved, |
| getInputs(), |
| expandedCommandLines.getParamFiles(), |
| filesetMappings, |
| reportOutputs, |
| pathMapper); |
| } |
| |
| @ForOverride |
| protected CommandLineLimits getCommandLineLimits() { |
| return getOwner().getBuildConfigurationInfo().getCommandLineLimits(); |
| } |
| |
| Spawn getSpawnForExtraAction() throws CommandLineExpansionException, InterruptedException { |
| return getSpawn(getInputs()); |
| } |
| |
| @Override |
| protected void computeKey( |
| ActionKeyContext actionKeyContext, |
| @Nullable ArtifactExpander artifactExpander, |
| Fingerprint fp) |
| throws CommandLineExpansionException, InterruptedException { |
| fp.addString(GUID); |
| commandLines.addToFingerprint(actionKeyContext, artifactExpander, fp); |
| fp.addString(mnemonic); |
| // We don't need the toolManifests here, because they are a subset of the inputManifests by |
| // definition and the output of an action shouldn't change whether something is considered a |
| // tool or not. |
| fp.addPaths(runfilesSupplier.getRunfilesDirs()); |
| ImmutableList<Artifact> runfilesManifests = runfilesSupplier.getManifests(); |
| fp.addInt(runfilesManifests.size()); |
| for (Artifact runfilesManifest : runfilesManifests) { |
| fp.addPath(runfilesManifest.getExecPath()); |
| } |
| env.addTo(fp); |
| fp.addStringMap(getExecutionInfo()); |
| PathMappers.addToFingerprint(getMnemonic(), getExecutionInfo(), outputPathsMode, fp); |
| } |
| |
| @Override |
| public String describeKey() { |
| StringBuilder message = new StringBuilder(); |
| message.append(getProgressMessage()); |
| message.append('\n'); |
| for (Map.Entry<String, String> entry : env.getFixedEnv().entrySet()) { |
| message.append(" Environment variable: "); |
| message.append(ShellEscaper.escapeString(entry.getKey())); |
| message.append('='); |
| message.append(ShellEscaper.escapeString(entry.getValue())); |
| message.append('\n'); |
| } |
| for (String var : getClientEnvironmentVariables()) { |
| message.append(" Environment variables taken from the client environment: "); |
| message.append(ShellEscaper.escapeString(var)); |
| message.append('\n'); |
| } |
| try { |
| for (String argument : ShellEscaper.escapeAll(getArguments())) { |
| message.append(" Argument: "); |
| message.append(argument); |
| message.append('\n'); |
| } |
| } catch (InterruptedException ex) { |
| Thread.currentThread().interrupt(); |
| message.append("Interrupted while expanding command line\n"); |
| } catch (CommandLineExpansionException e) { |
| message.append("Could not expand command line: "); |
| message.append(e); |
| message.append('\n'); |
| } |
| message.append(" Output paths mode: "); |
| message.append(outputPathsMode); |
| message.append('\n'); |
| return message.toString(); |
| } |
| |
| @Override |
| public final String getMnemonic() { |
| return mnemonic; |
| } |
| |
| @Override |
| protected String getRawProgressMessage() { |
| if (progressMessage != null) { |
| return progressMessage.toString(); |
| } |
| return super.getRawProgressMessage(); |
| } |
| |
| @Override |
| public ExtraActionInfo.Builder getExtraActionInfo(ActionKeyContext actionKeyContext) |
| throws CommandLineExpansionException, InterruptedException { |
| return super.getExtraActionInfo(actionKeyContext) |
| .setExtension(SpawnInfo.spawnInfo, getExtraActionSpawnInfo()); |
| } |
| |
| /** |
| * Returns information about this spawn action for use by the extra action mechanism. |
| * |
| * <p>Subclasses of SpawnAction may override this in order to provide action-specific behaviour. |
| * This can be necessary, for example, when the action discovers inputs. |
| */ |
| private SpawnInfo getExtraActionSpawnInfo() |
| throws CommandLineExpansionException, InterruptedException { |
| SpawnInfo.Builder info = SpawnInfo.newBuilder(); |
| Spawn spawn = getSpawnForExtraAction(); |
| info.addAllArgument(spawn.getArguments()); |
| for (Map.Entry<String, String> variable : spawn.getEnvironment().entrySet()) { |
| info.addVariable( |
| EnvironmentVariable.newBuilder() |
| .setName(variable.getKey()) |
| .setValue(variable.getValue()) |
| .build()); |
| } |
| for (ActionInput input : spawn.getInputFiles().toList()) { |
| // Explicitly ignore middleman artifacts here. |
| if (!(input instanceof Artifact) || !((Artifact) input).isMiddlemanArtifact()) { |
| info.addInputFile(input.getExecPathString()); |
| } |
| } |
| info.addAllOutputFile(ActionInputHelper.toExecPaths(spawn.getOutputFiles())); |
| return info.build(); |
| } |
| |
| @Override |
| @VisibleForTesting |
| public final ImmutableMap<String, String> getIncompleteEnvironmentForTesting() { |
| // TODO(ulfjack): AbstractAction should declare getEnvironment with a return value of type |
| // ActionEnvironment to avoid developers misunderstanding the purpose of this method. That |
| // requires first updating all subclasses and callers to actually handle environments correctly, |
| // so it's not a small change. |
| return getEnvironment().getFixedEnv(); |
| } |
| |
| /** Returns the out-of-band execution data for this action. */ |
| @Override |
| public ImmutableMap<String, String> getExecutionInfo() { |
| return mergeMaps(super.getExecutionInfo(), executionInfo); |
| } |
| |
| /** A spawn instance that is tied to a specific SpawnAction. */ |
| private static final class ActionSpawn extends BaseSpawn { |
| private final NestedSet<ActionInput> inputs; |
| private final Map<Artifact, ImmutableList<FilesetOutputSymlink>> filesetMappings; |
| private final ImmutableMap<String, String> effectiveEnvironment; |
| private final boolean reportOutputs; |
| private final PathMapper pathMapper; |
| |
| /** |
| * Creates an ActionSpawn with the given environment variables. |
| * |
| * <p>Subclasses of ActionSpawn may subclass in order to provide action-specific values for |
| * environment variables or action inputs. |
| */ |
| private ActionSpawn( |
| ImmutableList<String> arguments, |
| SpawnAction parent, |
| Map<String, String> env, |
| boolean envResolved, |
| NestedSet<Artifact> inputs, |
| Iterable<? extends ActionInput> additionalInputs, |
| Map<Artifact, ImmutableList<FilesetOutputSymlink>> filesetMappings, |
| boolean reportOutputs, |
| PathMapper pathMapper) |
| throws CommandLineExpansionException { |
| super( |
| arguments, |
| ImmutableMap.of(), |
| parent.getExecutionInfo(), |
| parent.getRunfilesSupplier(), |
| parent, |
| parent.resourceSetOrBuilder); |
| NestedSetBuilder<ActionInput> inputsBuilder = NestedSetBuilder.stableOrder(); |
| ImmutableList<Artifact> manifests = getRunfilesSupplier().getManifests(); |
| for (Artifact input : inputs.toList()) { |
| if (!input.isFileset() && !manifests.contains(input)) { |
| inputsBuilder.add(input); |
| } |
| } |
| inputsBuilder.addAll(additionalInputs); |
| this.inputs = inputsBuilder.build(); |
| this.filesetMappings = filesetMappings; |
| this.pathMapper = pathMapper; |
| |
| // If the action environment is already resolved using the client environment, the given |
| // environment variables are used as they are. Otherwise, they are used as clientEnv to |
| // resolve the action environment variables. |
| if (envResolved) { |
| effectiveEnvironment = ImmutableMap.copyOf(env); |
| } else { |
| effectiveEnvironment = parent.getEffectiveEnvironment(env); |
| } |
| this.reportOutputs = reportOutputs; |
| } |
| |
| @Override |
| public PathMapper getPathMapper() { |
| return pathMapper; |
| } |
| |
| @Override |
| public ImmutableMap<String, String> getEnvironment() { |
| return effectiveEnvironment; |
| } |
| |
| @Override |
| public ImmutableMap<Artifact, ImmutableList<FilesetOutputSymlink>> getFilesetMappings() { |
| return ImmutableMap.copyOf(filesetMappings); |
| } |
| |
| @Override |
| public NestedSet<? extends ActionInput> getInputFiles() { |
| return inputs; |
| } |
| |
| @Override |
| public Collection<Artifact> getOutputFiles() { |
| return reportOutputs ? super.getOutputFiles() : ImmutableSet.of(); |
| } |
| } |
| |
| /** |
| * Builder class to construct {@link SpawnAction} instances. |
| */ |
| public static class Builder { |
| |
| private final NestedSetBuilder<Artifact> toolsBuilder = NestedSetBuilder.stableOrder(); |
| private final NestedSetBuilder<Artifact> inputsBuilder = NestedSetBuilder.stableOrder(); |
| private final List<Artifact> outputs = new ArrayList<>(); |
| private final List<RunfilesSupplier> inputRunfilesSuppliers = new ArrayList<>(); |
| private ResourceSetOrBuilder resourceSetOrBuilder = AbstractAction.DEFAULT_RESOURCE_SET; |
| private ImmutableMap<String, String> environment = ImmutableMap.of(); |
| private ImmutableMap<String, String> executionInfo = ImmutableMap.of(); |
| private boolean useDefaultShellEnvironment = false; |
| protected boolean executeUnconditionally; |
| private Object executableArg; |
| @Nullable private CustomCommandLine.Builder executableArgs; |
| private List<CommandLineAndParamFileInfo> commandLines = new ArrayList<>(); |
| |
| private CharSequence progressMessage; |
| private String mnemonic = "Unknown"; |
| private String execGroup = DEFAULT_EXEC_GROUP_NAME; |
| |
| /** Creates a SpawnAction builder. */ |
| public Builder() {} |
| |
| /** Creates a builder that is a copy of another builder. */ |
| public Builder(Builder other) { |
| this.toolsBuilder.addTransitive(other.toolsBuilder.build()); |
| this.inputsBuilder.addTransitive(other.inputsBuilder.build()); |
| this.outputs.addAll(other.outputs); |
| this.inputRunfilesSuppliers.addAll(other.inputRunfilesSuppliers); |
| this.resourceSetOrBuilder = other.resourceSetOrBuilder; |
| this.environment = other.environment; |
| this.executionInfo = other.executionInfo; |
| this.useDefaultShellEnvironment = other.useDefaultShellEnvironment; |
| this.executableArg = other.executableArg; |
| this.executableArgs = other.executableArgs; |
| this.commandLines = new ArrayList<>(other.commandLines); |
| this.progressMessage = other.progressMessage; |
| this.mnemonic = other.mnemonic; |
| } |
| |
| /** |
| * Builds the SpawnAction using the passed-in action configuration. |
| * |
| * <p>This method makes a copy of all the collections, so it is safe to reuse the builder after |
| * this method returns. |
| * |
| * <p>This is annotated with @CheckReturnValue, which causes a compiler error when you call this |
| * method and ignore its return value. This is because some time ago, calling .build() had the |
| * side-effect of registering it with the RuleContext that was passed in to the constructor. |
| * This logic was removed, but if people don't notice and still rely on the side-effect, things |
| * may break. |
| * |
| * @return the SpawnAction. |
| */ |
| @CheckReturnValue |
| public SpawnAction build(ActionConstructionContext context) { |
| return build(context.getActionOwner(execGroup), context.getConfiguration()); |
| } |
| |
| @VisibleForTesting |
| @CheckReturnValue |
| public SpawnAction build(ActionOwner owner, BuildConfigurationValue configuration) { |
| CommandLines.Builder result = CommandLines.builder(); |
| if (executableArg != null) { |
| result.addSingleArgument(executableArg); |
| } else if (executableArgs != null) { |
| result.addCommandLine(executableArgs.build()); |
| } |
| for (CommandLineAndParamFileInfo pair : this.commandLines) { |
| result.addCommandLine(pair); |
| } |
| CommandLines commandLines = result.build(); |
| ActionEnvironment env; |
| if (useDefaultShellEnvironment && environment != null) { |
| // Inherited variables override fixed variables in ActionEnvironment. Since we want the |
| // fixed part of the action-provided environment to override the inherited part of the |
| // user-provided environment, we have to explicitly filter the inherited part. |
| var userFilteredInheritedEnv = |
| ImmutableSet.copyOf( |
| Sets.difference( |
| configuration.getActionEnvironment().getInheritedEnv(), environment.keySet())); |
| // Do not create a new ActionEnvironment in the common case where no vars have been filtered |
| // out. |
| if (userFilteredInheritedEnv.equals( |
| configuration.getActionEnvironment().getInheritedEnv())) { |
| env = configuration.getActionEnvironment(); |
| } else { |
| env = |
| ActionEnvironment.create( |
| configuration.getActionEnvironment().getFixedEnv(), userFilteredInheritedEnv); |
| } |
| env = env.withAdditionalFixedVariables(environment); |
| } else if (useDefaultShellEnvironment) { |
| env = configuration.getActionEnvironment(); |
| } else { |
| env = ActionEnvironment.create(environment); |
| } |
| return buildSpawnAction(owner, commandLines, configuration, env); |
| } |
| |
| @CheckReturnValue |
| public SpawnAction buildForActionTemplate(ActionOwner owner) { |
| CommandLines.Builder result = CommandLines.builder(); |
| if (executableArg != null) { |
| result.addSingleArgument(executableArg); |
| } else { |
| result.addCommandLine(executableArgs.build()); |
| } |
| for (CommandLineAndParamFileInfo pair : commandLines) { |
| result.addCommandLine(pair.commandLine); |
| } |
| return buildSpawnAction(owner, result.build(), null, ActionEnvironment.create(environment)); |
| } |
| |
| /** |
| * Builds the SpawnAction using the passed-in action configuration. |
| * |
| * <p>This method makes a copy of all the collections, so it is safe to reuse the builder after |
| * this method returns. |
| */ |
| private SpawnAction buildSpawnAction( |
| ActionOwner owner, |
| CommandLines commandLines, |
| @Nullable BuildConfigurationValue configuration, |
| ActionEnvironment env) { |
| NestedSet<Artifact> tools = toolsBuilder.build(); |
| |
| // Don't call getInputsAndTools - it wouldn't reuse the built set of tools. |
| NestedSet<Artifact> inputsAndTools = |
| NestedSetBuilder.<Artifact>stableOrder() |
| .addTransitive(inputsBuilder.build()) |
| .addTransitive(tools) |
| .build(); |
| |
| return createSpawnAction( |
| owner, |
| tools, |
| inputsAndTools, |
| ImmutableSet.copyOf(outputs), |
| resourceSetOrBuilder, |
| commandLines, |
| env, |
| configuration, |
| configuration == null |
| ? executionInfo |
| : configuration.modifiedExecutionInfo(executionInfo, mnemonic), |
| progressMessage, |
| CompositeRunfilesSupplier.fromSuppliers(this.inputRunfilesSuppliers), |
| mnemonic); |
| } |
| |
| /** Creates a SpawnAction. */ |
| protected SpawnAction createSpawnAction( |
| ActionOwner owner, |
| NestedSet<Artifact> tools, |
| NestedSet<Artifact> inputsAndTools, |
| ImmutableSet<Artifact> outputs, |
| ResourceSetOrBuilder resourceSetOrBuilder, |
| CommandLines commandLines, |
| ActionEnvironment env, |
| @Nullable BuildConfigurationValue configuration, |
| ImmutableMap<String, String> executionInfo, |
| CharSequence progressMessage, |
| RunfilesSupplier runfilesSupplier, |
| String mnemonic) { |
| return new SpawnAction( |
| owner, |
| tools, |
| inputsAndTools, |
| outputs, |
| resourceSetOrBuilder, |
| commandLines, |
| env, |
| executionInfo, |
| progressMessage, |
| runfilesSupplier, |
| mnemonic, |
| PathMappers.getOutputPathsMode(configuration)); |
| } |
| |
| /** |
| * Adds an artifact that is necessary for executing the spawn itself (e.g. a compiler), in |
| * contrast to an artifact that is necessary for the spawn to do its work (e.g. source code). |
| * |
| * <p>The artifact is implicitly added to the inputs of the action as well. |
| */ |
| @CanIgnoreReturnValue |
| public Builder addTool(Artifact tool) { |
| toolsBuilder.add(tool); |
| return this; |
| } |
| |
| /** |
| * Adds an executable and its runfiles, which is necessary for executing the spawn itself (e.g. |
| * a compiler), in contrast to artifacts that are necessary for the spawn to do its work (e.g. |
| * source code). |
| */ |
| @CanIgnoreReturnValue |
| public Builder addTool(FilesToRunProvider tool) { |
| addTransitiveTools(tool.getFilesToRun()); |
| addRunfilesSupplier(tool.getRunfilesSupplier()); |
| return this; |
| } |
| |
| /** Adds an input to this action. */ |
| @CanIgnoreReturnValue |
| public Builder addInput(Artifact artifact) { |
| inputsBuilder.add(artifact); |
| return this; |
| } |
| |
| /** Adds tools to this action. */ |
| @CanIgnoreReturnValue |
| Builder addTools(Iterable<Artifact> artifacts) { |
| toolsBuilder.addAll(artifacts); |
| return this; |
| } |
| |
| /** Adds tools to this action. */ |
| @CanIgnoreReturnValue |
| public Builder addTransitiveTools(NestedSet<Artifact> artifacts) { |
| toolsBuilder.addTransitive(artifacts); |
| return this; |
| } |
| |
| /** Adds inputs to this action. */ |
| @CanIgnoreReturnValue |
| public Builder addInputs(Iterable<Artifact> artifacts) { |
| inputsBuilder.addAll(artifacts); |
| return this; |
| } |
| |
| /** |
| * Returns the inputs that the spawn action will depend on. Tools are by definition a subset of |
| * the inputs, so they are also present. |
| * |
| * <p>Warning: this calls {@link NestedSetBuilder#build} on both inputs and tools. |
| */ |
| // TODO(antunesi): Refactor so this method isn't needed. Building new NestedSets on every call |
| // is a memory vulnerability, see b/291063247. |
| public NestedSet<Artifact> getInputsAndTools() { |
| return NestedSetBuilder.<Artifact>stableOrder() |
| .addTransitive(inputsBuilder.build()) |
| .addTransitive(toolsBuilder.build()) |
| .build(); |
| } |
| |
| /** Adds transitive inputs to this action. */ |
| @CanIgnoreReturnValue |
| public Builder addTransitiveInputs(NestedSet<Artifact> artifacts) { |
| inputsBuilder.addTransitive(artifacts); |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder addRunfilesSupplier(RunfilesSupplier supplier) { |
| inputRunfilesSuppliers.add(supplier); |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder addOutput(Artifact artifact) { |
| outputs.add(artifact); |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder addOutputs(Iterable<Artifact> artifacts) { |
| Iterables.addAll(outputs, artifacts); |
| return this; |
| } |
| |
| /** |
| * Sets RecourceSet for builder. If ResourceSetBuilder set, then ResourceSetBuilder will |
| * override setResources. |
| */ |
| @CanIgnoreReturnValue |
| public Builder setResources(ResourceSetOrBuilder resourceSetOrBuilder) { |
| this.resourceSetOrBuilder = resourceSetOrBuilder; |
| return this; |
| } |
| |
| /** |
| * Sets the map of environment variables. Do not use! This makes the builder ignore the 'default |
| * shell environment', which is computed from the --action_env command line option. |
| */ |
| @CanIgnoreReturnValue |
| public Builder setEnvironment(Map<String, String> environment) { |
| this.environment = ImmutableMap.copyOf(environment); |
| this.useDefaultShellEnvironment = false; |
| return this; |
| } |
| |
| /** Sets the map of execution info. */ |
| @CanIgnoreReturnValue |
| public Builder setExecutionInfo(Map<String, String> info) { |
| this.executionInfo = ImmutableMap.copyOf(info); |
| return this; |
| } |
| |
| /** |
| * Sets the environment to the configuration's default shell environment. |
| * |
| * <p><b>All actions should set this if possible and avoid using {@link #setEnvironment}.</b> |
| * |
| * <p>When this property is set, the action will use a minimal, standardized environment map. |
| * |
| * <p>The list of envvars available to the action (the keys in this map) comes from two places: |
| * from the rule class provider and from the command line or rc-files via {@code --action_env} |
| * flags. |
| * |
| * <p>The values for these variables may come from one of three places: from the configuration |
| * fragment, or from the {@code --action_env} flag (when the flag specifies a name-value pair, |
| * e.g. {@code --action_env=FOO=bar}), or from the client environment (when the flag only |
| * specifies a name, e.g. {@code --action_env=HOME}). |
| * |
| * <p>The client environment is specified by the {@code --client_env} flags. The Bazel client |
| * passes these flags to the Bazel server upon each build (e.g. {@code |
| * --client_env=HOME=/home/johndoe}), so the server can keep track of environmental changes |
| * between builds, and always use the up-to-date environment (as opposed to calling {@code |
| * System.getenv}, which it should never do, though as of 2017-08-02 it still does in a few |
| * places). |
| * |
| * <p>The {@code --action_env} has priority over configuration-fragment-dictated envvar values, |
| * i.e. if the configuration fragment tries to add FOO=bar to the environment, and there's also |
| * {@code --action_env=FOO=baz} or {@code --action_env=FOO}, then FOO will be available to the |
| * action and its value will be "baz", or whatever the corresponding {@code --client_env} flag |
| * specified, respectively. |
| * |
| * @see BuildConfigurationValue#getLocalShellEnvironment |
| */ |
| @CanIgnoreReturnValue |
| public Builder useDefaultShellEnvironment() { |
| this.environment = null; |
| this.useDefaultShellEnvironment = true; |
| return this; |
| } |
| |
| /** |
| * Same as {@link #useDefaultShellEnvironment()}, but additionally sets the provided fixed |
| * environment variables, which take precedence over the variables contained in the default |
| * shell environment. |
| */ |
| @CanIgnoreReturnValue |
| public Builder useDefaultShellEnvironmentWithOverrides(Map<String, String> environment) { |
| this.environment = ImmutableMap.copyOf(environment); |
| this.useDefaultShellEnvironment = true; |
| return this; |
| } |
| |
| /** |
| * Sets the executable path; the path is interpreted relative to the execution root, unless it's |
| * a bare file name. |
| * |
| * <p><b>Caution</b>: if the executable is a bare file name ("foo"), it will be interpreted |
| * relative to PATH. See https://github.com/bazelbuild/bazel/issues/13189 for details. To avoid |
| * that, use {@link #setExecutable(Artifact)} instead. |
| * |
| * <p>Calling this method overrides any previous values set via calls to {@link #setExecutable} |
| * or {@link #setShellCommand}. |
| */ |
| @CanIgnoreReturnValue |
| public Builder setExecutable(PathFragment executable) { |
| this.executableArg = executable; |
| this.executableArgs = null; |
| return this; |
| } |
| |
| /** |
| * Sets the executable as an artifact. |
| * |
| * <p>Calling this method overrides any previous values set via calls to {@link #setExecutable} |
| * or {@link #setShellCommand}. |
| */ |
| @CanIgnoreReturnValue |
| public Builder setExecutable(Artifact executable) { |
| addTool(executable); |
| this.executableArg = ensureCallable(executable); |
| this.executableArgs = null; |
| return this; |
| } |
| |
| /** |
| * Sets the executable as a configured target. Automatically adds the files to run to the tools |
| * and inputs and uses the executable of the target as the executable. |
| * |
| * <p>Calling this method overrides any previous values set via calls to {@link #setExecutable} |
| * or {@link #setShellCommand}. |
| */ |
| @CanIgnoreReturnValue |
| public Builder setExecutable(TransitiveInfoCollection executable) { |
| FilesToRunProvider provider = checkNotNull(executable.getProvider(FilesToRunProvider.class)); |
| return setExecutable(provider); |
| } |
| |
| /** |
| * Sets the executable as a configured target. Automatically adds the files to run to the tools |
| * and inputs and uses the executable of the target as the executable. |
| * |
| * <p>Calling this method overrides any previous values set via calls to {@link #setExecutable} |
| * or {@link #setShellCommand}. |
| */ |
| @CanIgnoreReturnValue |
| public Builder setExecutable(FilesToRunProvider executableProvider) { |
| Artifact executable = |
| checkNotNull( |
| executableProvider.getExecutable(), "The target does not have an executable"); |
| this.executableArg = ensureCallable(executable); |
| this.executableArgs = null; |
| return addTool(executableProvider); |
| } |
| |
| /** |
| * Sets the executable as a String. |
| * |
| * <p><b>Caution</b>: this is an optimisation intended to be used only by {@link |
| * com.google.devtools.build.lib.analysis.starlark.StarlarkActionFactory}. It prevents reference |
| * duplication when passing {@link PathFragment} to Starlark as a String and then executing with |
| * it. |
| * |
| * <p>Calling this method overrides any previous values set via calls to {@link #setExecutable} |
| * or {@link #setShellCommand}. |
| */ |
| @CanIgnoreReturnValue |
| public Builder setExecutableAsString(String executable) { |
| this.executableArg = executable; |
| this.executableArgs = null; |
| return this; |
| } |
| |
| /** |
| * Sets the executable to be a jar executed from the given deploy jar. The deploy jar is |
| * automatically added to the action inputs. |
| * |
| * <p>Assumes that the Jar artifact declares a main class. |
| * |
| * <p>Calling this method overrides any previous values set via calls to {@link #setExecutable} |
| * or {@link #setShellCommand}. |
| */ |
| @CanIgnoreReturnValue |
| public Builder setJarExecutable( |
| PathFragment javaExecutable, Artifact deployJar, NestedSet<String> jvmArgs) { |
| this.executableArgs = |
| CustomCommandLine.builder() |
| .addPath(javaExecutable) |
| .addAll(jvmArgs) |
| .add("-jar") |
| .addExecPath(deployJar); |
| this.executableArg = null; |
| toolsBuilder.add(deployJar); |
| return this; |
| } |
| |
| /** |
| * Sets the executable to be the shell and adds the given command as the command to be executed. |
| * |
| * <p>Note that this will not clear the arguments, so any arguments will be passed in addition |
| * to the command given here. |
| * |
| * <p>Calling this method overrides any previous values set via calls to {@link #setExecutable} |
| * or {@link #setShellCommand}. |
| */ |
| @CanIgnoreReturnValue |
| public Builder setShellCommand(PathFragment shExecutable, String command, boolean pad) { |
| this.executableArg = new ShellCommand(shExecutable, command, pad); |
| this.executableArgs = null; |
| return this; |
| } |
| |
| /** |
| * Sets the executable to be the shell and adds the given interned commands as the commands to |
| * be executed. |
| */ |
| @CanIgnoreReturnValue |
| public Builder setShellCommand(Iterable<String> command, boolean pad) { |
| this.executableArgs = CustomCommandLine.builder().addAll(ImmutableList.copyOf(command)); |
| if (pad) { |
| executableArgs.add(""); |
| } |
| this.executableArg = null; |
| return this; |
| } |
| |
| /** Appends the arguments to the list of executable arguments. */ |
| @CanIgnoreReturnValue |
| public Builder addExecutableArguments(String... arguments) { |
| if (executableArg != null) { |
| executableArgs = CustomCommandLine.builder().addObject(executableArg); |
| executableArg = null; |
| } |
| executableArgs.addAll(ImmutableList.copyOf(arguments)); |
| return this; |
| } |
| |
| /** |
| * Adds a delegate to compute the command line at a later time. |
| * |
| * <p>The arguments are added after the executable arguments. If you add multiple command lines, |
| * they are expanded in the corresponding order. |
| * |
| * <p>The main intention of this method is to save memory by allowing client-controlled sharing |
| * between actions and configured targets. Objects passed to this method MUST be immutable. |
| * |
| * <p>See also {@link CustomCommandLine}. |
| */ |
| @CanIgnoreReturnValue |
| public Builder addCommandLine(CommandLine commandLine) { |
| this.commandLines.add(new CommandLineAndParamFileInfo(commandLine, null)); |
| return this; |
| } |
| |
| /** |
| * Adds a delegate to compute the command line at a later time, optionally spilled to a params |
| * file. |
| * |
| * <p>The arguments are added after the executable arguments. If you add multiple command lines, |
| * they are expanded in the corresponding order. If the command line is spilled to a params |
| * file, it is replaced with an argument pointing to the param file. |
| * |
| * <p>The main intention of this method is to save memory by allowing client-controlled sharing |
| * between actions and configured targets. Objects passed to this method MUST be immutable. |
| * |
| * <p>See also {@link CustomCommandLine}. |
| */ |
| @CanIgnoreReturnValue |
| public Builder addCommandLine(CommandLine commandLine, @Nullable ParamFileInfo paramFileInfo) { |
| this.commandLines.add(new CommandLineAndParamFileInfo(commandLine, paramFileInfo)); |
| return this; |
| } |
| |
| /** |
| * Sets the progress message. |
| * |
| * <p>The message may contain <code>%{label}</code>, <code>%{input}</code> or <code>%{output} |
| * </code> patterns, which are substituted with label string, first input or output's path, |
| * respectively. |
| */ |
| @CanIgnoreReturnValue |
| public Builder setProgressMessage(@CompileTimeConstant String progressMessage) { |
| this.progressMessage = progressMessage; |
| return this; |
| } |
| |
| /** |
| * Sets the progress message. The string is lazily evaluated. |
| * |
| * @param progressMessage The message to display |
| * @param subject Passed to {@link String#format} |
| * @deprecated Use {@link #setProgressMessage(String)} with provided patterns. |
| */ |
| @FormatMethod |
| @Deprecated |
| @CanIgnoreReturnValue |
| public Builder setProgressMessage(@FormatString String progressMessage, Object subject) { |
| return setProgressMessage( |
| new OnDemandString() { |
| @Override |
| public String toString() { |
| return String.format(progressMessage, subject); |
| } |
| }); |
| } |
| |
| /** |
| * Sets the progress message. The string is lazily evaluated. |
| * |
| * @param progressMessage The message to display |
| * @param subject0 Passed to {@link String#format} |
| * @param subject1 Passed to {@link String#format} |
| * @deprecated Use {@link #setProgressMessage(String)} with provided patterns. |
| */ |
| @FormatMethod |
| @Deprecated |
| @CanIgnoreReturnValue |
| public Builder setProgressMessage( |
| @FormatString String progressMessage, Object subject0, Object subject1) { |
| return setProgressMessage( |
| new OnDemandString() { |
| @Override |
| public String toString() { |
| return String.format(progressMessage, subject0, subject1); |
| } |
| }); |
| } |
| |
| /** |
| * Sets the progress message. The string is lazily evaluated. |
| * |
| * @param progressMessage The message to display |
| * @param subject0 Passed to {@link String#format} |
| * @param subject1 Passed to {@link String#format} |
| * @param subject2 Passed to {@link String#format} |
| * @deprecated Use {@link #setProgressMessage(String)} with provided patterns. |
| */ |
| @FormatMethod |
| @Deprecated |
| @CanIgnoreReturnValue |
| public Builder setProgressMessage( |
| @FormatString String progressMessage, Object subject0, Object subject1, Object subject2) { |
| return setProgressMessage( |
| new OnDemandString() { |
| @Override |
| public String toString() { |
| return String.format(progressMessage, subject0, subject1, subject2); |
| } |
| }); |
| } |
| |
| /** |
| * Sets a lazily computed progress message. |
| * |
| * <p>When possible, prefer use of one of the overloads that use {@link String#format}. If you |
| * do use this overload, take care not to capture anything expensive. |
| */ |
| @CanIgnoreReturnValue |
| private Builder setProgressMessage(OnDemandString progressMessage) { |
| this.progressMessage = progressMessage; |
| return this; |
| } |
| |
| /** |
| * Sets the progress message. |
| * |
| * <p>Same as {@link #setProgressMessage(String)}, except that it may be used with non compile |
| * time constants (needed for Starlark literals). |
| */ |
| @CanIgnoreReturnValue |
| public Builder setProgressMessageFromStarlark(String progressMessage) { |
| this.progressMessage = progressMessage; |
| return this; |
| } |
| |
| @CanIgnoreReturnValue |
| public Builder setMnemonic(String mnemonic) { |
| checkArgument( |
| !mnemonic.isEmpty() && CharMatcher.javaLetterOrDigit().matchesAllOf(mnemonic), |
| "mnemonic must only contain letters and/or digits, and have non-zero length, was: \"%s\"", |
| mnemonic); |
| this.mnemonic = mnemonic; |
| return this; |
| } |
| |
| /** |
| * Sets the exec group for this action by name. This does not check that {@code execGroup} is |
| * being set to a valid exec group (i.e. one that actually exists). This method expects callers |
| * to do that work. |
| */ |
| @CanIgnoreReturnValue |
| public Builder setExecGroup(String execGroup) { |
| this.execGroup = execGroup; |
| return this; |
| } |
| } |
| |
| /** |
| * Returns a {@link CommandLineItem} for the given executable. |
| * |
| * <p>In the common case that the executable's exec path is already {@linkplain |
| * PathFragment#getCallablePathString callable} (contains {@link PathFragment#SEPARATOR_CHAR}), |
| * returns the executable as-is to avoid creating a new object. |
| * |
| * <p>The only time this method can't return {@code executable} as-is is for source artifacts in |
| * the root package, since their exec path contains no path separator. Note that derived artifacts |
| * are necessarily callable since they are always under an output directory. |
| */ |
| private static CommandLineItem ensureCallable(Artifact executable) { |
| PathFragment execPath = executable.getExecPath(); |
| return execPath.getCallablePathString().equals(executable.expandToCommandLine()) |
| ? executable |
| : execPath::getCallablePathString; |
| } |
| } |