blob: a196e8bc59a517eaf6ba5a46c87bac08eecf880a [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 Parsonsaffbb602020-12-23 12:02:11 -050049 Id int
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050050 // TODO(cparsons): Handle non-flat depsets.
Chris Parsonsaffbb602020-12-23 12:02:11 -050051 DirectArtifactIds []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 }
108 depsetIdToArtifactIds := map[int][]int{}
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500109 for _, depset := range aqueryResult.DepSetOfFiles {
110 depsetIdToArtifactIds[depset.Id] = depset.DirectArtifactIds
111 }
112
113 for _, actionEntry := range aqueryResult.Actions {
114 outputPaths := []string{}
115 for _, outputId := range actionEntry.OutputIds {
Chris Parsons4f069892021-01-15 12:22:41 -0500116 outputPath, exists := artifactIdToPath[outputId]
117 if !exists {
118 return nil, fmt.Errorf("undefined outputId %d", outputId)
119 }
120 outputPaths = append(outputPaths, outputPath)
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500121 }
122 inputPaths := []string{}
123 for _, inputDepSetId := range actionEntry.InputDepSetIds {
Chris Parsons4f069892021-01-15 12:22:41 -0500124 inputArtifacts, exists := depsetIdToArtifactIds[inputDepSetId]
125 if !exists {
126 return nil, fmt.Errorf("undefined input depsetId %d", inputDepSetId)
127 }
128 for _, inputId := range inputArtifacts {
129 inputPath, exists := artifactIdToPath[inputId]
130 if !exists {
131 return nil, fmt.Errorf("undefined input artifactId %d", inputId)
132 }
133 inputPaths = append(inputPaths, inputPath)
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500134 }
135 }
136 buildStatement := BuildStatement{
137 Command: strings.Join(proptools.ShellEscapeList(actionEntry.Arguments), " "),
138 OutputPaths: outputPaths,
139 InputPaths: inputPaths,
140 Env: actionEntry.EnvironmentVariables,
141 Mnemonic: actionEntry.Mnemonic}
142 buildStatements = append(buildStatements, buildStatement)
143 }
144
Chris Parsons4f069892021-01-15 12:22:41 -0500145 return buildStatements, nil
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500146}
Chris Parsonsaffbb602020-12-23 12:02:11 -0500147
148func expandPathFragment(id int, pathFragmentsMap map[int]pathFragment) (string, error) {
149 labels := []string{}
150 currId := id
151 // Only positive IDs are valid for path fragments. An ID of zero indicates a terminal node.
152 for currId > 0 {
153 currFragment, ok := pathFragmentsMap[currId]
154 if !ok {
Chris Parsons4f069892021-01-15 12:22:41 -0500155 return "", fmt.Errorf("undefined path fragment id %d", currId)
Chris Parsonsaffbb602020-12-23 12:02:11 -0500156 }
157 labels = append([]string{currFragment.Label}, labels...)
158 currId = currFragment.ParentId
159 }
160 return filepath.Join(labels...), nil
161}