blob: 1da734f15e8846eacd8e8ef3ff9d9594efa6f140 [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;
ulfjack7e7d94e2017-06-06 05:08:49 -040024import com.google.devtools.build.lib.actions.ExecutionRequirements;
Michajlo Matijkiw4a877382017-01-27 19:30:34 +000025import com.google.devtools.build.lib.actions.RunfilesSupplier;
Lukacs Berki6e91eb92015-09-21 09:12:37 +000026import com.google.devtools.build.lib.cmdline.Label;
lberki20d68f02018-09-19 06:30:24 -070027import com.google.devtools.build.lib.collect.nestedset.NestedSet;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010028import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
Dmitry Lomov82434eb2016-01-29 12:35:12 +000029import com.google.devtools.build.lib.util.OS;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010030import com.google.devtools.build.lib.util.Pair;
31import com.google.devtools.build.lib.vfs.PathFragment;
Kurt Alfred Kluever4192ca62022-07-05 06:30:29 -070032import com.google.errorprone.annotations.CanIgnoreReturnValue;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010033import java.util.Collection;
34import java.util.HashMap;
35import java.util.List;
36import java.util.Map;
Francois-Rene Rideau79be96f2015-10-07 19:25:38 +000037import javax.annotation.Nullable;
adonovan450c7ad2020-09-14 13:00:21 -070038import net.starlark.java.eval.Sequence;
39import net.starlark.java.eval.StarlarkList;
Francois-Rene Rideau79be96f2015-10-07 19:25:38 +000040
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010041/**
John Cater1ba41a82017-01-20 16:19:53 +000042 * Provides shared functionality for parameterized command-line launching.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010043 * Also used by {@link com.google.devtools.build.lib.rules.extra.ExtraActionFactory}.
44 *
45 * Two largely independent separate sets of functionality are provided:
46 * 1- string interpolation for {@code $(location[s] ...)} and {@code $(MakeVariable)}
47 * 2- a utility to build potentially large command lines (presumably made of multiple commands),
48 * that if presumed too large for the kernel's taste can be dumped into a shell script
49 * that will contain the same commands,
50 * at which point the shell script is added to the list of inputs.
51 */
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +010052public final class CommandHelper {
53
54 /**
jcater71e46d02018-08-21 00:43:31 -070055 * Returns a new {@link Builder} to create a {@link CommandHelper} based on the given {@link
56 * RuleContext}.
57 */
58 public static Builder builder(RuleContext ruleContext) {
59 return new Builder(ruleContext);
60 }
61
62 /**
63 * Builder class to assist with creating an instance of {@link CommandHelper}. The Builder can
64 * optionally add additional tools as dependencies, and a map of labels to be resolved.
65 */
66 public static final class Builder {
67 private final RuleContext ruleContext;
68 private final ImmutableList.Builder<Iterable<? extends TransitiveInfoCollection>>
69 toolDependencies = ImmutableList.builder();
70 private final ImmutableMap.Builder<Label, Iterable<Artifact>> labelMap = ImmutableMap.builder();
71
72 private Builder(RuleContext ruleContext) {
73 this.ruleContext = ruleContext;
74 }
75
76 /**
77 * Adds tools, as a set of executable binaries, by fetching them from the given attribute on the
Googler92f0d6a2023-01-06 09:45:16 -080078 * {@code ruleContext}. Populates manifests, remoteRunfiles and label map where required.
jcater71e46d02018-08-21 00:43:31 -070079 */
jcatercb8e89b2019-06-05 11:19:25 -070080 public Builder addToolDependencies(String toolAttributeName) {
81 List<? extends TransitiveInfoCollection> dependencies =
jcatere9168c42020-08-31 10:35:12 -070082 ruleContext.getPrerequisites(toolAttributeName);
jcatercb8e89b2019-06-05 11:19:25 -070083 return addToolDependencies(dependencies);
84 }
85
86 /**
jcater71e46d02018-08-21 00:43:31 -070087 * Adds tools, as a set of executable binaries. Populates manifests, remoteRunfiles and label
88 * map where required.
89 */
Kurt Alfred Kluever4192ca62022-07-05 06:30:29 -070090 @CanIgnoreReturnValue
jcater71e46d02018-08-21 00:43:31 -070091 public Builder addToolDependencies(
92 Iterable<? extends TransitiveInfoCollection> toolDependencies) {
93 this.toolDependencies.add(toolDependencies);
94 return this;
95 }
96
97 /** Adds files to set of known files of label. Used for resolving $(location) variables. */
Kurt Alfred Kluever4192ca62022-07-05 06:30:29 -070098 @CanIgnoreReturnValue
jcater71e46d02018-08-21 00:43:31 -070099 public Builder addLabelMap(Map<Label, ? extends Iterable<Artifact>> labelMap) {
100 this.labelMap.putAll(labelMap);
101 return this;
102 }
103
104 /** Returns the built {@link CommandHelper}. */
105 public CommandHelper build() {
Googler92ce1152022-02-01 06:37:06 -0800106 return new CommandHelper(ruleContext, toolDependencies.build(), labelMap.buildOrThrow());
jcater71e46d02018-08-21 00:43:31 -0700107 }
108 }
109
110 /**
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100111 * Maximum total command-line length, in bytes, not counting "/bin/bash -c ".
112 * If the command is very long, then we write the command to a script file,
113 * to avoid overflowing any limits on command-line length.
114 * For short commands, we just use /bin/bash -c command.
Dmitry Lomov82434eb2016-01-29 12:35:12 +0000115 *
116 * Maximum command line length on Windows is 32767[1], but for cmd.exe it is 8192[2].
117 * [1] https://msdn.microsoft.com/en-us/library/ms682425(VS.85).aspx
118 * [2] https://support.microsoft.com/en-us/kb/830473.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100119 */
120 @VisibleForTesting
Dmitry Lomov82434eb2016-01-29 12:35:12 +0000121 public static int maxCommandLength = OS.getCurrent() == OS.WINDOWS ? 8000 : 64000;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100122
Michajlo Matijkiw4a877382017-01-27 19:30:34 +0000123 /** {@link RunfilesSupplier}s for tools used by this rule. */
Googler4871cb02019-11-12 17:16:42 -0800124 private final Sequence<RunfilesSupplier> toolsRunfilesSuppliers;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100125
126 /**
127 * Use labelMap for heuristically expanding labels (does not include "outs")
128 * This is similar to heuristic location expansion in LocationExpander
129 * and should be kept in sync.
130 */
ulfjackaf677742017-10-02 10:55:41 +0200131 private final ImmutableMap<Label, ImmutableCollection<Artifact>> labelMap;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100132
133 /**
134 * The ruleContext this helper works on
135 */
136 private final RuleContext ruleContext;
137
138 /**
139 * Output executable files from the 'tools' attribute.
140 */
lberki20d68f02018-09-19 06:30:24 -0700141 private final NestedSet<Artifact> resolvedTools;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100142
143 /**
Googlerf49bb0c2015-02-11 16:18:28 +0000144 * Creates a {@link CommandHelper}.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100145 *
jcater71e46d02018-08-21 00:43:31 -0700146 * @param toolsList resolves sets of tools into set of executable binaries. Populates manifests,
Googlerf49bb0c2015-02-11 16:18:28 +0000147 * remoteRunfiles and label map where required.
148 * @param labelMap adds files to set of known files of label. Used for resolving $(location)
149 * variables.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100150 */
jcater71e46d02018-08-21 00:43:31 -0700151 private CommandHelper(
Brian Silverman53c3ce12015-08-28 09:17:14 +0000152 RuleContext ruleContext,
jcater71e46d02018-08-21 00:43:31 -0700153 ImmutableList<Iterable<? extends TransitiveInfoCollection>> toolsList,
John Caterc637fbb2017-01-27 15:55:53 +0000154 ImmutableMap<Label, ? extends Iterable<Artifact>> labelMap) {
jcater71e46d02018-08-21 00:43:31 -0700155
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100156 this.ruleContext = ruleContext;
157
lberki20d68f02018-09-19 06:30:24 -0700158 NestedSetBuilder<Artifact> resolvedToolsBuilder = NestedSetBuilder.stableOrder();
Michajlo Matijkiw4a877382017-01-27 19:30:34 +0000159 ImmutableList.Builder<RunfilesSupplier> toolsRunfilesBuilder = ImmutableList.builder();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100160 Map<Label, Collection<Artifact>> tempLabelMap = new HashMap<>();
161
John Caterc637fbb2017-01-27 15:55:53 +0000162 for (Map.Entry<Label, ? extends Iterable<Artifact>> entry : labelMap.entrySet()) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100163 Iterables.addAll(mapGet(tempLabelMap, entry.getKey()), entry.getValue());
164 }
165
jcater71e46d02018-08-21 00:43:31 -0700166 for (Iterable<? extends TransitiveInfoCollection> tools : toolsList) {
Googler92f0d6a2023-01-06 09:45:16 -0800167 for (TransitiveInfoCollection dep : tools) { // (Note: exec configuration)
Googlerfdfe0f12018-09-18 08:13:30 -0700168
jcater71e46d02018-08-21 00:43:31 -0700169 FilesToRunProvider tool = dep.getProvider(FilesToRunProvider.class);
170 if (tool == null) {
171 continue;
172 }
Brian Silverman53c3ce12015-08-28 09:17:14 +0000173
lberki20d68f02018-09-19 06:30:24 -0700174 NestedSet<Artifact> files = tool.getFilesToRun();
leba92ae4e32021-06-28 07:47:02 -0700175 resolvedToolsBuilder.addTransitive(files);
jhorvitzfb1c3692020-12-08 10:27:10 -0800176
177 Label label = AliasProvider.getDependencyLabel(dep);
jcater71e46d02018-08-21 00:43:31 -0700178 Artifact executableArtifact = tool.getExecutable();
179 // If the label has an executable artifact add that to the multimaps.
180 if (executableArtifact != null) {
181 mapGet(tempLabelMap, label).add(executableArtifact);
leba92ae4e32021-06-28 07:47:02 -0700182 // Also send the runfiles when running remotely.
183 toolsRunfilesBuilder.add(tool.getRunfilesSupplier());
jcater71e46d02018-08-21 00:43:31 -0700184 } else {
185 // Map all depArtifacts to the respective label using the multimaps.
ulfjack6c3908c2019-12-18 05:03:36 -0800186 mapGet(tempLabelMap, label).addAll(files.toList());
jcater71e46d02018-08-21 00:43:31 -0700187 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100188 }
189 }
190
lberki20d68f02018-09-19 06:30:24 -0700191 this.resolvedTools = resolvedToolsBuilder.build();
Googler92578702019-11-21 12:19:31 -0800192 this.toolsRunfilesSuppliers = StarlarkList.immutableCopyOf(toolsRunfilesBuilder.build());
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100193 ImmutableMap.Builder<Label, ImmutableCollection<Artifact>> labelMapBuilder =
194 ImmutableMap.builder();
jcater36745912018-05-01 13:20:00 -0700195 for (Map.Entry<Label, Collection<Artifact>> entry : tempLabelMap.entrySet()) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100196 labelMapBuilder.put(entry.getKey(), ImmutableList.copyOf(entry.getValue()));
197 }
Googler92ce1152022-02-01 06:37:06 -0800198 this.labelMap = labelMapBuilder.buildOrThrow();
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100199 }
200
lberki20d68f02018-09-19 06:30:24 -0700201 public NestedSet<Artifact> getResolvedTools() {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100202 return resolvedTools;
203 }
204
Googler4871cb02019-11-12 17:16:42 -0800205 public Sequence<RunfilesSupplier> getToolsRunfilesSuppliers() {
Michajlo Matijkiw4a877382017-01-27 19:30:34 +0000206 return toolsRunfilesSuppliers;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100207 }
208
ulfjack515ba892017-12-04 05:48:31 -0800209 public ImmutableMap<Label, ImmutableCollection<Artifact>> getLabelMap() {
210 return labelMap;
211 }
212
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100213 // Returns the value in the specified corresponding to 'key', creating and
214 // inserting an empty container if absent. We use Map not Multimap because
215 // we need to distinguish the cases of "empty value" and "absent key".
216 private static Collection<Artifact> mapGet(Map<Label, Collection<Artifact>> map, Label key) {
217 Collection<Artifact> values = map.get(key);
218 if (values == null) {
219 // We use sets not lists, because it's conceivable that the same artifact
220 // could appear twice, e.g. in "srcs" and "deps".
221 values = Sets.newHashSet();
222 map.put(key, values);
223 }
224 return values;
225 }
226
gregceca48e9a2020-04-14 08:54:38 -0700227 /** Resolves a command, and expands known locations for $(location) variables. */
228 @Deprecated // Only exists to support a legacy Starlark API.
schmittcaa9e082017-12-19 14:46:14 -0800229 public String resolveCommandAndExpandLabels(
230 String command, @Nullable String attribute, boolean allowDataInLabel) {
231 LocationExpander expander;
232 if (allowDataInLabel) {
233 expander = LocationExpander.withExecPathsAndData(ruleContext, labelMap);
234 } else {
235 expander = LocationExpander.withExecPaths(ruleContext, labelMap);
ulfjackaf677742017-10-02 10:55:41 +0200236 }
schmittcaa9e082017-12-19 14:46:14 -0800237 if (attribute != null) {
238 command = expander.expandAttribute(attribute, command);
239 } else {
240 command = expander.expand(command);
241 }
242 return command;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100243 }
244
245 /**
246 * Expands labels occurring in the string "expr" in the rule 'cmd'.
247 * Each label must be valid, be a declared prerequisite, and expand to a
248 * unique path.
249 *
250 * <p>If the expansion fails, an attribute error is reported and the original
251 * expression is returned.
252 */
ulfjack515ba892017-12-04 05:48:31 -0800253 public String expandLabelsHeuristically(String expr) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100254 try {
255 return LabelExpander.expand(expr, labelMap, ruleContext.getLabel());
256 } catch (LabelExpander.NotUniqueExpansionException nuee) {
257 ruleContext.attributeError("cmd", nuee.getMessage());
258 return expr;
259 }
260 }
261
262 private static Pair<List<String>, Artifact> buildCommandLineMaybeWithScriptFile(
Yun Pengae470e12019-07-31 05:56:58 -0700263 RuleContext ruleContext, String command, CommandConstructor constructor) {
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100264 List<String> argv;
265 Artifact scriptFileArtifact = null;
266 if (command.length() <= maxCommandLength) {
Yun Pengae470e12019-07-31 05:56:58 -0700267 argv = constructor.asExecArgv(command);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100268 } else {
269 // Use script file.
Yun Pengae470e12019-07-31 05:56:58 -0700270 scriptFileArtifact = constructor.commandAsScript(ruleContext, command);
271 argv = constructor.asExecArgv(scriptFileArtifact);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100272 }
273 return Pair.of(argv, scriptFileArtifact);
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100274 }
275
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100276 /**
László Csomor36c2ac52017-08-31 14:35:12 +0200277 * If {@code command} is too long, creates a helper shell script that runs that command.
278 *
279 * <p>Returns the {@link Artifact} corresponding to that script.
280 *
281 * <p>Otherwise, when {@code command} is shorter than the platform's shell's command length limit,
282 * this method does nothing and returns null.
283 */
284 @Nullable
Yun Pengae470e12019-07-31 05:56:58 -0700285 public static Artifact commandHelperScriptMaybe(
286 RuleContext ruleCtx, String command, CommandConstructor constructor) {
László Csomor36c2ac52017-08-31 14:35:12 +0200287 if (command.length() <= maxCommandLength) {
288 return null;
289 } else {
Yun Pengae470e12019-07-31 05:56:58 -0700290 return constructor.commandAsScript(ruleCtx, command);
László Csomor36c2ac52017-08-31 14:35:12 +0200291 }
292 }
293
294 /**
Googlerf49bb0c2015-02-11 16:18:28 +0000295 * Builds the set of command-line arguments using the specified shell path. Creates a bash script
Laszlo Csomor382089e2018-05-28 06:33:47 -0700296 * if the command line is longer than the allowed maximum {@link #maxCommandLength}. Fixes up the
297 * input artifact list with the created bash script when required.
Googlerf49bb0c2015-02-11 16:18:28 +0000298 */
299 public List<String> buildCommandLine(
Yun Pengae470e12019-07-31 05:56:58 -0700300 String command, NestedSetBuilder<Artifact> inputs, CommandConstructor constructor) {
Googlerf49bb0c2015-02-11 16:18:28 +0000301 Pair<List<String>, Artifact> argvAndScriptFile =
Yun Pengae470e12019-07-31 05:56:58 -0700302 buildCommandLineMaybeWithScriptFile(ruleContext, command, constructor);
Googlerf49bb0c2015-02-11 16:18:28 +0000303 if (argvAndScriptFile.second != null) {
304 inputs.add(argvAndScriptFile.second);
305 }
306 return argvAndScriptFile.first;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100307 }
308
309 /**
Laszlo Csomor382089e2018-05-28 06:33:47 -0700310 * Builds the set of command-line arguments. Creates a bash script if the command line is longer
311 * than the allowed maximum {@link #maxCommandLength}. Fixes up the input artifact list with the
312 * created bash script when required.
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100313 */
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100314 public List<String> buildCommandLine(
Yun Pengae470e12019-07-31 05:56:58 -0700315 String command, List<Artifact> inputs, CommandConstructor constructor) {
Laszlo Csomor382089e2018-05-28 06:33:47 -0700316 Pair<List<String>, Artifact> argvAndScriptFile =
Yun Pengae470e12019-07-31 05:56:58 -0700317 buildCommandLineMaybeWithScriptFile(ruleContext, command, constructor);
Googlerf49bb0c2015-02-11 16:18:28 +0000318 if (argvAndScriptFile.second != null) {
319 inputs.add(argvAndScriptFile.second);
320 }
321 return argvAndScriptFile.first;
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100322 }
Chris Parsons9a9573b2016-02-10 21:15:11 +0000323
Laszlo Csomor382089e2018-05-28 06:33:47 -0700324 /** Returns the path to the shell for an action with the given execution requirements. */
Yun Pengae470e12019-07-31 05:56:58 -0700325 private static PathFragment shellPath(
326 Map<String, String> executionInfo, PathFragment shExecutable) {
Chris Parsons9a9573b2016-02-10 21:15:11 +0000327 // Use vanilla /bin/bash for actions running on mac machines.
John Cater1f425812017-02-03 16:42:22 +0000328 return executionInfo.containsKey(ExecutionRequirements.REQUIRES_DARWIN)
lberki78651d42018-04-06 01:52:58 -0700329 ? PathFragment.create("/bin/bash")
Laszlo Csomor382089e2018-05-28 06:33:47 -0700330 : shExecutable;
Chris Parsons9a9573b2016-02-10 21:15:11 +0000331 }
Yun Pengae470e12019-07-31 05:56:58 -0700332
333 public static BashCommandConstructor buildBashCommandConstructor(
334 Map<String, String> executionInfo, PathFragment shExecutable, String scriptPostFix) {
335 return new BashCommandConstructor(shellPath(executionInfo, shExecutable), scriptPostFix);
336 }
Yun Pengd3bacfb2019-08-01 03:42:29 -0700337
338 public static WindowsBatchCommandConstructor buildWindowsBatchCommandConstructor(
339 String scriptPostFix) {
340 return new WindowsBatchCommandConstructor(scriptPostFix);
341 }
342
343 public static WindowsPowershellCommandConstructor buildWindowsPowershellCommandConstructor(
344 String scriptPostFix) {
345 return new WindowsPowershellCommandConstructor(scriptPostFix);
346 }
Han-Wen Nienhuysd08b27f2015-02-25 16:45:20 +0100347}