blob: d4879b107ce7f6a39aa7b571a4c50ec809cfc203 [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 Parsonsaffbb602020-12-23 12:02:11 -050020 "fmt"
21 "path/filepath"
Chris Parsons0bfb1c02022-05-12 16:43:01 -040022 "reflect"
Chris Parsons0bfb1c02022-05-12 16:43:01 -040023 "sort"
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050024 "strings"
Liz Kammera4655a92023-02-10 17:17:28 -050025 "sync"
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050026
Liz Kammer690fbac2023-02-10 11:11:17 -050027 analysis_v2_proto "prebuilts/bazel/common/proto/analysis_v2"
28
29 "github.com/google/blueprint/metrics"
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050030 "github.com/google/blueprint/proptools"
Jason Wu118fd2b2022-10-27 18:41:15 +000031 "google.golang.org/protobuf/proto"
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050032)
33
Usta Shrestha6298cc52022-05-27 17:40:21 -040034type artifactId int
35type depsetId int
36type pathFragmentId int
37
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050038// artifact contains relevant portions of Bazel's aquery proto, Artifact.
39// Represents a single artifact, whether it's a source file or a derived output file.
40type artifact struct {
Usta Shrestha6298cc52022-05-27 17:40:21 -040041 Id artifactId
42 PathFragmentId pathFragmentId
Chris Parsonsaffbb602020-12-23 12:02:11 -050043}
44
45type pathFragment struct {
Usta Shrestha6298cc52022-05-27 17:40:21 -040046 Id pathFragmentId
Chris Parsonsaffbb602020-12-23 12:02:11 -050047 Label string
Usta Shrestha6298cc52022-05-27 17:40:21 -040048 ParentId pathFragmentId
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050049}
50
51// KeyValuePair represents Bazel's aquery proto, KeyValuePair.
52type KeyValuePair struct {
53 Key string
54 Value string
55}
56
Chris Parsons1a7aca02022-04-25 22:35:15 -040057// AqueryDepset is a depset definition from Bazel's aquery response. This is
Chris Parsons0bfb1c02022-05-12 16:43:01 -040058// akin to the `depSetOfFiles` in the response proto, except:
Colin Crossd079e0b2022-08-16 10:27:33 -070059// - direct artifacts are enumerated by full path instead of by ID
60// - it has a hash of the depset contents, instead of an int ID (for determinism)
61//
Chris Parsons1a7aca02022-04-25 22:35:15 -040062// A depset is a data structure for efficient transitive handling of artifact
63// paths. A single depset consists of one or more artifact paths and one or
64// more "child" depsets.
65type AqueryDepset struct {
Chris Parsons0bfb1c02022-05-12 16:43:01 -040066 ContentHash string
67 DirectArtifacts []string
68 TransitiveDepSetHashes []string
Chris Parsons1a7aca02022-04-25 22:35:15 -040069}
70
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050071// depSetOfFiles contains relevant portions of Bazel's aquery proto, DepSetOfFiles.
72// Represents a data structure containing one or more files. Depsets in Bazel are an efficient
73// data structure for storing large numbers of file paths.
74type depSetOfFiles struct {
Usta Shrestha6298cc52022-05-27 17:40:21 -040075 Id depsetId
76 DirectArtifactIds []artifactId
77 TransitiveDepSetIds []depsetId
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050078}
79
80// action contains relevant portions of Bazel's aquery proto, Action.
81// Represents a single command line invocation in the Bazel build graph.
82type action struct {
83 Arguments []string
84 EnvironmentVariables []KeyValuePair
Usta Shrestha6298cc52022-05-27 17:40:21 -040085 InputDepSetIds []depsetId
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050086 Mnemonic string
Usta Shrestha6298cc52022-05-27 17:40:21 -040087 OutputIds []artifactId
Wei Li455ba832021-11-04 22:58:12 +000088 TemplateContent string
89 Substitutions []KeyValuePair
Sasha Smundak1da064c2022-06-08 16:36:16 -070090 FileContents string
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -050091}
92
93// actionGraphContainer contains relevant portions of Bazel's aquery proto, ActionGraphContainer.
94// An aquery response from Bazel contains a single ActionGraphContainer proto.
95type actionGraphContainer struct {
96 Artifacts []artifact
97 Actions []action
98 DepSetOfFiles []depSetOfFiles
Chris Parsonsaffbb602020-12-23 12:02:11 -050099 PathFragments []pathFragment
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500100}
101
102// BuildStatement contains information to register a build statement corresponding (one to one)
103// with a Bazel action from Bazel's action graph.
104type BuildStatement struct {
Liz Kammerc49e6822021-06-08 15:04:11 -0400105 Command string
106 Depfile *string
107 OutputPaths []string
Liz Kammerc49e6822021-06-08 15:04:11 -0400108 SymlinkPaths []string
Liz Kammer00629db2023-02-09 14:28:15 -0500109 Env []*analysis_v2_proto.KeyValuePair
Liz Kammerc49e6822021-06-08 15:04:11 -0400110 Mnemonic string
Chris Parsons1a7aca02022-04-25 22:35:15 -0400111
112 // Inputs of this build statement, either as unexpanded depsets or expanded
113 // input paths. There should be no overlap between these fields; an input
114 // path should either be included as part of an unexpanded depset or a raw
115 // input path string, but not both.
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400116 InputDepsetHashes []string
117 InputPaths []string
Cole Fauste9ae4802023-07-18 19:36:41 -0700118 OrderOnlyInputs []string
Sasha Smundak1da064c2022-06-08 16:36:16 -0700119 FileContents string
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500120}
121
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400122// A helper type for aquery processing which facilitates retrieval of path IDs from their
123// less readable Bazel structures (depset and path fragment).
124type aqueryArtifactHandler struct {
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400125 // Maps depset id to AqueryDepset, a representation of depset which is
126 // post-processed for middleman artifact handling, unhandled artifact
127 // dropping, content hashing, etc.
Usta Shrestha6298cc52022-05-27 17:40:21 -0400128 depsetIdToAqueryDepset map[depsetId]AqueryDepset
Usta Shrestha13fd5ae2023-01-27 10:55:34 -0500129 emptyDepsetIds map[depsetId]struct{}
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400130 // Maps content hash to AqueryDepset.
131 depsetHashToAqueryDepset map[string]AqueryDepset
132
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400133 // depsetIdToArtifactIdsCache is a memoization of depset flattening, because flattening
134 // may be an expensive operation.
Liz Kammera4655a92023-02-10 17:17:28 -0500135 depsetHashToArtifactPathsCache sync.Map
Usta Shrestha6298cc52022-05-27 17:40:21 -0400136 // Maps artifact ids to fully expanded paths.
137 artifactIdToPath map[artifactId]string
Cole Fauste9ae4802023-07-18 19:36:41 -0700138
139 extraBuildStatements []*BuildStatement
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400140}
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500141
Wei Li455ba832021-11-04 22:58:12 +0000142// The tokens should be substituted with the value specified here, instead of the
143// one returned in 'substitutions' of TemplateExpand action.
Usta Shrestha6298cc52022-05-27 17:40:21 -0400144var templateActionOverriddenTokens = map[string]string{
Wei Li455ba832021-11-04 22:58:12 +0000145 // Uses "python3" for %python_binary% instead of the value returned by aquery
146 // which is "py3wrapper.sh". See removePy3wrapperScript.
147 "%python_binary%": "python3",
148}
149
Liz Kammer00629db2023-02-09 14:28:15 -0500150const (
151 middlemanMnemonic = "Middleman"
152 // The file name of py3wrapper.sh, which is used by py_binary targets.
153 py3wrapperFileName = "/py3wrapper.sh"
154)
Wei Li455ba832021-11-04 22:58:12 +0000155
Usta Shrestha6298cc52022-05-27 17:40:21 -0400156func indexBy[K comparable, V any](values []V, keyFn func(v V) K) map[K]V {
157 m := map[K]V{}
158 for _, v := range values {
159 m[keyFn(v)] = v
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500160 }
Usta Shrestha6298cc52022-05-27 17:40:21 -0400161 return m
162}
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400163
Liz Kammer00629db2023-02-09 14:28:15 -0500164func newAqueryHandler(aqueryResult *analysis_v2_proto.ActionGraphContainer) (*aqueryArtifactHandler, error) {
165 pathFragments := indexBy(aqueryResult.PathFragments, func(pf *analysis_v2_proto.PathFragment) pathFragmentId {
166 return pathFragmentId(pf.Id)
Usta Shrestha6298cc52022-05-27 17:40:21 -0400167 })
168
Cole Fauste9ae4802023-07-18 19:36:41 -0700169 var extraBuildStatements []*BuildStatement
Liz Kammer00629db2023-02-09 14:28:15 -0500170 artifactIdToPath := make(map[artifactId]string, len(aqueryResult.Artifacts))
Chris Parsonsaffbb602020-12-23 12:02:11 -0500171 for _, artifact := range aqueryResult.Artifacts {
Liz Kammer00629db2023-02-09 14:28:15 -0500172 artifactPath, err := expandPathFragment(pathFragmentId(artifact.PathFragmentId), pathFragments)
Chris Parsonsaffbb602020-12-23 12:02:11 -0500173 if err != nil {
Chris Parsons4f069892021-01-15 12:22:41 -0500174 return nil, err
Chris Parsonsaffbb602020-12-23 12:02:11 -0500175 }
Cole Fauste9ae4802023-07-18 19:36:41 -0700176 if strings.HasSuffix(artifactPath, "DumpPlatformClassPath.java") {
177 // TODO(b/291828210): This is a workaround for the fact that DumpPlatformClassPath.java
178 // has a timestamp in 2033. We'll copy it to a new file using a order-only dep, so that
179 // the file is not recopied every build. Technically the order-only dep would produce
180 // incremental build issues if this was a regular file produced as part of the build,
181 // but this file is actually created by bazel during analysis, so it's not an issue.
182 outputPath := "hack_for_b291828210/DumpPlatformClassPath.java"
183 extraBuildStatements = append(extraBuildStatements, &BuildStatement{
184 Command: fmt.Sprintf("cp %s %s", artifactPath, outputPath),
185 OutputPaths: []string{outputPath},
186 OrderOnlyInputs: []string{artifactPath},
187 })
188 artifactIdToPath[artifactId(artifact.Id)] = outputPath
189 } else {
190 artifactIdToPath[artifactId(artifact.Id)] = artifactPath
191 }
Chris Parsonsaffbb602020-12-23 12:02:11 -0500192 }
Chris Parsons943f2432021-01-19 11:36:50 -0500193
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400194 // Map middleman artifact ContentHash to input artifact depset ID.
Chris Parsons1a7aca02022-04-25 22:35:15 -0400195 // Middleman artifacts are treated as "substitute" artifacts for mixed builds. For example,
Usta Shrestha16ac1352022-06-22 11:01:55 -0400196 // if we find a middleman action which has inputs [foo, bar], and output [baz_middleman], then,
Chris Parsons1a7aca02022-04-25 22:35:15 -0400197 // for each other action which has input [baz_middleman], we add [foo, bar] to the inputs for
198 // that action instead.
Liz Kammer00629db2023-02-09 14:28:15 -0500199 middlemanIdToDepsetIds := map[artifactId][]uint32{}
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500200 for _, actionEntry := range aqueryResult.Actions {
Liz Kammer00629db2023-02-09 14:28:15 -0500201 if actionEntry.Mnemonic == middlemanMnemonic {
Chris Parsons8d6e4332021-02-22 16:13:50 -0500202 for _, outputId := range actionEntry.OutputIds {
Liz Kammer00629db2023-02-09 14:28:15 -0500203 middlemanIdToDepsetIds[artifactId(outputId)] = actionEntry.InputDepSetIds
Chris Parsons8d6e4332021-02-22 16:13:50 -0500204 }
205 }
206 }
Chris Parsons1a7aca02022-04-25 22:35:15 -0400207
Liz Kammer00629db2023-02-09 14:28:15 -0500208 depsetIdToDepset := indexBy(aqueryResult.DepSetOfFiles, func(d *analysis_v2_proto.DepSetOfFiles) depsetId {
209 return depsetId(d.Id)
Usta Shrestha6298cc52022-05-27 17:40:21 -0400210 })
Chris Parsons1a7aca02022-04-25 22:35:15 -0400211
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400212 aqueryHandler := aqueryArtifactHandler{
Usta Shrestha6298cc52022-05-27 17:40:21 -0400213 depsetIdToAqueryDepset: map[depsetId]AqueryDepset{},
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400214 depsetHashToAqueryDepset: map[string]AqueryDepset{},
Liz Kammera4655a92023-02-10 17:17:28 -0500215 depsetHashToArtifactPathsCache: sync.Map{},
Usta Shrestha13fd5ae2023-01-27 10:55:34 -0500216 emptyDepsetIds: make(map[depsetId]struct{}, 0),
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400217 artifactIdToPath: artifactIdToPath,
Cole Fauste9ae4802023-07-18 19:36:41 -0700218 extraBuildStatements: extraBuildStatements,
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400219 }
220
221 // Validate and adjust aqueryResult.DepSetOfFiles values.
222 for _, depset := range aqueryResult.DepSetOfFiles {
223 _, err := aqueryHandler.populateDepsetMaps(depset, middlemanIdToDepsetIds, depsetIdToDepset)
224 if err != nil {
225 return nil, err
226 }
227 }
228
229 return &aqueryHandler, nil
230}
231
232// Ensures that the handler's depsetIdToAqueryDepset map contains an entry for the given
233// depset.
Liz Kammer00629db2023-02-09 14:28:15 -0500234func (a *aqueryArtifactHandler) populateDepsetMaps(depset *analysis_v2_proto.DepSetOfFiles, middlemanIdToDepsetIds map[artifactId][]uint32, depsetIdToDepset map[depsetId]*analysis_v2_proto.DepSetOfFiles) (*AqueryDepset, error) {
235 if aqueryDepset, containsDepset := a.depsetIdToAqueryDepset[depsetId(depset.Id)]; containsDepset {
Usta Shrestha13fd5ae2023-01-27 10:55:34 -0500236 return &aqueryDepset, nil
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400237 }
238 transitiveDepsetIds := depset.TransitiveDepSetIds
Liz Kammer00629db2023-02-09 14:28:15 -0500239 directArtifactPaths := make([]string, 0, len(depset.DirectArtifactIds))
240 for _, id := range depset.DirectArtifactIds {
241 aId := artifactId(id)
242 path, pathExists := a.artifactIdToPath[aId]
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400243 if !pathExists {
Liz Kammer00629db2023-02-09 14:28:15 -0500244 return nil, fmt.Errorf("undefined input artifactId %d", aId)
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400245 }
246 // Filter out any inputs which are universally dropped, and swap middleman
247 // artifacts with their corresponding depsets.
Liz Kammer00629db2023-02-09 14:28:15 -0500248 if depsetsToUse, isMiddleman := middlemanIdToDepsetIds[aId]; isMiddleman {
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400249 // Swap middleman artifacts with their corresponding depsets and drop the middleman artifacts.
250 transitiveDepsetIds = append(transitiveDepsetIds, depsetsToUse...)
Usta Shresthaef922252022-06-02 14:23:02 -0400251 } else if strings.HasSuffix(path, py3wrapperFileName) ||
Usta Shresthaef922252022-06-02 14:23:02 -0400252 strings.HasPrefix(path, "../bazel_tools") {
Usta Shrestha13fd5ae2023-01-27 10:55:34 -0500253 continue
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400254 // Drop these artifacts.
255 // See go/python-binary-host-mixed-build for more details.
Sasha Smundakc180dbd2022-07-03 14:55:58 -0700256 // 1) Drop py3wrapper.sh, just use python binary, the launcher script generated by the
257 // TemplateExpandAction handles everything necessary to launch a Pythin application.
258 // 2) ../bazel_tools: they have MODIFY timestamp 10years in the future and would cause the
Usta Shresthaef922252022-06-02 14:23:02 -0400259 // containing depset to always be considered newer than their outputs.
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400260 } else {
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400261 directArtifactPaths = append(directArtifactPaths, path)
262 }
263 }
264
Liz Kammer00629db2023-02-09 14:28:15 -0500265 childDepsetHashes := make([]string, 0, len(transitiveDepsetIds))
266 for _, id := range transitiveDepsetIds {
267 childDepsetId := depsetId(id)
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400268 childDepset, exists := depsetIdToDepset[childDepsetId]
269 if !exists {
Usta Shrestha13fd5ae2023-01-27 10:55:34 -0500270 if _, empty := a.emptyDepsetIds[childDepsetId]; empty {
271 continue
272 } else {
273 return nil, fmt.Errorf("undefined input depsetId %d (referenced by depsetId %d)", childDepsetId, depset.Id)
274 }
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400275 }
Usta Shrestha13fd5ae2023-01-27 10:55:34 -0500276 if childAqueryDepset, err := a.populateDepsetMaps(childDepset, middlemanIdToDepsetIds, depsetIdToDepset); err != nil {
277 return nil, err
278 } else if childAqueryDepset == nil {
279 continue
280 } else {
281 childDepsetHashes = append(childDepsetHashes, childAqueryDepset.ContentHash)
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400282 }
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400283 }
Usta Shresthaef922252022-06-02 14:23:02 -0400284 if len(directArtifactPaths) == 0 && len(childDepsetHashes) == 0 {
Liz Kammer00629db2023-02-09 14:28:15 -0500285 a.emptyDepsetIds[depsetId(depset.Id)] = struct{}{}
Usta Shrestha13fd5ae2023-01-27 10:55:34 -0500286 return nil, nil
Usta Shresthaef922252022-06-02 14:23:02 -0400287 }
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400288 aqueryDepset := AqueryDepset{
289 ContentHash: depsetContentHash(directArtifactPaths, childDepsetHashes),
290 DirectArtifacts: directArtifactPaths,
291 TransitiveDepSetHashes: childDepsetHashes,
292 }
Liz Kammer00629db2023-02-09 14:28:15 -0500293 a.depsetIdToAqueryDepset[depsetId(depset.Id)] = aqueryDepset
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400294 a.depsetHashToAqueryDepset[aqueryDepset.ContentHash] = aqueryDepset
Usta Shrestha13fd5ae2023-01-27 10:55:34 -0500295 return &aqueryDepset, nil
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400296}
297
Chris Parsons1a7aca02022-04-25 22:35:15 -0400298// getInputPaths flattens the depsets of the given IDs and returns all transitive
299// input paths contained in these depsets.
300// This is a potentially expensive operation, and should not be invoked except
301// for actions which need specialized input handling.
Liz Kammer00629db2023-02-09 14:28:15 -0500302func (a *aqueryArtifactHandler) getInputPaths(depsetIds []uint32) ([]string, error) {
Usta Shrestha6298cc52022-05-27 17:40:21 -0400303 var inputPaths []string
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400304
Liz Kammer00629db2023-02-09 14:28:15 -0500305 for _, id := range depsetIds {
306 inputDepSetId := depsetId(id)
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400307 depset := a.depsetIdToAqueryDepset[inputDepSetId]
308 inputArtifacts, err := a.artifactPathsFromDepsetHash(depset.ContentHash)
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400309 if err != nil {
310 return nil, err
311 }
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400312 for _, inputPath := range inputArtifacts {
Chris Parsons1a7aca02022-04-25 22:35:15 -0400313 inputPaths = append(inputPaths, inputPath)
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400314 }
315 }
Wei Li455ba832021-11-04 22:58:12 +0000316
Chris Parsons1a7aca02022-04-25 22:35:15 -0400317 return inputPaths, nil
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400318}
319
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400320func (a *aqueryArtifactHandler) artifactPathsFromDepsetHash(depsetHash string) ([]string, error) {
Liz Kammera4655a92023-02-10 17:17:28 -0500321 if result, exists := a.depsetHashToArtifactPathsCache.Load(depsetHash); exists {
322 return result.([]string), nil
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400323 }
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400324 if depset, exists := a.depsetHashToAqueryDepset[depsetHash]; exists {
325 result := depset.DirectArtifacts
326 for _, childHash := range depset.TransitiveDepSetHashes {
327 childArtifactIds, err := a.artifactPathsFromDepsetHash(childHash)
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400328 if err != nil {
329 return nil, err
330 }
331 result = append(result, childArtifactIds...)
332 }
Liz Kammera4655a92023-02-10 17:17:28 -0500333 a.depsetHashToArtifactPathsCache.Store(depsetHash, result)
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400334 return result, nil
335 } else {
Usta Shrestha2ccdb422022-06-02 10:19:13 -0400336 return nil, fmt.Errorf("undefined input depset hash %s", depsetHash)
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400337 }
338}
339
Chris Parsons1a7aca02022-04-25 22:35:15 -0400340// AqueryBuildStatements returns a slice of BuildStatements and a slice of AqueryDepset
Usta Shrestha6298cc52022-05-27 17:40:21 -0400341// which should be registered (and output to a ninja file) to correspond with Bazel's
Chris Parsons1a7aca02022-04-25 22:35:15 -0400342// action graph, as described by the given action graph json proto.
343// BuildStatements are one-to-one with actions in the given action graph, and AqueryDepsets
344// are one-to-one with Bazel's depSetOfFiles objects.
Liz Kammera4655a92023-02-10 17:17:28 -0500345func AqueryBuildStatements(aqueryJsonProto []byte, eventHandler *metrics.EventHandler) ([]*BuildStatement, []AqueryDepset, error) {
Jason Wu118fd2b2022-10-27 18:41:15 +0000346 aqueryProto := &analysis_v2_proto.ActionGraphContainer{}
347 err := proto.Unmarshal(aqueryJsonProto, aqueryProto)
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400348 if err != nil {
Chris Parsons1a7aca02022-04-25 22:35:15 -0400349 return nil, nil, err
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400350 }
Chris Parsons8d6e4332021-02-22 16:13:50 -0500351
Liz Kammer690fbac2023-02-10 11:11:17 -0500352 var aqueryHandler *aqueryArtifactHandler
353 {
354 eventHandler.Begin("init_handler")
355 defer eventHandler.End("init_handler")
Liz Kammer00629db2023-02-09 14:28:15 -0500356 aqueryHandler, err = newAqueryHandler(aqueryProto)
Chris Parsons1a7aca02022-04-25 22:35:15 -0400357 if err != nil {
358 return nil, nil, err
Chris Parsons8d6e4332021-02-22 16:13:50 -0500359 }
Liz Kammer690fbac2023-02-10 11:11:17 -0500360 }
361
Liz Kammera4655a92023-02-10 17:17:28 -0500362 // allocate both length and capacity so each goroutine can write to an index independently without
363 // any need for synchronization for slice access.
364 buildStatements := make([]*BuildStatement, len(aqueryProto.Actions))
Liz Kammer690fbac2023-02-10 11:11:17 -0500365 {
366 eventHandler.Begin("build_statements")
367 defer eventHandler.End("build_statements")
Liz Kammera4655a92023-02-10 17:17:28 -0500368 wg := sync.WaitGroup{}
369 var errOnce sync.Once
370
371 for i, actionEntry := range aqueryProto.Actions {
372 wg.Add(1)
373 go func(i int, actionEntry *analysis_v2_proto.Action) {
374 buildStatement, aErr := aqueryHandler.actionToBuildStatement(actionEntry)
375 if aErr != nil {
376 errOnce.Do(func() {
377 err = aErr
378 })
379 } else {
380 // set build statement at an index rather than appending such that each goroutine does not
381 // impact other goroutines
382 buildStatements[i] = buildStatement
383 }
384 wg.Done()
385 }(i, actionEntry)
Liz Kammer690fbac2023-02-10 11:11:17 -0500386 }
Liz Kammera4655a92023-02-10 17:17:28 -0500387 wg.Wait()
388 }
389 if err != nil {
390 return nil, nil, err
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500391 }
Cole Fauste9ae4802023-07-18 19:36:41 -0700392 buildStatements = append(buildStatements, aqueryHandler.extraBuildStatements...)
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500393
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400394 depsetsByHash := map[string]AqueryDepset{}
Liz Kammer00629db2023-02-09 14:28:15 -0500395 depsets := make([]AqueryDepset, 0, len(aqueryHandler.depsetIdToAqueryDepset))
Liz Kammer690fbac2023-02-10 11:11:17 -0500396 {
397 eventHandler.Begin("depsets")
398 defer eventHandler.End("depsets")
399 for _, aqueryDepset := range aqueryHandler.depsetIdToAqueryDepset {
400 if prevEntry, hasKey := depsetsByHash[aqueryDepset.ContentHash]; hasKey {
401 // Two depsets collide on hash. Ensure that their contents are identical.
402 if !reflect.DeepEqual(aqueryDepset, prevEntry) {
403 return nil, nil, fmt.Errorf("two different depsets have the same hash: %v, %v", prevEntry, aqueryDepset)
404 }
405 } else {
406 depsetsByHash[aqueryDepset.ContentHash] = aqueryDepset
407 depsets = append(depsets, aqueryDepset)
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400408 }
Chris Parsons1a7aca02022-04-25 22:35:15 -0400409 }
Chris Parsons1a7aca02022-04-25 22:35:15 -0400410 }
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400411
Liz Kammer690fbac2023-02-10 11:11:17 -0500412 eventHandler.Do("build_statement_sort", func() {
413 // Build Statements and depsets must be sorted by their content hash to
414 // preserve determinism between builds (this will result in consistent ninja file
415 // output). Note they are not sorted by their original IDs nor their Bazel ordering,
416 // as Bazel gives nondeterministic ordering / identifiers in aquery responses.
417 sort.Slice(buildStatements, func(i, j int) bool {
Liz Kammera4655a92023-02-10 17:17:28 -0500418 // Sort all nil statements to the end of the slice
419 if buildStatements[i] == nil {
420 return false
421 } else if buildStatements[j] == nil {
422 return true
423 }
424 //For build statements, compare output lists. In Bazel, each output file
Liz Kammer690fbac2023-02-10 11:11:17 -0500425 // may only have one action which generates it, so this will provide
426 // a deterministic ordering.
427 outputs_i := buildStatements[i].OutputPaths
428 outputs_j := buildStatements[j].OutputPaths
429 if len(outputs_i) != len(outputs_j) {
430 return len(outputs_i) < len(outputs_j)
431 }
432 if len(outputs_i) == 0 {
433 // No outputs for these actions, so compare commands.
434 return buildStatements[i].Command < buildStatements[j].Command
435 }
436 // There may be multiple outputs, but the output ordering is deterministic.
437 return outputs_i[0] < outputs_j[0]
438 })
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400439 })
Liz Kammer690fbac2023-02-10 11:11:17 -0500440 eventHandler.Do("depset_sort", func() {
441 sort.Slice(depsets, func(i, j int) bool {
442 return depsets[i].ContentHash < depsets[j].ContentHash
443 })
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400444 })
Chris Parsons1a7aca02022-04-25 22:35:15 -0400445 return buildStatements, depsets, nil
446}
447
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400448// depsetContentHash computes and returns a SHA256 checksum of the contents of
449// the given depset. This content hash may serve as the depset's identifier.
450// Using a content hash for an identifier is superior for determinism. (For example,
451// using an integer identifier which depends on the order in which the depsets are
452// created would result in nondeterministic depset IDs.)
453func depsetContentHash(directPaths []string, transitiveDepsetHashes []string) string {
454 h := sha256.New()
455 // Use newline as delimiter, as paths cannot contain newline.
456 h.Write([]byte(strings.Join(directPaths, "\n")))
Usta Shrestha2ccdb422022-06-02 10:19:13 -0400457 h.Write([]byte(strings.Join(transitiveDepsetHashes, "")))
458 fullHash := base64.RawURLEncoding.EncodeToString(h.Sum(nil))
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400459 return fullHash
460}
461
Liz Kammer00629db2023-02-09 14:28:15 -0500462func (a *aqueryArtifactHandler) depsetContentHashes(inputDepsetIds []uint32) ([]string, error) {
Usta Shrestha6298cc52022-05-27 17:40:21 -0400463 var hashes []string
Liz Kammer00629db2023-02-09 14:28:15 -0500464 for _, id := range inputDepsetIds {
465 dId := depsetId(id)
466 if aqueryDepset, exists := a.depsetIdToAqueryDepset[dId]; !exists {
467 if _, empty := a.emptyDepsetIds[dId]; !empty {
468 return nil, fmt.Errorf("undefined (not even empty) input depsetId %d", dId)
Usta Shrestha13fd5ae2023-01-27 10:55:34 -0500469 }
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400470 } else {
471 hashes = append(hashes, aqueryDepset.ContentHash)
Chris Parsons1a7aca02022-04-25 22:35:15 -0400472 }
473 }
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400474 return hashes, nil
Chris Parsons1a7aca02022-04-25 22:35:15 -0400475}
476
Spandan Dasda724862023-06-16 23:35:55 +0000477// escapes the args received from aquery and creates a command string
478func commandString(actionEntry *analysis_v2_proto.Action) string {
479 switch actionEntry.Mnemonic {
480 case "GoCompilePkg":
481 argsEscaped := []string{}
482 for _, arg := range actionEntry.Arguments {
483 if arg == "" {
484 // If this is an empty string, add ''
485 // And not
486 // 1. (literal empty)
487 // 2. `''\'''\'''` (escaped version of '')
488 //
489 // If we had used (1), then this would appear as a whitespace when we strings.Join
490 argsEscaped = append(argsEscaped, "''")
491 } else {
492 argsEscaped = append(argsEscaped, proptools.ShellEscapeIncludingSpaces(arg))
493 }
494 }
495 return strings.Join(argsEscaped, " ")
496 default:
497 return strings.Join(proptools.ShellEscapeListIncludingSpaces(actionEntry.Arguments), " ")
498 }
499}
500
Liz Kammer00629db2023-02-09 14:28:15 -0500501func (a *aqueryArtifactHandler) normalActionBuildStatement(actionEntry *analysis_v2_proto.Action) (*BuildStatement, error) {
Spandan Dasda724862023-06-16 23:35:55 +0000502 command := commandString(actionEntry)
Usta Shresthac2372492022-05-27 10:45:00 -0400503 inputDepsetHashes, err := a.depsetContentHashes(actionEntry.InputDepSetIds)
Chris Parsons1a7aca02022-04-25 22:35:15 -0400504 if err != nil {
Liz Kammer00629db2023-02-09 14:28:15 -0500505 return nil, err
Chris Parsons1a7aca02022-04-25 22:35:15 -0400506 }
Usta Shresthac2372492022-05-27 10:45:00 -0400507 outputPaths, depfile, err := a.getOutputPaths(actionEntry)
Chris Parsons1a7aca02022-04-25 22:35:15 -0400508 if err != nil {
Liz Kammer00629db2023-02-09 14:28:15 -0500509 return nil, err
Chris Parsons1a7aca02022-04-25 22:35:15 -0400510 }
511
Liz Kammer00629db2023-02-09 14:28:15 -0500512 buildStatement := &BuildStatement{
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400513 Command: command,
514 Depfile: depfile,
515 OutputPaths: outputPaths,
516 InputDepsetHashes: inputDepsetHashes,
517 Env: actionEntry.EnvironmentVariables,
518 Mnemonic: actionEntry.Mnemonic,
Chris Parsons1a7aca02022-04-25 22:35:15 -0400519 }
520 return buildStatement, nil
521}
522
Liz Kammer00629db2023-02-09 14:28:15 -0500523func (a *aqueryArtifactHandler) templateExpandActionBuildStatement(actionEntry *analysis_v2_proto.Action) (*BuildStatement, error) {
Usta Shresthac2372492022-05-27 10:45:00 -0400524 outputPaths, depfile, err := a.getOutputPaths(actionEntry)
Chris Parsons1a7aca02022-04-25 22:35:15 -0400525 if err != nil {
Liz Kammer00629db2023-02-09 14:28:15 -0500526 return nil, err
Chris Parsons1a7aca02022-04-25 22:35:15 -0400527 }
528 if len(outputPaths) != 1 {
Liz Kammer00629db2023-02-09 14:28:15 -0500529 return nil, fmt.Errorf("Expect 1 output to template expand action, got: output %q", outputPaths)
Chris Parsons1a7aca02022-04-25 22:35:15 -0400530 }
531 expandedTemplateContent := expandTemplateContent(actionEntry)
532 // The expandedTemplateContent is escaped for being used in double quotes and shell unescape,
533 // and the new line characters (\n) are also changed to \\n which avoids some Ninja escape on \n, which might
534 // change \n to space and mess up the format of Python programs.
535 // sed is used to convert \\n back to \n before saving to output file.
536 // See go/python-binary-host-mixed-build for more details.
537 command := fmt.Sprintf(`/bin/bash -c 'echo "%[1]s" | sed "s/\\\\n/\\n/g" > %[2]s && chmod a+x %[2]s'`,
538 escapeCommandlineArgument(expandedTemplateContent), outputPaths[0])
Usta Shresthac2372492022-05-27 10:45:00 -0400539 inputDepsetHashes, err := a.depsetContentHashes(actionEntry.InputDepSetIds)
Chris Parsons1a7aca02022-04-25 22:35:15 -0400540 if err != nil {
Liz Kammer00629db2023-02-09 14:28:15 -0500541 return nil, err
Chris Parsons1a7aca02022-04-25 22:35:15 -0400542 }
543
Liz Kammer00629db2023-02-09 14:28:15 -0500544 buildStatement := &BuildStatement{
Chris Parsons0bfb1c02022-05-12 16:43:01 -0400545 Command: command,
546 Depfile: depfile,
547 OutputPaths: outputPaths,
548 InputDepsetHashes: inputDepsetHashes,
549 Env: actionEntry.EnvironmentVariables,
550 Mnemonic: actionEntry.Mnemonic,
Chris Parsons1a7aca02022-04-25 22:35:15 -0400551 }
552 return buildStatement, nil
553}
554
Liz Kammer00629db2023-02-09 14:28:15 -0500555func (a *aqueryArtifactHandler) fileWriteActionBuildStatement(actionEntry *analysis_v2_proto.Action) (*BuildStatement, error) {
Sasha Smundak1da064c2022-06-08 16:36:16 -0700556 outputPaths, _, err := a.getOutputPaths(actionEntry)
557 var depsetHashes []string
558 if err == nil {
559 depsetHashes, err = a.depsetContentHashes(actionEntry.InputDepSetIds)
560 }
561 if err != nil {
Liz Kammer00629db2023-02-09 14:28:15 -0500562 return nil, err
Sasha Smundak1da064c2022-06-08 16:36:16 -0700563 }
Liz Kammer00629db2023-02-09 14:28:15 -0500564 return &BuildStatement{
Sasha Smundak1da064c2022-06-08 16:36:16 -0700565 Depfile: nil,
566 OutputPaths: outputPaths,
567 Env: actionEntry.EnvironmentVariables,
568 Mnemonic: actionEntry.Mnemonic,
569 InputDepsetHashes: depsetHashes,
570 FileContents: actionEntry.FileContents,
571 }, nil
572}
573
Liz Kammer00629db2023-02-09 14:28:15 -0500574func (a *aqueryArtifactHandler) symlinkTreeActionBuildStatement(actionEntry *analysis_v2_proto.Action) (*BuildStatement, error) {
Sasha Smundakc180dbd2022-07-03 14:55:58 -0700575 outputPaths, _, err := a.getOutputPaths(actionEntry)
576 if err != nil {
Liz Kammer00629db2023-02-09 14:28:15 -0500577 return nil, err
Sasha Smundakc180dbd2022-07-03 14:55:58 -0700578 }
579 inputPaths, err := a.getInputPaths(actionEntry.InputDepSetIds)
580 if err != nil {
Liz Kammer00629db2023-02-09 14:28:15 -0500581 return nil, err
Sasha Smundakc180dbd2022-07-03 14:55:58 -0700582 }
583 if len(inputPaths) != 1 || len(outputPaths) != 1 {
Liz Kammer00629db2023-02-09 14:28:15 -0500584 return nil, fmt.Errorf("Expect 1 input and 1 output to symlink action, got: input %q, output %q", inputPaths, outputPaths)
Sasha Smundakc180dbd2022-07-03 14:55:58 -0700585 }
586 // The actual command is generated in bazelSingleton.GenerateBuildActions
Liz Kammer00629db2023-02-09 14:28:15 -0500587 return &BuildStatement{
Sasha Smundakc180dbd2022-07-03 14:55:58 -0700588 Depfile: nil,
589 OutputPaths: outputPaths,
590 Env: actionEntry.EnvironmentVariables,
591 Mnemonic: actionEntry.Mnemonic,
592 InputPaths: inputPaths,
593 }, nil
594}
595
Liz Kammer00629db2023-02-09 14:28:15 -0500596func (a *aqueryArtifactHandler) symlinkActionBuildStatement(actionEntry *analysis_v2_proto.Action) (*BuildStatement, error) {
Usta Shresthac2372492022-05-27 10:45:00 -0400597 outputPaths, depfile, err := a.getOutputPaths(actionEntry)
Chris Parsons1a7aca02022-04-25 22:35:15 -0400598 if err != nil {
Liz Kammer00629db2023-02-09 14:28:15 -0500599 return nil, err
Chris Parsons1a7aca02022-04-25 22:35:15 -0400600 }
601
Usta Shresthac2372492022-05-27 10:45:00 -0400602 inputPaths, err := a.getInputPaths(actionEntry.InputDepSetIds)
Chris Parsons1a7aca02022-04-25 22:35:15 -0400603 if err != nil {
Liz Kammer00629db2023-02-09 14:28:15 -0500604 return nil, err
Chris Parsons1a7aca02022-04-25 22:35:15 -0400605 }
606 if len(inputPaths) != 1 || len(outputPaths) != 1 {
Liz Kammer00629db2023-02-09 14:28:15 -0500607 return nil, fmt.Errorf("Expect 1 input and 1 output to symlink action, got: input %q, output %q", inputPaths, outputPaths)
Chris Parsons1a7aca02022-04-25 22:35:15 -0400608 }
609 out := outputPaths[0]
610 outDir := proptools.ShellEscapeIncludingSpaces(filepath.Dir(out))
611 out = proptools.ShellEscapeIncludingSpaces(out)
612 in := filepath.Join("$PWD", proptools.ShellEscapeIncludingSpaces(inputPaths[0]))
613 // Use absolute paths, because some soong actions don't play well with relative paths (for example, `cp -d`).
614 command := fmt.Sprintf("mkdir -p %[1]s && rm -f %[2]s && ln -sf %[3]s %[2]s", outDir, out, in)
615 symlinkPaths := outputPaths[:]
616
Liz Kammer00629db2023-02-09 14:28:15 -0500617 buildStatement := &BuildStatement{
Chris Parsons1a7aca02022-04-25 22:35:15 -0400618 Command: command,
619 Depfile: depfile,
620 OutputPaths: outputPaths,
621 InputPaths: inputPaths,
622 Env: actionEntry.EnvironmentVariables,
623 Mnemonic: actionEntry.Mnemonic,
624 SymlinkPaths: symlinkPaths,
625 }
626 return buildStatement, nil
627}
628
Liz Kammer00629db2023-02-09 14:28:15 -0500629func (a *aqueryArtifactHandler) getOutputPaths(actionEntry *analysis_v2_proto.Action) (outputPaths []string, depfile *string, err error) {
Chris Parsons1a7aca02022-04-25 22:35:15 -0400630 for _, outputId := range actionEntry.OutputIds {
Liz Kammer00629db2023-02-09 14:28:15 -0500631 outputPath, exists := a.artifactIdToPath[artifactId(outputId)]
Chris Parsons1a7aca02022-04-25 22:35:15 -0400632 if !exists {
633 err = fmt.Errorf("undefined outputId %d", outputId)
634 return
635 }
636 ext := filepath.Ext(outputPath)
637 if ext == ".d" {
638 if depfile != nil {
639 err = fmt.Errorf("found multiple potential depfiles %q, %q", *depfile, outputPath)
640 return
641 } else {
642 depfile = &outputPath
643 }
644 } else {
645 outputPaths = append(outputPaths, outputPath)
646 }
647 }
648 return
Chris Parsonsdbcb1ff2020-12-10 17:19:18 -0500649}
Chris Parsonsaffbb602020-12-23 12:02:11 -0500650
Wei Li455ba832021-11-04 22:58:12 +0000651// expandTemplateContent substitutes the tokens in a template.
Liz Kammer00629db2023-02-09 14:28:15 -0500652func expandTemplateContent(actionEntry *analysis_v2_proto.Action) string {
653 replacerString := make([]string, len(actionEntry.Substitutions)*2)
654 for i, pair := range actionEntry.Substitutions {
Wei Li455ba832021-11-04 22:58:12 +0000655 value := pair.Value
Usta Shrestha6298cc52022-05-27 17:40:21 -0400656 if val, ok := templateActionOverriddenTokens[pair.Key]; ok {
Wei Li455ba832021-11-04 22:58:12 +0000657 value = val
658 }
Liz Kammer00629db2023-02-09 14:28:15 -0500659 replacerString[i*2] = pair.Key
660 replacerString[i*2+1] = value
Wei Li455ba832021-11-04 22:58:12 +0000661 }
662 replacer := strings.NewReplacer(replacerString...)
663 return replacer.Replace(actionEntry.TemplateContent)
664}
665
Liz Kammerf15a0792023-02-09 14:28:36 -0500666// \->\\, $->\$, `->\`, "->\", \n->\\n, '->'"'"'
667var commandLineArgumentReplacer = strings.NewReplacer(
668 `\`, `\\`,
669 `$`, `\$`,
670 "`", "\\`",
671 `"`, `\"`,
672 "\n", "\\n",
673 `'`, `'"'"'`,
674)
675
Wei Li455ba832021-11-04 22:58:12 +0000676func escapeCommandlineArgument(str string) string {
Liz Kammerf15a0792023-02-09 14:28:36 -0500677 return commandLineArgumentReplacer.Replace(str)
Wei Li455ba832021-11-04 22:58:12 +0000678}
679
Liz Kammer00629db2023-02-09 14:28:15 -0500680func (a *aqueryArtifactHandler) actionToBuildStatement(actionEntry *analysis_v2_proto.Action) (*BuildStatement, error) {
681 switch actionEntry.Mnemonic {
Chris Parsonsc4fb1332021-05-18 12:31:25 -0400682 // Middleman actions are not handled like other actions; they are handled separately as a
683 // preparatory step so that their inputs may be relayed to actions depending on middleman
684 // artifacts.
Liz Kammer00629db2023-02-09 14:28:15 -0500685 case middlemanMnemonic:
686 return nil, nil
Sasha Smundakc180dbd2022-07-03 14:55:58 -0700687 // PythonZipper is bogus action returned by aquery, ignore it (b/236198693)
Liz Kammer00629db2023-02-09 14:28:15 -0500688 case "PythonZipper":
689 return nil, nil
Chris Parsons8d6e4332021-02-22 16:13:50 -0500690 // Skip "Fail" actions, which are placeholder actions designed to always fail.
Liz Kammer00629db2023-02-09 14:28:15 -0500691 case "Fail":
692 return nil, nil
693 case "BaselineCoverage":
694 return nil, nil
695 case "Symlink", "SolibSymlink", "ExecutableSymlink":
696 return a.symlinkActionBuildStatement(actionEntry)
697 case "TemplateExpand":
698 if len(actionEntry.Arguments) < 1 {
699 return a.templateExpandActionBuildStatement(actionEntry)
700 }
Cole Faust950689a2023-06-21 15:07:21 -0700701 case "FileWrite", "SourceSymlinkManifest", "RepoMappingManifest":
Liz Kammer00629db2023-02-09 14:28:15 -0500702 return a.fileWriteActionBuildStatement(actionEntry)
703 case "SymlinkTree":
704 return a.symlinkTreeActionBuildStatement(actionEntry)
Chris Parsons8d6e4332021-02-22 16:13:50 -0500705 }
Liz Kammer00629db2023-02-09 14:28:15 -0500706
707 if len(actionEntry.Arguments) < 1 {
708 return nil, fmt.Errorf("received action with no command: [%s]", actionEntry.Mnemonic)
Yu Liu8d82ac52022-05-17 15:13:28 -0700709 }
Liz Kammer00629db2023-02-09 14:28:15 -0500710 return a.normalActionBuildStatement(actionEntry)
711
Chris Parsons8d6e4332021-02-22 16:13:50 -0500712}
713
Liz Kammer00629db2023-02-09 14:28:15 -0500714func expandPathFragment(id pathFragmentId, pathFragmentsMap map[pathFragmentId]*analysis_v2_proto.PathFragment) (string, error) {
Usta Shrestha6298cc52022-05-27 17:40:21 -0400715 var labels []string
Chris Parsonsaffbb602020-12-23 12:02:11 -0500716 currId := id
717 // Only positive IDs are valid for path fragments. An ID of zero indicates a terminal node.
718 for currId > 0 {
719 currFragment, ok := pathFragmentsMap[currId]
720 if !ok {
Chris Parsons4f069892021-01-15 12:22:41 -0500721 return "", fmt.Errorf("undefined path fragment id %d", currId)
Chris Parsonsaffbb602020-12-23 12:02:11 -0500722 }
723 labels = append([]string{currFragment.Label}, labels...)
Liz Kammer00629db2023-02-09 14:28:15 -0500724 parentId := pathFragmentId(currFragment.ParentId)
725 if currId == parentId {
Sasha Smundakfe9a5b82022-07-27 14:51:45 -0700726 return "", fmt.Errorf("fragment cannot refer to itself as parent %#v", currFragment)
Liz Kammerc49e6822021-06-08 15:04:11 -0400727 }
Liz Kammer00629db2023-02-09 14:28:15 -0500728 currId = parentId
Chris Parsonsaffbb602020-12-23 12:02:11 -0500729 }
730 return filepath.Join(labels...), nil
731}