Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3d680512 authored by Colin Cross's avatar Colin Cross
Browse files

Move genrule on top of RuleBuilder

In preparation for more complicated sandboxing support in sbox, use
a single implementation of the sbox sandboxing by moving genrule to
use RuleBuilder's sbox support instead of creating an sbox rule
directly.

Also move genrule's input list hash support into RuleBuilder.

Test: genrule_test.go
Test: rule_builder_test.go
Change-Id: I292184d02743c7e6887ebbcd232ba565db2ab0cc
parent beab64ea
Loading
Loading
Loading
Loading
+49 −17
Original line number Diff line number Diff line
@@ -15,7 +15,9 @@
package android

import (
	"crypto/sha256"
	"fmt"
	"path/filepath"
	"sort"
	"strings"

@@ -25,6 +27,8 @@ import (
	"android/soong/shared"
)

const sboxOutDir = "__SBOX_OUT_DIR__"

// RuleBuilder provides an alternative to ModuleContext.Rule and ModuleContext.Build to add a command line to the build
// graph.
type RuleBuilder struct {
@@ -134,7 +138,7 @@ func (r *RuleBuilder) Install(from Path, to string) {
func (r *RuleBuilder) Command() *RuleBuilderCommand {
	command := &RuleBuilderCommand{
		sbox:   r.sbox,
		sboxOutDir: r.sboxOutDir,
		outDir: r.sboxOutDir,
	}
	r.commands = append(r.commands, command)
	return command
@@ -163,7 +167,7 @@ func (r *RuleBuilder) DeleteTemporaryFiles() {
}

// Inputs returns the list of paths that were passed to the RuleBuilderCommand methods that take
// input paths, such as RuleBuilderCommand.Input, RuleBuilderComand.Implicit, or
// 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 {
@@ -362,7 +366,7 @@ func (r *RuleBuilder) Commands() []string {
	return commands
}

// NinjaEscapedCommands returns a slice containin the built command line after ninja escaping for each call to
// NinjaEscapedCommands returns a slice containing the built command line after ninja escaping for each call to
// RuleBuilder.Command.
func (r *RuleBuilder) NinjaEscapedCommands() []string {
	var commands []string
@@ -427,6 +431,7 @@ func (r *RuleBuilder) Build(pctx PackageContext, ctx BuilderContext, name string
	tools := r.Tools()
	commands := r.NinjaEscapedCommands()
	outputs := r.Outputs()
	inputs := r.Inputs()

	if len(commands) == 0 {
		return
@@ -440,7 +445,7 @@ func (r *RuleBuilder) Build(pctx PackageContext, ctx BuilderContext, name string
	if r.sbox {
		sboxOutputs := make([]string, len(outputs))
		for i, output := range outputs {
			sboxOutputs[i] = "__SBOX_OUT_DIR__/" + Rel(ctx, r.sboxOutDir.String(), output.String())
			sboxOutputs[i] = filepath.Join(sboxOutDir, Rel(ctx, r.sboxOutDir.String(), output.String()))
		}

		commandString = proptools.ShellEscape(commandString)
@@ -458,10 +463,19 @@ func (r *RuleBuilder) Build(pctx PackageContext, ctx BuilderContext, name string
			sboxCmd.Flag("--depfile-out").Text(depFile.String())
		}

		// Add a hash of the list of input files to the xbox command line so that ninja reruns
		// it when the list of input files changes.
		sboxCmd.FlagWithArg("--input-hash ", hashSrcFiles(inputs))

		sboxCmd.Flags(sboxOutputs)

		commandString = sboxCmd.buf.String()
		tools = append(tools, sboxCmd.tools...)
	} 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
@@ -499,7 +513,7 @@ func (r *RuleBuilder) Build(pctx PackageContext, ctx BuilderContext, name string
			Pool:           pool,
		}),
		Inputs:          rspFileInputs,
		Implicits:       r.Inputs(),
		Implicits:       inputs,
		Output:          output,
		ImplicitOutputs: implicitOutputs,
		SymlinkOutputs:  r.SymlinkOutputs(),
@@ -528,13 +542,15 @@ type RuleBuilderCommand struct {
	unescapedSpans [][2]int

	sbox bool
	sboxOutDir WritablePath
	// outDir is the directory that will contain the output files of the rules.  sbox will copy
	// the output files from the sandbox directory to this directory when it finishes.
	outDir WritablePath
}

func (c *RuleBuilderCommand) addInput(path Path) string {
	if c.sbox {
		if rel, isRel, _ := maybeRelErr(c.sboxOutDir.String(), path.String()); isRel {
			return "__SBOX_OUT_DIR__/" + rel
		if rel, isRel, _ := maybeRelErr(c.outDir.String(), path.String()); isRel {
			return filepath.Join(sboxOutDir, rel)
		}
	}
	c.inputs = append(c.inputs, path)
@@ -543,8 +559,8 @@ func (c *RuleBuilderCommand) addInput(path Path) string {

func (c *RuleBuilderCommand) addImplicit(path Path) string {
	if c.sbox {
		if rel, isRel, _ := maybeRelErr(c.sboxOutDir.String(), path.String()); isRel {
			return "__SBOX_OUT_DIR__/" + rel
		if rel, isRel, _ := maybeRelErr(c.outDir.String(), path.String()); isRel {
			return filepath.Join(sboxOutDir, rel)
		}
	}
	c.implicits = append(c.implicits, path)
@@ -555,15 +571,22 @@ func (c *RuleBuilderCommand) addOrderOnly(path Path) {
	c.orderOnlys = append(c.orderOnlys, path)
}

func (c *RuleBuilderCommand) outputStr(path Path) string {
func (c *RuleBuilderCommand) outputStr(path WritablePath) string {
	if c.sbox {
		// Errors will be handled in RuleBuilder.Build where we have a context to report them
		rel, _, _ := maybeRelErr(c.sboxOutDir.String(), path.String())
		return "__SBOX_OUT_DIR__/" + rel
		return SboxPathForOutput(path, c.outDir)
	}
	return path.String()
}

// SboxPathForOutput takes an output path and the out directory passed to RuleBuilder.Sbox(),
// and returns the corresponding path for the output in the sbox sandbox.  This can be used
// on the RuleBuilder command line to reference the output.
func SboxPathForOutput(path WritablePath, outDir WritablePath) string {
	// Errors will be handled in RuleBuilder.Build where we have a context to report them
	rel, _, _ := maybeRelErr(outDir.String(), path.String())
	return filepath.Join(sboxOutDir, rel)
}

// 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 {
@@ -727,7 +750,7 @@ func (c *RuleBuilderCommand) OutputDir() *RuleBuilderCommand {
	if !c.sbox {
		panic("OutputDir only valid with Sbox")
	}
	return c.Text("__SBOX_OUT_DIR__")
	return c.Text(sboxOutDir)
}

// DepFile adds the specified depfile path to the paths returned by RuleBuilder.DepFiles and adds it to the command
@@ -906,3 +929,12 @@ func ninjaNameEscape(s string) string {
	}
	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))
}
+87 −8
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ import (
	"fmt"
	"path/filepath"
	"reflect"
	"regexp"
	"strings"
	"testing"

@@ -441,7 +442,7 @@ func testRuleBuilderFactory() Module {
type testRuleBuilderModule struct {
	ModuleBase
	properties struct {
		Src string
		Srcs []string

		Restat bool
		Sbox   bool
@@ -449,7 +450,7 @@ type testRuleBuilderModule struct {
}

func (t *testRuleBuilderModule) GenerateAndroidBuildActions(ctx ModuleContext) {
	in := PathForSource(ctx, t.properties.Src)
	in := PathsForSource(ctx, t.properties.Srcs)
	out := PathForModuleOut(ctx, ctx.ModuleName())
	outDep := PathForModuleOut(ctx, ctx.ModuleName()+".d")
	outDir := PathForModuleOut(ctx)
@@ -468,17 +469,17 @@ func (t *testRuleBuilderSingleton) GenerateBuildActions(ctx SingletonContext) {
	out := PathForOutput(ctx, "baz")
	outDep := PathForOutput(ctx, "baz.d")
	outDir := PathForOutput(ctx)
	testRuleBuilder_Build(ctx, in, out, outDep, outDir, true, false)
	testRuleBuilder_Build(ctx, Paths{in}, out, outDep, outDir, true, false)
}

func testRuleBuilder_Build(ctx BuilderContext, in Path, out, outDep, outDir WritablePath, restat, sbox bool) {
func testRuleBuilder_Build(ctx BuilderContext, in Paths, out, outDep, outDir WritablePath, restat, sbox bool) {
	rule := NewRuleBuilder()

	if sbox {
		rule.Sbox(outDir)
	}

	rule.Command().Tool(PathForSource(ctx, "cp")).Input(in).Output(out).ImplicitDepFile(outDep)
	rule.Command().Tool(PathForSource(ctx, "cp")).Inputs(in).Output(out).ImplicitDepFile(outDep)

	if restat {
		rule.Restat()
@@ -496,12 +497,12 @@ func TestRuleBuilder_Build(t *testing.T) {
	bp := `
		rule_builder_test {
			name: "foo",
			src: "bar",
			srcs: ["bar"],
			restat: true,
		}
		rule_builder_test {
			name: "foo_sbox",
			src: "bar",
			srcs: ["bar"],
			sbox: true,
		}
	`
@@ -519,7 +520,10 @@ func TestRuleBuilder_Build(t *testing.T) {

	check := func(t *testing.T, params TestingBuildParams, wantCommand, wantOutput, wantDepfile string, wantRestat bool, extraCmdDeps []string) {
		t.Helper()
		if params.RuleParams.Command != wantCommand {
		command := params.RuleParams.Command
		re := regexp.MustCompile(" (# hash of input list:|--input-hash) [a-z0-9]*")
		command = re.ReplaceAllLiteralString(command, "")
		if command != wantCommand {
			t.Errorf("\nwant RuleParams.Command = %q\n                      got %q", wantCommand, params.RuleParams.Command)
		}

@@ -651,3 +655,78 @@ func Test_ninjaEscapeExceptForSpans(t *testing.T) {
		})
	}
}

func TestRuleBuilderHashInputs(t *testing.T) {
	// The basic idea here is to verify that the command (in the case of a
	// non-sbox rule) or the sbox textproto manifest contain a hash of the
	// inputs.

	// By including a hash of the inputs, we cause the rule to re-run if
	// the list of inputs changes because the command line or a dependency
	// changes.

	bp := `
			rule_builder_test {
				name: "hash0",
				srcs: ["in1.txt", "in2.txt"],
			}
			rule_builder_test {
				name: "hash0_sbox",
				srcs: ["in1.txt", "in2.txt"],
				sbox: true,
			}
			rule_builder_test {
				name: "hash1",
				srcs: ["in1.txt", "in2.txt", "in3.txt"],
			}
			rule_builder_test {
				name: "hash1_sbox",
				srcs: ["in1.txt", "in2.txt", "in3.txt"],
				sbox: true,
			}
		`
	testcases := []struct {
		name         string
		expectedHash string
	}{
		{
			name: "hash0",
			// sha256 value obtained from: echo -en 'in1.txt\nin2.txt' | sha256sum
			expectedHash: "18da75b9b1cc74b09e365b4ca2e321b5d618f438cc632b387ad9dc2ab4b20e9d",
		},
		{
			name: "hash1",
			// sha256 value obtained from: echo -en 'in1.txt\nin2.txt\nin3.txt' | sha256sum
			expectedHash: "a38d432a4b19df93140e1f1fe26c97ff0387dae01fe506412b47208f0595fb45",
		},
	}

	config := TestConfig(buildDir, nil, bp, nil)
	ctx := NewTestContext(config)
	ctx.RegisterModuleType("rule_builder_test", testRuleBuilderFactory)
	ctx.Register()

	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
	FailIfErrored(t, errs)
	_, errs = ctx.PrepareBuildActions(config)
	FailIfErrored(t, errs)

	for _, test := range testcases {
		t.Run(test.name, func(t *testing.T) {
			t.Run("sbox", func(t *testing.T) {
				gen := ctx.ModuleForTests(test.name+"_sbox", "")
				command := gen.Output(test.name + "_sbox").RuleParams.Command
				if g, w := command, " --input-hash "+test.expectedHash; !strings.Contains(g, w) {
					t.Errorf("Expected command line to end with %q, got %q", w, g)
				}
			})
			t.Run("", func(t *testing.T) {
				gen := ctx.ModuleForTests(test.name+"", "")
				command := gen.Output(test.name).RuleParams.Command
				if g, w := command, " # hash of input list: "+test.expectedHash; !strings.HasSuffix(g, w) {
					t.Errorf("Expected command line to end with %q, got %q", w, g)
				}
			})
		})
	}
}
+5 −1
Original line number Diff line number Diff line
@@ -75,7 +75,11 @@ func genYacc(ctx android.ModuleContext, rule *android.RuleBuilder, yaccFile andr
	cmd := rule.Command()

	// Fix up #line markers to not use the sbox temporary directory
	sedCmd := "sed -i.bak 's#__SBOX_OUT_DIR__#" + outDir.String() + "#'"
	// android.SboxPathForOutput(outDir, outDir) returns the sbox placeholder for the out
	// directory itself, without any filename appended.
	// TODO(ccross): make this cmd.PathForOutput(outDir) instead.
	sboxOutDir := android.SboxPathForOutput(outDir, outDir)
	sedCmd := "sed -i.bak 's#" + sboxOutDir + "#" + outDir.String() + "#'"
	rule.Command().Text(sedCmd).Input(outFile)
	rule.Command().Text(sedCmd).Input(headerFile)

+6 −6
Original line number Diff line number Diff line
@@ -66,14 +66,14 @@ func TestArchGenruleCmd(t *testing.T) {

	gen := ctx.ModuleForTests("gen", "android_arm_armv7-a-neon").Output("out_arm")
	expected := []string{"foo"}
	if !reflect.DeepEqual(expected, gen.Inputs.Strings()) {
		t.Errorf(`want arm inputs %v, got %v`, expected, gen.Inputs.Strings())
	if !reflect.DeepEqual(expected, gen.Implicits.Strings()[:len(expected)]) {
		t.Errorf(`want arm inputs %v, got %v`, expected, gen.Implicits.Strings())
	}

	gen = ctx.ModuleForTests("gen", "android_arm64_armv8-a").Output("out_arm64")
	expected = []string{"bar"}
	if !reflect.DeepEqual(expected, gen.Inputs.Strings()) {
		t.Errorf(`want arm64 inputs %v, got %v`, expected, gen.Inputs.Strings())
	if !reflect.DeepEqual(expected, gen.Implicits.Strings()[:len(expected)]) {
		t.Errorf(`want arm64 inputs %v, got %v`, expected, gen.Implicits.Strings())
	}
}

@@ -108,10 +108,10 @@ func TestLibraryGenruleCmd(t *testing.T) {
	gen := ctx.ModuleForTests("gen", "android_arm_armv7-a-neon").Output("out")
	expected := []string{"libboth.so", "libshared.so", "libstatic.a"}
	var got []string
	for _, input := range gen.Inputs {
	for _, input := range gen.Implicits {
		got = append(got, input.Base())
	}
	if !reflect.DeepEqual(expected, got) {
	if !reflect.DeepEqual(expected, got[:len(expected)]) {
		t.Errorf(`want inputs %v, got %v`, expected, got)
	}
}
+80 −136
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ package genrule
import (
	"fmt"
	"io"
	"path/filepath"
	"strconv"
	"strings"

@@ -25,9 +26,6 @@ import (
	"github.com/google/blueprint/proptools"

	"android/soong/android"
	"android/soong/shared"
	"crypto/sha256"
	"path/filepath"
)

func init() {
@@ -158,9 +156,9 @@ type taskFunc func(ctx android.ModuleContext, rawCommand string, srcFiles androi
type generateTask struct {
	in      android.Paths
	out     android.WritablePaths
	depFile android.WritablePath
	copyTo  android.WritablePaths
	genDir  android.WritablePath
	sandboxOuts []string
	cmd     string
	shard   int
	shards  int
@@ -330,19 +328,23 @@ func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	var zipArgs strings.Builder

	for _, task := range g.taskGenerator(ctx, String(g.properties.Cmd), srcFiles) {
		if len(task.out) == 0 {
			ctx.ModuleErrorf("must have at least one output file")
			return
		}

		for _, out := range task.out {
			addLocationLabel(out.Rel(), []string{filepath.Join("__SBOX_OUT_DIR__", out.Rel())})
			addLocationLabel(out.Rel(), []string{android.SboxPathForOutput(out, task.genDir)})
		}

		referencedIn := false
		referencedDepfile := false

		rawCommand, err := android.ExpandNinjaEscaped(task.cmd, func(name string) (string, bool, error) {
		rawCommand, err := android.Expand(task.cmd, func(name string) (string, error) {
			// report the error directly without returning an error to android.Expand to catch multiple errors in a
			// single run
			reportError := func(fmt string, args ...interface{}) (string, bool, error) {
			reportError := func(fmt string, args ...interface{}) (string, error) {
				ctx.PropertyErrorf("cmd", fmt, args...)
				return "SOONG_ERROR", false, nil
				return "SOONG_ERROR", nil
			}

			switch name {
@@ -357,20 +359,23 @@ func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
					return reportError("default label %q has multiple files, use $(locations %s) to reference it",
						firstLabel, firstLabel)
				}
				return locationLabels[firstLabel][0], false, nil
				return locationLabels[firstLabel][0], nil
			case "in":
				referencedIn = true
				return "${in}", true, nil
				return strings.Join(srcFiles.Strings(), " "), nil
			case "out":
				return "__SBOX_OUT_FILES__", false, nil
				var sandboxOuts []string
				for _, out := range task.out {
					sandboxOuts = append(sandboxOuts, android.SboxPathForOutput(out, task.genDir))
				}
				return strings.Join(sandboxOuts, " "), nil
			case "depfile":
				referencedDepfile = true
				if !Bool(g.properties.Depfile) {
					return reportError("$(depfile) used without depfile property")
				}
				return "__SBOX_DEPFILE__", false, nil
				return "__SBOX_DEPFILE__", nil
			case "genDir":
				return "__SBOX_OUT_DIR__", false, nil
				return android.SboxPathForOutput(task.genDir, task.genDir), nil
			default:
				if strings.HasPrefix(name, "location ") {
					label := strings.TrimSpace(strings.TrimPrefix(name, "location "))
@@ -381,7 +386,7 @@ func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
							return reportError("label %q has multiple files, use $(locations %s) to reference it",
								label, label)
						}
						return paths[0], false, nil
						return paths[0], nil
					} else {
						return reportError("unknown location label %q", label)
					}
@@ -391,7 +396,7 @@ func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
						if len(paths) == 0 {
							return reportError("label %q has no files", label)
						}
						return strings.Join(paths, " "), false, nil
						return strings.Join(paths, " "), nil
					} else {
						return reportError("unknown locations label %q", label)
					}
@@ -410,50 +415,39 @@ func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
			ctx.PropertyErrorf("cmd", "specified depfile=true but did not include a reference to '${depfile}' in cmd")
			return
		}

		// tell the sbox command which directory to use as its sandbox root
		buildDir := android.PathForOutput(ctx).String()
		sandboxPath := shared.TempDirForOutDir(buildDir)

		// recall that Sprintf replaces percent sign expressions, whereas dollar signs expressions remain as written,
		// to be replaced later by ninja_strings.go
		depfilePlaceholder := ""
		if Bool(g.properties.Depfile) {
			depfilePlaceholder = "$depfileArgs"
		}

		// Escape the command for the shell
		rawCommand = "'" + strings.Replace(rawCommand, "'", `'\''`, -1) + "'"
		g.rawCommands = append(g.rawCommands, rawCommand)

		sandboxCommand := fmt.Sprintf("rm -rf %s && $sboxCmd --sandbox-path %s --output-root %s",
			task.genDir, sandboxPath, task.genDir)

		if !referencedIn {
			sandboxCommand = sandboxCommand + hashSrcFiles(srcFiles)
		// Pick a unique rule name and the user-visible description.
		desc := "generate"
		name := "generator"
		if task.shards > 0 {
			desc += " " + strconv.Itoa(task.shard)
			name += strconv.Itoa(task.shard)
		} else if len(task.out) == 1 {
			desc += " " + task.out[0].Base()
		}

		sandboxCommand = sandboxCommand + fmt.Sprintf(" -c %s %s $allouts",
			rawCommand, depfilePlaceholder)

		ruleParams := blueprint.RuleParams{
			Command:     sandboxCommand,
			CommandDeps: []string{"$sboxCmd"},
		}
		args := []string{"allouts"}
		// Use a RuleBuilder to create a rule that runs the command inside an sbox sandbox.
		rule := android.NewRuleBuilder().Sbox(task.genDir)
		cmd := rule.Command()
		cmd.Text(rawCommand)
		cmd.ImplicitOutputs(task.out)
		cmd.Implicits(task.in)
		cmd.Implicits(g.deps)
		if Bool(g.properties.Depfile) {
			ruleParams.Deps = blueprint.DepsGCC
			args = append(args, "depfileArgs")
		}
		name := "generator"
		if task.shards > 1 {
			name += strconv.Itoa(task.shard)
			cmd.ImplicitDepFile(task.depFile)
		}
		rule := ctx.Rule(pctx, name, ruleParams, args...)

		g.generateSourceFile(ctx, task, rule)
		// Create the rule to run the genrule command inside sbox.
		rule.Build(pctx, ctx, name, desc)

		if len(task.copyTo) > 0 {
			// If copyTo is set, multiple shards need to be copied into a single directory.
			// task.out contains the per-shard paths, and copyTo contains the corresponding
			// final path.  The files need to be copied into the final directory by a
			// single rule so it can remove the directory before it starts to ensure no
			// old files remain.  zipsync already does this, so build up zipArgs that
			// zip all the per-shard directories into a single zip.
			outputFiles = append(outputFiles, task.copyTo...)
			copyFrom = append(copyFrom, task.out.Paths()...)
			zipArgs.WriteString(" -C " + task.genDir.String())
@@ -464,6 +458,8 @@ func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	}

	if len(copyFrom) > 0 {
		// Create a rule that zips all the per-shard directories into a single zip and then
		// uses zipsync to unzip it into the final directory.
		ctx.Build(pctx, android.BuildParams{
			Rule:      gensrcsMerge,
			Implicits: copyFrom,
@@ -501,51 +497,6 @@ func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
		}
	}
}
func hashSrcFiles(srcFiles android.Paths) string {
	h := sha256.New()
	for _, src := range srcFiles {
		h.Write([]byte(src.String()))
	}
	return fmt.Sprintf(" --input-hash %x", h.Sum(nil))
}

func (g *Module) generateSourceFile(ctx android.ModuleContext, task generateTask, rule blueprint.Rule) {
	desc := "generate"
	if len(task.out) == 0 {
		ctx.ModuleErrorf("must have at least one output file")
		return
	}
	if len(task.out) == 1 {
		desc += " " + task.out[0].Base()
	}

	var depFile android.ModuleGenPath
	if Bool(g.properties.Depfile) {
		depFile = android.PathForModuleGen(ctx, task.out[0].Rel()+".d")
	}

	if task.shards > 1 {
		desc += " " + strconv.Itoa(task.shard)
	}

	params := android.BuildParams{
		Rule:            rule,
		Description:     desc,
		Output:          task.out[0],
		ImplicitOutputs: task.out[1:],
		Inputs:          task.in,
		Implicits:       g.deps,
		Args: map[string]string{
			"allouts": strings.Join(task.sandboxOuts, " "),
		},
	}
	if Bool(g.properties.Depfile) {
		params.Depfile = android.PathForModuleGen(ctx, task.out[0].Rel()+".d")
		params.Args["depfileArgs"] = "--depfile-out " + depFile.String()
	}

	ctx.Build(pctx, params)
}

// Collect information for opening IDE project files in java/jdeps.go.
func (g *Module) IDEInfo(dpInfo *android.IdeInfo) {
@@ -610,16 +561,6 @@ func (x noopImageInterface) ExtraImageVariations(ctx android.BaseModuleContext)
func (x noopImageInterface) SetImageVariation(ctx android.BaseModuleContext, variation string, module android.Module) {
}

// replace "out" with "__SBOX_OUT_DIR__/<the value of ${out}>"
func pathToSandboxOut(path android.Path, genDir android.Path) string {
	relOut, err := filepath.Rel(genDir.String(), path.String())
	if err != nil {
		panic(fmt.Sprintf("Could not make ${out} relative: %v", err))
	}
	return filepath.Join("__SBOX_OUT_DIR__", relOut)

}

func NewGenSrcs() *Module {
	properties := &genSrcsProperties{}

@@ -638,7 +579,7 @@ func NewGenSrcs() *Module {
			var outFiles android.WritablePaths
			var copyTo android.WritablePaths
			var shardDir android.WritablePath
			var sandboxOuts []string
			var depFile android.WritablePath

			if len(shards) > 1 {
				shardDir = android.PathForModuleGen(ctx, strconv.Itoa(i))
@@ -646,9 +587,11 @@ func NewGenSrcs() *Module {
				shardDir = genDir
			}

			for _, in := range shard {
			for j, in := range shard {
				outFile := android.GenPathWithExt(ctx, "gensrcs", in, String(properties.Output_extension))
				sandboxOutfile := pathToSandboxOut(outFile, genDir)
				if j == 0 {
					depFile = outFile.ReplaceExtension(ctx, "d")
				}

				if len(shards) > 1 {
					shardFile := android.GenPathWithExt(ctx, strconv.Itoa(i), in, String(properties.Output_extension))
@@ -657,14 +600,13 @@ func NewGenSrcs() *Module {
				}

				outFiles = append(outFiles, outFile)
				sandboxOuts = append(sandboxOuts, sandboxOutfile)

				command, err := android.Expand(rawCommand, func(name string) (string, error) {
					switch name {
					case "in":
						return in.String(), nil
					case "out":
						return sandboxOutfile, nil
						return android.SboxPathForOutput(outFile, shardDir), nil
					default:
						return "$(" + name + ")", nil
					}
@@ -682,9 +624,9 @@ func NewGenSrcs() *Module {
			generateTasks = append(generateTasks, generateTask{
				in:      shard,
				out:     outFiles,
				depFile: depFile,
				copyTo:  copyTo,
				genDir:  shardDir,
				sandboxOuts: sandboxOuts,
				cmd:     fullCommand,
				shard:   i,
				shards:  len(shards),
@@ -720,17 +662,19 @@ func NewGenRule() *Module {

	taskGenerator := func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask {
		outs := make(android.WritablePaths, len(properties.Out))
		sandboxOuts := make([]string, len(properties.Out))
		genDir := android.PathForModuleGen(ctx)
		var depFile android.WritablePath
		for i, out := range properties.Out {
			outs[i] = android.PathForModuleGen(ctx, out)
			sandboxOuts[i] = pathToSandboxOut(outs[i], genDir)
			outPath := android.PathForModuleGen(ctx, out)
			if i == 0 {
				depFile = outPath.ReplaceExtension(ctx, "d")
			}
			outs[i] = outPath
		}
		return []generateTask{{
			in:      srcFiles,
			out:     outs,
			depFile: depFile,
			genDir:  android.PathForModuleGen(ctx),
			sandboxOuts: sandboxOuts,
			cmd:     rawCommand,
		}}
	}
Loading