genrule: add $(location) for inputs and outputs

There was no way to select a single source file from a genrule that
has multiple source files.  Make Soong's genrule closer to Bazel
by supporting $(location) for input and outputs.  Change the
label used for tools referenced through filegroups to the name
of the module instead of the name of the file, which means it
matches the string used in the tools property.

Also support :module format in the tools property in preparation
for deprecating the tool_files property and putting both files
and modules in the tools property.

Bug: 117354232
Test: genrule_test.go
Change-Id: Id31a4ac4ff7064076a576c1d8ffeb7c19fc6b9a4
This commit is contained in:
Colin Cross
2018-10-04 23:29:14 -07:00
parent 651276f4f4
commit 098d22b5f6
2 changed files with 188 additions and 34 deletions

View File

@@ -52,10 +52,9 @@ type HostToolProvider interface {
type hostToolDependencyTag struct { type hostToolDependencyTag struct {
blueprint.BaseDependencyTag blueprint.BaseDependencyTag
label string
} }
var hostToolDepTag hostToolDependencyTag
type generatorProperties struct { type generatorProperties struct {
// The command to run on one or more input files. Cmd supports substitution of a few variables // The command to run on one or more input files. Cmd supports substitution of a few variables
// (the actual substitution is implemented in GenerateAndroidBuildActions below) // (the actual substitution is implemented in GenerateAndroidBuildActions below)
@@ -63,7 +62,7 @@ type generatorProperties struct {
// Available variables for substitution: // Available variables for substitution:
// //
// $(location): the path to the first entry in tools or tool_files // $(location): the path to the first entry in tools or tool_files
// $(location <label>): the path to the tool or tool_file with name <label> // $(location <label>): the path to the tool, tool_file, input or output with name <label>
// $(in): one or more input files // $(in): one or more input files
// $(out): a single output file // $(out): a single output file
// $(depfile): a file to which dependencies will be written, if the depfile property is set to true // $(depfile): a file to which dependencies will be written, if the depfile property is set to true
@@ -141,10 +140,14 @@ func (g *Module) DepsMutator(ctx android.BottomUpMutatorContext) {
android.ExtractSourcesDeps(ctx, g.properties.Srcs) android.ExtractSourcesDeps(ctx, g.properties.Srcs)
android.ExtractSourcesDeps(ctx, g.properties.Tool_files) android.ExtractSourcesDeps(ctx, g.properties.Tool_files)
if g, ok := ctx.Module().(*Module); ok { if g, ok := ctx.Module().(*Module); ok {
if len(g.properties.Tools) > 0 { for _, tool := range g.properties.Tools {
tag := hostToolDependencyTag{label: tool}
if m := android.SrcIsModule(tool); m != "" {
tool = m
}
ctx.AddFarVariationDependencies([]blueprint.Variation{ ctx.AddFarVariationDependencies([]blueprint.Variation{
{Mutator: "arch", Variation: ctx.Config().BuildOsVariant}, {Mutator: "arch", Variation: ctx.Config().BuildOsVariant},
}, hostToolDepTag, g.properties.Tools...) }, tag, tool)
} }
} }
} }
@@ -159,12 +162,25 @@ func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
g.exportedIncludeDirs = append(g.exportedIncludeDirs, android.PathForModuleGen(ctx, "")) g.exportedIncludeDirs = append(g.exportedIncludeDirs, android.PathForModuleGen(ctx, ""))
} }
tools := map[string]android.Path{} locationLabels := map[string][]string{}
firstLabel := ""
addLocationLabel := func(label string, paths []string) {
if firstLabel == "" {
firstLabel = label
}
if _, exists := locationLabels[label]; !exists {
locationLabels[label] = paths
} else {
ctx.ModuleErrorf("multiple labels for %q, %q and %q",
label, strings.Join(locationLabels[label], " "), strings.Join(paths, " "))
}
}
if len(g.properties.Tools) > 0 { if len(g.properties.Tools) > 0 {
ctx.VisitDirectDepsBlueprint(func(module blueprint.Module) { ctx.VisitDirectDepsBlueprint(func(module blueprint.Module) {
switch ctx.OtherModuleDependencyTag(module) { switch tag := ctx.OtherModuleDependencyTag(module).(type) {
case hostToolDepTag: case hostToolDependencyTag:
tool := ctx.OtherModuleName(module) tool := ctx.OtherModuleName(module)
var path android.OptionalPath var path android.OptionalPath
@@ -192,11 +208,7 @@ func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
if path.Valid() { if path.Valid() {
g.deps = append(g.deps, path.Path()) g.deps = append(g.deps, path.Path())
if _, exists := tools[tool]; !exists { addLocationLabel(tag.label, []string{path.Path().String()})
tools[tool] = path.Path()
} else {
ctx.ModuleErrorf("multiple tools for %q, %q and %q", tool, tools[tool], path.Path().String())
}
} else { } else {
ctx.ModuleErrorf("host tool %q missing output file", tool) ctx.ModuleErrorf("host tool %q missing output file", tool)
} }
@@ -208,21 +220,27 @@ func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
return return
} }
toolFiles := ctx.ExpandSources(g.properties.Tool_files, nil) for _, toolFile := range g.properties.Tool_files {
for _, tool := range toolFiles { paths := ctx.ExpandSources([]string{toolFile}, nil)
g.deps = append(g.deps, tool) g.deps = append(g.deps, paths...)
if _, exists := tools[tool.Rel()]; !exists { addLocationLabel(toolFile, paths.Strings())
tools[tool.Rel()] = tool
} else {
ctx.ModuleErrorf("multiple tools for %q, %q and %q", tool, tools[tool.Rel()], tool.Rel())
} }
var srcFiles android.Paths
for _, in := range g.properties.Srcs {
paths := ctx.ExpandSources([]string{in}, nil)
srcFiles = append(srcFiles, paths...)
addLocationLabel(in, paths.Strings())
}
task := g.taskGenerator(ctx, String(g.properties.Cmd), srcFiles)
for _, out := range task.out {
addLocationLabel(out.Rel(), []string{filepath.Join("__SBOX_OUT_DIR__", out.Rel())})
} }
referencedDepfile := false referencedDepfile := false
srcFiles := ctx.ExpandSources(g.properties.Srcs, nil)
task := g.taskGenerator(ctx, String(g.properties.Cmd), srcFiles)
rawCommand, err := android.Expand(task.cmd, func(name string) (string, 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 // report the error directly without returning an error to android.Expand to catch multiple errors in a
// single run // single run
@@ -233,13 +251,17 @@ func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
switch name { switch name {
case "location": case "location":
if len(g.properties.Tools) == 0 && len(toolFiles) == 0 { if len(g.properties.Tools) == 0 && len(g.properties.Tool_files) == 0 {
return reportError("at least one `tools` or `tool_files` is required if $(location) is used") return reportError("at least one `tools` or `tool_files` is required if $(location) is used")
} else if len(g.properties.Tools) > 0 {
return tools[g.properties.Tools[0]].String(), nil
} else {
return tools[toolFiles[0].Rel()].String(), nil
} }
paths := locationLabels[firstLabel]
if len(paths) == 0 {
return reportError("default label %q has no files", firstLabel)
} else if len(paths) > 1 {
return reportError("default label %q has multiple files, use $(locations %s) to reference it",
firstLabel, firstLabel)
}
return locationLabels[firstLabel][0], nil
case "in": case "in":
return "${in}", nil return "${in}", nil
case "out": case "out":
@@ -255,14 +277,31 @@ func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
default: default:
if strings.HasPrefix(name, "location ") { if strings.HasPrefix(name, "location ") {
label := strings.TrimSpace(strings.TrimPrefix(name, "location ")) label := strings.TrimSpace(strings.TrimPrefix(name, "location "))
if tool, ok := tools[label]; ok { if paths, ok := locationLabels[label]; ok {
return tool.String(), nil if len(paths) == 0 {
return reportError("label %q has no files", label)
} else if len(paths) > 1 {
return reportError("label %q has multiple files, use $(locations %s) to reference it",
label, label)
}
return paths[0], nil
} else { } else {
return reportError("unknown location label %q", label) return reportError("unknown location label %q", label)
} }
} else if strings.HasPrefix(name, "locations ") {
label := strings.TrimSpace(strings.TrimPrefix(name, "locations "))
if paths, ok := locationLabels[label]; ok {
if len(paths) == 0 {
return reportError("label %q has no files", label)
} }
return strings.Join(paths, " "), nil
} else {
return reportError("unknown locations label %q", label)
}
} else {
return reportError("unknown variable '$(%s)'", name) return reportError("unknown variable '$(%s)'", name)
} }
}
}) })
if err != nil { if err != nil {

View File

@@ -132,6 +132,15 @@ func TestGenruleCmd(t *testing.T) {
`, `,
expect: "out/tool > __SBOX_OUT_FILES__", expect: "out/tool > __SBOX_OUT_FILES__",
}, },
{
name: "empty location tool2",
prop: `
tools: [":tool"],
out: ["out"],
cmd: "$(location) > $(out)",
`,
expect: "out/tool > __SBOX_OUT_FILES__",
},
{ {
name: "empty location tool file", name: "empty location tool file",
prop: ` prop: `
@@ -169,6 +178,15 @@ func TestGenruleCmd(t *testing.T) {
`, `,
expect: "out/tool > __SBOX_OUT_FILES__", expect: "out/tool > __SBOX_OUT_FILES__",
}, },
{
name: "tool2",
prop: `
tools: [":tool"],
out: ["out"],
cmd: "$(location :tool) > $(out)",
`,
expect: "out/tool > __SBOX_OUT_FILES__",
},
{ {
name: "tool file", name: "tool file",
prop: ` prop: `
@@ -183,7 +201,7 @@ func TestGenruleCmd(t *testing.T) {
prop: ` prop: `
tool_files: [":1tool_file"], tool_files: [":1tool_file"],
out: ["out"], out: ["out"],
cmd: "$(location tool_file1) > $(out)", cmd: "$(location :1tool_file) > $(out)",
`, `,
expect: "tool_file1 > __SBOX_OUT_FILES__", expect: "tool_file1 > __SBOX_OUT_FILES__",
}, },
@@ -192,7 +210,7 @@ func TestGenruleCmd(t *testing.T) {
prop: ` prop: `
tool_files: [":tool_files"], tool_files: [":tool_files"],
out: ["out"], out: ["out"],
cmd: "$(location tool_file1) $(location tool_file2) > $(out)", cmd: "$(locations :tool_files) > $(out)",
`, `,
expect: "tool_file1 tool_file2 > __SBOX_OUT_FILES__", expect: "tool_file1 tool_file2 > __SBOX_OUT_FILES__",
}, },
@@ -232,6 +250,42 @@ func TestGenruleCmd(t *testing.T) {
`, `,
expect: "cat ${in} > __SBOX_OUT_FILES__", expect: "cat ${in} > __SBOX_OUT_FILES__",
}, },
{
name: "location in1",
prop: `
srcs: ["in1"],
out: ["out"],
cmd: "cat $(location in1) > $(out)",
`,
expect: "cat in1 > __SBOX_OUT_FILES__",
},
{
name: "location in1 fg",
prop: `
srcs: [":1in"],
out: ["out"],
cmd: "cat $(location :1in) > $(out)",
`,
expect: "cat in1 > __SBOX_OUT_FILES__",
},
{
name: "location ins",
prop: `
srcs: ["in1", "in2"],
out: ["out"],
cmd: "cat $(location in1) > $(out)",
`,
expect: "cat in1 > __SBOX_OUT_FILES__",
},
{
name: "location ins fg",
prop: `
srcs: [":ins"],
out: ["out"],
cmd: "cat $(locations :ins) > $(out)",
`,
expect: "cat in1 in2 > __SBOX_OUT_FILES__",
},
{ {
name: "outs", name: "outs",
prop: ` prop: `
@@ -240,6 +294,14 @@ func TestGenruleCmd(t *testing.T) {
`, `,
expect: "echo foo > __SBOX_OUT_FILES__", expect: "echo foo > __SBOX_OUT_FILES__",
}, },
{
name: "location out",
prop: `
out: ["out", "out2"],
cmd: "echo foo > $(location out2)",
`,
expect: "echo foo > __SBOX_OUT_DIR__/out2",
},
{ {
name: "depfile", name: "depfile",
prop: ` prop: `
@@ -266,6 +328,24 @@ func TestGenruleCmd(t *testing.T) {
`, `,
err: "at least one `tools` or `tool_files` is required if $(location) is used", err: "at least one `tools` or `tool_files` is required if $(location) is used",
}, },
{
name: "error empty location no files",
prop: `
tool_files: [":empty"],
out: ["out"],
cmd: "$(location) > $(out)",
`,
err: `default label ":empty" has no files`,
},
{
name: "error empty location multiple files",
prop: `
tool_files: [":tool_files"],
out: ["out"],
cmd: "$(location) > $(out)",
`,
err: `default label ":tool_files" has multiple files`,
},
{ {
name: "error location", name: "error location",
prop: ` prop: `
@@ -274,6 +354,41 @@ func TestGenruleCmd(t *testing.T) {
`, `,
err: `unknown location label "missing"`, err: `unknown location label "missing"`,
}, },
{
name: "error locations",
prop: `
out: ["out"],
cmd: "echo foo > $(locations missing)",
`,
err: `unknown locations label "missing"`,
},
{
name: "error location no files",
prop: `
out: ["out"],
srcs: [":empty"],
cmd: "echo $(location :empty) > $(out)",
`,
err: `label ":empty" has no files`,
},
{
name: "error locations no files",
prop: `
out: ["out"],
srcs: [":empty"],
cmd: "echo $(locations :empty) > $(out)",
`,
err: `label ":empty" has no files`,
},
{
name: "error location multiple files",
prop: `
out: ["out"],
srcs: [":ins"],
cmd: "echo $(location :ins) > $(out)",
`,
err: `label ":ins" has multiple files`,
},
{ {
name: "error variable", name: "error variable",
prop: ` prop: `