blob: 41689a6761fa8557792fcab5d2637672ee22316a [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2014 The Bazel Authors. All rights reserved.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +01002//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package com.google.devtools.build.lib.analysis;
16
17import com.google.common.annotations.VisibleForTesting;
18import com.google.common.collect.ImmutableCollection;
19import com.google.common.collect.ImmutableList;
20import com.google.common.collect.ImmutableMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010021import com.google.common.collect.Iterables;
22import com.google.common.collect.Sets;
23import com.google.devtools.build.lib.actions.Artifact;
24import com.google.devtools.build.lib.actions.BaseSpawn;
25import com.google.devtools.build.lib.analysis.actions.FileWriteAction;
Lukacs Berki6e91eb92015-09-21 09:12:37 +000026import com.google.devtools.build.lib.cmdline.Label;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010027import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
Francois-Rene Rideauc0a8c582016-01-28 18:36:22 +000028import com.google.devtools.build.lib.syntax.SkylarkDict;
29import com.google.devtools.build.lib.syntax.SkylarkList;
30import com.google.devtools.build.lib.syntax.SkylarkList.MutableList;
Lukacs Berkiffa73ad2015-09-18 11:40:12 +000031import com.google.devtools.build.lib.syntax.Type;
Dmitry Lomov82434eb2016-01-29 12:35:12 +000032import com.google.devtools.build.lib.util.OS;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010033import com.google.devtools.build.lib.util.Pair;
34import com.google.devtools.build.lib.vfs.PathFragment;
35
36import java.util.Collection;
37import java.util.HashMap;
38import java.util.List;
39import java.util.Map;
40import java.util.Map.Entry;
41
Francois-Rene Rideau79be96f2015-10-07 19:25:38 +000042import javax.annotation.Nullable;
43
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010044/**
45 * Provides shared functionality for parameterized command-line launching
46 * e.g. {@link com.google.devtools.build.lib.view.genrule.GenRule}
47 * Also used by {@link com.google.devtools.build.lib.rules.extra.ExtraActionFactory}.
48 *
49 * Two largely independent separate sets of functionality are provided:
50 * 1- string interpolation for {@code $(location[s] ...)} and {@code $(MakeVariable)}
51 * 2- a utility to build potentially large command lines (presumably made of multiple commands),
52 * that if presumed too large for the kernel's taste can be dumped into a shell script
53 * that will contain the same commands,
54 * at which point the shell script is added to the list of inputs.
55 */
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010056public final class CommandHelper {
57
58 /**
59 * Maximum total command-line length, in bytes, not counting "/bin/bash -c ".
60 * If the command is very long, then we write the command to a script file,
61 * to avoid overflowing any limits on command-line length.
62 * For short commands, we just use /bin/bash -c command.
Dmitry Lomov82434eb2016-01-29 12:35:12 +000063 *
64 * Maximum command line length on Windows is 32767[1], but for cmd.exe it is 8192[2].
65 * [1] https://msdn.microsoft.com/en-us/library/ms682425(VS.85).aspx
66 * [2] https://support.microsoft.com/en-us/kb/830473.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010067 */
68 @VisibleForTesting
Dmitry Lomov82434eb2016-01-29 12:35:12 +000069 public static int maxCommandLength = OS.getCurrent() == OS.WINDOWS ? 8000 : 64000;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010070
71 /**
72 * A map of remote path prefixes and corresponding runfiles manifests for tools
73 * used by this rule.
74 */
Francois-Rene Rideauc0a8c582016-01-28 18:36:22 +000075 private final SkylarkDict<PathFragment, Artifact> remoteRunfileManifestMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010076
77 /**
78 * Use labelMap for heuristically expanding labels (does not include "outs")
79 * This is similar to heuristic location expansion in LocationExpander
80 * and should be kept in sync.
81 */
Francois-Rene Rideauc0a8c582016-01-28 18:36:22 +000082 private final SkylarkDict<Label, ImmutableCollection<Artifact>> labelMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010083
84 /**
85 * The ruleContext this helper works on
86 */
87 private final RuleContext ruleContext;
88
89 /**
90 * Output executable files from the 'tools' attribute.
91 */
Francois-Rene Rideauc0a8c582016-01-28 18:36:22 +000092 private final SkylarkList<Artifact> resolvedTools;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010093
94 /**
Googlerf49bb0c2015-02-11 16:18:28 +000095 * Creates a {@link CommandHelper}.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010096 *
Googlerf49bb0c2015-02-11 16:18:28 +000097 * @param tools resolves set of tools into set of executable binaries. Populates manifests,
98 * remoteRunfiles and label map where required.
99 * @param labelMap adds files to set of known files of label. Used for resolving $(location)
100 * variables.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100101 */
Brian Silverman53c3ce12015-08-28 09:17:14 +0000102 public CommandHelper(
103 RuleContext ruleContext,
104 Iterable<? extends TransitiveInfoCollection> tools,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100105 ImmutableMap<Label, Iterable<Artifact>> labelMap) {
106 this.ruleContext = ruleContext;
107
108 ImmutableList.Builder<Artifact> resolvedToolsBuilder = ImmutableList.builder();
109 ImmutableMap.Builder<PathFragment, Artifact> remoteRunfileManifestBuilder =
110 ImmutableMap.builder();
111 Map<Label, Collection<Artifact>> tempLabelMap = new HashMap<>();
112
113 for (Map.Entry<Label, Iterable<Artifact>> entry : labelMap.entrySet()) {
114 Iterables.addAll(mapGet(tempLabelMap, entry.getKey()), entry.getValue());
115 }
116
Brian Silverman53c3ce12015-08-28 09:17:14 +0000117 for (TransitiveInfoCollection dep : tools) { // (Note: host configuration)
118 Label label = dep.getLabel();
119 FilesToRunProvider tool = dep.getProvider(FilesToRunProvider.class);
120 if (tool == null) {
121 continue;
122 }
123
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100124 Collection<Artifact> files = tool.getFilesToRun();
125 resolvedToolsBuilder.addAll(files);
126 Artifact executableArtifact = tool.getExecutable();
127 // If the label has an executable artifact add that to the multimaps.
128 if (executableArtifact != null) {
129 mapGet(tempLabelMap, label).add(executableArtifact);
130 // Also send the runfiles when running remotely.
131 Artifact runfilesManifest = tool.getRunfilesManifest();
132 if (runfilesManifest != null) {
133 remoteRunfileManifestBuilder.put(
134 BaseSpawn.runfilesForFragment(executableArtifact.getExecPath()), runfilesManifest);
135 }
136 } else {
137 // Map all depArtifacts to the respective label using the multimaps.
Ulf Adams07dba942015-03-05 14:47:37 +0000138 mapGet(tempLabelMap, label).addAll(files);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100139 }
140 }
141
Francois-Rene Rideauc0a8c582016-01-28 18:36:22 +0000142 this.resolvedTools = new MutableList(resolvedToolsBuilder.build());
143 this.remoteRunfileManifestMap = SkylarkDict.copyOf(null, remoteRunfileManifestBuilder.build());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100144 ImmutableMap.Builder<Label, ImmutableCollection<Artifact>> labelMapBuilder =
145 ImmutableMap.builder();
146 for (Entry<Label, Collection<Artifact>> entry : tempLabelMap.entrySet()) {
147 labelMapBuilder.put(entry.getKey(), ImmutableList.copyOf(entry.getValue()));
148 }
Francois-Rene Rideauc0a8c582016-01-28 18:36:22 +0000149 this.labelMap = SkylarkDict.copyOf(null, labelMapBuilder.build());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100150 }
151
Francois-Rene Rideauc0a8c582016-01-28 18:36:22 +0000152 public SkylarkList<Artifact> getResolvedTools() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100153 return resolvedTools;
154 }
155
Francois-Rene Rideauc0a8c582016-01-28 18:36:22 +0000156 public SkylarkDict<PathFragment, Artifact> getRemoteRunfileManifestMap() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100157 return remoteRunfileManifestMap;
158 }
159
160 // Returns the value in the specified corresponding to 'key', creating and
161 // inserting an empty container if absent. We use Map not Multimap because
162 // we need to distinguish the cases of "empty value" and "absent key".
163 private static Collection<Artifact> mapGet(Map<Label, Collection<Artifact>> map, Label key) {
164 Collection<Artifact> values = map.get(key);
165 if (values == null) {
166 // We use sets not lists, because it's conceivable that the same artifact
167 // could appear twice, e.g. in "srcs" and "deps".
168 values = Sets.newHashSet();
169 map.put(key, values);
170 }
171 return values;
172 }
173
174 /**
Francois-Rene Rideau79be96f2015-10-07 19:25:38 +0000175 * Resolves a command, and expands known locations for $(location)
176 * variables.
177 */
178 public String resolveCommandAndExpandLabels(
179 String command,
180 @Nullable String attribute,
181 Boolean supportLegacyExpansion,
182 Boolean allowDataInLabel) {
Francois-Rene Rideauc0a8c582016-01-28 18:36:22 +0000183 LocationExpander expander = new LocationExpander(
184 ruleContext, ImmutableMap.copyOf(labelMap), allowDataInLabel);
Francois-Rene Rideau79be96f2015-10-07 19:25:38 +0000185 if (attribute != null) {
186 command = expander.expandAttribute(attribute, command);
187 } else {
188 command = expander.expand(command);
189 }
190 if (supportLegacyExpansion) {
191 command = expandLabels(command, labelMap);
192 }
193 return command;
194 }
195
196 /**
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100197 * Resolves the 'cmd' attribute, and expands known locations for $(location)
198 * variables.
199 */
Florian Weikert05698b82015-07-10 09:06:12 +0000200 public String resolveCommandAndExpandLabels(
201 Boolean supportLegacyExpansion, Boolean allowDataInLabel) {
Francois-Rene Rideau79be96f2015-10-07 19:25:38 +0000202 return resolveCommandAndExpandLabels(
203 ruleContext.attributes().get("cmd", Type.STRING),
204 "cmd",
205 supportLegacyExpansion,
206 allowDataInLabel);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100207 }
208
209 /**
210 * Expands labels occurring in the string "expr" in the rule 'cmd'.
211 * Each label must be valid, be a declared prerequisite, and expand to a
212 * unique path.
213 *
214 * <p>If the expansion fails, an attribute error is reported and the original
215 * expression is returned.
216 */
217 private <T extends Iterable<Artifact>> String expandLabels(String expr, Map<Label, T> labelMap) {
218 try {
219 return LabelExpander.expand(expr, labelMap, ruleContext.getLabel());
220 } catch (LabelExpander.NotUniqueExpansionException nuee) {
221 ruleContext.attributeError("cmd", nuee.getMessage());
222 return expr;
223 }
224 }
225
226 private static Pair<List<String>, Artifact> buildCommandLineMaybeWithScriptFile(
Googlerf49bb0c2015-02-11 16:18:28 +0000227 RuleContext ruleContext, String command, String scriptPostFix, PathFragment shellPath) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100228 List<String> argv;
229 Artifact scriptFileArtifact = null;
230 if (command.length() <= maxCommandLength) {
Googlerf49bb0c2015-02-11 16:18:28 +0000231 argv = buildCommandLineSimpleArgv(command, shellPath);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100232 } else {
233 // Use script file.
234 scriptFileArtifact = buildCommandLineArtifact(ruleContext, command, scriptPostFix);
Googlerf49bb0c2015-02-11 16:18:28 +0000235 argv = buildCommandLineArgvWithArtifact(scriptFileArtifact, shellPath);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100236 }
237 return Pair.of(argv, scriptFileArtifact);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100238 }
239
Googlerf49bb0c2015-02-11 16:18:28 +0000240 private static ImmutableList<String> buildCommandLineArgvWithArtifact(Artifact scriptFileArtifact,
241 PathFragment shellPath) {
242 return ImmutableList.of(shellPath.getPathString(), scriptFileArtifact.getExecPathString());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100243 }
244
245 private static Artifact buildCommandLineArtifact(RuleContext ruleContext, String command,
246 String scriptPostFix) {
247 String scriptFileName = ruleContext.getTarget().getName() + scriptPostFix;
248 String scriptFileContents = "#!/bin/bash\n" + command;
249 Artifact scriptFileArtifact = FileWriteAction.createFile(
250 ruleContext, scriptFileName, scriptFileContents, /*executable=*/true);
251 return scriptFileArtifact;
252 }
253
Googlerf49bb0c2015-02-11 16:18:28 +0000254 private static ImmutableList<String> buildCommandLineSimpleArgv(String command,
255 PathFragment shellPath) {
256 return ImmutableList.of(shellPath.getPathString(), "-c", command);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100257 }
258
259 /**
260 * Builds the set of command-line arguments. Creates a bash script if the
Googlerf49bb0c2015-02-11 16:18:28 +0000261 * command line is longer than the allowed maximum {@link #maxCommandLength}.
262 * Fixes up the input artifact list with the created bash script when required.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100263 */
264 public List<String> buildCommandLine(
265 String command, NestedSetBuilder<Artifact> inputs, String scriptPostFix) {
Googlere6402cb2015-04-13 14:54:27 +0000266 return buildCommandLine(command, inputs, scriptPostFix, ImmutableMap.<String, String>of());
Googlerf49bb0c2015-02-11 16:18:28 +0000267 }
268
269 /**
270 * Builds the set of command-line arguments using the specified shell path. Creates a bash script
271 * if the command line is longer than the allowed maximum {@link #maxCommandLength}.
272 * Fixes up the input artifact list with the created bash script when required.
273 *
Googlere6402cb2015-04-13 14:54:27 +0000274 * @param executionInfo an execution info map of the action associated with the command line to be
275 * built.
Googlerf49bb0c2015-02-11 16:18:28 +0000276 */
277 public List<String> buildCommandLine(
278 String command, NestedSetBuilder<Artifact> inputs, String scriptPostFix,
Googlere6402cb2015-04-13 14:54:27 +0000279 Map<String, String> executionInfo) {
280 // Use vanilla /bin/bash for actions running on mac machines.
281 PathFragment shellPath = executionInfo.containsKey("requires-darwin")
282 ? new PathFragment("/bin/bash") : ruleContext.getConfiguration().getShExecutable();
Googlerf49bb0c2015-02-11 16:18:28 +0000283 Pair<List<String>, Artifact> argvAndScriptFile =
284 buildCommandLineMaybeWithScriptFile(ruleContext, command, scriptPostFix, shellPath);
285 if (argvAndScriptFile.second != null) {
286 inputs.add(argvAndScriptFile.second);
287 }
288 return argvAndScriptFile.first;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100289 }
290
291 /**
292 * Builds the set of command-line arguments. Creates a bash script if the
Googlerf49bb0c2015-02-11 16:18:28 +0000293 * command line is longer than the allowed maximum {@link #maxCommandLength}.
294 * Fixes up the input artifact list with the created bash script when required.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100295 */
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100296 public List<String> buildCommandLine(
297 String command, List<Artifact> inputs, String scriptPostFix) {
Googlerf49bb0c2015-02-11 16:18:28 +0000298 Pair<List<String>, Artifact> argvAndScriptFile = buildCommandLineMaybeWithScriptFile(
299 ruleContext, command, scriptPostFix, ruleContext.getConfiguration().getShExecutable());
300 if (argvAndScriptFile.second != null) {
301 inputs.add(argvAndScriptFile.second);
302 }
303 return argvAndScriptFile.first;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100304 }
305}