blob: eb4208aef48f31410590aaa7c7ee3b769a9a12b3 [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;
Lukacs Berki7894c182016-05-10 12:07:01 +000028import com.google.devtools.build.lib.rules.AliasProvider;
Francois-Rene Rideauab049e02016-02-17 16:13:46 +000029import com.google.devtools.build.lib.syntax.SkylarkDict;
30import com.google.devtools.build.lib.syntax.SkylarkList;
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;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010035import java.util.Collection;
36import java.util.HashMap;
37import java.util.List;
38import java.util.Map;
39import java.util.Map.Entry;
Francois-Rene Rideau79be96f2015-10-07 19:25:38 +000040import javax.annotation.Nullable;
41
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010042/**
43 * Provides shared functionality for parameterized command-line launching
44 * e.g. {@link com.google.devtools.build.lib.view.genrule.GenRule}
45 * Also used by {@link com.google.devtools.build.lib.rules.extra.ExtraActionFactory}.
46 *
47 * Two largely independent separate sets of functionality are provided:
48 * 1- string interpolation for {@code $(location[s] ...)} and {@code $(MakeVariable)}
49 * 2- a utility to build potentially large command lines (presumably made of multiple commands),
50 * that if presumed too large for the kernel's taste can be dumped into a shell script
51 * that will contain the same commands,
52 * at which point the shell script is added to the list of inputs.
53 */
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010054public final class CommandHelper {
55
56 /**
57 * Maximum total command-line length, in bytes, not counting "/bin/bash -c ".
58 * If the command is very long, then we write the command to a script file,
59 * to avoid overflowing any limits on command-line length.
60 * For short commands, we just use /bin/bash -c command.
Dmitry Lomov82434eb2016-01-29 12:35:12 +000061 *
62 * Maximum command line length on Windows is 32767[1], but for cmd.exe it is 8192[2].
63 * [1] https://msdn.microsoft.com/en-us/library/ms682425(VS.85).aspx
64 * [2] https://support.microsoft.com/en-us/kb/830473.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010065 */
66 @VisibleForTesting
Dmitry Lomov82434eb2016-01-29 12:35:12 +000067 public static int maxCommandLength = OS.getCurrent() == OS.WINDOWS ? 8000 : 64000;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010068
69 /**
70 * A map of remote path prefixes and corresponding runfiles manifests for tools
71 * used by this rule.
72 */
Francois-Rene Rideauab049e02016-02-17 16:13:46 +000073 private final SkylarkDict<PathFragment, Artifact> remoteRunfileManifestMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010074
75 /**
76 * Use labelMap for heuristically expanding labels (does not include "outs")
77 * This is similar to heuristic location expansion in LocationExpander
78 * and should be kept in sync.
79 */
Francois-Rene Rideauab049e02016-02-17 16:13:46 +000080 private final SkylarkDict<Label, ImmutableCollection<Artifact>> labelMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010081
82 /**
83 * The ruleContext this helper works on
84 */
85 private final RuleContext ruleContext;
86
87 /**
88 * Output executable files from the 'tools' attribute.
89 */
Francois-Rene Rideauab049e02016-02-17 16:13:46 +000090 private final SkylarkList<Artifact> resolvedTools;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010091
92 /**
Googlerf49bb0c2015-02-11 16:18:28 +000093 * Creates a {@link CommandHelper}.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010094 *
Googlerf49bb0c2015-02-11 16:18:28 +000095 * @param tools resolves set of tools into set of executable binaries. Populates manifests,
96 * remoteRunfiles and label map where required.
97 * @param labelMap adds files to set of known files of label. Used for resolving $(location)
98 * variables.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010099 */
Brian Silverman53c3ce12015-08-28 09:17:14 +0000100 public CommandHelper(
101 RuleContext ruleContext,
102 Iterable<? extends TransitiveInfoCollection> tools,
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100103 ImmutableMap<Label, Iterable<Artifact>> labelMap) {
104 this.ruleContext = ruleContext;
105
106 ImmutableList.Builder<Artifact> resolvedToolsBuilder = ImmutableList.builder();
107 ImmutableMap.Builder<PathFragment, Artifact> remoteRunfileManifestBuilder =
108 ImmutableMap.builder();
109 Map<Label, Collection<Artifact>> tempLabelMap = new HashMap<>();
110
111 for (Map.Entry<Label, Iterable<Artifact>> entry : labelMap.entrySet()) {
112 Iterables.addAll(mapGet(tempLabelMap, entry.getKey()), entry.getValue());
113 }
114
Brian Silverman53c3ce12015-08-28 09:17:14 +0000115 for (TransitiveInfoCollection dep : tools) { // (Note: host configuration)
Lukacs Berki7894c182016-05-10 12:07:01 +0000116 Label label = AliasProvider.getDependencyLabel(dep);
Brian Silverman53c3ce12015-08-28 09:17:14 +0000117 FilesToRunProvider tool = dep.getProvider(FilesToRunProvider.class);
118 if (tool == null) {
119 continue;
120 }
121
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100122 Collection<Artifact> files = tool.getFilesToRun();
123 resolvedToolsBuilder.addAll(files);
124 Artifact executableArtifact = tool.getExecutable();
125 // If the label has an executable artifact add that to the multimaps.
126 if (executableArtifact != null) {
127 mapGet(tempLabelMap, label).add(executableArtifact);
128 // Also send the runfiles when running remotely.
129 Artifact runfilesManifest = tool.getRunfilesManifest();
130 if (runfilesManifest != null) {
131 remoteRunfileManifestBuilder.put(
132 BaseSpawn.runfilesForFragment(executableArtifact.getExecPath()), runfilesManifest);
133 }
134 } else {
135 // Map all depArtifacts to the respective label using the multimaps.
Ulf Adams07dba942015-03-05 14:47:37 +0000136 mapGet(tempLabelMap, label).addAll(files);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100137 }
138 }
139
Dmitry Lomov5fd7da52016-09-05 09:44:48 +0000140 this.resolvedTools = SkylarkList.createImmutable(resolvedToolsBuilder.build());
Francois-Rene Rideauab049e02016-02-17 16:13:46 +0000141 this.remoteRunfileManifestMap = SkylarkDict.copyOf(null, remoteRunfileManifestBuilder.build());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100142 ImmutableMap.Builder<Label, ImmutableCollection<Artifact>> labelMapBuilder =
143 ImmutableMap.builder();
144 for (Entry<Label, Collection<Artifact>> entry : tempLabelMap.entrySet()) {
145 labelMapBuilder.put(entry.getKey(), ImmutableList.copyOf(entry.getValue()));
146 }
Francois-Rene Rideauab049e02016-02-17 16:13:46 +0000147 this.labelMap = SkylarkDict.copyOf(null, labelMapBuilder.build());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100148 }
149
Francois-Rene Rideauab049e02016-02-17 16:13:46 +0000150 public SkylarkList<Artifact> getResolvedTools() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100151 return resolvedTools;
152 }
153
Francois-Rene Rideauab049e02016-02-17 16:13:46 +0000154 public SkylarkDict<PathFragment, Artifact> getRemoteRunfileManifestMap() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100155 return remoteRunfileManifestMap;
156 }
157
158 // Returns the value in the specified corresponding to 'key', creating and
159 // inserting an empty container if absent. We use Map not Multimap because
160 // we need to distinguish the cases of "empty value" and "absent key".
161 private static Collection<Artifact> mapGet(Map<Label, Collection<Artifact>> map, Label key) {
162 Collection<Artifact> values = map.get(key);
163 if (values == null) {
164 // We use sets not lists, because it's conceivable that the same artifact
165 // could appear twice, e.g. in "srcs" and "deps".
166 values = Sets.newHashSet();
167 map.put(key, values);
168 }
169 return values;
170 }
171
172 /**
Francois-Rene Rideau79be96f2015-10-07 19:25:38 +0000173 * Resolves a command, and expands known locations for $(location)
174 * variables.
175 */
176 public String resolveCommandAndExpandLabels(
177 String command,
178 @Nullable String attribute,
179 Boolean supportLegacyExpansion,
180 Boolean allowDataInLabel) {
Francois-Rene Rideauab049e02016-02-17 16:13:46 +0000181 LocationExpander expander = new LocationExpander(
182 ruleContext, ImmutableMap.copyOf(labelMap), allowDataInLabel);
Francois-Rene Rideau79be96f2015-10-07 19:25:38 +0000183 if (attribute != null) {
184 command = expander.expandAttribute(attribute, command);
185 } else {
186 command = expander.expand(command);
187 }
188 if (supportLegacyExpansion) {
189 command = expandLabels(command, labelMap);
190 }
191 return command;
192 }
193
194 /**
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100195 * Resolves the 'cmd' attribute, and expands known locations for $(location)
196 * variables.
197 */
Florian Weikert05698b82015-07-10 09:06:12 +0000198 public String resolveCommandAndExpandLabels(
199 Boolean supportLegacyExpansion, Boolean allowDataInLabel) {
Francois-Rene Rideau79be96f2015-10-07 19:25:38 +0000200 return resolveCommandAndExpandLabels(
201 ruleContext.attributes().get("cmd", Type.STRING),
202 "cmd",
203 supportLegacyExpansion,
204 allowDataInLabel);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100205 }
206
207 /**
208 * Expands labels occurring in the string "expr" in the rule 'cmd'.
209 * Each label must be valid, be a declared prerequisite, and expand to a
210 * unique path.
211 *
212 * <p>If the expansion fails, an attribute error is reported and the original
213 * expression is returned.
214 */
215 private <T extends Iterable<Artifact>> String expandLabels(String expr, Map<Label, T> labelMap) {
216 try {
217 return LabelExpander.expand(expr, labelMap, ruleContext.getLabel());
218 } catch (LabelExpander.NotUniqueExpansionException nuee) {
219 ruleContext.attributeError("cmd", nuee.getMessage());
220 return expr;
221 }
222 }
223
224 private static Pair<List<String>, Artifact> buildCommandLineMaybeWithScriptFile(
Googlerf49bb0c2015-02-11 16:18:28 +0000225 RuleContext ruleContext, String command, String scriptPostFix, PathFragment shellPath) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100226 List<String> argv;
227 Artifact scriptFileArtifact = null;
228 if (command.length() <= maxCommandLength) {
Googlerf49bb0c2015-02-11 16:18:28 +0000229 argv = buildCommandLineSimpleArgv(command, shellPath);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100230 } else {
231 // Use script file.
232 scriptFileArtifact = buildCommandLineArtifact(ruleContext, command, scriptPostFix);
Googlerf49bb0c2015-02-11 16:18:28 +0000233 argv = buildCommandLineArgvWithArtifact(scriptFileArtifact, shellPath);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100234 }
235 return Pair.of(argv, scriptFileArtifact);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100236 }
237
Googlerf49bb0c2015-02-11 16:18:28 +0000238 private static ImmutableList<String> buildCommandLineArgvWithArtifact(Artifact scriptFileArtifact,
239 PathFragment shellPath) {
240 return ImmutableList.of(shellPath.getPathString(), scriptFileArtifact.getExecPathString());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100241 }
242
243 private static Artifact buildCommandLineArtifact(RuleContext ruleContext, String command,
244 String scriptPostFix) {
245 String scriptFileName = ruleContext.getTarget().getName() + scriptPostFix;
246 String scriptFileContents = "#!/bin/bash\n" + command;
247 Artifact scriptFileArtifact = FileWriteAction.createFile(
248 ruleContext, scriptFileName, scriptFileContents, /*executable=*/true);
249 return scriptFileArtifact;
250 }
251
Googlerf49bb0c2015-02-11 16:18:28 +0000252 private static ImmutableList<String> buildCommandLineSimpleArgv(String command,
253 PathFragment shellPath) {
254 return ImmutableList.of(shellPath.getPathString(), "-c", command);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100255 }
256
257 /**
258 * Builds the set of command-line arguments. Creates a bash script if the
Googlerf49bb0c2015-02-11 16:18:28 +0000259 * command line is longer than the allowed maximum {@link #maxCommandLength}.
260 * Fixes up the input artifact list with the created bash script when required.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100261 */
262 public List<String> buildCommandLine(
263 String command, NestedSetBuilder<Artifact> inputs, String scriptPostFix) {
Googlere6402cb2015-04-13 14:54:27 +0000264 return buildCommandLine(command, inputs, scriptPostFix, ImmutableMap.<String, String>of());
Googlerf49bb0c2015-02-11 16:18:28 +0000265 }
266
267 /**
268 * Builds the set of command-line arguments using the specified shell path. Creates a bash script
269 * if the command line is longer than the allowed maximum {@link #maxCommandLength}.
270 * Fixes up the input artifact list with the created bash script when required.
271 *
Googlere6402cb2015-04-13 14:54:27 +0000272 * @param executionInfo an execution info map of the action associated with the command line to be
273 * built.
Googlerf49bb0c2015-02-11 16:18:28 +0000274 */
275 public List<String> buildCommandLine(
276 String command, NestedSetBuilder<Artifact> inputs, String scriptPostFix,
Googlere6402cb2015-04-13 14:54:27 +0000277 Map<String, String> executionInfo) {
Googlerf49bb0c2015-02-11 16:18:28 +0000278 Pair<List<String>, Artifact> argvAndScriptFile =
Chris Parsons9a9573b2016-02-10 21:15:11 +0000279 buildCommandLineMaybeWithScriptFile(ruleContext, command, scriptPostFix,
280 shellPath(executionInfo));
Googlerf49bb0c2015-02-11 16:18:28 +0000281 if (argvAndScriptFile.second != null) {
282 inputs.add(argvAndScriptFile.second);
283 }
284 return argvAndScriptFile.first;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100285 }
286
287 /**
288 * Builds the set of command-line arguments. Creates a bash script if the
Googlerf49bb0c2015-02-11 16:18:28 +0000289 * command line is longer than the allowed maximum {@link #maxCommandLength}.
290 * Fixes up the input artifact list with the created bash script when required.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100291 */
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100292 public List<String> buildCommandLine(
Chris Parsons9a9573b2016-02-10 21:15:11 +0000293 String command, List<Artifact> inputs, String scriptPostFix,
294 Map<String, String> executionInfo) {
Googlerf49bb0c2015-02-11 16:18:28 +0000295 Pair<List<String>, Artifact> argvAndScriptFile = buildCommandLineMaybeWithScriptFile(
Chris Parsons9a9573b2016-02-10 21:15:11 +0000296 ruleContext, command, scriptPostFix, shellPath(executionInfo));
Googlerf49bb0c2015-02-11 16:18:28 +0000297 if (argvAndScriptFile.second != null) {
298 inputs.add(argvAndScriptFile.second);
299 }
300 return argvAndScriptFile.first;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100301 }
Chris Parsons9a9573b2016-02-10 21:15:11 +0000302
303 /**
304 * Returns the path to the shell for an action with the given execution requirements.
305 */
306 private PathFragment shellPath(Map<String, String> executionInfo) {
307 // Use vanilla /bin/bash for actions running on mac machines.
308 return executionInfo.containsKey("requires-darwin")
Lukacs Berkiae7be6f2016-09-21 13:21:56 +0000309 ? new PathFragment("/bin/bash") : ruleContext.getConfiguration().getShellExecutable();
Chris Parsons9a9573b2016-02-10 21:15:11 +0000310 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100311}