a20d947329
The "bazel sandwich" is a mechanism for bazel to depend on make/soong outputs. The name comes from the fact that bazel is now at the top and bottom of the build graph. This is intended to allow us to work on converting the partition builds to bazel while not all of the dependencies of the partition have been converted. It works by adding the bazel_sandwich_import_file rule, which emits a dangling symlink that starts with bazel_sandwich:, and includes information that the aquery handler in soong reads. The aquery handler rewrites the symlink so that it points to a file generated by make/soong, and adds a ninja dependency from the symlink to the file it's targeting. This allows us to depend on make-built files from bazel, but notably it doesn't allow us to depend on analysis-time information from make. This shouldn't be a problem for the partitions, but limits the use of the bazel sandwich to similar, less complicated types of builds. go/roboleaf-bazel-sandwich Bug: 265127181 Test: m bazel_sandwich Change-Id: Ic41bae7be0b55f251d04a6a95f846c50ce897adc
791 lines
29 KiB
Go
791 lines
29 KiB
Go
// Copyright 2020 Google Inc. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package bazel
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"path/filepath"
|
|
analysis_v2_proto "prebuilts/bazel/common/proto/analysis_v2"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/google/blueprint/metrics"
|
|
"github.com/google/blueprint/proptools"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
type artifactId int
|
|
type depsetId int
|
|
type pathFragmentId int
|
|
|
|
// artifact contains relevant portions of Bazel's aquery proto, Artifact.
|
|
// Represents a single artifact, whether it's a source file or a derived output file.
|
|
type artifact struct {
|
|
Id artifactId
|
|
PathFragmentId pathFragmentId
|
|
}
|
|
|
|
type pathFragment struct {
|
|
Id pathFragmentId
|
|
Label string
|
|
ParentId pathFragmentId
|
|
}
|
|
|
|
// KeyValuePair represents Bazel's aquery proto, KeyValuePair.
|
|
type KeyValuePair struct {
|
|
Key string
|
|
Value string
|
|
}
|
|
|
|
// AqueryDepset is a depset definition from Bazel's aquery response. This is
|
|
// akin to the `depSetOfFiles` in the response proto, except:
|
|
// - direct artifacts are enumerated by full path instead of by ID
|
|
// - it has a hash of the depset contents, instead of an int ID (for determinism)
|
|
//
|
|
// A depset is a data structure for efficient transitive handling of artifact
|
|
// paths. A single depset consists of one or more artifact paths and one or
|
|
// more "child" depsets.
|
|
type AqueryDepset struct {
|
|
ContentHash string
|
|
DirectArtifacts []string
|
|
TransitiveDepSetHashes []string
|
|
}
|
|
|
|
// depSetOfFiles contains relevant portions of Bazel's aquery proto, DepSetOfFiles.
|
|
// Represents a data structure containing one or more files. Depsets in Bazel are an efficient
|
|
// data structure for storing large numbers of file paths.
|
|
type depSetOfFiles struct {
|
|
Id depsetId
|
|
DirectArtifactIds []artifactId
|
|
TransitiveDepSetIds []depsetId
|
|
}
|
|
|
|
// action contains relevant portions of Bazel's aquery proto, Action.
|
|
// Represents a single command line invocation in the Bazel build graph.
|
|
type action struct {
|
|
Arguments []string
|
|
EnvironmentVariables []KeyValuePair
|
|
InputDepSetIds []depsetId
|
|
Mnemonic string
|
|
OutputIds []artifactId
|
|
TemplateContent string
|
|
Substitutions []KeyValuePair
|
|
FileContents string
|
|
}
|
|
|
|
// actionGraphContainer contains relevant portions of Bazel's aquery proto, ActionGraphContainer.
|
|
// An aquery response from Bazel contains a single ActionGraphContainer proto.
|
|
type actionGraphContainer struct {
|
|
Artifacts []artifact
|
|
Actions []action
|
|
DepSetOfFiles []depSetOfFiles
|
|
PathFragments []pathFragment
|
|
}
|
|
|
|
// BuildStatement contains information to register a build statement corresponding (one to one)
|
|
// with a Bazel action from Bazel's action graph.
|
|
type BuildStatement struct {
|
|
Command string
|
|
Depfile *string
|
|
OutputPaths []string
|
|
SymlinkPaths []string
|
|
Env []*analysis_v2_proto.KeyValuePair
|
|
Mnemonic string
|
|
|
|
// Inputs of this build statement, either as unexpanded depsets or expanded
|
|
// input paths. There should be no overlap between these fields; an input
|
|
// path should either be included as part of an unexpanded depset or a raw
|
|
// input path string, but not both.
|
|
InputDepsetHashes []string
|
|
InputPaths []string
|
|
FileContents string
|
|
// If ShouldRunInSbox is true, Soong will use sbox to created an isolated environment
|
|
// and run the mixed build action there
|
|
ShouldRunInSbox bool
|
|
// A list of files to add as implicit deps to the outputs of this BuildStatement.
|
|
// Unlike most properties in BuildStatement, these paths must be relative to the root of
|
|
// the whole out/ folder, instead of relative to ctx.Config().BazelContext.OutputBase()
|
|
ImplicitDeps []string
|
|
}
|
|
|
|
// A helper type for aquery processing which facilitates retrieval of path IDs from their
|
|
// less readable Bazel structures (depset and path fragment).
|
|
type aqueryArtifactHandler struct {
|
|
// Maps depset id to AqueryDepset, a representation of depset which is
|
|
// post-processed for middleman artifact handling, unhandled artifact
|
|
// dropping, content hashing, etc.
|
|
depsetIdToAqueryDepset map[depsetId]AqueryDepset
|
|
emptyDepsetIds map[depsetId]struct{}
|
|
// Maps content hash to AqueryDepset.
|
|
depsetHashToAqueryDepset map[string]AqueryDepset
|
|
|
|
// depsetIdToArtifactIdsCache is a memoization of depset flattening, because flattening
|
|
// may be an expensive operation.
|
|
depsetHashToArtifactPathsCache sync.Map
|
|
// Maps artifact ids to fully expanded paths.
|
|
artifactIdToPath map[artifactId]string
|
|
}
|
|
|
|
// The tokens should be substituted with the value specified here, instead of the
|
|
// one returned in 'substitutions' of TemplateExpand action.
|
|
var templateActionOverriddenTokens = map[string]string{
|
|
// Uses "python3" for %python_binary% instead of the value returned by aquery
|
|
// which is "py3wrapper.sh". See removePy3wrapperScript.
|
|
"%python_binary%": "python3",
|
|
}
|
|
|
|
const (
|
|
middlemanMnemonic = "Middleman"
|
|
// The file name of py3wrapper.sh, which is used by py_binary targets.
|
|
py3wrapperFileName = "/py3wrapper.sh"
|
|
)
|
|
|
|
func indexBy[K comparable, V any](values []V, keyFn func(v V) K) map[K]V {
|
|
m := map[K]V{}
|
|
for _, v := range values {
|
|
m[keyFn(v)] = v
|
|
}
|
|
return m
|
|
}
|
|
|
|
func newAqueryHandler(aqueryResult *analysis_v2_proto.ActionGraphContainer) (*aqueryArtifactHandler, error) {
|
|
pathFragments := indexBy(aqueryResult.PathFragments, func(pf *analysis_v2_proto.PathFragment) pathFragmentId {
|
|
return pathFragmentId(pf.Id)
|
|
})
|
|
|
|
artifactIdToPath := make(map[artifactId]string, len(aqueryResult.Artifacts))
|
|
for _, artifact := range aqueryResult.Artifacts {
|
|
artifactPath, err := expandPathFragment(pathFragmentId(artifact.PathFragmentId), pathFragments)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
artifactIdToPath[artifactId(artifact.Id)] = artifactPath
|
|
}
|
|
|
|
// Map middleman artifact ContentHash to input artifact depset ID.
|
|
// Middleman artifacts are treated as "substitute" artifacts for mixed builds. For example,
|
|
// if we find a middleman action which has inputs [foo, bar], and output [baz_middleman], then,
|
|
// for each other action which has input [baz_middleman], we add [foo, bar] to the inputs for
|
|
// that action instead.
|
|
middlemanIdToDepsetIds := map[artifactId][]uint32{}
|
|
for _, actionEntry := range aqueryResult.Actions {
|
|
if actionEntry.Mnemonic == middlemanMnemonic {
|
|
for _, outputId := range actionEntry.OutputIds {
|
|
middlemanIdToDepsetIds[artifactId(outputId)] = actionEntry.InputDepSetIds
|
|
}
|
|
}
|
|
}
|
|
|
|
depsetIdToDepset := indexBy(aqueryResult.DepSetOfFiles, func(d *analysis_v2_proto.DepSetOfFiles) depsetId {
|
|
return depsetId(d.Id)
|
|
})
|
|
|
|
aqueryHandler := aqueryArtifactHandler{
|
|
depsetIdToAqueryDepset: map[depsetId]AqueryDepset{},
|
|
depsetHashToAqueryDepset: map[string]AqueryDepset{},
|
|
depsetHashToArtifactPathsCache: sync.Map{},
|
|
emptyDepsetIds: make(map[depsetId]struct{}, 0),
|
|
artifactIdToPath: artifactIdToPath,
|
|
}
|
|
|
|
// Validate and adjust aqueryResult.DepSetOfFiles values.
|
|
for _, depset := range aqueryResult.DepSetOfFiles {
|
|
_, err := aqueryHandler.populateDepsetMaps(depset, middlemanIdToDepsetIds, depsetIdToDepset)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return &aqueryHandler, nil
|
|
}
|
|
|
|
// Ensures that the handler's depsetIdToAqueryDepset map contains an entry for the given
|
|
// depset.
|
|
func (a *aqueryArtifactHandler) populateDepsetMaps(depset *analysis_v2_proto.DepSetOfFiles, middlemanIdToDepsetIds map[artifactId][]uint32, depsetIdToDepset map[depsetId]*analysis_v2_proto.DepSetOfFiles) (*AqueryDepset, error) {
|
|
if aqueryDepset, containsDepset := a.depsetIdToAqueryDepset[depsetId(depset.Id)]; containsDepset {
|
|
return &aqueryDepset, nil
|
|
}
|
|
transitiveDepsetIds := depset.TransitiveDepSetIds
|
|
directArtifactPaths := make([]string, 0, len(depset.DirectArtifactIds))
|
|
for _, id := range depset.DirectArtifactIds {
|
|
aId := artifactId(id)
|
|
path, pathExists := a.artifactIdToPath[aId]
|
|
if !pathExists {
|
|
return nil, fmt.Errorf("undefined input artifactId %d", aId)
|
|
}
|
|
// Filter out any inputs which are universally dropped, and swap middleman
|
|
// artifacts with their corresponding depsets.
|
|
if depsetsToUse, isMiddleman := middlemanIdToDepsetIds[aId]; isMiddleman {
|
|
// Swap middleman artifacts with their corresponding depsets and drop the middleman artifacts.
|
|
transitiveDepsetIds = append(transitiveDepsetIds, depsetsToUse...)
|
|
} else if strings.HasSuffix(path, py3wrapperFileName) ||
|
|
strings.HasPrefix(path, "../bazel_tools") {
|
|
continue
|
|
// Drop these artifacts.
|
|
// See go/python-binary-host-mixed-build for more details.
|
|
// 1) Drop py3wrapper.sh, just use python binary, the launcher script generated by the
|
|
// TemplateExpandAction handles everything necessary to launch a Pythin application.
|
|
// 2) ../bazel_tools: they have MODIFY timestamp 10years in the future and would cause the
|
|
// containing depset to always be considered newer than their outputs.
|
|
} else {
|
|
directArtifactPaths = append(directArtifactPaths, path)
|
|
}
|
|
}
|
|
|
|
childDepsetHashes := make([]string, 0, len(transitiveDepsetIds))
|
|
for _, id := range transitiveDepsetIds {
|
|
childDepsetId := depsetId(id)
|
|
childDepset, exists := depsetIdToDepset[childDepsetId]
|
|
if !exists {
|
|
if _, empty := a.emptyDepsetIds[childDepsetId]; empty {
|
|
continue
|
|
} else {
|
|
return nil, fmt.Errorf("undefined input depsetId %d (referenced by depsetId %d)", childDepsetId, depset.Id)
|
|
}
|
|
}
|
|
if childAqueryDepset, err := a.populateDepsetMaps(childDepset, middlemanIdToDepsetIds, depsetIdToDepset); err != nil {
|
|
return nil, err
|
|
} else if childAqueryDepset == nil {
|
|
continue
|
|
} else {
|
|
childDepsetHashes = append(childDepsetHashes, childAqueryDepset.ContentHash)
|
|
}
|
|
}
|
|
if len(directArtifactPaths) == 0 && len(childDepsetHashes) == 0 {
|
|
a.emptyDepsetIds[depsetId(depset.Id)] = struct{}{}
|
|
return nil, nil
|
|
}
|
|
aqueryDepset := AqueryDepset{
|
|
ContentHash: depsetContentHash(directArtifactPaths, childDepsetHashes),
|
|
DirectArtifacts: directArtifactPaths,
|
|
TransitiveDepSetHashes: childDepsetHashes,
|
|
}
|
|
a.depsetIdToAqueryDepset[depsetId(depset.Id)] = aqueryDepset
|
|
a.depsetHashToAqueryDepset[aqueryDepset.ContentHash] = aqueryDepset
|
|
return &aqueryDepset, nil
|
|
}
|
|
|
|
// getInputPaths flattens the depsets of the given IDs and returns all transitive
|
|
// input paths contained in these depsets.
|
|
// This is a potentially expensive operation, and should not be invoked except
|
|
// for actions which need specialized input handling.
|
|
func (a *aqueryArtifactHandler) getInputPaths(depsetIds []uint32) ([]string, error) {
|
|
var inputPaths []string
|
|
|
|
for _, id := range depsetIds {
|
|
inputDepSetId := depsetId(id)
|
|
depset := a.depsetIdToAqueryDepset[inputDepSetId]
|
|
inputArtifacts, err := a.artifactPathsFromDepsetHash(depset.ContentHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, inputPath := range inputArtifacts {
|
|
inputPaths = append(inputPaths, inputPath)
|
|
}
|
|
}
|
|
|
|
return inputPaths, nil
|
|
}
|
|
|
|
func (a *aqueryArtifactHandler) artifactPathsFromDepsetHash(depsetHash string) ([]string, error) {
|
|
if result, exists := a.depsetHashToArtifactPathsCache.Load(depsetHash); exists {
|
|
return result.([]string), nil
|
|
}
|
|
if depset, exists := a.depsetHashToAqueryDepset[depsetHash]; exists {
|
|
result := depset.DirectArtifacts
|
|
for _, childHash := range depset.TransitiveDepSetHashes {
|
|
childArtifactIds, err := a.artifactPathsFromDepsetHash(childHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result = append(result, childArtifactIds...)
|
|
}
|
|
a.depsetHashToArtifactPathsCache.Store(depsetHash, result)
|
|
return result, nil
|
|
} else {
|
|
return nil, fmt.Errorf("undefined input depset hash %s", depsetHash)
|
|
}
|
|
}
|
|
|
|
// AqueryBuildStatements returns a slice of BuildStatements and a slice of AqueryDepset
|
|
// which should be registered (and output to a ninja file) to correspond with Bazel's
|
|
// action graph, as described by the given action graph json proto.
|
|
// BuildStatements are one-to-one with actions in the given action graph, and AqueryDepsets
|
|
// are one-to-one with Bazel's depSetOfFiles objects.
|
|
func AqueryBuildStatements(aqueryJsonProto []byte, eventHandler *metrics.EventHandler) ([]*BuildStatement, []AqueryDepset, error) {
|
|
aqueryProto := &analysis_v2_proto.ActionGraphContainer{}
|
|
err := proto.Unmarshal(aqueryJsonProto, aqueryProto)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
var aqueryHandler *aqueryArtifactHandler
|
|
{
|
|
eventHandler.Begin("init_handler")
|
|
defer eventHandler.End("init_handler")
|
|
aqueryHandler, err = newAqueryHandler(aqueryProto)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
|
|
// allocate both length and capacity so each goroutine can write to an index independently without
|
|
// any need for synchronization for slice access.
|
|
buildStatements := make([]*BuildStatement, len(aqueryProto.Actions))
|
|
{
|
|
eventHandler.Begin("build_statements")
|
|
defer eventHandler.End("build_statements")
|
|
wg := sync.WaitGroup{}
|
|
var errOnce sync.Once
|
|
|
|
for i, actionEntry := range aqueryProto.Actions {
|
|
wg.Add(1)
|
|
go func(i int, actionEntry *analysis_v2_proto.Action) {
|
|
buildStatement, aErr := aqueryHandler.actionToBuildStatement(actionEntry)
|
|
if aErr != nil {
|
|
errOnce.Do(func() {
|
|
err = aErr
|
|
})
|
|
} else {
|
|
// set build statement at an index rather than appending such that each goroutine does not
|
|
// impact other goroutines
|
|
buildStatements[i] = buildStatement
|
|
}
|
|
wg.Done()
|
|
}(i, actionEntry)
|
|
}
|
|
wg.Wait()
|
|
}
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
depsetsByHash := map[string]AqueryDepset{}
|
|
depsets := make([]AqueryDepset, 0, len(aqueryHandler.depsetIdToAqueryDepset))
|
|
{
|
|
eventHandler.Begin("depsets")
|
|
defer eventHandler.End("depsets")
|
|
for _, aqueryDepset := range aqueryHandler.depsetIdToAqueryDepset {
|
|
if prevEntry, hasKey := depsetsByHash[aqueryDepset.ContentHash]; hasKey {
|
|
// Two depsets collide on hash. Ensure that their contents are identical.
|
|
if !reflect.DeepEqual(aqueryDepset, prevEntry) {
|
|
return nil, nil, fmt.Errorf("two different depsets have the same hash: %v, %v", prevEntry, aqueryDepset)
|
|
}
|
|
} else {
|
|
depsetsByHash[aqueryDepset.ContentHash] = aqueryDepset
|
|
depsets = append(depsets, aqueryDepset)
|
|
}
|
|
}
|
|
}
|
|
|
|
eventHandler.Do("build_statement_sort", func() {
|
|
// Build Statements and depsets must be sorted by their content hash to
|
|
// preserve determinism between builds (this will result in consistent ninja file
|
|
// output). Note they are not sorted by their original IDs nor their Bazel ordering,
|
|
// as Bazel gives nondeterministic ordering / identifiers in aquery responses.
|
|
sort.Slice(buildStatements, func(i, j int) bool {
|
|
// Sort all nil statements to the end of the slice
|
|
if buildStatements[i] == nil {
|
|
return false
|
|
} else if buildStatements[j] == nil {
|
|
return true
|
|
}
|
|
//For build statements, compare output lists. In Bazel, each output file
|
|
// may only have one action which generates it, so this will provide
|
|
// a deterministic ordering.
|
|
outputs_i := buildStatements[i].OutputPaths
|
|
outputs_j := buildStatements[j].OutputPaths
|
|
if len(outputs_i) != len(outputs_j) {
|
|
return len(outputs_i) < len(outputs_j)
|
|
}
|
|
if len(outputs_i) == 0 {
|
|
// No outputs for these actions, so compare commands.
|
|
return buildStatements[i].Command < buildStatements[j].Command
|
|
}
|
|
// There may be multiple outputs, but the output ordering is deterministic.
|
|
return outputs_i[0] < outputs_j[0]
|
|
})
|
|
})
|
|
eventHandler.Do("depset_sort", func() {
|
|
sort.Slice(depsets, func(i, j int) bool {
|
|
return depsets[i].ContentHash < depsets[j].ContentHash
|
|
})
|
|
})
|
|
return buildStatements, depsets, nil
|
|
}
|
|
|
|
// depsetContentHash computes and returns a SHA256 checksum of the contents of
|
|
// the given depset. This content hash may serve as the depset's identifier.
|
|
// Using a content hash for an identifier is superior for determinism. (For example,
|
|
// using an integer identifier which depends on the order in which the depsets are
|
|
// created would result in nondeterministic depset IDs.)
|
|
func depsetContentHash(directPaths []string, transitiveDepsetHashes []string) string {
|
|
h := sha256.New()
|
|
// Use newline as delimiter, as paths cannot contain newline.
|
|
h.Write([]byte(strings.Join(directPaths, "\n")))
|
|
h.Write([]byte(strings.Join(transitiveDepsetHashes, "")))
|
|
fullHash := base64.RawURLEncoding.EncodeToString(h.Sum(nil))
|
|
return fullHash
|
|
}
|
|
|
|
func (a *aqueryArtifactHandler) depsetContentHashes(inputDepsetIds []uint32) ([]string, error) {
|
|
var hashes []string
|
|
for _, id := range inputDepsetIds {
|
|
dId := depsetId(id)
|
|
if aqueryDepset, exists := a.depsetIdToAqueryDepset[dId]; !exists {
|
|
if _, empty := a.emptyDepsetIds[dId]; !empty {
|
|
return nil, fmt.Errorf("undefined (not even empty) input depsetId %d", dId)
|
|
}
|
|
} else {
|
|
hashes = append(hashes, aqueryDepset.ContentHash)
|
|
}
|
|
}
|
|
return hashes, nil
|
|
}
|
|
|
|
// escapes the args received from aquery and creates a command string
|
|
func commandString(actionEntry *analysis_v2_proto.Action) string {
|
|
switch actionEntry.Mnemonic {
|
|
case "GoCompilePkg":
|
|
argsEscaped := []string{}
|
|
for _, arg := range actionEntry.Arguments {
|
|
if arg == "" {
|
|
// If this is an empty string, add ''
|
|
// And not
|
|
// 1. (literal empty)
|
|
// 2. `''\'''\'''` (escaped version of '')
|
|
//
|
|
// If we had used (1), then this would appear as a whitespace when we strings.Join
|
|
argsEscaped = append(argsEscaped, "''")
|
|
} else {
|
|
argsEscaped = append(argsEscaped, proptools.ShellEscapeIncludingSpaces(arg))
|
|
}
|
|
}
|
|
return strings.Join(argsEscaped, " ")
|
|
default:
|
|
return strings.Join(proptools.ShellEscapeListIncludingSpaces(actionEntry.Arguments), " ")
|
|
}
|
|
}
|
|
|
|
func (a *aqueryArtifactHandler) normalActionBuildStatement(actionEntry *analysis_v2_proto.Action) (*BuildStatement, error) {
|
|
command := commandString(actionEntry)
|
|
inputDepsetHashes, err := a.depsetContentHashes(actionEntry.InputDepSetIds)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
outputPaths, depfile, err := a.getOutputPaths(actionEntry)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
buildStatement := &BuildStatement{
|
|
Command: command,
|
|
Depfile: depfile,
|
|
OutputPaths: outputPaths,
|
|
InputDepsetHashes: inputDepsetHashes,
|
|
Env: actionEntry.EnvironmentVariables,
|
|
Mnemonic: actionEntry.Mnemonic,
|
|
}
|
|
if buildStatement.Mnemonic == "GoToolchainBinaryBuild" {
|
|
// Unlike b's execution root, mixed build execution root contains a symlink to prebuilts/go
|
|
// This causes issues for `GOCACHE=$(mktemp -d) go build ...`
|
|
// To prevent this, sandbox this action in mixed builds as well
|
|
buildStatement.ShouldRunInSbox = true
|
|
}
|
|
return buildStatement, nil
|
|
}
|
|
|
|
func (a *aqueryArtifactHandler) templateExpandActionBuildStatement(actionEntry *analysis_v2_proto.Action) (*BuildStatement, error) {
|
|
outputPaths, depfile, err := a.getOutputPaths(actionEntry)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(outputPaths) != 1 {
|
|
return nil, fmt.Errorf("Expect 1 output to template expand action, got: output %q", outputPaths)
|
|
}
|
|
expandedTemplateContent := expandTemplateContent(actionEntry)
|
|
// The expandedTemplateContent is escaped for being used in double quotes and shell unescape,
|
|
// and the new line characters (\n) are also changed to \\n which avoids some Ninja escape on \n, which might
|
|
// change \n to space and mess up the format of Python programs.
|
|
// sed is used to convert \\n back to \n before saving to output file.
|
|
// See go/python-binary-host-mixed-build for more details.
|
|
command := fmt.Sprintf(`/bin/bash -c 'echo "%[1]s" | sed "s/\\\\n/\\n/g" > %[2]s && chmod a+x %[2]s'`,
|
|
escapeCommandlineArgument(expandedTemplateContent), outputPaths[0])
|
|
inputDepsetHashes, err := a.depsetContentHashes(actionEntry.InputDepSetIds)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
buildStatement := &BuildStatement{
|
|
Command: command,
|
|
Depfile: depfile,
|
|
OutputPaths: outputPaths,
|
|
InputDepsetHashes: inputDepsetHashes,
|
|
Env: actionEntry.EnvironmentVariables,
|
|
Mnemonic: actionEntry.Mnemonic,
|
|
}
|
|
return buildStatement, nil
|
|
}
|
|
|
|
func (a *aqueryArtifactHandler) fileWriteActionBuildStatement(actionEntry *analysis_v2_proto.Action) (*BuildStatement, error) {
|
|
outputPaths, _, err := a.getOutputPaths(actionEntry)
|
|
var depsetHashes []string
|
|
if err == nil {
|
|
depsetHashes, err = a.depsetContentHashes(actionEntry.InputDepSetIds)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &BuildStatement{
|
|
Depfile: nil,
|
|
OutputPaths: outputPaths,
|
|
Env: actionEntry.EnvironmentVariables,
|
|
Mnemonic: actionEntry.Mnemonic,
|
|
InputDepsetHashes: depsetHashes,
|
|
FileContents: actionEntry.FileContents,
|
|
}, nil
|
|
}
|
|
|
|
func (a *aqueryArtifactHandler) symlinkTreeActionBuildStatement(actionEntry *analysis_v2_proto.Action) (*BuildStatement, error) {
|
|
outputPaths, _, err := a.getOutputPaths(actionEntry)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
inputPaths, err := a.getInputPaths(actionEntry.InputDepSetIds)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(inputPaths) != 1 || len(outputPaths) != 1 {
|
|
return nil, fmt.Errorf("Expect 1 input and 1 output to symlink action, got: input %q, output %q", inputPaths, outputPaths)
|
|
}
|
|
// The actual command is generated in bazelSingleton.GenerateBuildActions
|
|
return &BuildStatement{
|
|
Depfile: nil,
|
|
OutputPaths: outputPaths,
|
|
Env: actionEntry.EnvironmentVariables,
|
|
Mnemonic: actionEntry.Mnemonic,
|
|
InputPaths: inputPaths,
|
|
}, nil
|
|
}
|
|
|
|
type bazelSandwichJson struct {
|
|
Target string `json:"target"`
|
|
DependOnTarget *bool `json:"depend_on_target,omitempty"`
|
|
ImplicitDeps []string `json:"implicit_deps"`
|
|
}
|
|
|
|
func (a *aqueryArtifactHandler) unresolvedSymlinkActionBuildStatement(actionEntry *analysis_v2_proto.Action) (*BuildStatement, error) {
|
|
outputPaths, depfile, err := a.getOutputPaths(actionEntry)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(actionEntry.InputDepSetIds) != 0 || len(outputPaths) != 1 {
|
|
return nil, fmt.Errorf("expected 0 inputs and 1 output to symlink action, got: input %q, output %q", actionEntry.InputDepSetIds, outputPaths)
|
|
}
|
|
target := actionEntry.UnresolvedSymlinkTarget
|
|
if target == "" {
|
|
return nil, fmt.Errorf("expected an unresolved_symlink_target, but didn't get one")
|
|
}
|
|
if filepath.Clean(target) != target {
|
|
return nil, fmt.Errorf("expected %q, got %q", filepath.Clean(target), target)
|
|
}
|
|
if strings.HasPrefix(target, "/") {
|
|
return nil, fmt.Errorf("no absolute symlinks allowed: %s", target)
|
|
}
|
|
|
|
out := outputPaths[0]
|
|
outDir := filepath.Dir(out)
|
|
var implicitDeps []string
|
|
if strings.HasPrefix(target, "bazel_sandwich:") {
|
|
j := bazelSandwichJson{}
|
|
err := json.Unmarshal([]byte(target[len("bazel_sandwich:"):]), &j)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if proptools.BoolDefault(j.DependOnTarget, true) {
|
|
implicitDeps = append(implicitDeps, j.Target)
|
|
}
|
|
implicitDeps = append(implicitDeps, j.ImplicitDeps...)
|
|
dotDotsToReachCwd := ""
|
|
if outDir != "." {
|
|
dotDotsToReachCwd = strings.Repeat("../", strings.Count(outDir, "/")+1)
|
|
}
|
|
target = proptools.ShellEscapeIncludingSpaces(j.Target)
|
|
target = "{DOTDOTS_TO_OUTPUT_ROOT}" + dotDotsToReachCwd + target
|
|
} else {
|
|
target = proptools.ShellEscapeIncludingSpaces(target)
|
|
}
|
|
|
|
outDir = proptools.ShellEscapeIncludingSpaces(outDir)
|
|
out = proptools.ShellEscapeIncludingSpaces(out)
|
|
// Use absolute paths, because some soong actions don't play well with relative paths (for example, `cp -d`).
|
|
command := fmt.Sprintf("mkdir -p %[1]s && rm -f %[2]s && ln -sf %[3]s %[2]s", outDir, out, target)
|
|
symlinkPaths := outputPaths[:]
|
|
|
|
buildStatement := &BuildStatement{
|
|
Command: command,
|
|
Depfile: depfile,
|
|
OutputPaths: outputPaths,
|
|
Env: actionEntry.EnvironmentVariables,
|
|
Mnemonic: actionEntry.Mnemonic,
|
|
SymlinkPaths: symlinkPaths,
|
|
ImplicitDeps: implicitDeps,
|
|
}
|
|
return buildStatement, nil
|
|
}
|
|
|
|
func (a *aqueryArtifactHandler) symlinkActionBuildStatement(actionEntry *analysis_v2_proto.Action) (*BuildStatement, error) {
|
|
outputPaths, depfile, err := a.getOutputPaths(actionEntry)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
inputPaths, err := a.getInputPaths(actionEntry.InputDepSetIds)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(inputPaths) != 1 || len(outputPaths) != 1 {
|
|
return nil, fmt.Errorf("Expect 1 input and 1 output to symlink action, got: input %q, output %q", inputPaths, outputPaths)
|
|
}
|
|
out := outputPaths[0]
|
|
outDir := proptools.ShellEscapeIncludingSpaces(filepath.Dir(out))
|
|
out = proptools.ShellEscapeIncludingSpaces(out)
|
|
in := filepath.Join("$PWD", proptools.ShellEscapeIncludingSpaces(inputPaths[0]))
|
|
// Use absolute paths, because some soong actions don't play well with relative paths (for example, `cp -d`).
|
|
command := fmt.Sprintf("mkdir -p %[1]s && rm -f %[2]s && ln -sf %[3]s %[2]s", outDir, out, in)
|
|
symlinkPaths := outputPaths[:]
|
|
|
|
buildStatement := &BuildStatement{
|
|
Command: command,
|
|
Depfile: depfile,
|
|
OutputPaths: outputPaths,
|
|
InputPaths: inputPaths,
|
|
Env: actionEntry.EnvironmentVariables,
|
|
Mnemonic: actionEntry.Mnemonic,
|
|
SymlinkPaths: symlinkPaths,
|
|
}
|
|
return buildStatement, nil
|
|
}
|
|
|
|
func (a *aqueryArtifactHandler) getOutputPaths(actionEntry *analysis_v2_proto.Action) (outputPaths []string, depfile *string, err error) {
|
|
for _, outputId := range actionEntry.OutputIds {
|
|
outputPath, exists := a.artifactIdToPath[artifactId(outputId)]
|
|
if !exists {
|
|
err = fmt.Errorf("undefined outputId %d", outputId)
|
|
return
|
|
}
|
|
ext := filepath.Ext(outputPath)
|
|
if ext == ".d" {
|
|
if depfile != nil {
|
|
err = fmt.Errorf("found multiple potential depfiles %q, %q", *depfile, outputPath)
|
|
return
|
|
} else {
|
|
depfile = &outputPath
|
|
}
|
|
} else {
|
|
outputPaths = append(outputPaths, outputPath)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// expandTemplateContent substitutes the tokens in a template.
|
|
func expandTemplateContent(actionEntry *analysis_v2_proto.Action) string {
|
|
replacerString := make([]string, len(actionEntry.Substitutions)*2)
|
|
for i, pair := range actionEntry.Substitutions {
|
|
value := pair.Value
|
|
if val, ok := templateActionOverriddenTokens[pair.Key]; ok {
|
|
value = val
|
|
}
|
|
replacerString[i*2] = pair.Key
|
|
replacerString[i*2+1] = value
|
|
}
|
|
replacer := strings.NewReplacer(replacerString...)
|
|
return replacer.Replace(actionEntry.TemplateContent)
|
|
}
|
|
|
|
// \->\\, $->\$, `->\`, "->\", \n->\\n, '->'"'"'
|
|
var commandLineArgumentReplacer = strings.NewReplacer(
|
|
`\`, `\\`,
|
|
`$`, `\$`,
|
|
"`", "\\`",
|
|
`"`, `\"`,
|
|
"\n", "\\n",
|
|
`'`, `'"'"'`,
|
|
)
|
|
|
|
func escapeCommandlineArgument(str string) string {
|
|
return commandLineArgumentReplacer.Replace(str)
|
|
}
|
|
|
|
func (a *aqueryArtifactHandler) actionToBuildStatement(actionEntry *analysis_v2_proto.Action) (*BuildStatement, error) {
|
|
switch actionEntry.Mnemonic {
|
|
// Middleman actions are not handled like other actions; they are handled separately as a
|
|
// preparatory step so that their inputs may be relayed to actions depending on middleman
|
|
// artifacts.
|
|
case middlemanMnemonic:
|
|
return nil, nil
|
|
// PythonZipper is bogus action returned by aquery, ignore it (b/236198693)
|
|
case "PythonZipper":
|
|
return nil, nil
|
|
// Skip "Fail" actions, which are placeholder actions designed to always fail.
|
|
case "Fail":
|
|
return nil, nil
|
|
case "BaselineCoverage":
|
|
return nil, nil
|
|
case "Symlink", "SolibSymlink", "ExecutableSymlink":
|
|
return a.symlinkActionBuildStatement(actionEntry)
|
|
case "TemplateExpand":
|
|
if len(actionEntry.Arguments) < 1 {
|
|
return a.templateExpandActionBuildStatement(actionEntry)
|
|
}
|
|
case "FileWrite", "SourceSymlinkManifest", "RepoMappingManifest":
|
|
return a.fileWriteActionBuildStatement(actionEntry)
|
|
case "SymlinkTree":
|
|
return a.symlinkTreeActionBuildStatement(actionEntry)
|
|
case "UnresolvedSymlink":
|
|
return a.unresolvedSymlinkActionBuildStatement(actionEntry)
|
|
}
|
|
|
|
if len(actionEntry.Arguments) < 1 {
|
|
return nil, fmt.Errorf("received action with no command: [%s]", actionEntry.Mnemonic)
|
|
}
|
|
return a.normalActionBuildStatement(actionEntry)
|
|
|
|
}
|
|
|
|
func expandPathFragment(id pathFragmentId, pathFragmentsMap map[pathFragmentId]*analysis_v2_proto.PathFragment) (string, error) {
|
|
var labels []string
|
|
currId := id
|
|
// Only positive IDs are valid for path fragments. An ID of zero indicates a terminal node.
|
|
for currId > 0 {
|
|
currFragment, ok := pathFragmentsMap[currId]
|
|
if !ok {
|
|
return "", fmt.Errorf("undefined path fragment id %d", currId)
|
|
}
|
|
labels = append([]string{currFragment.Label}, labels...)
|
|
parentId := pathFragmentId(currFragment.ParentId)
|
|
if currId == parentId {
|
|
return "", fmt.Errorf("fragment cannot refer to itself as parent %#v", currFragment)
|
|
}
|
|
currId = parentId
|
|
}
|
|
return filepath.Join(labels...), nil
|
|
}
|