// 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.actions;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.actions.Artifact.ArtifactExpander;
import com.google.devtools.build.lib.actions.ParameterFile.ParameterFileType;
import com.google.devtools.build.lib.actions.cache.VirtualActionInput;
import com.google.devtools.build.lib.collect.IterablesChain;
import com.google.devtools.build.lib.util.Fingerprint;
import com.google.devtools.build.lib.vfs.PathFragment;
import com.google.protobuf.ByteString;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import javax.annotation.Nullable;
/**
* A class that keeps a list of command lines and optional associated parameter file info.
*
*
This class is used by {@link com.google.devtools.build.lib.exec.SpawnRunner} implementations
* to expand the command lines into a master argument list + any param files needed to be written.
*/
public class CommandLines {
// A (hopefully) conservative estimate of how much long each param file arg would be
// eg. the length of '@path/to/param_file'.
private static final int PARAM_FILE_ARG_LENGTH_ESTIMATE = 512;
private static final UUID PARAM_FILE_UUID =
UUID.fromString("106c1389-88d7-4cc1-8f05-f8a61fd8f7b1");
/** Command line OS limitations, such as the max length. */
public static class CommandLineLimits {
/**
* "Unlimited" command line limits.
*
*
Use these limits when you want to prohibit param files, or you don't use param files so
* you don't care what the limit is.
*/
public static final CommandLineLimits UNLIMITED = new CommandLineLimits(Integer.MAX_VALUE);
public final int maxLength;
public CommandLineLimits(int maxLength) {
this.maxLength = maxLength;
}
}
/** A simple tuple of a {@link CommandLine} and a {@link ParamFileInfo}. */
public static class CommandLineAndParamFileInfo {
public final CommandLine commandLine;
@Nullable public final ParamFileInfo paramFileInfo;
public CommandLineAndParamFileInfo(
CommandLine commandLine, @Nullable ParamFileInfo paramFileInfo) {
this.commandLine = commandLine;
this.paramFileInfo = paramFileInfo;
}
}
/**
* Memory optimization: Store as Object instead of List.
*
*
We store either a single CommandLine or CommandLineAndParamFileInfo, or list of Objects
* where each item is either a CommandLine or CommandLineAndParamFileInfo. This minimizes unneeded
* wrapper objects.
*
*
In the case of actions with a single CommandLine, this saves 48 bytes per action.
*/
private final Object commandLines;
private CommandLines(Object commandLines) {
this.commandLines = commandLines;
}
/**
* Expands this object into a single primary command line and (0-N) param files. The spawn runner
* is expected to write these param files prior to execution of an action.
*
* @param artifactExpander The artifact expander to use.
* @param paramFileBasePath Used to derive param file names. Often the first output of an action.
* @param limits The command line limits the host OS can support.
* @return The expanded command line and its param files (if any).
*/
public ExpandedCommandLines expand(
ArtifactExpander artifactExpander, PathFragment paramFileBasePath, CommandLineLimits limits)
throws CommandLineExpansionException {
return expand(artifactExpander, paramFileBasePath, limits, PARAM_FILE_ARG_LENGTH_ESTIMATE);
}
/**
* Returns all arguments, including ones inside of param files.
*
*
Suitable for debugging and printing messages to users. This expands all command lines, so it
* is potentially expensive.
*/
public ImmutableList allArguments() throws CommandLineExpansionException {
ImmutableList.Builder arguments = ImmutableList.builder();
for (CommandLineAndParamFileInfo pair : getCommandLines()) {
arguments.addAll(pair.commandLine.arguments());
}
return arguments.build();
}
@VisibleForTesting
ExpandedCommandLines expand(
ArtifactExpander artifactExpander,
PathFragment paramFileBasePath,
CommandLineLimits limits,
int paramFileArgLengthEstimate)
throws CommandLineExpansionException {
// Optimize for simple case of single command line
if (commandLines instanceof CommandLine) {
CommandLine commandLine = (CommandLine) commandLines;
Iterable arguments = commandLine.arguments(artifactExpander);
return new ExpandedCommandLines(arguments, ImmutableList.of());
}
List commandLines = getCommandLines();
IterablesChain.Builder arguments = IterablesChain.builder();
ArrayList paramFiles = new ArrayList<>(commandLines.size());
int conservativeMaxLength = limits.maxLength - commandLines.size() * paramFileArgLengthEstimate;
int cmdLineLength = 0;
// We name based on the output, starting at