platform_build_soong/android/rule_builder.go
Colin Cross ef972743e8 Support sbox-in-RBE
Allow passing a remoteexec.REParams to RuleBuilder to configure it to
run the rule remotely through RBE.  Requires the rule to use
SandboxInputs, which ensures that RuleBuilder is aware of all of the
inputs and outputs of the rule.

Running sbox in RBE initially seems unnecessary, as RBE is already a
good sandbox, but reproxy can execute RBE actions locally when configured
for local execution, local fallback or racing.  Using sbox in RBE ensures
that these local actions are also sandboxed, giving consistent results
between directly executed actions, local RBE actions, and remote RBE
actions.

Bug: 182612695
Test: manual
Change-Id: Icf2f24dde8dee833eb680ba22566a8e1c0143b15
2021-03-18 16:17:34 -07:00

1276 lines
44 KiB
Go

// Copyright 2018 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 android
import (
"crypto/sha256"
"fmt"
"path/filepath"
"sort"
"strings"
"testing"
"github.com/golang/protobuf/proto"
"github.com/google/blueprint"
"github.com/google/blueprint/proptools"
"android/soong/cmd/sbox/sbox_proto"
"android/soong/remoteexec"
"android/soong/shared"
)
const sboxSandboxBaseDir = "__SBOX_SANDBOX_DIR__"
const sboxOutSubDir = "out"
const sboxToolsSubDir = "tools"
const sboxOutDir = sboxSandboxBaseDir + "/" + sboxOutSubDir
// RuleBuilder provides an alternative to ModuleContext.Rule and ModuleContext.Build to add a command line to the build
// graph.
type RuleBuilder struct {
pctx PackageContext
ctx BuilderContext
commands []*RuleBuilderCommand
installs RuleBuilderInstalls
temporariesSet map[WritablePath]bool
restat bool
sbox bool
highmem bool
remoteable RemoteRuleSupports
rbeParams *remoteexec.REParams
outDir WritablePath
sboxTools bool
sboxInputs bool
sboxManifestPath WritablePath
missingDeps []string
}
// NewRuleBuilder returns a newly created RuleBuilder.
func NewRuleBuilder(pctx PackageContext, ctx BuilderContext) *RuleBuilder {
return &RuleBuilder{
pctx: pctx,
ctx: ctx,
temporariesSet: make(map[WritablePath]bool),
}
}
// RuleBuilderInstall is a tuple of install from and to locations.
type RuleBuilderInstall struct {
From Path
To string
}
type RuleBuilderInstalls []RuleBuilderInstall
// String returns the RuleBuilderInstalls in the form used by $(call copy-many-files) in Make, a space separated
// list of from:to tuples.
func (installs RuleBuilderInstalls) String() string {
sb := strings.Builder{}
for i, install := range installs {
if i != 0 {
sb.WriteRune(' ')
}
sb.WriteString(install.From.String())
sb.WriteRune(':')
sb.WriteString(install.To)
}
return sb.String()
}
// MissingDeps adds modules to the list of missing dependencies. If MissingDeps
// is called with a non-empty input, any call to Build will result in a rule
// that will print an error listing the missing dependencies and fail.
// MissingDeps should only be called if Config.AllowMissingDependencies() is
// true.
func (r *RuleBuilder) MissingDeps(missingDeps []string) {
r.missingDeps = append(r.missingDeps, missingDeps...)
}
// Restat marks the rule as a restat rule, which will be passed to ModuleContext.Rule in BuildParams.Restat.
//
// Restat is not compatible with Sbox()
func (r *RuleBuilder) Restat() *RuleBuilder {
if r.sbox {
panic("Restat() is not compatible with Sbox()")
}
r.restat = true
return r
}
// HighMem marks the rule as a high memory rule, which will limit how many run in parallel with other high memory
// rules.
func (r *RuleBuilder) HighMem() *RuleBuilder {
r.highmem = true
return r
}
// Remoteable marks the rule as supporting remote execution.
func (r *RuleBuilder) Remoteable(supports RemoteRuleSupports) *RuleBuilder {
r.remoteable = supports
return r
}
// Rewrapper marks the rule as running inside rewrapper using the given params in order to support
// running on RBE. During RuleBuilder.Build the params will be combined with the inputs, outputs
// and tools known to RuleBuilder to prepend an appropriate rewrapper command line to the rule's
// command line.
func (r *RuleBuilder) Rewrapper(params *remoteexec.REParams) *RuleBuilder {
if !r.sboxInputs {
panic(fmt.Errorf("RuleBuilder.Rewrapper must be called after RuleBuilder.SandboxInputs"))
}
r.rbeParams = params
return r
}
// Sbox marks the rule as needing to be wrapped by sbox. The outputDir should point to the output
// directory that sbox will wipe. It should not be written to by any other rule. manifestPath should
// point to a location where sbox's manifest will be written and must be outside outputDir. sbox
// will ensure that all outputs have been written, and will discard any output files that were not
// specified.
//
// Sbox is not compatible with Restat()
func (r *RuleBuilder) Sbox(outputDir WritablePath, manifestPath WritablePath) *RuleBuilder {
if r.sbox {
panic("Sbox() may not be called more than once")
}
if len(r.commands) > 0 {
panic("Sbox() may not be called after Command()")
}
if r.restat {
panic("Sbox() is not compatible with Restat()")
}
r.sbox = true
r.outDir = outputDir
r.sboxManifestPath = manifestPath
return r
}
// SandboxTools enables tool sandboxing for the rule by copying any referenced tools into the
// sandbox.
func (r *RuleBuilder) SandboxTools() *RuleBuilder {
if !r.sbox {
panic("SandboxTools() must be called after Sbox()")
}
if len(r.commands) > 0 {
panic("SandboxTools() may not be called after Command()")
}
r.sboxTools = true
return r
}
// SandboxInputs enables input sandboxing for the rule by copying any referenced inputs into the
// sandbox. It also implies SandboxTools().
//
// Sandboxing inputs requires RuleBuilder to be aware of all references to input paths. Paths
// that are passed to RuleBuilder outside of the methods that expect inputs, for example
// FlagWithArg, must use RuleBuilderCommand.PathForInput to translate the path to one that matches
// the sandbox layout.
func (r *RuleBuilder) SandboxInputs() *RuleBuilder {
if !r.sbox {
panic("SandboxInputs() must be called after Sbox()")
}
if len(r.commands) > 0 {
panic("SandboxInputs() may not be called after Command()")
}
r.sboxTools = true
r.sboxInputs = true
return r
}
// Install associates an output of the rule with an install location, which can be retrieved later using
// RuleBuilder.Installs.
func (r *RuleBuilder) Install(from Path, to string) {
r.installs = append(r.installs, RuleBuilderInstall{from, to})
}
// Command returns a new RuleBuilderCommand for the rule. The commands will be ordered in the rule by when they were
// created by this method. That can be mutated through their methods in any order, as long as the mutations do not
// race with any call to Build.
func (r *RuleBuilder) Command() *RuleBuilderCommand {
command := &RuleBuilderCommand{
rule: r,
}
r.commands = append(r.commands, command)
return command
}
// Temporary marks an output of a command as an intermediate file that will be used as an input to another command
// in the same rule, and should not be listed in Outputs.
func (r *RuleBuilder) Temporary(path WritablePath) {
r.temporariesSet[path] = true
}
// DeleteTemporaryFiles adds a command to the rule that deletes any outputs that have been marked using Temporary
// when the rule runs. DeleteTemporaryFiles should be called after all calls to Temporary.
func (r *RuleBuilder) DeleteTemporaryFiles() {
var temporariesList WritablePaths
for intermediate := range r.temporariesSet {
temporariesList = append(temporariesList, intermediate)
}
sort.Slice(temporariesList, func(i, j int) bool {
return temporariesList[i].String() < temporariesList[j].String()
})
r.Command().Text("rm").Flag("-f").Outputs(temporariesList)
}
// Inputs returns the list of paths that were passed to the RuleBuilderCommand methods that take
// input paths, such as RuleBuilderCommand.Input, RuleBuilderCommand.Implicit, or
// RuleBuilderCommand.FlagWithInput. Inputs to a command that are also outputs of another command
// in the same RuleBuilder are filtered out. The list is sorted and duplicates removed.
func (r *RuleBuilder) Inputs() Paths {
outputs := r.outputSet()
depFiles := r.depFileSet()
inputs := make(map[string]Path)
for _, c := range r.commands {
for _, input := range append(c.inputs, c.implicits...) {
inputStr := input.String()
if _, isOutput := outputs[inputStr]; !isOutput {
if _, isDepFile := depFiles[inputStr]; !isDepFile {
inputs[input.String()] = input
}
}
}
}
var inputList Paths
for _, input := range inputs {
inputList = append(inputList, input)
}
sort.Slice(inputList, func(i, j int) bool {
return inputList[i].String() < inputList[j].String()
})
return inputList
}
// OrderOnlys returns the list of paths that were passed to the RuleBuilderCommand.OrderOnly or
// RuleBuilderCommand.OrderOnlys. The list is sorted and duplicates removed.
func (r *RuleBuilder) OrderOnlys() Paths {
orderOnlys := make(map[string]Path)
for _, c := range r.commands {
for _, orderOnly := range c.orderOnlys {
orderOnlys[orderOnly.String()] = orderOnly
}
}
var orderOnlyList Paths
for _, orderOnly := range orderOnlys {
orderOnlyList = append(orderOnlyList, orderOnly)
}
sort.Slice(orderOnlyList, func(i, j int) bool {
return orderOnlyList[i].String() < orderOnlyList[j].String()
})
return orderOnlyList
}
func (r *RuleBuilder) outputSet() map[string]WritablePath {
outputs := make(map[string]WritablePath)
for _, c := range r.commands {
for _, output := range c.outputs {
outputs[output.String()] = output
}
}
return outputs
}
// Outputs returns the list of paths that were passed to the RuleBuilderCommand methods that take
// output paths, such as RuleBuilderCommand.Output, RuleBuilderCommand.ImplicitOutput, or
// RuleBuilderCommand.FlagWithInput. The list is sorted and duplicates removed.
func (r *RuleBuilder) Outputs() WritablePaths {
outputs := r.outputSet()
var outputList WritablePaths
for _, output := range outputs {
if !r.temporariesSet[output] {
outputList = append(outputList, output)
}
}
sort.Slice(outputList, func(i, j int) bool {
return outputList[i].String() < outputList[j].String()
})
return outputList
}
func (r *RuleBuilder) symlinkOutputSet() map[string]WritablePath {
symlinkOutputs := make(map[string]WritablePath)
for _, c := range r.commands {
for _, symlinkOutput := range c.symlinkOutputs {
symlinkOutputs[symlinkOutput.String()] = symlinkOutput
}
}
return symlinkOutputs
}
// SymlinkOutputs returns the list of paths that the executor (Ninja) would
// verify, after build edge completion, that:
//
// 1) Created output symlinks match the list of paths in this list exactly (no more, no fewer)
// 2) Created output files are *not* declared in this list.
//
// These symlink outputs are expected to be a subset of outputs or implicit
// outputs, or they would fail validation at build param construction time
// later, to support other non-rule-builder approaches for constructing
// statements.
func (r *RuleBuilder) SymlinkOutputs() WritablePaths {
symlinkOutputs := r.symlinkOutputSet()
var symlinkOutputList WritablePaths
for _, symlinkOutput := range symlinkOutputs {
symlinkOutputList = append(symlinkOutputList, symlinkOutput)
}
sort.Slice(symlinkOutputList, func(i, j int) bool {
return symlinkOutputList[i].String() < symlinkOutputList[j].String()
})
return symlinkOutputList
}
func (r *RuleBuilder) depFileSet() map[string]WritablePath {
depFiles := make(map[string]WritablePath)
for _, c := range r.commands {
for _, depFile := range c.depFiles {
depFiles[depFile.String()] = depFile
}
}
return depFiles
}
// DepFiles returns the list of paths that were passed to the RuleBuilderCommand methods that take depfile paths, such
// as RuleBuilderCommand.DepFile or RuleBuilderCommand.FlagWithDepFile.
func (r *RuleBuilder) DepFiles() WritablePaths {
var depFiles WritablePaths
for _, c := range r.commands {
for _, depFile := range c.depFiles {
depFiles = append(depFiles, depFile)
}
}
return depFiles
}
// Installs returns the list of tuples passed to Install.
func (r *RuleBuilder) Installs() RuleBuilderInstalls {
return append(RuleBuilderInstalls(nil), r.installs...)
}
func (r *RuleBuilder) toolsSet() map[string]Path {
tools := make(map[string]Path)
for _, c := range r.commands {
for _, tool := range c.tools {
tools[tool.String()] = tool
}
}
return tools
}
// Tools returns the list of paths that were passed to the RuleBuilderCommand.Tool method. The
// list is sorted and duplicates removed.
func (r *RuleBuilder) Tools() Paths {
toolsSet := r.toolsSet()
var toolsList Paths
for _, tool := range toolsSet {
toolsList = append(toolsList, tool)
}
sort.Slice(toolsList, func(i, j int) bool {
return toolsList[i].String() < toolsList[j].String()
})
return toolsList
}
// RspFileInputs returns the list of paths that were passed to the RuleBuilderCommand.FlagWithRspFileInputList method.
func (r *RuleBuilder) RspFileInputs() Paths {
var rspFileInputs Paths
for _, c := range r.commands {
if c.rspFileInputs != nil {
if rspFileInputs != nil {
panic("Multiple commands in a rule may not have rsp file inputs")
}
rspFileInputs = c.rspFileInputs
}
}
return rspFileInputs
}
// RspFile returns the path to the rspfile that was passed to the RuleBuilderCommand.FlagWithRspFileInputList method.
func (r *RuleBuilder) RspFile() WritablePath {
var rspFile WritablePath
for _, c := range r.commands {
if c.rspFile != nil {
if rspFile != nil {
panic("Multiple commands in a rule may not have rsp file inputs")
}
rspFile = c.rspFile
}
}
return rspFile
}
// Commands returns a slice containing the built command line for each call to RuleBuilder.Command.
func (r *RuleBuilder) Commands() []string {
var commands []string
for _, c := range r.commands {
commands = append(commands, c.String())
}
return commands
}
// BuilderContext is a subset of ModuleContext and SingletonContext.
type BuilderContext interface {
PathContext
Rule(PackageContext, string, blueprint.RuleParams, ...string) blueprint.Rule
Build(PackageContext, BuildParams)
}
var _ BuilderContext = ModuleContext(nil)
var _ BuilderContext = SingletonContext(nil)
func (r *RuleBuilder) depFileMergerCmd(depFiles WritablePaths) *RuleBuilderCommand {
return r.Command().
BuiltTool("dep_fixer").
Inputs(depFiles.Paths())
}
// composeRspFileContent returns a string that will serve as the contents of the rsp file to pass
// the listed input files to the command running in the sandbox.
func (r *RuleBuilder) composeRspFileContent(rspFileInputs Paths) string {
if r.sboxInputs {
if len(rspFileInputs) > 0 {
// When SandboxInputs is used the paths need to be rewritten to be relative to the sandbox
// directory so that they are valid after sbox chdirs into the sandbox directory.
return proptools.NinjaEscape(strings.Join(r.sboxPathsForInputsRel(rspFileInputs), " "))
} else {
// If the list of inputs is empty fall back to "$in" so that the rspfilecontent Ninja
// variable is set to something non-empty, otherwise ninja will complain. The inputs
// will be empty (all the non-rspfile inputs are implicits), so $in will evaluate to
// an empty string.
return "$in"
}
} else {
return "$in"
}
}
// Build adds the built command line to the build graph, with dependencies on Inputs and Tools, and output files for
// Outputs.
func (r *RuleBuilder) Build(name string, desc string) {
name = ninjaNameEscape(name)
if len(r.missingDeps) > 0 {
r.ctx.Build(pctx, BuildParams{
Rule: ErrorRule,
Outputs: r.Outputs(),
OrderOnly: r.OrderOnlys(),
Description: desc,
Args: map[string]string{
"error": "missing dependencies: " + strings.Join(r.missingDeps, ", "),
},
})
return
}
var depFile WritablePath
var depFormat blueprint.Deps
if depFiles := r.DepFiles(); len(depFiles) > 0 {
depFile = depFiles[0]
depFormat = blueprint.DepsGCC
if len(depFiles) > 1 {
// Add a command locally that merges all depfiles together into the first depfile.
r.depFileMergerCmd(depFiles)
if r.sbox {
// Check for Rel() errors, as all depfiles should be in the output dir. Errors
// will be reported to the ctx.
for _, path := range depFiles[1:] {
Rel(r.ctx, r.outDir.String(), path.String())
}
}
}
}
tools := r.Tools()
commands := r.Commands()
outputs := r.Outputs()
inputs := r.Inputs()
rspFileInputs := r.RspFileInputs()
rspFilePath := r.RspFile()
if len(commands) == 0 {
return
}
if len(outputs) == 0 {
panic("No outputs specified from any Commands")
}
commandString := strings.Join(commands, " && ")
if r.sbox {
// If running the command inside sbox, write the rule data out to an sbox
// manifest.textproto.
manifest := sbox_proto.Manifest{}
command := sbox_proto.Command{}
manifest.Commands = append(manifest.Commands, &command)
command.Command = proto.String(commandString)
if depFile != nil {
manifest.OutputDepfile = proto.String(depFile.String())
}
// If sandboxing tools is enabled, add copy rules to the manifest to copy each tool
// into the sbox directory.
if r.sboxTools {
for _, tool := range tools {
command.CopyBefore = append(command.CopyBefore, &sbox_proto.Copy{
From: proto.String(tool.String()),
To: proto.String(sboxPathForToolRel(r.ctx, tool)),
})
}
for _, c := range r.commands {
for _, tool := range c.packagedTools {
command.CopyBefore = append(command.CopyBefore, &sbox_proto.Copy{
From: proto.String(tool.srcPath.String()),
To: proto.String(sboxPathForPackagedToolRel(tool)),
Executable: proto.Bool(tool.executable),
})
tools = append(tools, tool.srcPath)
}
}
}
// If sandboxing inputs is enabled, add copy rules to the manifest to copy each input
// into the sbox directory.
if r.sboxInputs {
for _, input := range inputs {
command.CopyBefore = append(command.CopyBefore, &sbox_proto.Copy{
From: proto.String(input.String()),
To: proto.String(r.sboxPathForInputRel(input)),
})
}
// If using an rsp file copy it into the sbox directory.
if rspFilePath != nil {
command.CopyBefore = append(command.CopyBefore, &sbox_proto.Copy{
From: proto.String(rspFilePath.String()),
To: proto.String(r.sboxPathForInputRel(rspFilePath)),
})
}
command.Chdir = proto.Bool(true)
}
// Add copy rules to the manifest to copy each output file from the sbox directory.
// to the output directory after running the commands.
sboxOutputs := make([]string, len(outputs))
for i, output := range outputs {
rel := Rel(r.ctx, r.outDir.String(), output.String())
sboxOutputs[i] = filepath.Join(sboxOutDir, rel)
command.CopyAfter = append(command.CopyAfter, &sbox_proto.Copy{
From: proto.String(filepath.Join(sboxOutSubDir, rel)),
To: proto.String(output.String()),
})
}
// Outputs that were marked Temporary will not be checked that they are in the output
// directory by the loop above, check them here.
for path := range r.temporariesSet {
Rel(r.ctx, r.outDir.String(), path.String())
}
// Add a hash of the list of input files to the manifest so that the textproto file
// changes when the list of input files changes and causes the sbox rule that
// depends on it to rerun.
command.InputHash = proto.String(hashSrcFiles(inputs))
// Verify that the manifest textproto is not inside the sbox output directory, otherwise
// it will get deleted when the sbox rule clears its output directory.
_, manifestInOutDir := MaybeRel(r.ctx, r.outDir.String(), r.sboxManifestPath.String())
if manifestInOutDir {
ReportPathErrorf(r.ctx, "sbox rule %q manifestPath %q must not be in outputDir %q",
name, r.sboxManifestPath.String(), r.outDir.String())
}
// Create a rule to write the manifest as a the textproto.
WriteFileRule(r.ctx, r.sboxManifestPath, proto.MarshalTextString(&manifest))
// Generate a new string to use as the command line of the sbox rule. This uses
// a RuleBuilderCommand as a convenience method of building the command line, then
// converts it to a string to replace commandString.
sboxCmd := &RuleBuilderCommand{
rule: &RuleBuilder{
ctx: r.ctx,
},
}
sboxCmd.Text("rm -rf").Output(r.outDir)
sboxCmd.Text("&&")
sboxCmd.BuiltTool("sbox").
Flag("--sandbox-path").Text(shared.TempDirForOutDir(PathForOutput(r.ctx).String())).
Flag("--manifest").Input(r.sboxManifestPath)
// Replace the command string, and add the sbox tool and manifest textproto to the
// dependencies of the final sbox rule.
commandString = sboxCmd.buf.String()
tools = append(tools, sboxCmd.tools...)
inputs = append(inputs, sboxCmd.inputs...)
if r.rbeParams != nil {
var remoteInputs []string
remoteInputs = append(remoteInputs, inputs.Strings()...)
remoteInputs = append(remoteInputs, tools.Strings()...)
remoteInputs = append(remoteInputs, rspFileInputs.Strings()...)
if rspFilePath != nil {
remoteInputs = append(remoteInputs, rspFilePath.String())
}
inputsListFile := r.sboxManifestPath.ReplaceExtension(r.ctx, "rbe_inputs.list")
inputsListContents := rspFileForInputs(remoteInputs)
WriteFileRule(r.ctx, inputsListFile, inputsListContents)
inputs = append(inputs, inputsListFile)
r.rbeParams.OutputFiles = outputs.Strings()
r.rbeParams.RSPFile = inputsListFile.String()
rewrapperCommand := r.rbeParams.NoVarTemplate(r.ctx.Config().RBEWrapper())
commandString = rewrapperCommand + " bash -c '" + strings.ReplaceAll(commandString, `'`, `'\''`) + "'"
}
} else {
// If not using sbox the rule will run the command directly, put the hash of the
// list of input files in a comment at the end of the command line to ensure ninja
// reruns the rule when the list of input files changes.
commandString += " # hash of input list: " + hashSrcFiles(inputs)
}
// Ninja doesn't like multiple outputs when depfiles are enabled, move all but the first output to
// ImplicitOutputs. RuleBuilder doesn't use "$out", so the distinction between Outputs and
// ImplicitOutputs doesn't matter.
output := outputs[0]
implicitOutputs := outputs[1:]
var rspFile, rspFileContent string
if rspFilePath != nil {
rspFile = rspFilePath.String()
rspFileContent = r.composeRspFileContent(rspFileInputs)
}
var pool blueprint.Pool
if r.ctx.Config().UseGoma() && r.remoteable.Goma {
// When USE_GOMA=true is set and the rule is supported by goma, allow jobs to run outside the local pool.
} else if r.ctx.Config().UseRBE() && r.remoteable.RBE {
// When USE_RBE=true is set and the rule is supported by RBE, use the remotePool.
pool = remotePool
} else if r.highmem {
pool = highmemPool
} else if r.ctx.Config().UseRemoteBuild() {
pool = localPool
}
r.ctx.Build(r.pctx, BuildParams{
Rule: r.ctx.Rule(pctx, name, blueprint.RuleParams{
Command: proptools.NinjaEscape(commandString),
CommandDeps: proptools.NinjaEscapeList(tools.Strings()),
Restat: r.restat,
Rspfile: proptools.NinjaEscape(rspFile),
RspfileContent: rspFileContent,
Pool: pool,
}),
Inputs: rspFileInputs,
Implicits: inputs,
Output: output,
ImplicitOutputs: implicitOutputs,
SymlinkOutputs: r.SymlinkOutputs(),
Depfile: depFile,
Deps: depFormat,
Description: desc,
})
}
// RuleBuilderCommand is a builder for a command in a command line. It can be mutated by its methods to add to the
// command and track dependencies. The methods mutate the RuleBuilderCommand in place, as well as return the
// RuleBuilderCommand, so they can be used chained or unchained. All methods that add text implicitly add a single
// space as a separator from the previous method.
type RuleBuilderCommand struct {
rule *RuleBuilder
buf strings.Builder
inputs Paths
implicits Paths
orderOnlys Paths
outputs WritablePaths
symlinkOutputs WritablePaths
depFiles WritablePaths
tools Paths
packagedTools []PackagingSpec
rspFileInputs Paths
rspFile WritablePath
}
func (c *RuleBuilderCommand) addInput(path Path) string {
c.inputs = append(c.inputs, path)
return c.PathForInput(path)
}
func (c *RuleBuilderCommand) addImplicit(path Path) {
c.implicits = append(c.implicits, path)
}
func (c *RuleBuilderCommand) addOrderOnly(path Path) {
c.orderOnlys = append(c.orderOnlys, path)
}
// PathForInput takes an input path and returns the appropriate path to use on the command line. If
// sbox was enabled via a call to RuleBuilder.Sbox() and the path was an output path it returns a
// path with the placeholder prefix used for outputs in sbox. If sbox is not enabled it returns the
// original path.
func (c *RuleBuilderCommand) PathForInput(path Path) string {
if c.rule.sbox {
rel, inSandbox := c.rule._sboxPathForInputRel(path)
if inSandbox {
rel = filepath.Join(sboxSandboxBaseDir, rel)
}
return rel
}
return path.String()
}
// PathsForInputs takes a list of input paths and returns the appropriate paths to use on the
// command line. If sbox was enabled via a call to RuleBuilder.Sbox() a path was an output path, it
// returns the path with the placeholder prefix used for outputs in sbox. If sbox is not enabled it
// returns the original paths.
func (c *RuleBuilderCommand) PathsForInputs(paths Paths) []string {
ret := make([]string, len(paths))
for i, path := range paths {
ret[i] = c.PathForInput(path)
}
return ret
}
// PathForOutput takes an output path and returns the appropriate path to use on the command
// line. If sbox was enabled via a call to RuleBuilder.Sbox(), it returns a path with the
// placeholder prefix used for outputs in sbox. If sbox is not enabled it returns the
// original path.
func (c *RuleBuilderCommand) PathForOutput(path WritablePath) string {
if c.rule.sbox {
// Errors will be handled in RuleBuilder.Build where we have a context to report them
rel, _, _ := maybeRelErr(c.rule.outDir.String(), path.String())
return filepath.Join(sboxOutDir, rel)
}
return path.String()
}
// SboxPathForTool takes a path to a tool, which may be an output file or a source file, and returns
// the corresponding path for the tool in the sbox sandbox. It assumes that sandboxing and tool
// sandboxing are enabled.
func SboxPathForTool(ctx BuilderContext, path Path) string {
return filepath.Join(sboxSandboxBaseDir, sboxPathForToolRel(ctx, path))
}
func sboxPathForToolRel(ctx BuilderContext, path Path) string {
// Errors will be handled in RuleBuilder.Build where we have a context to report them
relOut, isRelOut, _ := maybeRelErr(PathForOutput(ctx, "host", ctx.Config().PrebuiltOS()).String(), path.String())
if isRelOut {
// The tool is in the output directory, it will be copied to __SBOX_OUT_DIR__/tools/out
return filepath.Join(sboxToolsSubDir, "out", relOut)
}
// The tool is in the source directory, it will be copied to __SBOX_OUT_DIR__/tools/src
return filepath.Join(sboxToolsSubDir, "src", path.String())
}
func (r *RuleBuilder) _sboxPathForInputRel(path Path) (rel string, inSandbox bool) {
// Errors will be handled in RuleBuilder.Build where we have a context to report them
rel, isRelSboxOut, _ := maybeRelErr(r.outDir.String(), path.String())
if isRelSboxOut {
return filepath.Join(sboxOutSubDir, rel), true
}
if r.sboxInputs {
// When sandboxing inputs all inputs have to be copied into the sandbox. Input files that
// are outputs of other rules could be an arbitrary absolute path if OUT_DIR is set, so they
// will be copied to relative paths under __SBOX_OUT_DIR__/out.
rel, isRelOut, _ := maybeRelErr(PathForOutput(r.ctx).String(), path.String())
if isRelOut {
return filepath.Join(sboxOutSubDir, rel), true
}
}
return path.String(), false
}
func (r *RuleBuilder) sboxPathForInputRel(path Path) string {
rel, _ := r._sboxPathForInputRel(path)
return rel
}
func (r *RuleBuilder) sboxPathsForInputsRel(paths Paths) []string {
ret := make([]string, len(paths))
for i, path := range paths {
ret[i] = r.sboxPathForInputRel(path)
}
return ret
}
// SboxPathForPackagedTool takes a PackageSpec for a tool and returns the corresponding path for the
// tool after copying it into the sandbox. This can be used on the RuleBuilder command line to
// reference the tool.
func SboxPathForPackagedTool(spec PackagingSpec) string {
return filepath.Join(sboxSandboxBaseDir, sboxPathForPackagedToolRel(spec))
}
func sboxPathForPackagedToolRel(spec PackagingSpec) string {
return filepath.Join(sboxToolsSubDir, "out", spec.relPathInPackage)
}
// PathForTool takes a path to a tool, which may be an output file or a source file, and returns
// the corresponding path for the tool in the sbox sandbox if sbox is enabled, or the original path
// if it is not. This can be used on the RuleBuilder command line to reference the tool.
func (c *RuleBuilderCommand) PathForTool(path Path) string {
if c.rule.sbox && c.rule.sboxTools {
return filepath.Join(sboxSandboxBaseDir, sboxPathForToolRel(c.rule.ctx, path))
}
return path.String()
}
// PackagedTool adds the specified tool path to the command line. It can only be used with tool
// sandboxing enabled by SandboxTools(), and will copy the tool into the sandbox.
func (c *RuleBuilderCommand) PackagedTool(spec PackagingSpec) *RuleBuilderCommand {
if !c.rule.sboxTools {
panic("PackagedTool() requires SandboxTools()")
}
c.packagedTools = append(c.packagedTools, spec)
c.Text(sboxPathForPackagedToolRel(spec))
return c
}
// ImplicitPackagedTool copies the specified tool into the sandbox without modifying the command
// line. It can only be used with tool sandboxing enabled by SandboxTools().
func (c *RuleBuilderCommand) ImplicitPackagedTool(spec PackagingSpec) *RuleBuilderCommand {
if !c.rule.sboxTools {
panic("ImplicitPackagedTool() requires SandboxTools()")
}
c.packagedTools = append(c.packagedTools, spec)
return c
}
// ImplicitPackagedTools copies the specified tools into the sandbox without modifying the command
// line. It can only be used with tool sandboxing enabled by SandboxTools().
func (c *RuleBuilderCommand) ImplicitPackagedTools(specs []PackagingSpec) *RuleBuilderCommand {
if !c.rule.sboxTools {
panic("ImplicitPackagedTools() requires SandboxTools()")
}
c.packagedTools = append(c.packagedTools, specs...)
return c
}
// Text adds the specified raw text to the command line. The text should not contain input or output paths or the
// rule will not have them listed in its dependencies or outputs.
func (c *RuleBuilderCommand) Text(text string) *RuleBuilderCommand {
if c.buf.Len() > 0 {
c.buf.WriteByte(' ')
}
c.buf.WriteString(text)
return c
}
// Textf adds the specified formatted text to the command line. The text should not contain input or output paths or
// the rule will not have them listed in its dependencies or outputs.
func (c *RuleBuilderCommand) Textf(format string, a ...interface{}) *RuleBuilderCommand {
return c.Text(fmt.Sprintf(format, a...))
}
// Flag adds the specified raw text to the command line. The text should not contain input or output paths or the
// rule will not have them listed in its dependencies or outputs.
func (c *RuleBuilderCommand) Flag(flag string) *RuleBuilderCommand {
return c.Text(flag)
}
// OptionalFlag adds the specified raw text to the command line if it is not nil. The text should not contain input or
// output paths or the rule will not have them listed in its dependencies or outputs.
func (c *RuleBuilderCommand) OptionalFlag(flag *string) *RuleBuilderCommand {
if flag != nil {
c.Text(*flag)
}
return c
}
// Flags adds the specified raw text to the command line. The text should not contain input or output paths or the
// rule will not have them listed in its dependencies or outputs.
func (c *RuleBuilderCommand) Flags(flags []string) *RuleBuilderCommand {
for _, flag := range flags {
c.Text(flag)
}
return c
}
// FlagWithArg adds the specified flag and argument text to the command line, with no separator between them. The flag
// and argument should not contain input or output paths or the rule will not have them listed in its dependencies or
// outputs.
func (c *RuleBuilderCommand) FlagWithArg(flag, arg string) *RuleBuilderCommand {
return c.Text(flag + arg)
}
// FlagForEachArg adds the specified flag joined with each argument to the command line. The result is identical to
// calling FlagWithArg for argument.
func (c *RuleBuilderCommand) FlagForEachArg(flag string, args []string) *RuleBuilderCommand {
for _, arg := range args {
c.FlagWithArg(flag, arg)
}
return c
}
// FlagWithList adds the specified flag and list of arguments to the command line, with the arguments joined by sep
// and no separator between the flag and arguments. The flag and arguments should not contain input or output paths or
// the rule will not have them listed in its dependencies or outputs.
func (c *RuleBuilderCommand) FlagWithList(flag string, list []string, sep string) *RuleBuilderCommand {
return c.Text(flag + strings.Join(list, sep))
}
// Tool adds the specified tool path to the command line. The path will be also added to the dependencies returned by
// RuleBuilder.Tools.
func (c *RuleBuilderCommand) Tool(path Path) *RuleBuilderCommand {
c.tools = append(c.tools, path)
return c.Text(c.PathForTool(path))
}
// Tool adds the specified tool path to the dependencies returned by RuleBuilder.Tools.
func (c *RuleBuilderCommand) ImplicitTool(path Path) *RuleBuilderCommand {
c.tools = append(c.tools, path)
return c
}
// Tool adds the specified tool path to the dependencies returned by RuleBuilder.Tools.
func (c *RuleBuilderCommand) ImplicitTools(paths Paths) *RuleBuilderCommand {
c.tools = append(c.tools, paths...)
return c
}
// BuiltTool adds the specified tool path that was built using a host Soong module to the command line. The path will
// be also added to the dependencies returned by RuleBuilder.Tools.
//
// It is equivalent to:
// cmd.Tool(ctx.Config().HostToolPath(ctx, tool))
func (c *RuleBuilderCommand) BuiltTool(tool string) *RuleBuilderCommand {
return c.Tool(c.rule.ctx.Config().HostToolPath(c.rule.ctx, tool))
}
// PrebuiltBuildTool adds the specified tool path from prebuils/build-tools. The path will be also added to the
// dependencies returned by RuleBuilder.Tools.
//
// It is equivalent to:
// cmd.Tool(ctx.Config().PrebuiltBuildTool(ctx, tool))
func (c *RuleBuilderCommand) PrebuiltBuildTool(ctx PathContext, tool string) *RuleBuilderCommand {
return c.Tool(ctx.Config().PrebuiltBuildTool(ctx, tool))
}
// Input adds the specified input path to the command line. The path will also be added to the dependencies returned by
// RuleBuilder.Inputs.
func (c *RuleBuilderCommand) Input(path Path) *RuleBuilderCommand {
return c.Text(c.addInput(path))
}
// Inputs adds the specified input paths to the command line, separated by spaces. The paths will also be added to the
// dependencies returned by RuleBuilder.Inputs.
func (c *RuleBuilderCommand) Inputs(paths Paths) *RuleBuilderCommand {
for _, path := range paths {
c.Input(path)
}
return c
}
// Implicit adds the specified input path to the dependencies returned by RuleBuilder.Inputs without modifying the
// command line.
func (c *RuleBuilderCommand) Implicit(path Path) *RuleBuilderCommand {
c.addImplicit(path)
return c
}
// Implicits adds the specified input paths to the dependencies returned by RuleBuilder.Inputs without modifying the
// command line.
func (c *RuleBuilderCommand) Implicits(paths Paths) *RuleBuilderCommand {
for _, path := range paths {
c.addImplicit(path)
}
return c
}
// GetImplicits returns the command's implicit inputs.
func (c *RuleBuilderCommand) GetImplicits() Paths {
return c.implicits
}
// OrderOnly adds the specified input path to the dependencies returned by RuleBuilder.OrderOnlys
// without modifying the command line.
func (c *RuleBuilderCommand) OrderOnly(path Path) *RuleBuilderCommand {
c.addOrderOnly(path)
return c
}
// OrderOnlys adds the specified input paths to the dependencies returned by RuleBuilder.OrderOnlys
// without modifying the command line.
func (c *RuleBuilderCommand) OrderOnlys(paths Paths) *RuleBuilderCommand {
for _, path := range paths {
c.addOrderOnly(path)
}
return c
}
// Output adds the specified output path to the command line. The path will also be added to the outputs returned by
// RuleBuilder.Outputs.
func (c *RuleBuilderCommand) Output(path WritablePath) *RuleBuilderCommand {
c.outputs = append(c.outputs, path)
return c.Text(c.PathForOutput(path))
}
// Outputs adds the specified output paths to the command line, separated by spaces. The paths will also be added to
// the outputs returned by RuleBuilder.Outputs.
func (c *RuleBuilderCommand) Outputs(paths WritablePaths) *RuleBuilderCommand {
for _, path := range paths {
c.Output(path)
}
return c
}
// OutputDir adds the output directory to the command line. This is only available when used with RuleBuilder.Sbox,
// and will be the temporary output directory managed by sbox, not the final one.
func (c *RuleBuilderCommand) OutputDir() *RuleBuilderCommand {
if !c.rule.sbox {
panic("OutputDir only valid with Sbox")
}
return c.Text(sboxOutDir)
}
// DepFile adds the specified depfile path to the paths returned by RuleBuilder.DepFiles and adds it to the command
// line, and causes RuleBuilder.Build file to set the depfile flag for ninja. If multiple depfiles are added to
// commands in a single RuleBuilder then RuleBuilder.Build will add an extra command to merge the depfiles together.
func (c *RuleBuilderCommand) DepFile(path WritablePath) *RuleBuilderCommand {
c.depFiles = append(c.depFiles, path)
return c.Text(c.PathForOutput(path))
}
// ImplicitOutput adds the specified output path to the dependencies returned by RuleBuilder.Outputs without modifying
// the command line.
func (c *RuleBuilderCommand) ImplicitOutput(path WritablePath) *RuleBuilderCommand {
c.outputs = append(c.outputs, path)
return c
}
// ImplicitOutputs adds the specified output paths to the dependencies returned by RuleBuilder.Outputs without modifying
// the command line.
func (c *RuleBuilderCommand) ImplicitOutputs(paths WritablePaths) *RuleBuilderCommand {
c.outputs = append(c.outputs, paths...)
return c
}
// ImplicitSymlinkOutput declares the specified path as an implicit output that
// will be a symlink instead of a regular file. Does not modify the command
// line.
func (c *RuleBuilderCommand) ImplicitSymlinkOutput(path WritablePath) *RuleBuilderCommand {
c.symlinkOutputs = append(c.symlinkOutputs, path)
return c.ImplicitOutput(path)
}
// ImplicitSymlinkOutputs declares the specified paths as implicit outputs that
// will be a symlinks instead of regular files. Does not modify the command
// line.
func (c *RuleBuilderCommand) ImplicitSymlinkOutputs(paths WritablePaths) *RuleBuilderCommand {
for _, path := range paths {
c.ImplicitSymlinkOutput(path)
}
return c
}
// SymlinkOutput declares the specified path as an output that will be a symlink
// instead of a regular file. Modifies the command line.
func (c *RuleBuilderCommand) SymlinkOutput(path WritablePath) *RuleBuilderCommand {
c.symlinkOutputs = append(c.symlinkOutputs, path)
return c.Output(path)
}
// SymlinkOutputsl declares the specified paths as outputs that will be symlinks
// instead of regular files. Modifies the command line.
func (c *RuleBuilderCommand) SymlinkOutputs(paths WritablePaths) *RuleBuilderCommand {
for _, path := range paths {
c.SymlinkOutput(path)
}
return c
}
// ImplicitDepFile adds the specified depfile path to the paths returned by RuleBuilder.DepFiles without modifying
// the command line, and causes RuleBuilder.Build file to set the depfile flag for ninja. If multiple depfiles
// are added to commands in a single RuleBuilder then RuleBuilder.Build will add an extra command to merge the
// depfiles together.
func (c *RuleBuilderCommand) ImplicitDepFile(path WritablePath) *RuleBuilderCommand {
c.depFiles = append(c.depFiles, path)
return c
}
// FlagWithInput adds the specified flag and input path to the command line, with no separator between them. The path
// will also be added to the dependencies returned by RuleBuilder.Inputs.
func (c *RuleBuilderCommand) FlagWithInput(flag string, path Path) *RuleBuilderCommand {
return c.Text(flag + c.addInput(path))
}
// FlagWithInputList adds the specified flag and input paths to the command line, with the inputs joined by sep
// and no separator between the flag and inputs. The input paths will also be added to the dependencies returned by
// RuleBuilder.Inputs.
func (c *RuleBuilderCommand) FlagWithInputList(flag string, paths Paths, sep string) *RuleBuilderCommand {
strs := make([]string, len(paths))
for i, path := range paths {
strs[i] = c.addInput(path)
}
return c.FlagWithList(flag, strs, sep)
}
// FlagForEachInput adds the specified flag joined with each input path to the command line. The input paths will also
// be added to the dependencies returned by RuleBuilder.Inputs. The result is identical to calling FlagWithInput for
// each input path.
func (c *RuleBuilderCommand) FlagForEachInput(flag string, paths Paths) *RuleBuilderCommand {
for _, path := range paths {
c.FlagWithInput(flag, path)
}
return c
}
// FlagWithOutput adds the specified flag and output path to the command line, with no separator between them. The path
// will also be added to the outputs returned by RuleBuilder.Outputs.
func (c *RuleBuilderCommand) FlagWithOutput(flag string, path WritablePath) *RuleBuilderCommand {
c.outputs = append(c.outputs, path)
return c.Text(flag + c.PathForOutput(path))
}
// FlagWithDepFile adds the specified flag and depfile path to the command line, with no separator between them. The path
// will also be added to the outputs returned by RuleBuilder.Outputs.
func (c *RuleBuilderCommand) FlagWithDepFile(flag string, path WritablePath) *RuleBuilderCommand {
c.depFiles = append(c.depFiles, path)
return c.Text(flag + c.PathForOutput(path))
}
// FlagWithRspFileInputList adds the specified flag and path to an rspfile to the command line, with no separator
// between them. The paths will be written to the rspfile. If sbox is enabled, the rspfile must
// be outside the sbox directory.
func (c *RuleBuilderCommand) FlagWithRspFileInputList(flag string, rspFile WritablePath, paths Paths) *RuleBuilderCommand {
if c.rspFileInputs != nil {
panic("FlagWithRspFileInputList cannot be called if rsp file inputs have already been provided")
}
// Use an empty slice if paths is nil, the non-nil slice is used as an indicator that the rsp file must be
// generated.
if paths == nil {
paths = Paths{}
}
c.rspFileInputs = paths
c.rspFile = rspFile
if c.rule.sbox {
if _, isRel, _ := maybeRelErr(c.rule.outDir.String(), rspFile.String()); isRel {
panic(fmt.Errorf("FlagWithRspFileInputList rspfile %q must not be inside out dir %q",
rspFile.String(), c.rule.outDir.String()))
}
}
c.FlagWithArg(flag, c.PathForInput(rspFile))
return c
}
// String returns the command line.
func (c *RuleBuilderCommand) String() string {
return c.buf.String()
}
// RuleBuilderSboxProtoForTests takes the BuildParams for the manifest passed to RuleBuilder.Sbox()
// and returns sbox testproto generated by the RuleBuilder.
func RuleBuilderSboxProtoForTests(t *testing.T, params TestingBuildParams) *sbox_proto.Manifest {
t.Helper()
content := ContentFromFileRuleForTests(t, params)
manifest := sbox_proto.Manifest{}
err := proto.UnmarshalText(content, &manifest)
if err != nil {
t.Fatalf("failed to unmarshal manifest: %s", err.Error())
}
return &manifest
}
func ninjaNameEscape(s string) string {
b := []byte(s)
escaped := false
for i, c := range b {
valid := (c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
(c == '_') ||
(c == '-') ||
(c == '.')
if !valid {
b[i] = '_'
escaped = true
}
}
if escaped {
s = string(b)
}
return s
}
// hashSrcFiles returns a hash of the list of source files. It is used to ensure the command line
// or the sbox textproto manifest change even if the input files are not listed on the command line.
func hashSrcFiles(srcFiles Paths) string {
h := sha256.New()
srcFileList := strings.Join(srcFiles.Strings(), "\n")
h.Write([]byte(srcFileList))
return fmt.Sprintf("%x", h.Sum(nil))
}
// BuilderContextForTesting returns a BuilderContext for the given config that can be used for tests
// that need to call methods that take a BuilderContext.
func BuilderContextForTesting(config Config) BuilderContext {
pathCtx := PathContextForTesting(config)
return builderContextForTests{
PathContext: pathCtx,
}
}
type builderContextForTests struct {
PathContext
}
func (builderContextForTests) Rule(PackageContext, string, blueprint.RuleParams, ...string) blueprint.Rule {
return nil
}
func (builderContextForTests) Build(PackageContext, BuildParams) {}
func rspFileForInputs(paths []string) string {
s := strings.Builder{}
for i, path := range paths {
if i != 0 {
s.WriteByte(' ')
}
s.WriteString(proptools.ShellEscape(path))
}
return s.String()
}