blob: e05cbd6a8578fe3305ab6ba2740991968bce66bb [file] [log] [blame]
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -05001// Copyright 2020 Google Inc. All rights reserved.
2//
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 bazel
16
17import (
18 "encoding/json"
Chris Parsonsaffbb602020-12-23 12:02:11 -050019 "fmt"
20 "path/filepath"
Wei Li455ba832021-11-04 22:58:12 +000021 "regexp"
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050022 "strings"
23
24 "github.com/google/blueprint/proptools"
25)
26
27// artifact contains relevant portions of Bazel's aquery proto, Artifact.
28// Represents a single artifact, whether it's a source file or a derived output file.
29type artifact struct {
Chris Parsonsaffbb602020-12-23 12:02:11 -050030 Id int
31 PathFragmentId int
32}
33
34type pathFragment struct {
35 Id int
36 Label string
37 ParentId int
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050038}
39
40// KeyValuePair represents Bazel's aquery proto, KeyValuePair.
41type KeyValuePair struct {
42 Key string
43 Value string
44}
45
Chris Parsons1a7aca02022-04-25 22:35:15 -040046// AqueryDepset is a depset definition from Bazel's aquery response. This is
47// akin to the `depSetOfFiles` in the response proto, except that direct
48// artifacts are enumerated by full path instead of by ID.
49// A depset is a data structure for efficient transitive handling of artifact
50// paths. A single depset consists of one or more artifact paths and one or
51// more "child" depsets.
52type AqueryDepset struct {
53 Id int
54 DirectArtifacts []string
55 TransitiveDepSetIds []int
56}
57
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050058// depSetOfFiles contains relevant portions of Bazel's aquery proto, DepSetOfFiles.
59// Represents a data structure containing one or more files. Depsets in Bazel are an efficient
60// data structure for storing large numbers of file paths.
61type depSetOfFiles struct {
Chris Parsons943f2432021-01-19 11:36:50 -050062 Id int
63 DirectArtifactIds []int
64 TransitiveDepSetIds []int
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050065}
66
67// action contains relevant portions of Bazel's aquery proto, Action.
68// Represents a single command line invocation in the Bazel build graph.
69type action struct {
70 Arguments []string
71 EnvironmentVariables []KeyValuePair
Chris Parsonsaffbb602020-12-23 12:02:11 -050072 InputDepSetIds []int
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050073 Mnemonic string
Chris Parsonsaffbb602020-12-23 12:02:11 -050074 OutputIds []int
Wei Li455ba832021-11-04 22:58:12 +000075 TemplateContent string
76 Substitutions []KeyValuePair
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050077}
78
79// actionGraphContainer contains relevant portions of Bazel's aquery proto, ActionGraphContainer.
80// An aquery response from Bazel contains a single ActionGraphContainer proto.
81type actionGraphContainer struct {
82 Artifacts []artifact
83 Actions []action
84 DepSetOfFiles []depSetOfFiles
Chris Parsonsaffbb602020-12-23 12:02:11 -050085 PathFragments []pathFragment
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050086}
87
88// BuildStatement contains information to register a build statement corresponding (one to one)
89// with a Bazel action from Bazel's action graph.
90type BuildStatement struct {
Liz Kammerc49e6822021-06-08 15:04:11 -040091 Command string
92 Depfile *string
93 OutputPaths []string
Liz Kammerc49e6822021-06-08 15:04:11 -040094 SymlinkPaths []string
95 Env []KeyValuePair
96 Mnemonic string
Chris Parsons1a7aca02022-04-25 22:35:15 -040097
98 // Inputs of this build statement, either as unexpanded depsets or expanded
99 // input paths. There should be no overlap between these fields; an input
100 // path should either be included as part of an unexpanded depset or a raw
101 // input path string, but not both.
102 InputDepsetIds []int
103 InputPaths []string
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500104}
105
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400106// A helper type for aquery processing which facilitates retrieval of path IDs from their
107// less readable Bazel structures (depset and path fragment).
108type aqueryArtifactHandler struct {
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400109 // Maps depset Id to depset struct.
110 depsetIdToDepset map[int]depSetOfFiles
111 // depsetIdToArtifactIdsCache is a memoization of depset flattening, because flattening
112 // may be an expensive operation.
113 depsetIdToArtifactIdsCache map[int][]int
114 // Maps artifact Id to fully expanded path.
115 artifactIdToPath map[int]string
116}
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500117
Wei Li455ba832021-11-04 22:58:12 +0000118// The tokens should be substituted with the value specified here, instead of the
119// one returned in 'substitutions' of TemplateExpand action.
120var TemplateActionOverriddenTokens = map[string]string{
121 // Uses "python3" for %python_binary% instead of the value returned by aquery
122 // which is "py3wrapper.sh". See removePy3wrapperScript.
123 "%python_binary%": "python3",
124}
125
126// This pattern matches the MANIFEST file created for a py_binary target.
127var manifestFilePattern = regexp.MustCompile(".*/.+\\.runfiles/MANIFEST$")
128
129// The file name of py3wrapper.sh, which is used by py_binary targets.
130var py3wrapperFileName = "/py3wrapper.sh"
131
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400132func newAqueryHandler(aqueryResult actionGraphContainer) (*aqueryArtifactHandler, error) {
Chris Parsonsaffbb602020-12-23 12:02:11 -0500133 pathFragments := map[int]pathFragment{}
134 for _, pathFragment := range aqueryResult.PathFragments {
135 pathFragments[pathFragment.Id] = pathFragment
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500136 }
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400137
Chris Parsonsaffbb602020-12-23 12:02:11 -0500138 artifactIdToPath := map[int]string{}
139 for _, artifact := range aqueryResult.Artifacts {
140 artifactPath, err := expandPathFragment(artifact.PathFragmentId, pathFragments)
141 if err != nil {
Chris Parsons4f069892021-01-15 12:22:41 -0500142 return nil, err
Chris Parsonsaffbb602020-12-23 12:02:11 -0500143 }
144 artifactIdToPath[artifact.Id] = artifactPath
145 }
Chris Parsons943f2432021-01-19 11:36:50 -0500146
Chris Parsons1a7aca02022-04-25 22:35:15 -0400147 // Map middleman artifact Id to input artifact depset ID.
148 // Middleman artifacts are treated as "substitute" artifacts for mixed builds. For example,
149 // if we find a middleman action which has outputs [foo, bar], and output [baz_middleman], then,
150 // for each other action which has input [baz_middleman], we add [foo, bar] to the inputs for
151 // that action instead.
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400152 middlemanIdToDepsetIds := map[int][]int{}
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500153 for _, actionEntry := range aqueryResult.Actions {
Chris Parsons8d6e4332021-02-22 16:13:50 -0500154 if actionEntry.Mnemonic == "Middleman" {
155 for _, outputId := range actionEntry.OutputIds {
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400156 middlemanIdToDepsetIds[outputId] = actionEntry.InputDepSetIds
Chris Parsons8d6e4332021-02-22 16:13:50 -0500157 }
158 }
159 }
Chris Parsons1a7aca02022-04-25 22:35:15 -0400160
161 // Store all depset IDs to validate all depset links are resolvable.
162 depsetIds := map[int]bool{}
163 for _, depset := range aqueryResult.DepSetOfFiles {
164 depsetIds[depset.Id] = true
165 }
166
167 depsetIdToDepset := map[int]depSetOfFiles{}
168 // Validate and adjust aqueryResult.DepSetOfFiles values.
169 for _, depset := range aqueryResult.DepSetOfFiles {
170 filteredArtifactIds := []int{}
171 for _, artifactId := range depset.DirectArtifactIds {
172 path, pathExists := artifactIdToPath[artifactId]
173 if !pathExists {
174 return nil, fmt.Errorf("undefined input artifactId %d", artifactId)
175 }
176 // Filter out any inputs which are universally dropped, and swap middleman
177 // artifacts with their corresponding depsets.
178 if depsetsToUse, isMiddleman := middlemanIdToDepsetIds[artifactId]; isMiddleman {
179 // Swap middleman artifacts with their corresponding depsets and drop the middleman artifacts.
180 depset.TransitiveDepSetIds = append(depset.TransitiveDepSetIds, depsetsToUse...)
181 } else if strings.HasSuffix(path, py3wrapperFileName) || manifestFilePattern.MatchString(path) {
182 // Drop these artifacts.
183 // See go/python-binary-host-mixed-build for more details.
184 // 1) For py3wrapper.sh, there is no action for creating py3wrapper.sh in the aquery output of
185 // Bazel py_binary targets, so there is no Ninja build statements generated for creating it.
186 // 2) For MANIFEST file, SourceSymlinkManifest action is in aquery output of Bazel py_binary targets,
187 // but it doesn't contain sufficient information so no Ninja build statements are generated
188 // for creating it.
189 // So in mixed build mode, when these two are used as input of some Ninja build statement,
190 // since there is no build statement to create them, they should be removed from input paths.
191 // TODO(b/197135294): Clean up this custom runfiles handling logic when
192 // SourceSymlinkManifest and SymlinkTree actions are supported.
193 } else {
194 // TODO(b/216194240): Filter out bazel tools.
195 filteredArtifactIds = append(filteredArtifactIds, artifactId)
196 }
197 }
198 depset.DirectArtifactIds = filteredArtifactIds
199 for _, childDepsetId := range depset.TransitiveDepSetIds {
200 if _, exists := depsetIds[childDepsetId]; !exists {
201 return nil, fmt.Errorf("undefined input depsetId %d (referenced by depsetId %d)", childDepsetId, depset.Id)
202 }
203 }
204 depsetIdToDepset[depset.Id] = depset
205 }
206
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400207 return &aqueryArtifactHandler{
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400208 depsetIdToDepset: depsetIdToDepset,
209 depsetIdToArtifactIdsCache: map[int][]int{},
210 artifactIdToPath: artifactIdToPath,
211 }, nil
212}
213
Chris Parsons1a7aca02022-04-25 22:35:15 -0400214// getInputPaths flattens the depsets of the given IDs and returns all transitive
215// input paths contained in these depsets.
216// This is a potentially expensive operation, and should not be invoked except
217// for actions which need specialized input handling.
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400218func (a *aqueryArtifactHandler) getInputPaths(depsetIds []int) ([]string, error) {
219 inputPaths := []string{}
220
221 for _, inputDepSetId := range depsetIds {
222 inputArtifacts, err := a.artifactIdsFromDepsetId(inputDepSetId)
223 if err != nil {
224 return nil, err
225 }
226 for _, inputId := range inputArtifacts {
Chris Parsons1a7aca02022-04-25 22:35:15 -0400227 inputPath, exists := a.artifactIdToPath[inputId]
228 if !exists {
229 return nil, fmt.Errorf("undefined input artifactId %d", inputId)
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400230 }
Chris Parsons1a7aca02022-04-25 22:35:15 -0400231 inputPaths = append(inputPaths, inputPath)
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400232 }
233 }
Wei Li455ba832021-11-04 22:58:12 +0000234
Chris Parsons1a7aca02022-04-25 22:35:15 -0400235 return inputPaths, nil
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400236}
237
238func (a *aqueryArtifactHandler) artifactIdsFromDepsetId(depsetId int) ([]int, error) {
239 if result, exists := a.depsetIdToArtifactIdsCache[depsetId]; exists {
240 return result, nil
241 }
242 if depset, exists := a.depsetIdToDepset[depsetId]; exists {
243 result := depset.DirectArtifactIds
244 for _, childId := range depset.TransitiveDepSetIds {
245 childArtifactIds, err := a.artifactIdsFromDepsetId(childId)
246 if err != nil {
247 return nil, err
248 }
249 result = append(result, childArtifactIds...)
250 }
251 a.depsetIdToArtifactIdsCache[depsetId] = result
252 return result, nil
253 } else {
254 return nil, fmt.Errorf("undefined input depsetId %d", depsetId)
255 }
256}
257
Chris Parsons1a7aca02022-04-25 22:35:15 -0400258// AqueryBuildStatements returns a slice of BuildStatements and a slice of AqueryDepset
259// which should be registered (and output to a ninja file) to correspond with Bazel's
260// action graph, as described by the given action graph json proto.
261// BuildStatements are one-to-one with actions in the given action graph, and AqueryDepsets
262// are one-to-one with Bazel's depSetOfFiles objects.
263func AqueryBuildStatements(aqueryJsonProto []byte) ([]BuildStatement, []AqueryDepset, error) {
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400264 buildStatements := []BuildStatement{}
Chris Parsons1a7aca02022-04-25 22:35:15 -0400265 depsets := []AqueryDepset{}
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400266
267 var aqueryResult actionGraphContainer
268 err := json.Unmarshal(aqueryJsonProto, &aqueryResult)
269 if err != nil {
Chris Parsons1a7aca02022-04-25 22:35:15 -0400270 return nil, nil, err
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400271 }
272 aqueryHandler, err := newAqueryHandler(aqueryResult)
273 if err != nil {
Chris Parsons1a7aca02022-04-25 22:35:15 -0400274 return nil, nil, err
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400275 }
Chris Parsons8d6e4332021-02-22 16:13:50 -0500276
277 for _, actionEntry := range aqueryResult.Actions {
278 if shouldSkipAction(actionEntry) {
279 continue
280 }
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400281
Chris Parsons1a7aca02022-04-25 22:35:15 -0400282 var buildStatement BuildStatement
Liz Kammerc49e6822021-06-08 15:04:11 -0400283 if isSymlinkAction(actionEntry) {
Chris Parsons1a7aca02022-04-25 22:35:15 -0400284 buildStatement, err = aqueryHandler.symlinkActionBuildStatement(actionEntry)
Wei Li455ba832021-11-04 22:58:12 +0000285 } else if isTemplateExpandAction(actionEntry) && len(actionEntry.Arguments) < 1 {
Chris Parsons1a7aca02022-04-25 22:35:15 -0400286 buildStatement, err = aqueryHandler.templateExpandActionBuildStatement(actionEntry)
Wei Li455ba832021-11-04 22:58:12 +0000287 } else if isPythonZipperAction(actionEntry) {
Chris Parsons1a7aca02022-04-25 22:35:15 -0400288 buildStatement, err = aqueryHandler.pythonZipperActionBuildStatement(actionEntry, buildStatements)
Liz Kammerc49e6822021-06-08 15:04:11 -0400289 } else if len(actionEntry.Arguments) < 1 {
Chris Parsons1a7aca02022-04-25 22:35:15 -0400290 return nil, nil, fmt.Errorf("received action with no command: [%s]", actionEntry.Mnemonic)
291 } else {
292 buildStatement, err = aqueryHandler.normalActionBuildStatement(actionEntry)
293 }
294
295 if err != nil {
296 return nil, nil, err
Chris Parsons8d6e4332021-02-22 16:13:50 -0500297 }
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500298 buildStatements = append(buildStatements, buildStatement)
299 }
300
Chris Parsons1a7aca02022-04-25 22:35:15 -0400301 // Iterate over depset IDs in the initial aquery order to preserve determinism.
302 for _, depset := range aqueryResult.DepSetOfFiles {
303 // Use the depset in the aqueryHandler, as this contains the augmented depsets.
304 depset = aqueryHandler.depsetIdToDepset[depset.Id]
305 directPaths := []string{}
306 for _, artifactId := range depset.DirectArtifactIds {
307 pathString := aqueryHandler.artifactIdToPath[artifactId]
308 directPaths = append(directPaths, pathString)
309 }
310 aqueryDepset := AqueryDepset{
311 Id: depset.Id,
312 DirectArtifacts: directPaths,
313 TransitiveDepSetIds: depset.TransitiveDepSetIds,
314 }
315 depsets = append(depsets, aqueryDepset)
316 }
317 return buildStatements, depsets, nil
318}
319
320func (aqueryHandler *aqueryArtifactHandler) validateInputDepsets(inputDepsetIds []int) ([]int, error) {
321 // Validate input depsets correspond to real depsets.
322 for _, depsetId := range inputDepsetIds {
323 if _, exists := aqueryHandler.depsetIdToDepset[depsetId]; !exists {
324 return nil, fmt.Errorf("undefined input depsetId %d", depsetId)
325 }
326 }
327 return inputDepsetIds, nil
328}
329
330func (aqueryHandler *aqueryArtifactHandler) normalActionBuildStatement(actionEntry action) (BuildStatement, error) {
331 command := strings.Join(proptools.ShellEscapeListIncludingSpaces(actionEntry.Arguments), " ")
332 inputDepsetIds, err := aqueryHandler.validateInputDepsets(actionEntry.InputDepSetIds)
333 if err != nil {
334 return BuildStatement{}, err
335 }
336 outputPaths, depfile, err := aqueryHandler.getOutputPaths(actionEntry)
337 if err != nil {
338 return BuildStatement{}, err
339 }
340
341 buildStatement := BuildStatement{
342 Command: command,
343 Depfile: depfile,
344 OutputPaths: outputPaths,
345 InputDepsetIds: inputDepsetIds,
346 Env: actionEntry.EnvironmentVariables,
347 Mnemonic: actionEntry.Mnemonic,
348 }
349 return buildStatement, nil
350}
351
352func (aqueryHandler *aqueryArtifactHandler) pythonZipperActionBuildStatement(actionEntry action, prevBuildStatements []BuildStatement) (BuildStatement, error) {
353 inputPaths, err := aqueryHandler.getInputPaths(actionEntry.InputDepSetIds)
354 if err != nil {
355 return BuildStatement{}, err
356 }
357 outputPaths, depfile, err := aqueryHandler.getOutputPaths(actionEntry)
358 if err != nil {
359 return BuildStatement{}, err
360 }
361
362 if len(inputPaths) < 1 || len(outputPaths) != 1 {
363 return BuildStatement{}, fmt.Errorf("Expect 1+ input and 1 output to python zipper action, got: input %q, output %q", inputPaths, outputPaths)
364 }
365 command := strings.Join(proptools.ShellEscapeListIncludingSpaces(actionEntry.Arguments), " ")
366 inputPaths, command = removePy3wrapperScript(inputPaths, command)
367 command = addCommandForPyBinaryRunfilesDir(command, inputPaths[0], outputPaths[0])
368 // Add the python zip file as input of the corresponding python binary stub script in Ninja build statements.
369 // In Ninja build statements, the outputs of dependents of a python binary have python binary stub script as input,
370 // which is not sufficient without the python zip file from which runfiles directory is created for py_binary.
371 //
372 // The following logic relies on that Bazel aquery output returns actions in the order that
373 // PythonZipper is after TemplateAction of creating Python binary stub script. If later Bazel doesn't return actions
374 // in that order, the following logic might not find the build statement generated for Python binary
375 // stub script and the build might fail. So the check of pyBinaryFound is added to help debug in case later Bazel might change aquery output.
376 // See go/python-binary-host-mixed-build for more details.
377 pythonZipFilePath := outputPaths[0]
378 pyBinaryFound := false
379 for i, _ := range prevBuildStatements {
380 if len(prevBuildStatements[i].OutputPaths) == 1 && prevBuildStatements[i].OutputPaths[0]+".zip" == pythonZipFilePath {
381 prevBuildStatements[i].InputPaths = append(prevBuildStatements[i].InputPaths, pythonZipFilePath)
382 pyBinaryFound = true
383 }
384 }
385 if !pyBinaryFound {
386 return BuildStatement{}, fmt.Errorf("Could not find the correspondinging Python binary stub script of PythonZipper: %q", outputPaths)
387 }
388
389 buildStatement := BuildStatement{
390 Command: command,
391 Depfile: depfile,
392 OutputPaths: outputPaths,
393 InputPaths: inputPaths,
394 Env: actionEntry.EnvironmentVariables,
395 Mnemonic: actionEntry.Mnemonic,
396 }
397 return buildStatement, nil
398}
399
400func (aqueryHandler *aqueryArtifactHandler) templateExpandActionBuildStatement(actionEntry action) (BuildStatement, error) {
401 outputPaths, depfile, err := aqueryHandler.getOutputPaths(actionEntry)
402 if err != nil {
403 return BuildStatement{}, err
404 }
405 if len(outputPaths) != 1 {
406 return BuildStatement{}, fmt.Errorf("Expect 1 output to template expand action, got: output %q", outputPaths)
407 }
408 expandedTemplateContent := expandTemplateContent(actionEntry)
409 // The expandedTemplateContent is escaped for being used in double quotes and shell unescape,
410 // and the new line characters (\n) are also changed to \\n which avoids some Ninja escape on \n, which might
411 // change \n to space and mess up the format of Python programs.
412 // sed is used to convert \\n back to \n before saving to output file.
413 // See go/python-binary-host-mixed-build for more details.
414 command := fmt.Sprintf(`/bin/bash -c 'echo "%[1]s" | sed "s/\\\\n/\\n/g" > %[2]s && chmod a+x %[2]s'`,
415 escapeCommandlineArgument(expandedTemplateContent), outputPaths[0])
416 inputDepsetIds, err := aqueryHandler.validateInputDepsets(actionEntry.InputDepSetIds)
417 if err != nil {
418 return BuildStatement{}, err
419 }
420
421 buildStatement := BuildStatement{
422 Command: command,
423 Depfile: depfile,
424 OutputPaths: outputPaths,
425 InputDepsetIds: inputDepsetIds,
426 Env: actionEntry.EnvironmentVariables,
427 Mnemonic: actionEntry.Mnemonic,
428 }
429 return buildStatement, nil
430}
431
432func (aqueryHandler *aqueryArtifactHandler) symlinkActionBuildStatement(actionEntry action) (BuildStatement, error) {
433 outputPaths, depfile, err := aqueryHandler.getOutputPaths(actionEntry)
434 if err != nil {
435 return BuildStatement{}, err
436 }
437
438 inputPaths, err := aqueryHandler.getInputPaths(actionEntry.InputDepSetIds)
439 if err != nil {
440 return BuildStatement{}, err
441 }
442 if len(inputPaths) != 1 || len(outputPaths) != 1 {
443 return BuildStatement{}, fmt.Errorf("Expect 1 input and 1 output to symlink action, got: input %q, output %q", inputPaths, outputPaths)
444 }
445 out := outputPaths[0]
446 outDir := proptools.ShellEscapeIncludingSpaces(filepath.Dir(out))
447 out = proptools.ShellEscapeIncludingSpaces(out)
448 in := filepath.Join("$PWD", proptools.ShellEscapeIncludingSpaces(inputPaths[0]))
449 // Use absolute paths, because some soong actions don't play well with relative paths (for example, `cp -d`).
450 command := fmt.Sprintf("mkdir -p %[1]s && rm -f %[2]s && ln -sf %[3]s %[2]s", outDir, out, in)
451 symlinkPaths := outputPaths[:]
452
453 buildStatement := BuildStatement{
454 Command: command,
455 Depfile: depfile,
456 OutputPaths: outputPaths,
457 InputPaths: inputPaths,
458 Env: actionEntry.EnvironmentVariables,
459 Mnemonic: actionEntry.Mnemonic,
460 SymlinkPaths: symlinkPaths,
461 }
462 return buildStatement, nil
463}
464
465func (aqueryHandler *aqueryArtifactHandler) getOutputPaths(actionEntry action) (outputPaths []string, depfile *string, err error) {
466 for _, outputId := range actionEntry.OutputIds {
467 outputPath, exists := aqueryHandler.artifactIdToPath[outputId]
468 if !exists {
469 err = fmt.Errorf("undefined outputId %d", outputId)
470 return
471 }
472 ext := filepath.Ext(outputPath)
473 if ext == ".d" {
474 if depfile != nil {
475 err = fmt.Errorf("found multiple potential depfiles %q, %q", *depfile, outputPath)
476 return
477 } else {
478 depfile = &outputPath
479 }
480 } else {
481 outputPaths = append(outputPaths, outputPath)
482 }
483 }
484 return
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500485}
Chris Parsonsaffbb602020-12-23 12:02:11 -0500486
Wei Li455ba832021-11-04 22:58:12 +0000487// expandTemplateContent substitutes the tokens in a template.
488func expandTemplateContent(actionEntry action) string {
489 replacerString := []string{}
490 for _, pair := range actionEntry.Substitutions {
491 value := pair.Value
492 if val, ok := TemplateActionOverriddenTokens[pair.Key]; ok {
493 value = val
494 }
495 replacerString = append(replacerString, pair.Key, value)
496 }
497 replacer := strings.NewReplacer(replacerString...)
498 return replacer.Replace(actionEntry.TemplateContent)
499}
500
501func escapeCommandlineArgument(str string) string {
502 // \->\\, $->\$, `->\`, "->\", \n->\\n, '->'"'"'
503 replacer := strings.NewReplacer(
504 `\`, `\\`,
505 `$`, `\$`,
506 "`", "\\`",
507 `"`, `\"`,
508 "\n", "\\n",
509 `'`, `'"'"'`,
510 )
511 return replacer.Replace(str)
512}
513
514// removePy3wrapperScript removes py3wrapper.sh from the input paths and command of the action of
515// creating python zip file in mixed build mode. py3wrapper.sh is returned as input by aquery but
516// there is no action returned by aquery for creating it. So in mixed build "python3" is used
517// as the PYTHON_BINARY in python binary stub script, and py3wrapper.sh is not needed and should be
518// removed from input paths and command of creating python zip file.
519// See go/python-binary-host-mixed-build for more details.
520// TODO(b/205879240) remove this after py3wrapper.sh could be created in the mixed build mode.
Chris Parsons1a7aca02022-04-25 22:35:15 -0400521func removePy3wrapperScript(inputPaths []string, command string) (newInputPaths []string, newCommand string) {
Wei Li455ba832021-11-04 22:58:12 +0000522 // Remove from inputs
523 filteredInputPaths := []string{}
Chris Parsons1a7aca02022-04-25 22:35:15 -0400524 for _, path := range inputPaths {
Wei Li455ba832021-11-04 22:58:12 +0000525 if !strings.HasSuffix(path, py3wrapperFileName) {
526 filteredInputPaths = append(filteredInputPaths, path)
527 }
528 }
529 newInputPaths = filteredInputPaths
530
531 // Remove from command line
532 var re = regexp.MustCompile(`\S*` + py3wrapperFileName)
Chris Parsons1a7aca02022-04-25 22:35:15 -0400533 newCommand = re.ReplaceAllString(command, "")
Wei Li455ba832021-11-04 22:58:12 +0000534 return
535}
536
537// addCommandForPyBinaryRunfilesDir adds commands creating python binary runfiles directory.
538// runfiles directory is created by using MANIFEST file and MANIFEST file is the output of
539// SourceSymlinkManifest action is in aquery output of Bazel py_binary targets,
540// but since SourceSymlinkManifest doesn't contain sufficient information
541// so MANIFEST file could not be created, which also blocks the creation of runfiles directory.
542// See go/python-binary-host-mixed-build for more details.
543// TODO(b/197135294) create runfiles directory from MANIFEST file once it can be created from SourceSymlinkManifest action.
Chris Parsons1a7aca02022-04-25 22:35:15 -0400544func addCommandForPyBinaryRunfilesDir(oldCommand string, zipperCommandPath, zipFilePath string) string {
Wei Li455ba832021-11-04 22:58:12 +0000545 // Unzip the zip file, zipFilePath looks like <python_binary>.zip
546 runfilesDirName := zipFilePath[0:len(zipFilePath)-4] + ".runfiles"
547 command := fmt.Sprintf("%s x %s -d %s", zipperCommandPath, zipFilePath, runfilesDirName)
548 // Create a symbolic link in <python_binary>.runfiles/, which is the expected structure
549 // when running the python binary stub script.
550 command += fmt.Sprintf(" && ln -sf runfiles/__main__ %s", runfilesDirName)
Chris Parsons1a7aca02022-04-25 22:35:15 -0400551 return oldCommand + " && " + command
Wei Li455ba832021-11-04 22:58:12 +0000552}
553
Liz Kammerc49e6822021-06-08 15:04:11 -0400554func isSymlinkAction(a action) bool {
555 return a.Mnemonic == "Symlink" || a.Mnemonic == "SolibSymlink"
556}
557
Wei Li455ba832021-11-04 22:58:12 +0000558func isTemplateExpandAction(a action) bool {
559 return a.Mnemonic == "TemplateExpand"
560}
561
562func isPythonZipperAction(a action) bool {
563 return a.Mnemonic == "PythonZipper"
564}
565
Chris Parsons8d6e4332021-02-22 16:13:50 -0500566func shouldSkipAction(a action) bool {
Liz Kammerc49e6822021-06-08 15:04:11 -0400567 // TODO(b/180945121): Handle complex symlink actions.
568 if a.Mnemonic == "SymlinkTree" || a.Mnemonic == "SourceSymlinkManifest" {
Chris Parsons8d6e4332021-02-22 16:13:50 -0500569 return true
570 }
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400571 // Middleman actions are not handled like other actions; they are handled separately as a
572 // preparatory step so that their inputs may be relayed to actions depending on middleman
573 // artifacts.
Chris Parsons8d6e4332021-02-22 16:13:50 -0500574 if a.Mnemonic == "Middleman" {
575 return true
576 }
577 // Skip "Fail" actions, which are placeholder actions designed to always fail.
578 if a.Mnemonic == "Fail" {
579 return true
580 }
581 // TODO(b/180946980): Handle FileWrite. The aquery proto currently contains no information
582 // about the contents that are written.
583 if a.Mnemonic == "FileWrite" {
584 return true
585 }
586 return false
587}
588
Chris Parsonsaffbb602020-12-23 12:02:11 -0500589func expandPathFragment(id int, pathFragmentsMap map[int]pathFragment) (string, error) {
590 labels := []string{}
591 currId := id
592 // Only positive IDs are valid for path fragments. An ID of zero indicates a terminal node.
593 for currId > 0 {
594 currFragment, ok := pathFragmentsMap[currId]
595 if !ok {
Chris Parsons4f069892021-01-15 12:22:41 -0500596 return "", fmt.Errorf("undefined path fragment id %d", currId)
Chris Parsonsaffbb602020-12-23 12:02:11 -0500597 }
598 labels = append([]string{currFragment.Label}, labels...)
Liz Kammerc49e6822021-06-08 15:04:11 -0400599 if currId == currFragment.ParentId {
600 return "", fmt.Errorf("Fragment cannot refer to itself as parent %#v", currFragment)
601 }
Chris Parsonsaffbb602020-12-23 12:02:11 -0500602 currId = currFragment.ParentId
603 }
604 return filepath.Join(labels...), nil
605}