blob: ae2b107e154eced675abee4231c45076946f745d [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 (
Chris Parsons0bfb1c02022-05-12 16:43:01 -040018 "crypto/sha256"
Usta Shrestha2ccdb422022-06-02 10:19:13 -040019 "encoding/base64"
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050020 "encoding/json"
Chris Parsonsaffbb602020-12-23 12:02:11 -050021 "fmt"
22 "path/filepath"
Chris Parsons0bfb1c02022-05-12 16:43:01 -040023 "reflect"
Chris Parsons0bfb1c02022-05-12 16:43:01 -040024 "sort"
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050025 "strings"
26
27 "github.com/google/blueprint/proptools"
28)
29
Usta Shrestha6298cc52022-05-27 17:40:21 -040030type artifactId int
31type depsetId int
32type pathFragmentId int
33
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050034// artifact contains relevant portions of Bazel's aquery proto, Artifact.
35// Represents a single artifact, whether it's a source file or a derived output file.
36type artifact struct {
Usta Shrestha6298cc52022-05-27 17:40:21 -040037 Id artifactId
38 PathFragmentId pathFragmentId
Chris Parsonsaffbb602020-12-23 12:02:11 -050039}
40
41type pathFragment struct {
Usta Shrestha6298cc52022-05-27 17:40:21 -040042 Id pathFragmentId
Chris Parsonsaffbb602020-12-23 12:02:11 -050043 Label string
Usta Shrestha6298cc52022-05-27 17:40:21 -040044 ParentId pathFragmentId
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050045}
46
47// KeyValuePair represents Bazel's aquery proto, KeyValuePair.
48type KeyValuePair struct {
49 Key string
50 Value string
51}
52
Chris Parsons1a7aca02022-04-25 22:35:15 -040053// AqueryDepset is a depset definition from Bazel's aquery response. This is
Chris Parsons0bfb1c02022-05-12 16:43:01 -040054// akin to the `depSetOfFiles` in the response proto, except:
55// * direct artifacts are enumerated by full path instead of by ID
MarkDacek75641272022-05-13 20:44:07 +000056// * it has a hash of the depset contents, instead of an int ID (for determinism)
Chris Parsons1a7aca02022-04-25 22:35:15 -040057// A depset is a data structure for efficient transitive handling of artifact
58// paths. A single depset consists of one or more artifact paths and one or
59// more "child" depsets.
60type AqueryDepset struct {
Chris Parsons0bfb1c02022-05-12 16:43:01 -040061 ContentHash string
62 DirectArtifacts []string
63 TransitiveDepSetHashes []string
Chris Parsons1a7aca02022-04-25 22:35:15 -040064}
65
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050066// depSetOfFiles contains relevant portions of Bazel's aquery proto, DepSetOfFiles.
67// Represents a data structure containing one or more files. Depsets in Bazel are an efficient
68// data structure for storing large numbers of file paths.
69type depSetOfFiles struct {
Usta Shrestha6298cc52022-05-27 17:40:21 -040070 Id depsetId
71 DirectArtifactIds []artifactId
72 TransitiveDepSetIds []depsetId
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050073}
74
75// action contains relevant portions of Bazel's aquery proto, Action.
76// Represents a single command line invocation in the Bazel build graph.
77type action struct {
78 Arguments []string
79 EnvironmentVariables []KeyValuePair
Usta Shrestha6298cc52022-05-27 17:40:21 -040080 InputDepSetIds []depsetId
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050081 Mnemonic string
Usta Shrestha6298cc52022-05-27 17:40:21 -040082 OutputIds []artifactId
Wei Li455ba832021-11-04 22:58:12 +000083 TemplateContent string
84 Substitutions []KeyValuePair
Sasha Smundak1da064c2022-06-08 16:36:16 -070085 FileContents string
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050086}
87
88// actionGraphContainer contains relevant portions of Bazel's aquery proto, ActionGraphContainer.
89// An aquery response from Bazel contains a single ActionGraphContainer proto.
90type actionGraphContainer struct {
91 Artifacts []artifact
92 Actions []action
93 DepSetOfFiles []depSetOfFiles
Chris Parsonsaffbb602020-12-23 12:02:11 -050094 PathFragments []pathFragment
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050095}
96
97// BuildStatement contains information to register a build statement corresponding (one to one)
98// with a Bazel action from Bazel's action graph.
99type BuildStatement struct {
Liz Kammerc49e6822021-06-08 15:04:11 -0400100 Command string
101 Depfile *string
102 OutputPaths []string
Liz Kammerc49e6822021-06-08 15:04:11 -0400103 SymlinkPaths []string
104 Env []KeyValuePair
105 Mnemonic string
Chris Parsons1a7aca02022-04-25 22:35:15 -0400106
107 // Inputs of this build statement, either as unexpanded depsets or expanded
108 // input paths. There should be no overlap between these fields; an input
109 // path should either be included as part of an unexpanded depset or a raw
110 // input path string, but not both.
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400111 InputDepsetHashes []string
112 InputPaths []string
Sasha Smundak1da064c2022-06-08 16:36:16 -0700113 FileContents string
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500114}
115
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400116// A helper type for aquery processing which facilitates retrieval of path IDs from their
117// less readable Bazel structures (depset and path fragment).
118type aqueryArtifactHandler struct {
Usta Shresthaef922252022-06-02 14:23:02 -0400119 // Switches to true if any depset contains only `bazelToolsDependencySentinel`
120 bazelToolsDependencySentinelNeeded bool
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400121 // Maps depset id to AqueryDepset, a representation of depset which is
122 // post-processed for middleman artifact handling, unhandled artifact
123 // dropping, content hashing, etc.
Usta Shrestha6298cc52022-05-27 17:40:21 -0400124 depsetIdToAqueryDepset map[depsetId]AqueryDepset
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400125 // Maps content hash to AqueryDepset.
126 depsetHashToAqueryDepset map[string]AqueryDepset
127
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400128 // depsetIdToArtifactIdsCache is a memoization of depset flattening, because flattening
129 // may be an expensive operation.
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400130 depsetHashToArtifactPathsCache map[string][]string
Usta Shrestha6298cc52022-05-27 17:40:21 -0400131 // Maps artifact ids to fully expanded paths.
132 artifactIdToPath map[artifactId]string
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400133}
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500134
Wei Li455ba832021-11-04 22:58:12 +0000135// The tokens should be substituted with the value specified here, instead of the
136// one returned in 'substitutions' of TemplateExpand action.
Usta Shrestha6298cc52022-05-27 17:40:21 -0400137var templateActionOverriddenTokens = map[string]string{
Wei Li455ba832021-11-04 22:58:12 +0000138 // Uses "python3" for %python_binary% instead of the value returned by aquery
139 // which is "py3wrapper.sh". See removePy3wrapperScript.
140 "%python_binary%": "python3",
141}
142
Wei Li455ba832021-11-04 22:58:12 +0000143// The file name of py3wrapper.sh, which is used by py_binary targets.
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400144const py3wrapperFileName = "/py3wrapper.sh"
Wei Li455ba832021-11-04 22:58:12 +0000145
Usta Shresthaef922252022-06-02 14:23:02 -0400146// A file to be put into depsets that are otherwise empty
147const bazelToolsDependencySentinel = "BAZEL_TOOLS_DEPENDENCY_SENTINEL"
148
Usta Shrestha6298cc52022-05-27 17:40:21 -0400149func indexBy[K comparable, V any](values []V, keyFn func(v V) K) map[K]V {
150 m := map[K]V{}
151 for _, v := range values {
152 m[keyFn(v)] = v
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500153 }
Usta Shrestha6298cc52022-05-27 17:40:21 -0400154 return m
155}
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400156
Usta Shrestha6298cc52022-05-27 17:40:21 -0400157func newAqueryHandler(aqueryResult actionGraphContainer) (*aqueryArtifactHandler, error) {
158 pathFragments := indexBy(aqueryResult.PathFragments, func(pf pathFragment) pathFragmentId {
159 return pf.Id
160 })
161
162 artifactIdToPath := map[artifactId]string{}
Chris Parsonsaffbb602020-12-23 12:02:11 -0500163 for _, artifact := range aqueryResult.Artifacts {
164 artifactPath, err := expandPathFragment(artifact.PathFragmentId, pathFragments)
165 if err != nil {
Chris Parsons4f069892021-01-15 12:22:41 -0500166 return nil, err
Chris Parsonsaffbb602020-12-23 12:02:11 -0500167 }
168 artifactIdToPath[artifact.Id] = artifactPath
169 }
Chris Parsons943f2432021-01-19 11:36:50 -0500170
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400171 // Map middleman artifact ContentHash to input artifact depset ID.
Chris Parsons1a7aca02022-04-25 22:35:15 -0400172 // Middleman artifacts are treated as "substitute" artifacts for mixed builds. For example,
Usta Shrestha16ac1352022-06-22 11:01:55 -0400173 // if we find a middleman action which has inputs [foo, bar], and output [baz_middleman], then,
Chris Parsons1a7aca02022-04-25 22:35:15 -0400174 // for each other action which has input [baz_middleman], we add [foo, bar] to the inputs for
175 // that action instead.
Usta Shrestha6298cc52022-05-27 17:40:21 -0400176 middlemanIdToDepsetIds := map[artifactId][]depsetId{}
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500177 for _, actionEntry := range aqueryResult.Actions {
Chris Parsons8d6e4332021-02-22 16:13:50 -0500178 if actionEntry.Mnemonic == "Middleman" {
179 for _, outputId := range actionEntry.OutputIds {
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400180 middlemanIdToDepsetIds[outputId] = actionEntry.InputDepSetIds
Chris Parsons8d6e4332021-02-22 16:13:50 -0500181 }
182 }
183 }
Chris Parsons1a7aca02022-04-25 22:35:15 -0400184
Usta Shrestha6298cc52022-05-27 17:40:21 -0400185 depsetIdToDepset := indexBy(aqueryResult.DepSetOfFiles, func(d depSetOfFiles) depsetId {
186 return d.Id
187 })
Chris Parsons1a7aca02022-04-25 22:35:15 -0400188
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400189 aqueryHandler := aqueryArtifactHandler{
Usta Shrestha6298cc52022-05-27 17:40:21 -0400190 depsetIdToAqueryDepset: map[depsetId]AqueryDepset{},
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400191 depsetHashToAqueryDepset: map[string]AqueryDepset{},
192 depsetHashToArtifactPathsCache: map[string][]string{},
193 artifactIdToPath: artifactIdToPath,
194 }
195
196 // Validate and adjust aqueryResult.DepSetOfFiles values.
197 for _, depset := range aqueryResult.DepSetOfFiles {
198 _, err := aqueryHandler.populateDepsetMaps(depset, middlemanIdToDepsetIds, depsetIdToDepset)
199 if err != nil {
200 return nil, err
201 }
202 }
203
204 return &aqueryHandler, nil
205}
206
207// Ensures that the handler's depsetIdToAqueryDepset map contains an entry for the given
208// depset.
Usta Shrestha6298cc52022-05-27 17:40:21 -0400209func (a *aqueryArtifactHandler) populateDepsetMaps(depset depSetOfFiles, middlemanIdToDepsetIds map[artifactId][]depsetId, depsetIdToDepset map[depsetId]depSetOfFiles) (AqueryDepset, error) {
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400210 if aqueryDepset, containsDepset := a.depsetIdToAqueryDepset[depset.Id]; containsDepset {
211 return aqueryDepset, nil
212 }
213 transitiveDepsetIds := depset.TransitiveDepSetIds
Usta Shrestha6298cc52022-05-27 17:40:21 -0400214 var directArtifactPaths []string
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400215 for _, artifactId := range depset.DirectArtifactIds {
216 path, pathExists := a.artifactIdToPath[artifactId]
217 if !pathExists {
218 return AqueryDepset{}, fmt.Errorf("undefined input artifactId %d", artifactId)
219 }
220 // Filter out any inputs which are universally dropped, and swap middleman
221 // artifacts with their corresponding depsets.
222 if depsetsToUse, isMiddleman := middlemanIdToDepsetIds[artifactId]; isMiddleman {
223 // Swap middleman artifacts with their corresponding depsets and drop the middleman artifacts.
224 transitiveDepsetIds = append(transitiveDepsetIds, depsetsToUse...)
Usta Shresthaef922252022-06-02 14:23:02 -0400225 } else if strings.HasSuffix(path, py3wrapperFileName) ||
Usta Shresthaef922252022-06-02 14:23:02 -0400226 strings.HasPrefix(path, "../bazel_tools") {
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400227 // Drop these artifacts.
228 // See go/python-binary-host-mixed-build for more details.
Sasha Smundakc180dbd2022-07-03 14:55:58 -0700229 // 1) Drop py3wrapper.sh, just use python binary, the launcher script generated by the
230 // TemplateExpandAction handles everything necessary to launch a Pythin application.
231 // 2) ../bazel_tools: they have MODIFY timestamp 10years in the future and would cause the
Usta Shresthaef922252022-06-02 14:23:02 -0400232 // containing depset to always be considered newer than their outputs.
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400233 } else {
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400234 directArtifactPaths = append(directArtifactPaths, path)
235 }
236 }
237
Usta Shrestha6298cc52022-05-27 17:40:21 -0400238 var childDepsetHashes []string
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400239 for _, childDepsetId := range transitiveDepsetIds {
240 childDepset, exists := depsetIdToDepset[childDepsetId]
241 if !exists {
242 return AqueryDepset{}, fmt.Errorf("undefined input depsetId %d (referenced by depsetId %d)", childDepsetId, depset.Id)
243 }
244 childAqueryDepset, err := a.populateDepsetMaps(childDepset, middlemanIdToDepsetIds, depsetIdToDepset)
245 if err != nil {
246 return AqueryDepset{}, err
247 }
248 childDepsetHashes = append(childDepsetHashes, childAqueryDepset.ContentHash)
249 }
Usta Shresthaef922252022-06-02 14:23:02 -0400250 if len(directArtifactPaths) == 0 && len(childDepsetHashes) == 0 {
251 // We could omit this depset altogether but that requires cleanup on
252 // transitive dependents.
253 // As a simpler alternative, we use this sentinel file as a dependency.
254 directArtifactPaths = append(directArtifactPaths, bazelToolsDependencySentinel)
255 a.bazelToolsDependencySentinelNeeded = true
256 }
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400257 aqueryDepset := AqueryDepset{
258 ContentHash: depsetContentHash(directArtifactPaths, childDepsetHashes),
259 DirectArtifacts: directArtifactPaths,
260 TransitiveDepSetHashes: childDepsetHashes,
261 }
262 a.depsetIdToAqueryDepset[depset.Id] = aqueryDepset
263 a.depsetHashToAqueryDepset[aqueryDepset.ContentHash] = aqueryDepset
264 return aqueryDepset, nil
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400265}
266
Chris Parsons1a7aca02022-04-25 22:35:15 -0400267// getInputPaths flattens the depsets of the given IDs and returns all transitive
268// input paths contained in these depsets.
269// This is a potentially expensive operation, and should not be invoked except
270// for actions which need specialized input handling.
Usta Shrestha6298cc52022-05-27 17:40:21 -0400271func (a *aqueryArtifactHandler) getInputPaths(depsetIds []depsetId) ([]string, error) {
272 var inputPaths []string
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400273
274 for _, inputDepSetId := range depsetIds {
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400275 depset := a.depsetIdToAqueryDepset[inputDepSetId]
276 inputArtifacts, err := a.artifactPathsFromDepsetHash(depset.ContentHash)
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400277 if err != nil {
278 return nil, err
279 }
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400280 for _, inputPath := range inputArtifacts {
Chris Parsons1a7aca02022-04-25 22:35:15 -0400281 inputPaths = append(inputPaths, inputPath)
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400282 }
283 }
Wei Li455ba832021-11-04 22:58:12 +0000284
Chris Parsons1a7aca02022-04-25 22:35:15 -0400285 return inputPaths, nil
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400286}
287
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400288func (a *aqueryArtifactHandler) artifactPathsFromDepsetHash(depsetHash string) ([]string, error) {
289 if result, exists := a.depsetHashToArtifactPathsCache[depsetHash]; exists {
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400290 return result, nil
291 }
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400292 if depset, exists := a.depsetHashToAqueryDepset[depsetHash]; exists {
293 result := depset.DirectArtifacts
294 for _, childHash := range depset.TransitiveDepSetHashes {
295 childArtifactIds, err := a.artifactPathsFromDepsetHash(childHash)
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400296 if err != nil {
297 return nil, err
298 }
299 result = append(result, childArtifactIds...)
300 }
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400301 a.depsetHashToArtifactPathsCache[depsetHash] = result
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400302 return result, nil
303 } else {
Usta Shrestha2ccdb422022-06-02 10:19:13 -0400304 return nil, fmt.Errorf("undefined input depset hash %s", depsetHash)
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400305 }
306}
307
Chris Parsons1a7aca02022-04-25 22:35:15 -0400308// AqueryBuildStatements returns a slice of BuildStatements and a slice of AqueryDepset
Usta Shrestha6298cc52022-05-27 17:40:21 -0400309// which should be registered (and output to a ninja file) to correspond with Bazel's
Chris Parsons1a7aca02022-04-25 22:35:15 -0400310// action graph, as described by the given action graph json proto.
311// BuildStatements are one-to-one with actions in the given action graph, and AqueryDepsets
312// are one-to-one with Bazel's depSetOfFiles objects.
313func AqueryBuildStatements(aqueryJsonProto []byte) ([]BuildStatement, []AqueryDepset, error) {
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400314 var aqueryResult actionGraphContainer
315 err := json.Unmarshal(aqueryJsonProto, &aqueryResult)
316 if err != nil {
Chris Parsons1a7aca02022-04-25 22:35:15 -0400317 return nil, nil, err
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400318 }
319 aqueryHandler, err := newAqueryHandler(aqueryResult)
320 if err != nil {
Chris Parsons1a7aca02022-04-25 22:35:15 -0400321 return nil, nil, err
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400322 }
Chris Parsons8d6e4332021-02-22 16:13:50 -0500323
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400324 var buildStatements []BuildStatement
Usta Shresthaef922252022-06-02 14:23:02 -0400325 if aqueryHandler.bazelToolsDependencySentinelNeeded {
326 buildStatements = append(buildStatements, BuildStatement{
327 Command: fmt.Sprintf("touch '%s'", bazelToolsDependencySentinel),
328 OutputPaths: []string{bazelToolsDependencySentinel},
329 Mnemonic: bazelToolsDependencySentinel,
330 })
331 }
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400332
Chris Parsons8d6e4332021-02-22 16:13:50 -0500333 for _, actionEntry := range aqueryResult.Actions {
334 if shouldSkipAction(actionEntry) {
335 continue
336 }
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400337
Chris Parsons1a7aca02022-04-25 22:35:15 -0400338 var buildStatement BuildStatement
Sasha Smundak1da064c2022-06-08 16:36:16 -0700339 if actionEntry.isSymlinkAction() {
Chris Parsons1a7aca02022-04-25 22:35:15 -0400340 buildStatement, err = aqueryHandler.symlinkActionBuildStatement(actionEntry)
Sasha Smundak1da064c2022-06-08 16:36:16 -0700341 } else if actionEntry.isTemplateExpandAction() && len(actionEntry.Arguments) < 1 {
Chris Parsons1a7aca02022-04-25 22:35:15 -0400342 buildStatement, err = aqueryHandler.templateExpandActionBuildStatement(actionEntry)
Sasha Smundak1da064c2022-06-08 16:36:16 -0700343 } else if actionEntry.isFileWriteAction() {
344 buildStatement, err = aqueryHandler.fileWriteActionBuildStatement(actionEntry)
Sasha Smundakc180dbd2022-07-03 14:55:58 -0700345 } else if actionEntry.isSymlinkTreeAction() {
346 buildStatement, err = aqueryHandler.symlinkTreeActionBuildStatement(actionEntry)
Liz Kammerc49e6822021-06-08 15:04:11 -0400347 } else if len(actionEntry.Arguments) < 1 {
Chris Parsons1a7aca02022-04-25 22:35:15 -0400348 return nil, nil, fmt.Errorf("received action with no command: [%s]", actionEntry.Mnemonic)
349 } else {
350 buildStatement, err = aqueryHandler.normalActionBuildStatement(actionEntry)
351 }
352
353 if err != nil {
354 return nil, nil, err
Chris Parsons8d6e4332021-02-22 16:13:50 -0500355 }
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500356 buildStatements = append(buildStatements, buildStatement)
357 }
358
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400359 depsetsByHash := map[string]AqueryDepset{}
Usta Shrestha6298cc52022-05-27 17:40:21 -0400360 var depsets []AqueryDepset
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400361 for _, aqueryDepset := range aqueryHandler.depsetIdToAqueryDepset {
362 if prevEntry, hasKey := depsetsByHash[aqueryDepset.ContentHash]; hasKey {
363 // Two depsets collide on hash. Ensure that their contents are identical.
364 if !reflect.DeepEqual(aqueryDepset, prevEntry) {
Usta Shrestha16ac1352022-06-22 11:01:55 -0400365 return nil, nil, fmt.Errorf("two different depsets have the same hash: %v, %v", prevEntry, aqueryDepset)
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400366 }
367 } else {
368 depsetsByHash[aqueryDepset.ContentHash] = aqueryDepset
369 depsets = append(depsets, aqueryDepset)
Chris Parsons1a7aca02022-04-25 22:35:15 -0400370 }
Chris Parsons1a7aca02022-04-25 22:35:15 -0400371 }
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400372
373 // Build Statements and depsets must be sorted by their content hash to
374 // preserve determinism between builds (this will result in consistent ninja file
375 // output). Note they are not sorted by their original IDs nor their Bazel ordering,
376 // as Bazel gives nondeterministic ordering / identifiers in aquery responses.
377 sort.Slice(buildStatements, func(i, j int) bool {
378 // For build statements, compare output lists. In Bazel, each output file
379 // may only have one action which generates it, so this will provide
380 // a deterministic ordering.
381 outputs_i := buildStatements[i].OutputPaths
382 outputs_j := buildStatements[j].OutputPaths
383 if len(outputs_i) != len(outputs_j) {
384 return len(outputs_i) < len(outputs_j)
385 }
386 if len(outputs_i) == 0 {
387 // No outputs for these actions, so compare commands.
388 return buildStatements[i].Command < buildStatements[j].Command
389 }
390 // There may be multiple outputs, but the output ordering is deterministic.
391 return outputs_i[0] < outputs_j[0]
392 })
393 sort.Slice(depsets, func(i, j int) bool {
394 return depsets[i].ContentHash < depsets[j].ContentHash
395 })
Chris Parsons1a7aca02022-04-25 22:35:15 -0400396 return buildStatements, depsets, nil
397}
398
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400399// depsetContentHash computes and returns a SHA256 checksum of the contents of
400// the given depset. This content hash may serve as the depset's identifier.
401// Using a content hash for an identifier is superior for determinism. (For example,
402// using an integer identifier which depends on the order in which the depsets are
403// created would result in nondeterministic depset IDs.)
404func depsetContentHash(directPaths []string, transitiveDepsetHashes []string) string {
405 h := sha256.New()
406 // Use newline as delimiter, as paths cannot contain newline.
407 h.Write([]byte(strings.Join(directPaths, "\n")))
Usta Shrestha2ccdb422022-06-02 10:19:13 -0400408 h.Write([]byte(strings.Join(transitiveDepsetHashes, "")))
409 fullHash := base64.RawURLEncoding.EncodeToString(h.Sum(nil))
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400410 return fullHash
411}
412
Usta Shrestha6298cc52022-05-27 17:40:21 -0400413func (a *aqueryArtifactHandler) depsetContentHashes(inputDepsetIds []depsetId) ([]string, error) {
414 var hashes []string
Chris Parsons1a7aca02022-04-25 22:35:15 -0400415 for _, depsetId := range inputDepsetIds {
Usta Shresthac2372492022-05-27 10:45:00 -0400416 if aqueryDepset, exists := a.depsetIdToAqueryDepset[depsetId]; !exists {
Chris Parsons1a7aca02022-04-25 22:35:15 -0400417 return nil, fmt.Errorf("undefined input depsetId %d", depsetId)
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400418 } else {
419 hashes = append(hashes, aqueryDepset.ContentHash)
Chris Parsons1a7aca02022-04-25 22:35:15 -0400420 }
421 }
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400422 return hashes, nil
Chris Parsons1a7aca02022-04-25 22:35:15 -0400423}
424
Usta Shresthac2372492022-05-27 10:45:00 -0400425func (a *aqueryArtifactHandler) normalActionBuildStatement(actionEntry action) (BuildStatement, error) {
Chris Parsons1a7aca02022-04-25 22:35:15 -0400426 command := strings.Join(proptools.ShellEscapeListIncludingSpaces(actionEntry.Arguments), " ")
Usta Shresthac2372492022-05-27 10:45:00 -0400427 inputDepsetHashes, err := a.depsetContentHashes(actionEntry.InputDepSetIds)
Chris Parsons1a7aca02022-04-25 22:35:15 -0400428 if err != nil {
429 return BuildStatement{}, err
430 }
Usta Shresthac2372492022-05-27 10:45:00 -0400431 outputPaths, depfile, err := a.getOutputPaths(actionEntry)
Chris Parsons1a7aca02022-04-25 22:35:15 -0400432 if err != nil {
433 return BuildStatement{}, err
434 }
435
436 buildStatement := BuildStatement{
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400437 Command: command,
438 Depfile: depfile,
439 OutputPaths: outputPaths,
440 InputDepsetHashes: inputDepsetHashes,
441 Env: actionEntry.EnvironmentVariables,
442 Mnemonic: actionEntry.Mnemonic,
Chris Parsons1a7aca02022-04-25 22:35:15 -0400443 }
444 return buildStatement, nil
445}
446
Usta Shresthac2372492022-05-27 10:45:00 -0400447func (a *aqueryArtifactHandler) templateExpandActionBuildStatement(actionEntry action) (BuildStatement, error) {
448 outputPaths, depfile, err := a.getOutputPaths(actionEntry)
Chris Parsons1a7aca02022-04-25 22:35:15 -0400449 if err != nil {
450 return BuildStatement{}, err
451 }
452 if len(outputPaths) != 1 {
453 return BuildStatement{}, fmt.Errorf("Expect 1 output to template expand action, got: output %q", outputPaths)
454 }
455 expandedTemplateContent := expandTemplateContent(actionEntry)
456 // The expandedTemplateContent is escaped for being used in double quotes and shell unescape,
457 // and the new line characters (\n) are also changed to \\n which avoids some Ninja escape on \n, which might
458 // change \n to space and mess up the format of Python programs.
459 // sed is used to convert \\n back to \n before saving to output file.
460 // See go/python-binary-host-mixed-build for more details.
461 command := fmt.Sprintf(`/bin/bash -c 'echo "%[1]s" | sed "s/\\\\n/\\n/g" > %[2]s && chmod a+x %[2]s'`,
462 escapeCommandlineArgument(expandedTemplateContent), outputPaths[0])
Usta Shresthac2372492022-05-27 10:45:00 -0400463 inputDepsetHashes, err := a.depsetContentHashes(actionEntry.InputDepSetIds)
Chris Parsons1a7aca02022-04-25 22:35:15 -0400464 if err != nil {
465 return BuildStatement{}, err
466 }
467
468 buildStatement := BuildStatement{
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400469 Command: command,
470 Depfile: depfile,
471 OutputPaths: outputPaths,
472 InputDepsetHashes: inputDepsetHashes,
473 Env: actionEntry.EnvironmentVariables,
474 Mnemonic: actionEntry.Mnemonic,
Chris Parsons1a7aca02022-04-25 22:35:15 -0400475 }
476 return buildStatement, nil
477}
478
Sasha Smundak1da064c2022-06-08 16:36:16 -0700479func (a *aqueryArtifactHandler) fileWriteActionBuildStatement(actionEntry action) (BuildStatement, error) {
480 outputPaths, _, err := a.getOutputPaths(actionEntry)
481 var depsetHashes []string
482 if err == nil {
483 depsetHashes, err = a.depsetContentHashes(actionEntry.InputDepSetIds)
484 }
485 if err != nil {
486 return BuildStatement{}, err
487 }
488 return BuildStatement{
489 Depfile: nil,
490 OutputPaths: outputPaths,
491 Env: actionEntry.EnvironmentVariables,
492 Mnemonic: actionEntry.Mnemonic,
493 InputDepsetHashes: depsetHashes,
494 FileContents: actionEntry.FileContents,
495 }, nil
496}
497
Sasha Smundakc180dbd2022-07-03 14:55:58 -0700498func (a *aqueryArtifactHandler) symlinkTreeActionBuildStatement(actionEntry action) (BuildStatement, error) {
499 outputPaths, _, err := a.getOutputPaths(actionEntry)
500 if err != nil {
501 return BuildStatement{}, err
502 }
503 inputPaths, err := a.getInputPaths(actionEntry.InputDepSetIds)
504 if err != nil {
505 return BuildStatement{}, err
506 }
507 if len(inputPaths) != 1 || len(outputPaths) != 1 {
508 return BuildStatement{}, fmt.Errorf("Expect 1 input and 1 output to symlink action, got: input %q, output %q", inputPaths, outputPaths)
509 }
510 // The actual command is generated in bazelSingleton.GenerateBuildActions
511 return BuildStatement{
512 Depfile: nil,
513 OutputPaths: outputPaths,
514 Env: actionEntry.EnvironmentVariables,
515 Mnemonic: actionEntry.Mnemonic,
516 InputPaths: inputPaths,
517 }, nil
518}
519
Usta Shresthac2372492022-05-27 10:45:00 -0400520func (a *aqueryArtifactHandler) symlinkActionBuildStatement(actionEntry action) (BuildStatement, error) {
521 outputPaths, depfile, err := a.getOutputPaths(actionEntry)
Chris Parsons1a7aca02022-04-25 22:35:15 -0400522 if err != nil {
523 return BuildStatement{}, err
524 }
525
Usta Shresthac2372492022-05-27 10:45:00 -0400526 inputPaths, err := a.getInputPaths(actionEntry.InputDepSetIds)
Chris Parsons1a7aca02022-04-25 22:35:15 -0400527 if err != nil {
528 return BuildStatement{}, err
529 }
530 if len(inputPaths) != 1 || len(outputPaths) != 1 {
531 return BuildStatement{}, fmt.Errorf("Expect 1 input and 1 output to symlink action, got: input %q, output %q", inputPaths, outputPaths)
532 }
533 out := outputPaths[0]
534 outDir := proptools.ShellEscapeIncludingSpaces(filepath.Dir(out))
535 out = proptools.ShellEscapeIncludingSpaces(out)
536 in := filepath.Join("$PWD", proptools.ShellEscapeIncludingSpaces(inputPaths[0]))
537 // Use absolute paths, because some soong actions don't play well with relative paths (for example, `cp -d`).
538 command := fmt.Sprintf("mkdir -p %[1]s && rm -f %[2]s && ln -sf %[3]s %[2]s", outDir, out, in)
539 symlinkPaths := outputPaths[:]
540
541 buildStatement := BuildStatement{
542 Command: command,
543 Depfile: depfile,
544 OutputPaths: outputPaths,
545 InputPaths: inputPaths,
546 Env: actionEntry.EnvironmentVariables,
547 Mnemonic: actionEntry.Mnemonic,
548 SymlinkPaths: symlinkPaths,
549 }
550 return buildStatement, nil
551}
552
Usta Shresthac2372492022-05-27 10:45:00 -0400553func (a *aqueryArtifactHandler) getOutputPaths(actionEntry action) (outputPaths []string, depfile *string, err error) {
Chris Parsons1a7aca02022-04-25 22:35:15 -0400554 for _, outputId := range actionEntry.OutputIds {
Usta Shresthac2372492022-05-27 10:45:00 -0400555 outputPath, exists := a.artifactIdToPath[outputId]
Chris Parsons1a7aca02022-04-25 22:35:15 -0400556 if !exists {
557 err = fmt.Errorf("undefined outputId %d", outputId)
558 return
559 }
560 ext := filepath.Ext(outputPath)
561 if ext == ".d" {
562 if depfile != nil {
563 err = fmt.Errorf("found multiple potential depfiles %q, %q", *depfile, outputPath)
564 return
565 } else {
566 depfile = &outputPath
567 }
568 } else {
569 outputPaths = append(outputPaths, outputPath)
570 }
571 }
572 return
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500573}
Chris Parsonsaffbb602020-12-23 12:02:11 -0500574
Wei Li455ba832021-11-04 22:58:12 +0000575// expandTemplateContent substitutes the tokens in a template.
576func expandTemplateContent(actionEntry action) string {
577 replacerString := []string{}
578 for _, pair := range actionEntry.Substitutions {
579 value := pair.Value
Usta Shrestha6298cc52022-05-27 17:40:21 -0400580 if val, ok := templateActionOverriddenTokens[pair.Key]; ok {
Wei Li455ba832021-11-04 22:58:12 +0000581 value = val
582 }
583 replacerString = append(replacerString, pair.Key, value)
584 }
585 replacer := strings.NewReplacer(replacerString...)
586 return replacer.Replace(actionEntry.TemplateContent)
587}
588
589func escapeCommandlineArgument(str string) string {
590 // \->\\, $->\$, `->\`, "->\", \n->\\n, '->'"'"'
591 replacer := strings.NewReplacer(
592 `\`, `\\`,
593 `$`, `\$`,
594 "`", "\\`",
595 `"`, `\"`,
596 "\n", "\\n",
597 `'`, `'"'"'`,
598 )
599 return replacer.Replace(str)
600}
601
Sasha Smundak1da064c2022-06-08 16:36:16 -0700602func (a action) isSymlinkAction() bool {
Trevor Radcliffeef9c9002022-05-13 20:55:35 +0000603 return a.Mnemonic == "Symlink" || a.Mnemonic == "SolibSymlink" || a.Mnemonic == "ExecutableSymlink"
Liz Kammerc49e6822021-06-08 15:04:11 -0400604}
605
Sasha Smundak1da064c2022-06-08 16:36:16 -0700606func (a action) isTemplateExpandAction() bool {
Wei Li455ba832021-11-04 22:58:12 +0000607 return a.Mnemonic == "TemplateExpand"
608}
609
Sasha Smundak1da064c2022-06-08 16:36:16 -0700610func (a action) isFileWriteAction() bool {
611 return a.Mnemonic == "FileWrite" || a.Mnemonic == "SourceSymlinkManifest"
612}
613
Sasha Smundakc180dbd2022-07-03 14:55:58 -0700614func (a action) isSymlinkTreeAction() bool {
615 return a.Mnemonic == "SymlinkTree"
616}
617
Chris Parsons8d6e4332021-02-22 16:13:50 -0500618func shouldSkipAction(a action) bool {
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400619 // Middleman actions are not handled like other actions; they are handled separately as a
620 // preparatory step so that their inputs may be relayed to actions depending on middleman
621 // artifacts.
Chris Parsons8d6e4332021-02-22 16:13:50 -0500622 if a.Mnemonic == "Middleman" {
623 return true
624 }
Sasha Smundakc180dbd2022-07-03 14:55:58 -0700625 // PythonZipper is bogus action returned by aquery, ignore it (b/236198693)
626 if a.Mnemonic == "PythonZipper" {
627 return true
628 }
Chris Parsons8d6e4332021-02-22 16:13:50 -0500629 // Skip "Fail" actions, which are placeholder actions designed to always fail.
630 if a.Mnemonic == "Fail" {
631 return true
632 }
Yu Liu8d82ac52022-05-17 15:13:28 -0700633 if a.Mnemonic == "BaselineCoverage" {
634 return true
635 }
Chris Parsons8d6e4332021-02-22 16:13:50 -0500636 return false
637}
638
Usta Shrestha6298cc52022-05-27 17:40:21 -0400639func expandPathFragment(id pathFragmentId, pathFragmentsMap map[pathFragmentId]pathFragment) (string, error) {
640 var labels []string
Chris Parsonsaffbb602020-12-23 12:02:11 -0500641 currId := id
642 // Only positive IDs are valid for path fragments. An ID of zero indicates a terminal node.
643 for currId > 0 {
644 currFragment, ok := pathFragmentsMap[currId]
645 if !ok {
Chris Parsons4f069892021-01-15 12:22:41 -0500646 return "", fmt.Errorf("undefined path fragment id %d", currId)
Chris Parsonsaffbb602020-12-23 12:02:11 -0500647 }
648 labels = append([]string{currFragment.Label}, labels...)
Liz Kammerc49e6822021-06-08 15:04:11 -0400649 if currId == currFragment.ParentId {
650 return "", fmt.Errorf("Fragment cannot refer to itself as parent %#v", currFragment)
651 }
Chris Parsonsaffbb602020-12-23 12:02:11 -0500652 currId = currFragment.ParentId
653 }
654 return filepath.Join(labels...), nil
655}