blob: 8741afbc5d63ce5ed9971ce9f6138a058b5bc8df [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 {
Liz Kammerc49e6822021-06-08 15:04:11 -040076 Command string
77 Depfile *string
78 OutputPaths []string
79 InputPaths []string
80 SymlinkPaths []string
81 Env []KeyValuePair
82 Mnemonic string
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050083}
84
Chris Parsonsc4fb1332021-05-18 12:31:25 -040085// A helper type for aquery processing which facilitates retrieval of path IDs from their
86// less readable Bazel structures (depset and path fragment).
87type aqueryArtifactHandler struct {
88 // Maps middleman artifact Id to input artifact depset ID.
89 // Middleman artifacts are treated as "substitute" artifacts for mixed builds. For example,
90 // if we find a middleman action which has outputs [foo, bar], and output [baz_middleman], then,
91 // for each other action which has input [baz_middleman], we add [foo, bar] to the inputs for
92 // that action instead.
93 middlemanIdToDepsetIds map[int][]int
94 // Maps depset Id to depset struct.
95 depsetIdToDepset map[int]depSetOfFiles
96 // depsetIdToArtifactIdsCache is a memoization of depset flattening, because flattening
97 // may be an expensive operation.
98 depsetIdToArtifactIdsCache map[int][]int
99 // Maps artifact Id to fully expanded path.
100 artifactIdToPath map[int]string
101}
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500102
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400103func newAqueryHandler(aqueryResult actionGraphContainer) (*aqueryArtifactHandler, error) {
Chris Parsonsaffbb602020-12-23 12:02:11 -0500104 pathFragments := map[int]pathFragment{}
105 for _, pathFragment := range aqueryResult.PathFragments {
106 pathFragments[pathFragment.Id] = pathFragment
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500107 }
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400108
Chris Parsonsaffbb602020-12-23 12:02:11 -0500109 artifactIdToPath := map[int]string{}
110 for _, artifact := range aqueryResult.Artifacts {
111 artifactPath, err := expandPathFragment(artifact.PathFragmentId, pathFragments)
112 if err != nil {
Chris Parsons4f069892021-01-15 12:22:41 -0500113 return nil, err
Chris Parsonsaffbb602020-12-23 12:02:11 -0500114 }
115 artifactIdToPath[artifact.Id] = artifactPath
116 }
Chris Parsons943f2432021-01-19 11:36:50 -0500117
118 depsetIdToDepset := map[int]depSetOfFiles{}
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500119 for _, depset := range aqueryResult.DepSetOfFiles {
Chris Parsons943f2432021-01-19 11:36:50 -0500120 depsetIdToDepset[depset.Id] = depset
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500121 }
122
Chris Parsons8d6e4332021-02-22 16:13:50 -0500123 // Do a pass through all actions to identify which artifacts are middleman artifacts.
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400124 middlemanIdToDepsetIds := map[int][]int{}
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500125 for _, actionEntry := range aqueryResult.Actions {
Chris Parsons8d6e4332021-02-22 16:13:50 -0500126 if actionEntry.Mnemonic == "Middleman" {
127 for _, outputId := range actionEntry.OutputIds {
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400128 middlemanIdToDepsetIds[outputId] = actionEntry.InputDepSetIds
Chris Parsons8d6e4332021-02-22 16:13:50 -0500129 }
130 }
131 }
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400132 return &aqueryArtifactHandler{
133 middlemanIdToDepsetIds: middlemanIdToDepsetIds,
134 depsetIdToDepset: depsetIdToDepset,
135 depsetIdToArtifactIdsCache: map[int][]int{},
136 artifactIdToPath: artifactIdToPath,
137 }, nil
138}
139
140func (a *aqueryArtifactHandler) getInputPaths(depsetIds []int) ([]string, error) {
141 inputPaths := []string{}
142
143 for _, inputDepSetId := range depsetIds {
144 inputArtifacts, err := a.artifactIdsFromDepsetId(inputDepSetId)
145 if err != nil {
146 return nil, err
147 }
148 for _, inputId := range inputArtifacts {
149 if middlemanInputDepsetIds, isMiddlemanArtifact := a.middlemanIdToDepsetIds[inputId]; isMiddlemanArtifact {
150 // Add all inputs from middleman actions which created middleman artifacts which are
151 // in the inputs for this action.
152 swappedInputPaths, err := a.getInputPaths(middlemanInputDepsetIds)
153 if err != nil {
154 return nil, err
155 }
156 inputPaths = append(inputPaths, swappedInputPaths...)
157 } else {
158 inputPath, exists := a.artifactIdToPath[inputId]
159 if !exists {
160 return nil, fmt.Errorf("undefined input artifactId %d", inputId)
161 }
162 inputPaths = append(inputPaths, inputPath)
163 }
164 }
165 }
166 return inputPaths, nil
167}
168
169func (a *aqueryArtifactHandler) artifactIdsFromDepsetId(depsetId int) ([]int, error) {
170 if result, exists := a.depsetIdToArtifactIdsCache[depsetId]; exists {
171 return result, nil
172 }
173 if depset, exists := a.depsetIdToDepset[depsetId]; exists {
174 result := depset.DirectArtifactIds
175 for _, childId := range depset.TransitiveDepSetIds {
176 childArtifactIds, err := a.artifactIdsFromDepsetId(childId)
177 if err != nil {
178 return nil, err
179 }
180 result = append(result, childArtifactIds...)
181 }
182 a.depsetIdToArtifactIdsCache[depsetId] = result
183 return result, nil
184 } else {
185 return nil, fmt.Errorf("undefined input depsetId %d", depsetId)
186 }
187}
188
189// AqueryBuildStatements returns an array of BuildStatements which should be registered (and output
190// to a ninja file) to correspond one-to-one with the given action graph json proto (from a bazel
191// aquery invocation).
192func AqueryBuildStatements(aqueryJsonProto []byte) ([]BuildStatement, error) {
193 buildStatements := []BuildStatement{}
194
195 var aqueryResult actionGraphContainer
196 err := json.Unmarshal(aqueryJsonProto, &aqueryResult)
197 if err != nil {
198 return nil, err
199 }
200 aqueryHandler, err := newAqueryHandler(aqueryResult)
201 if err != nil {
202 return nil, err
203 }
Chris Parsons8d6e4332021-02-22 16:13:50 -0500204
205 for _, actionEntry := range aqueryResult.Actions {
206 if shouldSkipAction(actionEntry) {
207 continue
208 }
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500209 outputPaths := []string{}
Liz Kammerde116852021-03-25 16:42:37 -0400210 var depfile *string
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500211 for _, outputId := range actionEntry.OutputIds {
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400212 outputPath, exists := aqueryHandler.artifactIdToPath[outputId]
Chris Parsons4f069892021-01-15 12:22:41 -0500213 if !exists {
214 return nil, fmt.Errorf("undefined outputId %d", outputId)
215 }
Liz Kammerde116852021-03-25 16:42:37 -0400216 ext := filepath.Ext(outputPath)
217 if ext == ".d" {
218 if depfile != nil {
219 return nil, fmt.Errorf("found multiple potential depfiles %q, %q", *depfile, outputPath)
220 } else {
221 depfile = &outputPath
222 }
223 } else {
224 outputPaths = append(outputPaths, outputPath)
225 }
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500226 }
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400227 inputPaths, err := aqueryHandler.getInputPaths(actionEntry.InputDepSetIds)
228 if err != nil {
229 return nil, err
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500230 }
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400231
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500232 buildStatement := BuildStatement{
233 Command: strings.Join(proptools.ShellEscapeList(actionEntry.Arguments), " "),
Liz Kammerde116852021-03-25 16:42:37 -0400234 Depfile: depfile,
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500235 OutputPaths: outputPaths,
236 InputPaths: inputPaths,
237 Env: actionEntry.EnvironmentVariables,
Liz Kammerc49e6822021-06-08 15:04:11 -0400238 Mnemonic: actionEntry.Mnemonic,
239 }
240
241 if isSymlinkAction(actionEntry) {
242 if len(inputPaths) != 1 || len(outputPaths) != 1 {
243 return nil, fmt.Errorf("Expect 1 input and 1 output to symlink action, got: input %q, output %q", inputPaths, outputPaths)
244 }
245 out := outputPaths[0]
246 outDir := proptools.ShellEscapeIncludingSpaces(filepath.Dir(out))
247 out = proptools.ShellEscapeIncludingSpaces(out)
248 in := proptools.ShellEscapeIncludingSpaces(inputPaths[0])
249 buildStatement.Command = fmt.Sprintf("mkdir -p %[1]s && rm -f %[2]s && ln -rsf %[3]s %[2]s", outDir, out, in)
250 buildStatement.SymlinkPaths = outputPaths[:]
251 } else if len(actionEntry.Arguments) < 1 {
Liz Kammerde116852021-03-25 16:42:37 -0400252 return nil, fmt.Errorf("received action with no command: [%v]", buildStatement)
Chris Parsons8d6e4332021-02-22 16:13:50 -0500253 }
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500254 buildStatements = append(buildStatements, buildStatement)
255 }
256
Chris Parsons4f069892021-01-15 12:22:41 -0500257 return buildStatements, nil
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500258}
Chris Parsonsaffbb602020-12-23 12:02:11 -0500259
Liz Kammerc49e6822021-06-08 15:04:11 -0400260func isSymlinkAction(a action) bool {
261 return a.Mnemonic == "Symlink" || a.Mnemonic == "SolibSymlink"
262}
263
Chris Parsons8d6e4332021-02-22 16:13:50 -0500264func shouldSkipAction(a action) bool {
Liz Kammerc49e6822021-06-08 15:04:11 -0400265 // TODO(b/180945121): Handle complex symlink actions.
266 if a.Mnemonic == "SymlinkTree" || a.Mnemonic == "SourceSymlinkManifest" {
Chris Parsons8d6e4332021-02-22 16:13:50 -0500267 return true
268 }
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400269 // Middleman actions are not handled like other actions; they are handled separately as a
270 // preparatory step so that their inputs may be relayed to actions depending on middleman
271 // artifacts.
Chris Parsons8d6e4332021-02-22 16:13:50 -0500272 if a.Mnemonic == "Middleman" {
273 return true
274 }
275 // Skip "Fail" actions, which are placeholder actions designed to always fail.
276 if a.Mnemonic == "Fail" {
277 return true
278 }
279 // TODO(b/180946980): Handle FileWrite. The aquery proto currently contains no information
280 // about the contents that are written.
281 if a.Mnemonic == "FileWrite" {
282 return true
283 }
284 return false
285}
286
Chris Parsonsaffbb602020-12-23 12:02:11 -0500287func expandPathFragment(id int, pathFragmentsMap map[int]pathFragment) (string, error) {
288 labels := []string{}
289 currId := id
290 // Only positive IDs are valid for path fragments. An ID of zero indicates a terminal node.
291 for currId > 0 {
292 currFragment, ok := pathFragmentsMap[currId]
293 if !ok {
Chris Parsons4f069892021-01-15 12:22:41 -0500294 return "", fmt.Errorf("undefined path fragment id %d", currId)
Chris Parsonsaffbb602020-12-23 12:02:11 -0500295 }
296 labels = append([]string{currFragment.Label}, labels...)
Liz Kammerc49e6822021-06-08 15:04:11 -0400297 if currId == currFragment.ParentId {
298 return "", fmt.Errorf("Fragment cannot refer to itself as parent %#v", currFragment)
299 }
Chris Parsonsaffbb602020-12-23 12:02:11 -0500300 currId = currFragment.ParentId
301 }
302 return filepath.Join(labels...), nil
303}