blob: 404be8ce04615f2637dde016e5e3f4c8d251371c [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).
86func AqueryBuildStatements(aqueryJsonProto []byte) []BuildStatement {
87 buildStatements := []BuildStatement{}
88
89 var aqueryResult actionGraphContainer
90 json.Unmarshal(aqueryJsonProto, &aqueryResult)
91
Chris Parsonsaffbb602020-12-23 12:02:11 -050092 pathFragments := map[int]pathFragment{}
93 for _, pathFragment := range aqueryResult.PathFragments {
94 pathFragments[pathFragment.Id] = pathFragment
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050095 }
Chris Parsonsaffbb602020-12-23 12:02:11 -050096 artifactIdToPath := map[int]string{}
97 for _, artifact := range aqueryResult.Artifacts {
98 artifactPath, err := expandPathFragment(artifact.PathFragmentId, pathFragments)
99 if err != nil {
100 // TODO(cparsons): Better error handling.
101 panic(err.Error())
102 }
103 artifactIdToPath[artifact.Id] = artifactPath
104 }
105 depsetIdToArtifactIds := map[int][]int{}
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500106 for _, depset := range aqueryResult.DepSetOfFiles {
107 depsetIdToArtifactIds[depset.Id] = depset.DirectArtifactIds
108 }
109
110 for _, actionEntry := range aqueryResult.Actions {
111 outputPaths := []string{}
112 for _, outputId := range actionEntry.OutputIds {
113 // TODO(cparsons): Validate the id is present.
114 outputPaths = append(outputPaths, artifactIdToPath[outputId])
115 }
116 inputPaths := []string{}
117 for _, inputDepSetId := range actionEntry.InputDepSetIds {
118 // TODO(cparsons): Validate the id is present.
119 for _, inputId := range depsetIdToArtifactIds[inputDepSetId] {
120 // TODO(cparsons): Validate the id is present.
121 inputPaths = append(inputPaths, artifactIdToPath[inputId])
122 }
123 }
124 buildStatement := BuildStatement{
125 Command: strings.Join(proptools.ShellEscapeList(actionEntry.Arguments), " "),
126 OutputPaths: outputPaths,
127 InputPaths: inputPaths,
128 Env: actionEntry.EnvironmentVariables,
129 Mnemonic: actionEntry.Mnemonic}
130 buildStatements = append(buildStatements, buildStatement)
131 }
132
133 return buildStatements
134}
Chris Parsonsaffbb602020-12-23 12:02:11 -0500135
136func expandPathFragment(id int, pathFragmentsMap map[int]pathFragment) (string, error) {
137 labels := []string{}
138 currId := id
139 // Only positive IDs are valid for path fragments. An ID of zero indicates a terminal node.
140 for currId > 0 {
141 currFragment, ok := pathFragmentsMap[currId]
142 if !ok {
143 return "", fmt.Errorf("undefined path fragment id '%s'", currId)
144 }
145 labels = append([]string{currFragment.Label}, labels...)
146 currId = currFragment.ParentId
147 }
148 return filepath.Join(labels...), nil
149}