blob: eb4bdfe99e280d5d5a68c68ebefcb264791da987 [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
77 OutputPaths []string
78 InputPaths []string
79 Env []KeyValuePair
80 Mnemonic string
81}
82
83// AqueryBuildStatements returns an array of BuildStatements which should be registered (and output
84// to a ninja file) to correspond one-to-one with the given action graph json proto (from a bazel
85// aquery invocation).
Chris Parsons4f069892021-01-15 12:22:41 -050086func AqueryBuildStatements(aqueryJsonProto []byte) ([]BuildStatement, error) {
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050087 buildStatements := []BuildStatement{}
88
89 var aqueryResult actionGraphContainer
Chris Parsons4f069892021-01-15 12:22:41 -050090 err := json.Unmarshal(aqueryJsonProto, &aqueryResult)
91
92 if err != nil {
93 return nil, err
94 }
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050095
Chris Parsonsaffbb602020-12-23 12:02:11 -050096 pathFragments := map[int]pathFragment{}
97 for _, pathFragment := range aqueryResult.PathFragments {
98 pathFragments[pathFragment.Id] = pathFragment
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050099 }
Chris Parsonsaffbb602020-12-23 12:02:11 -0500100 artifactIdToPath := map[int]string{}
101 for _, artifact := range aqueryResult.Artifacts {
102 artifactPath, err := expandPathFragment(artifact.PathFragmentId, pathFragments)
103 if err != nil {
Chris Parsons4f069892021-01-15 12:22:41 -0500104 return nil, err
Chris Parsonsaffbb602020-12-23 12:02:11 -0500105 }
106 artifactIdToPath[artifact.Id] = artifactPath
107 }
Chris Parsons943f2432021-01-19 11:36:50 -0500108
109 depsetIdToDepset := map[int]depSetOfFiles{}
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500110 for _, depset := range aqueryResult.DepSetOfFiles {
Chris Parsons943f2432021-01-19 11:36:50 -0500111 depsetIdToDepset[depset.Id] = depset
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500112 }
113
Chris Parsons943f2432021-01-19 11:36:50 -0500114 // depsetIdToArtifactIdsCache is a memoization of depset flattening, because flattening
115 // may be an expensive operation.
116 depsetIdToArtifactIdsCache := map[int][]int{}
117
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500118 for _, actionEntry := range aqueryResult.Actions {
119 outputPaths := []string{}
120 for _, outputId := range actionEntry.OutputIds {
Chris Parsons4f069892021-01-15 12:22:41 -0500121 outputPath, exists := artifactIdToPath[outputId]
122 if !exists {
123 return nil, fmt.Errorf("undefined outputId %d", outputId)
124 }
125 outputPaths = append(outputPaths, outputPath)
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500126 }
127 inputPaths := []string{}
128 for _, inputDepSetId := range actionEntry.InputDepSetIds {
Chris Parsons943f2432021-01-19 11:36:50 -0500129 inputArtifacts, err :=
130 artifactIdsFromDepsetId(depsetIdToDepset, depsetIdToArtifactIdsCache, inputDepSetId)
131 if err != nil {
132 return nil, err
Chris Parsons4f069892021-01-15 12:22:41 -0500133 }
134 for _, inputId := range inputArtifacts {
135 inputPath, exists := artifactIdToPath[inputId]
136 if !exists {
137 return nil, fmt.Errorf("undefined input artifactId %d", inputId)
138 }
139 inputPaths = append(inputPaths, inputPath)
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500140 }
141 }
142 buildStatement := BuildStatement{
143 Command: strings.Join(proptools.ShellEscapeList(actionEntry.Arguments), " "),
144 OutputPaths: outputPaths,
145 InputPaths: inputPaths,
146 Env: actionEntry.EnvironmentVariables,
147 Mnemonic: actionEntry.Mnemonic}
148 buildStatements = append(buildStatements, buildStatement)
149 }
150
Chris Parsons4f069892021-01-15 12:22:41 -0500151 return buildStatements, nil
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500152}
Chris Parsonsaffbb602020-12-23 12:02:11 -0500153
Chris Parsons943f2432021-01-19 11:36:50 -0500154func artifactIdsFromDepsetId(depsetIdToDepset map[int]depSetOfFiles,
155 depsetIdToArtifactIdsCache map[int][]int, depsetId int) ([]int, error) {
156 if result, exists := depsetIdToArtifactIdsCache[depsetId]; exists {
157 return result, nil
158 }
159 if depset, exists := depsetIdToDepset[depsetId]; exists {
160 result := depset.DirectArtifactIds
161 for _, childId := range depset.TransitiveDepSetIds {
162 childArtifactIds, err :=
163 artifactIdsFromDepsetId(depsetIdToDepset, depsetIdToArtifactIdsCache, childId)
164 if err != nil {
165 return nil, err
166 }
167 result = append(result, childArtifactIds...)
168 }
169 depsetIdToArtifactIdsCache[depsetId] = result
170 return result, nil
171 } else {
172 return nil, fmt.Errorf("undefined input depsetId %d", depsetId)
173 }
174}
175
Chris Parsonsaffbb602020-12-23 12:02:11 -0500176func expandPathFragment(id int, pathFragmentsMap map[int]pathFragment) (string, error) {
177 labels := []string{}
178 currId := id
179 // Only positive IDs are valid for path fragments. An ID of zero indicates a terminal node.
180 for currId > 0 {
181 currFragment, ok := pathFragmentsMap[currId]
182 if !ok {
Chris Parsons4f069892021-01-15 12:22:41 -0500183 return "", fmt.Errorf("undefined path fragment id %d", currId)
Chris Parsonsaffbb602020-12-23 12:02:11 -0500184 }
185 labels = append([]string{currFragment.Label}, labels...)
186 currId = currFragment.ParentId
187 }
188 return filepath.Join(labels...), nil
189}