blob: bda3a1ae6833b5505c50bcc59cffbfc489de2ccd [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"
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050021 "strings"
22
23 "github.com/google/blueprint/proptools"
24)
25
26// artifact contains relevant portions of Bazel's aquery proto, Artifact.
27// Represents a single artifact, whether it's a source file or a derived output file.
28type artifact struct {
Chris Parsonsaffbb602020-12-23 12:02:11 -050029 Id int
30 PathFragmentId int
31}
32
33type pathFragment struct {
34 Id int
35 Label string
36 ParentId int
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050037}
38
39// KeyValuePair represents Bazel's aquery proto, KeyValuePair.
40type KeyValuePair struct {
41 Key string
42 Value string
43}
44
45// depSetOfFiles contains relevant portions of Bazel's aquery proto, DepSetOfFiles.
46// Represents a data structure containing one or more files. Depsets in Bazel are an efficient
47// data structure for storing large numbers of file paths.
48type depSetOfFiles struct {
Chris Parsons943f2432021-01-19 11:36:50 -050049 Id int
50 DirectArtifactIds []int
51 TransitiveDepSetIds []int
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050052}
53
54// action contains relevant portions of Bazel's aquery proto, Action.
55// Represents a single command line invocation in the Bazel build graph.
56type action struct {
57 Arguments []string
58 EnvironmentVariables []KeyValuePair
Chris Parsonsaffbb602020-12-23 12:02:11 -050059 InputDepSetIds []int
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050060 Mnemonic string
Chris Parsonsaffbb602020-12-23 12:02:11 -050061 OutputIds []int
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050062}
63
64// actionGraphContainer contains relevant portions of Bazel's aquery proto, ActionGraphContainer.
65// An aquery response from Bazel contains a single ActionGraphContainer proto.
66type actionGraphContainer struct {
67 Artifacts []artifact
68 Actions []action
69 DepSetOfFiles []depSetOfFiles
Chris Parsonsaffbb602020-12-23 12:02:11 -050070 PathFragments []pathFragment
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050071}
72
73// BuildStatement contains information to register a build statement corresponding (one to one)
74// with a Bazel action from Bazel's action graph.
75type BuildStatement struct {
76 Command string
Liz Kammerde116852021-03-25 16:42:37 -040077 Depfile *string
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050078 OutputPaths []string
79 InputPaths []string
80 Env []KeyValuePair
81 Mnemonic string
82}
83
Chris Parsonsc4fb1332021-05-18 12:31:25 -040084// A helper type for aquery processing which facilitates retrieval of path IDs from their
85// less readable Bazel structures (depset and path fragment).
86type aqueryArtifactHandler struct {
87 // Maps middleman artifact Id to input artifact depset ID.
88 // Middleman artifacts are treated as "substitute" artifacts for mixed builds. For example,
89 // if we find a middleman action which has outputs [foo, bar], and output [baz_middleman], then,
90 // for each other action which has input [baz_middleman], we add [foo, bar] to the inputs for
91 // that action instead.
92 middlemanIdToDepsetIds map[int][]int
93 // Maps depset Id to depset struct.
94 depsetIdToDepset map[int]depSetOfFiles
95 // depsetIdToArtifactIdsCache is a memoization of depset flattening, because flattening
96 // may be an expensive operation.
97 depsetIdToArtifactIdsCache map[int][]int
98 // Maps artifact Id to fully expanded path.
99 artifactIdToPath map[int]string
100}
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500101
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400102func newAqueryHandler(aqueryResult actionGraphContainer) (*aqueryArtifactHandler, error) {
Chris Parsonsaffbb602020-12-23 12:02:11 -0500103 pathFragments := map[int]pathFragment{}
104 for _, pathFragment := range aqueryResult.PathFragments {
105 pathFragments[pathFragment.Id] = pathFragment
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500106 }
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400107
Chris Parsonsaffbb602020-12-23 12:02:11 -0500108 artifactIdToPath := map[int]string{}
109 for _, artifact := range aqueryResult.Artifacts {
110 artifactPath, err := expandPathFragment(artifact.PathFragmentId, pathFragments)
111 if err != nil {
Chris Parsons4f069892021-01-15 12:22:41 -0500112 return nil, err
Chris Parsonsaffbb602020-12-23 12:02:11 -0500113 }
114 artifactIdToPath[artifact.Id] = artifactPath
115 }
Chris Parsons943f2432021-01-19 11:36:50 -0500116
117 depsetIdToDepset := map[int]depSetOfFiles{}
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500118 for _, depset := range aqueryResult.DepSetOfFiles {
Chris Parsons943f2432021-01-19 11:36:50 -0500119 depsetIdToDepset[depset.Id] = depset
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500120 }
121
Chris Parsons8d6e4332021-02-22 16:13:50 -0500122 // Do a pass through all actions to identify which artifacts are middleman artifacts.
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400123 middlemanIdToDepsetIds := map[int][]int{}
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500124 for _, actionEntry := range aqueryResult.Actions {
Chris Parsons8d6e4332021-02-22 16:13:50 -0500125 if actionEntry.Mnemonic == "Middleman" {
126 for _, outputId := range actionEntry.OutputIds {
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400127 middlemanIdToDepsetIds[outputId] = actionEntry.InputDepSetIds
Chris Parsons8d6e4332021-02-22 16:13:50 -0500128 }
129 }
130 }
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400131 return &aqueryArtifactHandler{
132 middlemanIdToDepsetIds: middlemanIdToDepsetIds,
133 depsetIdToDepset: depsetIdToDepset,
134 depsetIdToArtifactIdsCache: map[int][]int{},
135 artifactIdToPath: artifactIdToPath,
136 }, nil
137}
138
139func (a *aqueryArtifactHandler) getInputPaths(depsetIds []int) ([]string, error) {
140 inputPaths := []string{}
141
142 for _, inputDepSetId := range depsetIds {
143 inputArtifacts, err := a.artifactIdsFromDepsetId(inputDepSetId)
144 if err != nil {
145 return nil, err
146 }
147 for _, inputId := range inputArtifacts {
148 if middlemanInputDepsetIds, isMiddlemanArtifact := a.middlemanIdToDepsetIds[inputId]; isMiddlemanArtifact {
149 // Add all inputs from middleman actions which created middleman artifacts which are
150 // in the inputs for this action.
151 swappedInputPaths, err := a.getInputPaths(middlemanInputDepsetIds)
152 if err != nil {
153 return nil, err
154 }
155 inputPaths = append(inputPaths, swappedInputPaths...)
156 } else {
157 inputPath, exists := a.artifactIdToPath[inputId]
158 if !exists {
159 return nil, fmt.Errorf("undefined input artifactId %d", inputId)
160 }
161 inputPaths = append(inputPaths, inputPath)
162 }
163 }
164 }
165 return inputPaths, nil
166}
167
168func (a *aqueryArtifactHandler) artifactIdsFromDepsetId(depsetId int) ([]int, error) {
169 if result, exists := a.depsetIdToArtifactIdsCache[depsetId]; exists {
170 return result, nil
171 }
172 if depset, exists := a.depsetIdToDepset[depsetId]; exists {
173 result := depset.DirectArtifactIds
174 for _, childId := range depset.TransitiveDepSetIds {
175 childArtifactIds, err := a.artifactIdsFromDepsetId(childId)
176 if err != nil {
177 return nil, err
178 }
179 result = append(result, childArtifactIds...)
180 }
181 a.depsetIdToArtifactIdsCache[depsetId] = result
182 return result, nil
183 } else {
184 return nil, fmt.Errorf("undefined input depsetId %d", depsetId)
185 }
186}
187
188// AqueryBuildStatements returns an array of BuildStatements which should be registered (and output
189// to a ninja file) to correspond one-to-one with the given action graph json proto (from a bazel
190// aquery invocation).
191func AqueryBuildStatements(aqueryJsonProto []byte) ([]BuildStatement, error) {
192 buildStatements := []BuildStatement{}
193
194 var aqueryResult actionGraphContainer
195 err := json.Unmarshal(aqueryJsonProto, &aqueryResult)
196 if err != nil {
197 return nil, err
198 }
199 aqueryHandler, err := newAqueryHandler(aqueryResult)
200 if err != nil {
201 return nil, err
202 }
Chris Parsons8d6e4332021-02-22 16:13:50 -0500203
204 for _, actionEntry := range aqueryResult.Actions {
205 if shouldSkipAction(actionEntry) {
206 continue
207 }
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500208 outputPaths := []string{}
Liz Kammerde116852021-03-25 16:42:37 -0400209 var depfile *string
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500210 for _, outputId := range actionEntry.OutputIds {
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400211 outputPath, exists := aqueryHandler.artifactIdToPath[outputId]
Chris Parsons4f069892021-01-15 12:22:41 -0500212 if !exists {
213 return nil, fmt.Errorf("undefined outputId %d", outputId)
214 }
Liz Kammerde116852021-03-25 16:42:37 -0400215 ext := filepath.Ext(outputPath)
216 if ext == ".d" {
217 if depfile != nil {
218 return nil, fmt.Errorf("found multiple potential depfiles %q, %q", *depfile, outputPath)
219 } else {
220 depfile = &outputPath
221 }
222 } else {
223 outputPaths = append(outputPaths, outputPath)
224 }
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500225 }
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400226 inputPaths, err := aqueryHandler.getInputPaths(actionEntry.InputDepSetIds)
227 if err != nil {
228 return nil, err
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500229 }
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400230
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500231 buildStatement := BuildStatement{
232 Command: strings.Join(proptools.ShellEscapeList(actionEntry.Arguments), " "),
Liz Kammerde116852021-03-25 16:42:37 -0400233 Depfile: depfile,
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500234 OutputPaths: outputPaths,
235 InputPaths: inputPaths,
236 Env: actionEntry.EnvironmentVariables,
237 Mnemonic: actionEntry.Mnemonic}
Chris Parsons8d6e4332021-02-22 16:13:50 -0500238 if len(actionEntry.Arguments) < 1 {
Liz Kammerde116852021-03-25 16:42:37 -0400239 return nil, fmt.Errorf("received action with no command: [%v]", buildStatement)
Chris Parsons8d6e4332021-02-22 16:13:50 -0500240 continue
241 }
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500242 buildStatements = append(buildStatements, buildStatement)
243 }
244
Chris Parsons4f069892021-01-15 12:22:41 -0500245 return buildStatements, nil
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500246}
Chris Parsonsaffbb602020-12-23 12:02:11 -0500247
Chris Parsons8d6e4332021-02-22 16:13:50 -0500248func shouldSkipAction(a action) bool {
249 // TODO(b/180945121): Handle symlink actions.
250 if a.Mnemonic == "Symlink" || a.Mnemonic == "SourceSymlinkManifest" || a.Mnemonic == "SymlinkTree" {
251 return true
252 }
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400253 // Middleman actions are not handled like other actions; they are handled separately as a
254 // preparatory step so that their inputs may be relayed to actions depending on middleman
255 // artifacts.
Chris Parsons8d6e4332021-02-22 16:13:50 -0500256 if a.Mnemonic == "Middleman" {
257 return true
258 }
259 // Skip "Fail" actions, which are placeholder actions designed to always fail.
260 if a.Mnemonic == "Fail" {
261 return true
262 }
263 // TODO(b/180946980): Handle FileWrite. The aquery proto currently contains no information
264 // about the contents that are written.
265 if a.Mnemonic == "FileWrite" {
266 return true
267 }
268 return false
269}
270
Chris Parsonsaffbb602020-12-23 12:02:11 -0500271func expandPathFragment(id int, pathFragmentsMap map[int]pathFragment) (string, error) {
272 labels := []string{}
273 currId := id
274 // Only positive IDs are valid for path fragments. An ID of zero indicates a terminal node.
275 for currId > 0 {
276 currFragment, ok := pathFragmentsMap[currId]
277 if !ok {
Chris Parsons4f069892021-01-15 12:22:41 -0500278 return "", fmt.Errorf("undefined path fragment id %d", currId)
Chris Parsonsaffbb602020-12-23 12:02:11 -0500279 }
280 labels = append([]string{currFragment.Label}, labels...)
281 currId = currFragment.ParentId
282 }
283 return filepath.Join(labels...), nil
284}