diff --git a/Android.bp b/Android.bp index a70f73c92..591e5c85f 100644 --- a/Android.bp +++ b/Android.bp @@ -61,6 +61,7 @@ bootstrap_go_package { "android/prebuilt_etc.go", "android/proto.go", "android/register.go", + "android/rule_builder.go", "android/sh_binary.go", "android/singleton.go", "android/testing.go", @@ -77,9 +78,11 @@ bootstrap_go_package { "android/expand_test.go", "android/namespace_test.go", "android/neverallow_test.go", + "android/onceper_test.go", "android/paths_test.go", "android/prebuilt_test.go", "android/prebuilt_etc_test.go", + "android/rule_builder_test.go", "android/util_test.go", "android/variable_test.go", ], diff --git a/android/apex.go b/android/apex.go index a93baf6d4..bf11ba25f 100644 --- a/android/apex.go +++ b/android/apex.go @@ -139,6 +139,7 @@ func (m *ApexModuleBase) CreateApexVariations(mctx BottomUpMutatorContext) []blu var apexData OncePer var apexNamesMapMutex sync.Mutex +var apexNamesKey = NewOnceKey("apexNames") // This structure maintains the global mapping in between modules and APEXes. // Examples: @@ -147,7 +148,7 @@ var apexNamesMapMutex sync.Mutex // apexNamesMap()["foo"]["bar"] == false: module foo is indirectly depended on by APEX bar // apexNamesMap()["foo"]["bar"] doesn't exist: foo is not built for APEX bar func apexNamesMap() map[string]map[string]bool { - return apexData.Once("apexNames", func() interface{} { + return apexData.Once(apexNamesKey, func() interface{} { return make(map[string]map[string]bool) }).(map[string]map[string]bool) } diff --git a/android/api_levels.go b/android/api_levels.go index 1b56625b2..51d470381 100644 --- a/android/api_levels.go +++ b/android/api_levels.go @@ -51,8 +51,10 @@ func GetApiLevelsJson(ctx PathContext) WritablePath { return PathForOutput(ctx, "api_levels.json") } +var apiLevelsMapKey = NewOnceKey("ApiLevelsMap") + func getApiLevelsMap(config Config) map[string]int { - return config.Once("ApiLevelsMap", func() interface{} { + return config.Once(apiLevelsMapKey, func() interface{} { baseApiLevel := 9000 apiLevelsMap := map[string]int{ "G": 9, diff --git a/android/arch.go b/android/arch.go index 953e6cfc7..ad812a4a0 100644 --- a/android/arch.go +++ b/android/arch.go @@ -879,7 +879,7 @@ func InitArchModule(m Module) { propertiesValue.Interface())) } - archPropTypes := archPropTypeMap.Once(t, func() interface{} { + archPropTypes := archPropTypeMap.Once(NewCustomOnceKey(t), func() interface{} { return createArchType(t) }).([]reflect.Type) diff --git a/android/onceper.go b/android/onceper.go index f19f75c08..6160506b0 100644 --- a/android/onceper.go +++ b/android/onceper.go @@ -24,8 +24,6 @@ type OncePer struct { valuesLock sync.Mutex } -type valueMap map[interface{}]interface{} - // Once computes a value the first time it is called with a given key per OncePer, and returns the // value without recomputing when called with the same key. key must be hashable. func (once *OncePer) Once(key interface{}, value func() interface{}) interface{} { @@ -50,6 +48,8 @@ func (once *OncePer) Once(key interface{}, value func() interface{}) interface{} return v } +// Get returns the value previously computed with Once for a given key. If Once has not been called for the given +// key Get will panic. func (once *OncePer) Get(key interface{}) interface{} { v, ok := once.values.Load(key) if !ok { @@ -59,10 +59,12 @@ func (once *OncePer) Get(key interface{}) interface{} { return v } +// OnceStringSlice is the same as Once, but returns the value cast to a []string func (once *OncePer) OnceStringSlice(key interface{}, value func() []string) []string { return once.Once(key, func() interface{} { return value() }).([]string) } +// OnceStringSlice is the same as Once, but returns two values cast to []string func (once *OncePer) Once2StringSlice(key interface{}, value func() ([]string, []string)) ([]string, []string) { type twoStringSlice [2][]string s := once.Once(key, func() interface{} { @@ -72,3 +74,21 @@ func (once *OncePer) Once2StringSlice(key interface{}, value func() ([]string, [ }).(twoStringSlice) return s[0], s[1] } + +// OnceKey is an opaque type to be used as the key in calls to Once. +type OnceKey struct { + key interface{} +} + +// NewOnceKey returns an opaque OnceKey object for the provided key. Two calls to NewOnceKey with the same key string +// DO NOT produce the same OnceKey object. +func NewOnceKey(key string) OnceKey { + return OnceKey{&key} +} + +// NewCustomOnceKey returns an opaque OnceKey object for the provided key. The key can be any type that is valid as the +// key in a map, i.e. comparable. Two calls to NewCustomOnceKey with key values that compare equal will return OnceKey +// objects that access the same value stored with Once. +func NewCustomOnceKey(key interface{}) OnceKey { + return OnceKey{key} +} diff --git a/android/onceper_test.go b/android/onceper_test.go new file mode 100644 index 000000000..d2ca9ad70 --- /dev/null +++ b/android/onceper_test.go @@ -0,0 +1,135 @@ +// Copyright 2019 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 ( + "testing" +) + +func TestOncePer_Once(t *testing.T) { + once := OncePer{} + key := NewOnceKey("key") + + a := once.Once(key, func() interface{} { return "a" }).(string) + b := once.Once(key, func() interface{} { return "b" }).(string) + + if a != "a" { + t.Errorf(`first call to Once should return "a": %q`, a) + } + + if b != "a" { + t.Errorf(`second call to Once with the same key should return "a": %q`, b) + } +} + +func TestOncePer_Get(t *testing.T) { + once := OncePer{} + key := NewOnceKey("key") + + a := once.Once(key, func() interface{} { return "a" }).(string) + b := once.Get(key).(string) + + if a != "a" { + t.Errorf(`first call to Once should return "a": %q`, a) + } + + if b != "a" { + t.Errorf(`Get with the same key should return "a": %q`, b) + } +} + +func TestOncePer_Get_panic(t *testing.T) { + once := OncePer{} + key := NewOnceKey("key") + + defer func() { + p := recover() + + if p == nil { + t.Error("call to Get for unused key should panic") + } + }() + + once.Get(key) +} + +func TestOncePer_OnceStringSlice(t *testing.T) { + once := OncePer{} + key := NewOnceKey("key") + + a := once.OnceStringSlice(key, func() []string { return []string{"a"} }) + b := once.OnceStringSlice(key, func() []string { return []string{"a"} }) + + if a[0] != "a" { + t.Errorf(`first call to OnceStringSlice should return ["a"]: %q`, a) + } + + if b[0] != "a" { + t.Errorf(`second call to OnceStringSlice with the same key should return ["a"]: %q`, b) + } +} + +func TestOncePer_Once2StringSlice(t *testing.T) { + once := OncePer{} + key := NewOnceKey("key") + + a, b := once.Once2StringSlice(key, func() ([]string, []string) { return []string{"a"}, []string{"b"} }) + c, d := once.Once2StringSlice(key, func() ([]string, []string) { return []string{"c"}, []string{"d"} }) + + if a[0] != "a" || b[0] != "b" { + t.Errorf(`first call to Once2StringSlice should return ["a"], ["b"]: %q, %q`, a, b) + } + + if c[0] != "a" || d[0] != "b" { + t.Errorf(`second call to Once2StringSlice with the same key should return ["a"], ["b"]: %q, %q`, c, d) + } +} + +func TestNewOnceKey(t *testing.T) { + once := OncePer{} + key1 := NewOnceKey("key") + key2 := NewOnceKey("key") + + a := once.Once(key1, func() interface{} { return "a" }).(string) + b := once.Once(key2, func() interface{} { return "b" }).(string) + + if a != "a" { + t.Errorf(`first call to Once should return "a": %q`, a) + } + + if b != "b" { + t.Errorf(`second call to Once with the NewOnceKey from same string should return "b": %q`, b) + } +} + +func TestNewCustomOnceKey(t *testing.T) { + type key struct { + key string + } + once := OncePer{} + key1 := NewCustomOnceKey(key{"key"}) + key2 := NewCustomOnceKey(key{"key"}) + + a := once.Once(key1, func() interface{} { return "a" }).(string) + b := once.Once(key2, func() interface{} { return "b" }).(string) + + if a != "a" { + t.Errorf(`first call to Once should return "a": %q`, a) + } + + if b != "a" { + t.Errorf(`second call to Once with the NewCustomOnceKey from equal key should return "a": %q`, b) + } +} diff --git a/android/rule_builder.go b/android/rule_builder.go new file mode 100644 index 000000000..d005839bd --- /dev/null +++ b/android/rule_builder.go @@ -0,0 +1,395 @@ +// 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 ( + "fmt" + "path/filepath" + "sort" + "strings" + + "github.com/google/blueprint" + "github.com/google/blueprint/proptools" +) + +// RuleBuilder provides an alternative to ModuleContext.Rule and ModuleContext.Build to add a command line to the build +// graph. +type RuleBuilder struct { + commands []*RuleBuilderCommand + installs []RuleBuilderInstall + temporariesSet map[string]bool + restat bool +} + +// NewRuleBuilder returns a newly created RuleBuilder. +func NewRuleBuilder() *RuleBuilder { + return &RuleBuilder{ + temporariesSet: make(map[string]bool), + } +} + +// RuleBuilderInstall is a tuple of install from and to locations. +type RuleBuilderInstall struct { + From, To string +} + +// Restat marks the rule as a restat rule, which will be passed to ModuleContext.Rule in BuildParams.Restat. +func (r *RuleBuilder) Restat() *RuleBuilder { + r.restat = 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, 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{} + 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 string) { + 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 []string + + for intermediate := range r.temporariesSet { + temporariesList = append(temporariesList, intermediate) + } + sort.Strings(temporariesList) + + 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, RuleBuilderComand.Implicit, or RuleBuilderCommand.FlagWithInput. Inputs to a command +// that are also outputs of another command in the same RuleBuilder are filtered out. +func (r *RuleBuilder) Inputs() []string { + outputs := r.outputSet() + + inputs := make(map[string]bool) + for _, c := range r.commands { + for _, input := range c.inputs { + if !outputs[input] { + inputs[input] = true + } + } + } + + var inputList []string + for input := range inputs { + inputList = append(inputList, input) + } + sort.Strings(inputList) + + return inputList +} + +func (r *RuleBuilder) outputSet() map[string]bool { + outputs := make(map[string]bool) + for _, c := range r.commands { + for _, output := range c.outputs { + outputs[output] = true + } + } + 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. +func (r *RuleBuilder) Outputs() []string { + outputs := r.outputSet() + + var outputList []string + for output := range outputs { + if !r.temporariesSet[output] { + outputList = append(outputList, output) + } + } + sort.Strings(outputList) + return outputList +} + +// Installs returns the list of tuples passed to Install. +func (r *RuleBuilder) Installs() []RuleBuilderInstall { + return append([]RuleBuilderInstall(nil), r.installs...) +} + +func (r *RuleBuilder) toolsSet() map[string]bool { + tools := make(map[string]bool) + for _, c := range r.commands { + for _, tool := range c.tools { + tools[tool] = true + } + } + + return tools +} + +// Tools returns the list of paths that were passed to the RuleBuilderCommand.Tool method. +func (r *RuleBuilder) Tools() []string { + toolsSet := r.toolsSet() + + var toolsList []string + for tool := range toolsSet { + toolsList = append(toolsList, tool) + } + sort.Strings(toolsList) + return toolsList +} + +// Commands returns a slice containing a 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, string(c.buf)) + } + 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) + +// 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(pctx PackageContext, ctx BuilderContext, name string, desc string) { + // TODO: convert RuleBuilder arguments and storage to Paths + mctx, _ := ctx.(ModuleContext) + var inputs Paths + for _, input := range r.Inputs() { + // Module output paths + if mctx != nil { + rel, isRel := MaybeRel(ctx, PathForModuleOut(mctx).String(), input) + if isRel { + inputs = append(inputs, PathForModuleOut(mctx, rel)) + continue + } + } + + // Other output paths + rel, isRel := MaybeRel(ctx, PathForOutput(ctx).String(), input) + if isRel { + inputs = append(inputs, PathForOutput(ctx, rel)) + continue + } + + // TODO: remove this once boot image is moved to where PathForOutput can find it. + inputs = append(inputs, &unknownRulePath{input}) + } + + var outputs WritablePaths + for _, output := range r.Outputs() { + if mctx != nil { + rel := Rel(ctx, PathForModuleOut(mctx).String(), output) + outputs = append(outputs, PathForModuleOut(mctx, rel)) + } else { + rel := Rel(ctx, PathForOutput(ctx).String(), output) + outputs = append(outputs, PathForOutput(ctx, rel)) + } + } + + if len(r.Commands()) > 0 { + ctx.Build(pctx, BuildParams{ + Rule: ctx.Rule(pctx, name, blueprint.RuleParams{ + Command: strings.Join(proptools.NinjaEscape(r.Commands()), " && "), + CommandDeps: r.Tools(), + }), + Implicits: inputs, + Outputs: outputs, + 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 { + buf []byte + inputs []string + outputs []string + tools []string +} + +// 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 len(c.buf) > 0 { + c.buf = append(c.buf, ' ') + } + c.buf = append(c.buf, 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) +} + +// 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) +} + +// FlagWithArg 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 string) *RuleBuilderCommand { + c.tools = append(c.tools, path) + return c.Text(path) +} + +// 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 string) *RuleBuilderCommand { + c.inputs = append(c.inputs, path) + return c.Text(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 []string) *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 string) *RuleBuilderCommand { + c.inputs = append(c.inputs, 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 []string) *RuleBuilderCommand { + c.inputs = append(c.inputs, paths...) + 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 string) *RuleBuilderCommand { + c.outputs = append(c.outputs, path) + return c.Text(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 []string) *RuleBuilderCommand { + for _, path := range paths { + c.Output(path) + } + return c +} + +// ImplicitOutput adds the specified output path to the dependencies returned by RuleBuilder.Outputs without modifying +// the command line. +func (c *RuleBuilderCommand) ImplicitOutput(path string) *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 []string) *RuleBuilderCommand { + c.outputs = append(c.outputs, paths...) + 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, path string) *RuleBuilderCommand { + c.inputs = append(c.inputs, path) + return c.Text(flag + 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 []string, sep string) *RuleBuilderCommand { + c.inputs = append(c.inputs, paths...) + return c.FlagWithList(flag, paths, 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 []string) *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, path string) *RuleBuilderCommand { + c.outputs = append(c.outputs, path) + return c.Text(flag + path) +} + +// String returns the command line. +func (c *RuleBuilderCommand) String() string { + return string(c.buf) +} + +type unknownRulePath struct { + path string +} + +var _ Path = (*unknownRulePath)(nil) + +func (p *unknownRulePath) String() string { return p.path } +func (p *unknownRulePath) Ext() string { return filepath.Ext(p.path) } +func (p *unknownRulePath) Base() string { return filepath.Base(p.path) } +func (p *unknownRulePath) Rel() string { return p.path } diff --git a/android/rule_builder_test.go b/android/rule_builder_test.go new file mode 100644 index 000000000..f7577a6e1 --- /dev/null +++ b/android/rule_builder_test.go @@ -0,0 +1,292 @@ +// Copyright 2019 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 ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "reflect" + "strings" + "testing" +) + +func ExampleRuleBuilder() { + rule := NewRuleBuilder() + + rule.Command().Tool("ld").Inputs([]string{"a.o", "b.o"}).FlagWithOutput("-o ", "linked") + rule.Command().Text("echo success") + + // To add the command to the build graph: + // rule.Build(pctx, ctx, "link", "link") + + fmt.Printf("commands: %q\n", strings.Join(rule.Commands(), " && ")) + fmt.Printf("tools: %q\n", rule.Tools()) + fmt.Printf("inputs: %q\n", rule.Inputs()) + fmt.Printf("outputs: %q\n", rule.Outputs()) + + // Output: + // commands: "ld a.o b.o -o linked && echo success" + // tools: ["ld"] + // inputs: ["a.o" "b.o"] + // outputs: ["linked"] +} + +func ExampleRuleBuilder_Temporary() { + rule := NewRuleBuilder() + + rule.Command().Tool("cp").Input("a").Output("b") + rule.Command().Tool("cp").Input("b").Output("c") + rule.Temporary("b") + + fmt.Printf("commands: %q\n", strings.Join(rule.Commands(), " && ")) + fmt.Printf("tools: %q\n", rule.Tools()) + fmt.Printf("inputs: %q\n", rule.Inputs()) + fmt.Printf("outputs: %q\n", rule.Outputs()) + + // Output: + // commands: "cp a b && cp b c" + // tools: ["cp"] + // inputs: ["a"] + // outputs: ["c"] +} + +func ExampleRuleBuilder_DeleteTemporaryFiles() { + rule := NewRuleBuilder() + + rule.Command().Tool("cp").Input("a").Output("b") + rule.Command().Tool("cp").Input("b").Output("c") + rule.Temporary("b") + rule.DeleteTemporaryFiles() + + fmt.Printf("commands: %q\n", strings.Join(rule.Commands(), " && ")) + fmt.Printf("tools: %q\n", rule.Tools()) + fmt.Printf("inputs: %q\n", rule.Inputs()) + fmt.Printf("outputs: %q\n", rule.Outputs()) + + // Output: + // commands: "cp a b && cp b c && rm -f b" + // tools: ["cp"] + // inputs: ["a"] + // outputs: ["c"] +} + +func ExampleRuleBuilderCommand() { + rule := NewRuleBuilder() + + // chained + rule.Command().Tool("ld").Inputs([]string{"a.o", "b.o"}).FlagWithOutput("-o ", "linked") + + // unchained + cmd := rule.Command() + cmd.Tool("ld") + cmd.Inputs([]string{"a.o", "b.o"}) + cmd.FlagWithOutput("-o ", "linked") + + // mixed: + cmd = rule.Command().Tool("ld") + cmd.Inputs([]string{"a.o", "b.o"}) + cmd.FlagWithOutput("-o ", "linked") +} + +func ExampleRuleBuilderCommand_Flag() { + fmt.Println(NewRuleBuilder().Command(). + Tool("ls").Flag("-l")) + // Output: + // ls -l +} + +func ExampleRuleBuilderCommand_FlagWithArg() { + fmt.Println(NewRuleBuilder().Command(). + Tool("ls"). + FlagWithArg("--sort=", "time")) + // Output: + // ls --sort=time +} + +func ExampleRuleBuilderCommand_FlagForEachInput() { + fmt.Println(NewRuleBuilder().Command(). + Tool("turbine"). + FlagForEachInput("--classpath ", []string{"a.jar", "b.jar"})) + // Output: + // turbine --classpath a.jar --classpath b.jar +} + +func ExampleRuleBuilderCommand_FlagWithInputList() { + fmt.Println(NewRuleBuilder().Command(). + Tool("java"). + FlagWithInputList("-classpath=", []string{"a.jar", "b.jar"}, ":")) + // Output: + // java -classpath=a.jar:b.jar +} + +func ExampleRuleBuilderCommand_FlagWithInput() { + fmt.Println(NewRuleBuilder().Command(). + Tool("java"). + FlagWithInput("-classpath=", "a")) + // Output: + // java -classpath=a +} + +func ExampleRuleBuilderCommand_FlagWithList() { + fmt.Println(NewRuleBuilder().Command(). + Tool("ls"). + FlagWithList("--sort=", []string{"time", "size"}, ",")) + // Output: + // ls --sort=time,size +} + +func TestRuleBuilder(t *testing.T) { + rule := NewRuleBuilder() + + cmd := rule.Command(). + Flag("Flag"). + FlagWithArg("FlagWithArg=", "arg"). + FlagWithInput("FlagWithInput=", "input"). + FlagWithOutput("FlagWithOutput=", "output"). + Implicit("Implicit"). + ImplicitOutput("ImplicitOutput"). + Input("Input"). + Output("Output"). + Text("Text"). + Tool("Tool") + + rule.Command(). + Text("command2"). + Input("input2"). + Output("output2"). + Tool("tool2") + + // Test updates to the first command after the second command has been started + cmd.Text("after command2") + // Test updating a command when the previous update did not replace the cmd variable + cmd.Text("old cmd") + + // Test a command that uses the output of a previous command as an input + rule.Command(). + Text("command3"). + Input("input3"). + Input("output2"). + Output("output3") + + wantCommands := []string{ + "Flag FlagWithArg=arg FlagWithInput=input FlagWithOutput=output Input Output Text Tool after command2 old cmd", + "command2 input2 output2 tool2", + "command3 input3 output2 output3", + } + wantInputs := []string{"Implicit", "Input", "input", "input2", "input3"} + wantOutputs := []string{"ImplicitOutput", "Output", "output", "output2", "output3"} + wantTools := []string{"Tool", "tool2"} + + if !reflect.DeepEqual(rule.Commands(), wantCommands) { + t.Errorf("\nwant rule.Commands() = %#v\n got %#v", wantCommands, rule.Commands()) + } + if !reflect.DeepEqual(rule.Inputs(), wantInputs) { + t.Errorf("\nwant rule.Inputs() = %#v\n got %#v", wantInputs, rule.Inputs()) + } + if !reflect.DeepEqual(rule.Outputs(), wantOutputs) { + t.Errorf("\nwant rule.Outputs() = %#v\n got %#v", wantOutputs, rule.Outputs()) + } + if !reflect.DeepEqual(rule.Tools(), wantTools) { + t.Errorf("\nwant rule.Tools() = %#v\n got %#v", wantTools, rule.Tools()) + } +} + +func testRuleBuilderFactory() Module { + module := &testRuleBuilderModule{} + module.AddProperties(&module.properties) + InitAndroidModule(module) + return module +} + +type testRuleBuilderModule struct { + ModuleBase + properties struct { + Src string + } +} + +func (t *testRuleBuilderModule) GenerateAndroidBuildActions(ctx ModuleContext) { + in := PathForSource(ctx, t.properties.Src) + out := PathForModuleOut(ctx, ctx.ModuleName()) + + testRuleBuilder_Build(ctx, in, out) +} + +type testRuleBuilderSingleton struct{} + +func testRuleBuilderSingletonFactory() Singleton { + return &testRuleBuilderSingleton{} +} + +func (t *testRuleBuilderSingleton) GenerateBuildActions(ctx SingletonContext) { + in := PathForSource(ctx, "bar") + out := PathForOutput(ctx, "baz") + testRuleBuilder_Build(ctx, in, out) +} + +func testRuleBuilder_Build(ctx BuilderContext, in Path, out WritablePath) { + rule := NewRuleBuilder() + + rule.Command().Tool("cp").Input(in.String()).Output(out.String()) + + rule.Build(pctx, ctx, "rule", "desc") +} + +func TestRuleBuilder_Build(t *testing.T) { + buildDir, err := ioutil.TempDir("", "soong_test_rule_builder") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(buildDir) + + bp := ` + rule_builder_test { + name: "foo", + src: "bar", + } + ` + + config := TestConfig(buildDir, nil) + ctx := NewTestContext() + ctx.MockFileSystem(map[string][]byte{ + "Android.bp": []byte(bp), + "bar": nil, + "cp": nil, + }) + ctx.RegisterModuleType("rule_builder_test", ModuleFactoryAdaptor(testRuleBuilderFactory)) + ctx.RegisterSingletonType("rule_builder_test", SingletonFactoryAdaptor(testRuleBuilderSingletonFactory)) + ctx.Register() + + _, errs := ctx.ParseFileList(".", []string{"Android.bp"}) + FailIfErrored(t, errs) + _, errs = ctx.PrepareBuildActions(config) + FailIfErrored(t, errs) + + foo := ctx.ModuleForTests("foo", "").Rule("rule") + + // TODO: make RuleParams accessible to tests and verify rule.Command().Tools() ends up in CommandDeps + + if len(foo.Implicits) != 1 || foo.Implicits[0].String() != "bar" { + t.Errorf("want foo.Implicits = [%q], got %q", "bar", foo.Implicits.Strings()) + } + + wantOutput := filepath.Join(buildDir, ".intermediates", "foo", "foo") + if len(foo.Outputs) != 1 || foo.Outputs[0].String() != wantOutput { + t.Errorf("want foo.Outputs = [%q], got %q", wantOutput, foo.Outputs.Strings()) + } + +} diff --git a/cc/compiler.go b/cc/compiler.go index fbe10b50a..0aee0bdb3 100644 --- a/cc/compiler.go +++ b/cc/compiler.go @@ -250,8 +250,8 @@ func warningsAreAllowed(subdir string) bool { return false } -func addToModuleList(ctx ModuleContext, list string, module string) { - getNamedMapForConfig(ctx.Config(), list).Store(module, true) +func addToModuleList(ctx ModuleContext, key android.OnceKey, module string) { + getNamedMapForConfig(ctx.Config(), key).Store(module, true) } // Create a Flags struct that collects the compile flags from global values, @@ -503,10 +503,10 @@ func (compiler *baseCompiler) compilerFlags(ctx ModuleContext, flags Flags, deps if len(compiler.Properties.Srcs) > 0 { module := ctx.ModuleDir() + "/Android.bp:" + ctx.ModuleName() if inList("-Wno-error", flags.CFlags) || inList("-Wno-error", flags.CppFlags) { - addToModuleList(ctx, modulesUsingWnoError, module) + addToModuleList(ctx, modulesUsingWnoErrorKey, module) } else if !inList("-Werror", flags.CFlags) && !inList("-Werror", flags.CppFlags) { if warningsAreAllowed(ctx.ModuleDir()) { - addToModuleList(ctx, modulesAddedWall, module) + addToModuleList(ctx, modulesAddedWallKey, module) flags.CFlags = append([]string{"-Wall"}, flags.CFlags...) } else { flags.CFlags = append([]string{"-Wall", "-Werror"}, flags.CFlags...) diff --git a/cc/library.go b/cc/library.go index d716ffac7..b4b89d2ac 100644 --- a/cc/library.go +++ b/cc/library.go @@ -968,8 +968,10 @@ func (library *libraryDecorator) stubsVersion() string { return library.MutatedProperties.StubsVersion } +var versioningMacroNamesListKey = android.NewOnceKey("versioningMacroNamesList") + func versioningMacroNamesList(config android.Config) *map[string]string { - return config.Once("versioningMacroNamesList", func() interface{} { + return config.Once(versioningMacroNamesListKey, func() interface{} { m := make(map[string]string) return &m }).(*map[string]string) @@ -1059,9 +1061,11 @@ func LinkageMutator(mctx android.BottomUpMutatorContext) { } } +var stubVersionsKey = android.NewOnceKey("stubVersions") + // maps a module name to the list of stubs versions available for the module func stubsVersionsFor(config android.Config) map[string][]string { - return config.Once("stubVersions", func() interface{} { + return config.Once(stubVersionsKey, func() interface{} { return make(map[string][]string) }).(map[string][]string) } diff --git a/cc/makevars.go b/cc/makevars.go index fb567babb..d91735f1f 100644 --- a/cc/makevars.go +++ b/cc/makevars.go @@ -24,24 +24,24 @@ import ( "android/soong/cc/config" ) -const ( - modulesAddedWall = "ModulesAddedWall" - modulesUsingWnoError = "ModulesUsingWnoError" - modulesMissingProfileFile = "ModulesMissingProfileFile" +var ( + modulesAddedWallKey = android.NewOnceKey("ModulesAddedWall") + modulesUsingWnoErrorKey = android.NewOnceKey("ModulesUsingWnoError") + modulesMissingProfileFileKey = android.NewOnceKey("ModulesMissingProfileFile") ) func init() { android.RegisterMakeVarsProvider(pctx, makeVarsProvider) } -func getNamedMapForConfig(config android.Config, name string) *sync.Map { - return config.Once(name, func() interface{} { +func getNamedMapForConfig(config android.Config, key android.OnceKey) *sync.Map { + return config.Once(key, func() interface{} { return &sync.Map{} }).(*sync.Map) } -func makeStringOfKeys(ctx android.MakeVarsContext, setName string) string { - set := getNamedMapForConfig(ctx.Config(), setName) +func makeStringOfKeys(ctx android.MakeVarsContext, key android.OnceKey) string { + set := getNamedMapForConfig(ctx.Config(), key) keys := []string{} set.Range(func(key interface{}, value interface{}) bool { keys = append(keys, key.(string)) @@ -117,9 +117,9 @@ func makeVarsProvider(ctx android.MakeVarsContext) { ctx.Strict("LSDUMP_PATHS", strings.Join(lsdumpPaths, " ")) ctx.Strict("ANDROID_WARNING_ALLOWED_PROJECTS", makeStringOfWarningAllowedProjects()) - ctx.Strict("SOONG_MODULES_ADDED_WALL", makeStringOfKeys(ctx, modulesAddedWall)) - ctx.Strict("SOONG_MODULES_USING_WNO_ERROR", makeStringOfKeys(ctx, modulesUsingWnoError)) - ctx.Strict("SOONG_MODULES_MISSING_PGO_PROFILE_FILE", makeStringOfKeys(ctx, modulesMissingProfileFile)) + ctx.Strict("SOONG_MODULES_ADDED_WALL", makeStringOfKeys(ctx, modulesAddedWallKey)) + ctx.Strict("SOONG_MODULES_USING_WNO_ERROR", makeStringOfKeys(ctx, modulesUsingWnoErrorKey)) + ctx.Strict("SOONG_MODULES_MISSING_PGO_PROFILE_FILE", makeStringOfKeys(ctx, modulesMissingProfileFileKey)) ctx.Strict("ADDRESS_SANITIZER_CONFIG_EXTRA_CFLAGS", strings.Join(asanCflags, " ")) ctx.Strict("ADDRESS_SANITIZER_CONFIG_EXTRA_LDFLAGS", strings.Join(asanLdflags, " ")) diff --git a/cc/pgo.go b/cc/pgo.go index a341ab9f9..9363916a7 100644 --- a/cc/pgo.go +++ b/cc/pgo.go @@ -36,7 +36,8 @@ var ( } ) -const pgoProfileProjectsConfigKey = "PgoProfileProjects" +var pgoProfileProjectsConfigKey = android.NewOnceKey("PgoProfileProjects") + const profileInstrumentFlag = "-fprofile-generate=/data/local/tmp" const profileSamplingFlag = "-gline-tables-only" const profileUseInstrumentFormat = "-fprofile-use=%s" @@ -49,7 +50,7 @@ func getPgoProfileProjects(config android.DeviceConfig) []string { } func recordMissingProfileFile(ctx BaseModuleContext, missing string) { - getNamedMapForConfig(ctx.Config(), modulesMissingProfileFile).Store(missing, true) + getNamedMapForConfig(ctx.Config(), modulesMissingProfileFileKey).Store(missing, true) } type PgoProperties struct { diff --git a/cc/sanitize.go b/cc/sanitize.go index bcc4de3fc..fc2ed5071 100644 --- a/cc/sanitize.go +++ b/cc/sanitize.go @@ -958,20 +958,26 @@ func sanitizerMutator(t sanitizerType) func(android.BottomUpMutatorContext) { } } +var cfiStaticLibsKey = android.NewOnceKey("cfiStaticLibs") + func cfiStaticLibs(config android.Config) *[]string { - return config.Once("cfiStaticLibs", func() interface{} { + return config.Once(cfiStaticLibsKey, func() interface{} { return &[]string{} }).(*[]string) } +var hwasanStaticLibsKey = android.NewOnceKey("hwasanStaticLibs") + func hwasanStaticLibs(config android.Config) *[]string { - return config.Once("hwasanStaticLibs", func() interface{} { + return config.Once(hwasanStaticLibsKey, func() interface{} { return &[]string{} }).(*[]string) } +var hwasanVendorStaticLibsKey = android.NewOnceKey("hwasanVendorStaticLibs") + func hwasanVendorStaticLibs(config android.Config) *[]string { - return config.Once("hwasanVendorStaticLibs", func() interface{} { + return config.Once(hwasanVendorStaticLibsKey, func() interface{} { return &[]string{} }).(*[]string) } diff --git a/dexpreopt/Android.bp b/dexpreopt/Android.bp index b83252983..c5f24e273 100644 --- a/dexpreopt/Android.bp +++ b/dexpreopt/Android.bp @@ -4,12 +4,12 @@ bootstrap_go_package { srcs: [ "config.go", "dexpreopt.go", - "script.go", ], testSrcs: [ "dexpreopt_test.go", ], deps: [ "blueprint-pathtools", + "soong-android", ], -} \ No newline at end of file +} diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go index f316be4b0..c38fbff9c 100644 --- a/dexpreopt/dexpreopt.go +++ b/dexpreopt/dexpreopt.go @@ -39,6 +39,8 @@ import ( "path/filepath" "strings" + "android/soong/android" + "github.com/google/blueprint/pathtools" ) @@ -47,7 +49,7 @@ const SystemOtherPartition = "/system_other/" // GenerateStripRule generates a set of commands that will take an APK or JAR as an input and strip the dex files if // they are no longer necessary after preopting. -func GenerateStripRule(global GlobalConfig, module ModuleConfig) (rule *Rule, err error) { +func GenerateStripRule(global GlobalConfig, module ModuleConfig) (rule *android.RuleBuilder, err error) { defer func() { if r := recover(); r != nil { if e, ok := r.(error); ok { @@ -61,7 +63,7 @@ func GenerateStripRule(global GlobalConfig, module ModuleConfig) (rule *Rule, er tools := global.Tools - rule = &Rule{} + rule = android.NewRuleBuilder() strip := shouldStripDex(module, global) @@ -81,7 +83,7 @@ func GenerateStripRule(global GlobalConfig, module ModuleConfig) (rule *Rule, er // GenerateDexpreoptRule generates a set of commands that will preopt a module based on a GlobalConfig and a // ModuleConfig. The produced files and their install locations will be available through rule.Installs(). -func GenerateDexpreoptRule(global GlobalConfig, module ModuleConfig) (rule *Rule, err error) { +func GenerateDexpreoptRule(global GlobalConfig, module ModuleConfig) (rule *android.RuleBuilder, err error) { defer func() { if r := recover(); r != nil { if e, ok := r.(error); ok { @@ -93,7 +95,7 @@ func GenerateDexpreoptRule(global GlobalConfig, module ModuleConfig) (rule *Rule } }() - rule = &Rule{} + rule = android.NewRuleBuilder() generateProfile := module.ProfileClassListing != "" && !global.DisableGenerateProfile @@ -141,7 +143,7 @@ func dexpreoptDisabled(global GlobalConfig, module ModuleConfig) bool { return false } -func profileCommand(global GlobalConfig, module ModuleConfig, rule *Rule) string { +func profileCommand(global GlobalConfig, module ModuleConfig, rule *android.RuleBuilder) string { profilePath := filepath.Join(filepath.Dir(module.BuildPath), "profile.prof") profileInstalledPath := module.DexLocation + ".prof" @@ -178,8 +180,8 @@ func profileCommand(global GlobalConfig, module ModuleConfig, rule *Rule) string return profilePath } -func dexpreoptCommand(global GlobalConfig, module ModuleConfig, rule *Rule, profile, arch, bootImageLocation string, - appImage, generateDM bool) { +func dexpreoptCommand(global GlobalConfig, module ModuleConfig, rule *android.RuleBuilder, + profile, arch, bootImageLocation string, appImage, generateDM bool) { // HACK: make soname in Soong-generated .odex files match Make. base := filepath.Base(module.DexLocation) diff --git a/dexpreopt/dexpreopt_gen/dexpreopt_gen.go b/dexpreopt/dexpreopt_gen/dexpreopt_gen.go index 46d8795a8..1467a028a 100644 --- a/dexpreopt/dexpreopt_gen/dexpreopt_gen.go +++ b/dexpreopt/dexpreopt_gen/dexpreopt_gen.go @@ -22,6 +22,7 @@ import ( "path/filepath" "runtime" + "android/soong/android" "android/soong/dexpreopt" "github.com/google/blueprint/pathtools" @@ -121,7 +122,7 @@ func writeScripts(global dexpreopt.GlobalConfig, module dexpreopt.ModuleConfig, panic(err) } - write := func(rule *dexpreopt.Rule, file string) { + write := func(rule *android.RuleBuilder, file string) { script := &bytes.Buffer{} script.WriteString(scriptHeader) for _, c := range rule.Commands() { diff --git a/dexpreopt/dexpreopt_test.go b/dexpreopt/dexpreopt_test.go index 073d46378..6e520f136 100644 --- a/dexpreopt/dexpreopt_test.go +++ b/dexpreopt/dexpreopt_test.go @@ -15,6 +15,7 @@ package dexpreopt import ( + "android/soong/android" "reflect" "strings" "testing" @@ -100,7 +101,7 @@ func TestDexPreopt(t *testing.T) { t.Error(err) } - wantInstalls := []Install{ + wantInstalls := []android.RuleBuilderInstall{ {"out/test/oat/arm/package.odex", "/system/app/test/oat/arm/test.odex"}, {"out/test/oat/arm/package.vdex", "/system/app/test/oat/arm/test.vdex"}, } @@ -126,7 +127,7 @@ func TestDexPreoptSystemOther(t *testing.T) { t.Error(err) } - wantInstalls := []Install{ + wantInstalls := []android.RuleBuilderInstall{ {"out/test/oat/arm/package.odex", "/system_other/app/test/oat/arm/test.odex"}, {"out/test/oat/arm/package.vdex", "/system_other/app/test/oat/arm/test.vdex"}, } @@ -150,7 +151,7 @@ func TestDexPreoptProfile(t *testing.T) { t.Error(err) } - wantInstalls := []Install{ + wantInstalls := []android.RuleBuilderInstall{ {"out/test/profile.prof", "/system/app/test/test.apk.prof"}, {"out/test/oat/arm/package.art", "/system/app/test/oat/arm/test.art"}, {"out/test/oat/arm/package.odex", "/system/app/test/oat/arm/test.odex"}, diff --git a/dexpreopt/script.go b/dexpreopt/script.go deleted file mode 100644 index 9d4329c5b..000000000 --- a/dexpreopt/script.go +++ /dev/null @@ -1,178 +0,0 @@ -// 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 dexpreopt - -import ( - "fmt" - "sort" - "strings" -) - -type Install struct { - From, To string -} - -type Rule struct { - commands []*Command - installs []Install -} - -func (r *Rule) Install(from, to string) { - r.installs = append(r.installs, Install{from, to}) -} - -func (r *Rule) Command() *Command { - command := &Command{} - r.commands = append(r.commands, command) - return command -} - -func (r *Rule) Inputs() []string { - outputs := r.outputSet() - - inputs := make(map[string]bool) - for _, c := range r.commands { - for _, input := range c.inputs { - if !outputs[input] { - inputs[input] = true - } - } - } - - var inputList []string - for input := range inputs { - inputList = append(inputList, input) - } - sort.Strings(inputList) - - return inputList -} - -func (r *Rule) outputSet() map[string]bool { - outputs := make(map[string]bool) - for _, c := range r.commands { - for _, output := range c.outputs { - outputs[output] = true - } - } - return outputs -} - -func (r *Rule) Outputs() []string { - outputs := r.outputSet() - - var outputList []string - for output := range outputs { - outputList = append(outputList, output) - } - sort.Strings(outputList) - return outputList -} - -func (r *Rule) Installs() []Install { - return append([]Install(nil), r.installs...) -} - -func (r *Rule) Tools() []string { - var tools []string - for _, c := range r.commands { - tools = append(tools, c.tools...) - } - return tools -} - -func (r *Rule) Commands() []string { - var commands []string - for _, c := range r.commands { - commands = append(commands, string(c.buf)) - } - return commands -} - -type Command struct { - buf []byte - inputs []string - outputs []string - tools []string -} - -func (c *Command) Text(text string) *Command { - if len(c.buf) > 0 { - c.buf = append(c.buf, ' ') - } - c.buf = append(c.buf, text...) - return c -} - -func (c *Command) Textf(format string, a ...interface{}) *Command { - return c.Text(fmt.Sprintf(format, a...)) -} - -func (c *Command) Flag(flag string) *Command { - return c.Text(flag) -} - -func (c *Command) FlagWithArg(flag, arg string) *Command { - return c.Text(flag + arg) -} - -func (c *Command) FlagWithList(flag string, list []string, sep string) *Command { - return c.Text(flag + strings.Join(list, sep)) -} - -func (c *Command) Tool(path string) *Command { - c.tools = append(c.tools, path) - return c.Text(path) -} - -func (c *Command) Input(path string) *Command { - c.inputs = append(c.inputs, path) - return c.Text(path) -} - -func (c *Command) Implicit(path string) *Command { - c.inputs = append(c.inputs, path) - return c -} - -func (c *Command) Implicits(paths []string) *Command { - c.inputs = append(c.inputs, paths...) - return c -} - -func (c *Command) Output(path string) *Command { - c.outputs = append(c.outputs, path) - return c.Text(path) -} - -func (c *Command) ImplicitOutput(path string) *Command { - c.outputs = append(c.outputs, path) - return c -} - -func (c *Command) FlagWithInput(flag, path string) *Command { - c.inputs = append(c.inputs, path) - return c.Text(flag + path) -} - -func (c *Command) FlagWithInputList(flag string, paths []string, sep string) *Command { - c.inputs = append(c.inputs, paths...) - return c.FlagWithList(flag, paths, sep) -} - -func (c *Command) FlagWithOutput(flag, path string) *Command { - c.outputs = append(c.outputs, path) - return c.Text(flag + path) -} diff --git a/java/android_resources.go b/java/android_resources.go index efd3e3dd0..44cb709e4 100644 --- a/java/android_resources.go +++ b/java/android_resources.go @@ -46,7 +46,7 @@ type overlayGlobResult struct { paths android.DirectorySortedPaths } -const overlayDataKey = "overlayDataKey" +var overlayDataKey = android.NewOnceKey("overlayDataKey") type globbedResourceDir struct { dir android.Path diff --git a/java/dexpreopt.go b/java/dexpreopt.go index 6e46bc91a..33c46f4fe 100644 --- a/java/dexpreopt.go +++ b/java/dexpreopt.go @@ -15,12 +15,6 @@ package java import ( - "path/filepath" - "strings" - - "github.com/google/blueprint" - "github.com/google/blueprint/proptools" - "android/soong/android" "android/soong/dexpreopt" ) @@ -87,12 +81,14 @@ func (d *dexpreopter) dexpreoptDisabled(ctx android.ModuleContext) bool { return false } +var dexpreoptGlobalConfigKey = android.NewOnceKey("DexpreoptGlobalConfig") + func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.ModuleOutPath) android.ModuleOutPath { if d.dexpreoptDisabled(ctx) { return dexJarFile } - globalConfig := ctx.Config().Once("DexpreoptGlobalConfig", func() interface{} { + globalConfig := ctx.Config().Once(dexpreoptGlobalConfigKey, func() interface{} { if f := ctx.Config().DexpreoptGlobalConfig(); f != "" { ctx.AddNinjaFileDeps(f) globalConfig, err := dexpreopt.LoadGlobalConfig(f) @@ -185,69 +181,19 @@ func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.Mo return dexJarFile } - var inputs android.Paths - for _, input := range dexpreoptRule.Inputs() { - if input == "" { - // Tests sometimes have empty configuration values that lead to empty inputs - continue - } - rel, isRel := android.MaybeRel(ctx, android.PathForModuleOut(ctx).String(), input) - if isRel { - inputs = append(inputs, android.PathForModuleOut(ctx, rel)) - } else { - // TODO: use PathForOutput once boot image is moved to where PathForOutput can find it. - inputs = append(inputs, &bootImagePath{input}) - } - } - - var outputs android.WritablePaths - for _, output := range dexpreoptRule.Outputs() { - rel := android.Rel(ctx, android.PathForModuleOut(ctx).String(), output) - outputs = append(outputs, android.PathForModuleOut(ctx, rel)) - } + dexpreoptRule.Build(pctx, ctx, "dexpreopt", "dexpreopt") for _, install := range dexpreoptRule.Installs() { d.builtInstalled = append(d.builtInstalled, install.From+":"+install.To) } - if len(dexpreoptRule.Commands()) > 0 { - ctx.Build(pctx, android.BuildParams{ - Rule: ctx.Rule(pctx, "dexpreopt", blueprint.RuleParams{ - Command: strings.Join(proptools.NinjaEscape(dexpreoptRule.Commands()), " && "), - CommandDeps: dexpreoptRule.Tools(), - }), - Implicits: inputs, - Outputs: outputs, - Description: "dexpreopt", - }) - } - stripRule, err := dexpreopt.GenerateStripRule(globalConfig, dexpreoptConfig) if err != nil { ctx.ModuleErrorf("error generating dexpreopt strip rule: %s", err.Error()) return dexJarFile } - ctx.Build(pctx, android.BuildParams{ - Rule: ctx.Rule(pctx, "dexpreopt_strip", blueprint.RuleParams{ - Command: strings.Join(proptools.NinjaEscape(stripRule.Commands()), " && "), - CommandDeps: stripRule.Tools(), - }), - Input: dexJarFile, - Output: strippedDexJarFile, - Description: "dexpreopt strip", - }) + stripRule.Build(pctx, ctx, "dexpreopt_strip", "dexpreopt strip") return strippedDexJarFile } - -type bootImagePath struct { - path string -} - -var _ android.Path = (*bootImagePath)(nil) - -func (p *bootImagePath) String() string { return p.path } -func (p *bootImagePath) Ext() string { return filepath.Ext(p.path) } -func (p *bootImagePath) Base() string { return filepath.Base(p.path) } -func (p *bootImagePath) Rel() string { return p.path } diff --git a/java/hiddenapi.go b/java/hiddenapi.go index 67df57552..c7eac733f 100644 --- a/java/hiddenapi.go +++ b/java/hiddenapi.go @@ -15,6 +15,7 @@ package java import ( + "path/filepath" "sort" "strings" "sync" @@ -32,7 +33,7 @@ var hiddenAPIGenerateCSVRule = pctx.AndroidStaticRule("hiddenAPIGenerateCSV", bl func hiddenAPIGenerateCSV(ctx android.ModuleContext, classesJar android.Path) { flagsCSV := android.PathForModuleOut(ctx, "hiddenapi", "flags.csv") metadataCSV := android.PathForModuleOut(ctx, "hiddenapi", "metadata.csv") - stubFlagsCSV := &bootImagePath{ctx.Config().HiddenAPIStubFlags()} + stubFlagsCSV := &hiddenAPIPath{ctx.Config().HiddenAPIStubFlags()} ctx.Build(pctx, android.BuildParams{ Rule: hiddenAPIGenerateCSVRule, @@ -80,7 +81,7 @@ var hiddenAPIEncodeDexRule = pctx.AndroidStaticRule("hiddenAPIEncodeDex", bluepr func hiddenAPIEncodeDex(ctx android.ModuleContext, output android.WritablePath, dexInput android.WritablePath, uncompressDex bool) { - flagsCsv := &bootImagePath{ctx.Config().HiddenAPIFlags()} + flagsCsv := &hiddenAPIPath{ctx.Config().HiddenAPIFlags()} // The encode dex rule requires unzipping and rezipping the classes.dex files, ensure that if it was uncompressed // in the input it stays uncompressed in the output. @@ -120,7 +121,7 @@ func hiddenAPIEncodeDex(ctx android.ModuleContext, output android.WritablePath, hiddenAPISaveDexInputs(ctx, dexInput) } -const hiddenAPIOutputsKey = "hiddenAPIOutputsKey" +var hiddenAPIOutputsKey = android.NewOnceKey("hiddenAPIOutputsKey") var hiddenAPIOutputsLock sync.Mutex @@ -168,3 +169,14 @@ func hiddenAPIMakeVars(ctx android.MakeVarsContext) { export("SOONG_HIDDENAPI_GREYLIST_METADATA", metadataCSVList) export("SOONG_HIDDENAPI_DEX_INPUTS", dexInputList) } + +type hiddenAPIPath struct { + path string +} + +var _ android.Path = (*hiddenAPIPath)(nil) + +func (p *hiddenAPIPath) String() string { return p.path } +func (p *hiddenAPIPath) Ext() string { return filepath.Ext(p.path) } +func (p *hiddenAPIPath) Base() string { return filepath.Base(p.path) } +func (p *hiddenAPIPath) Rel() string { return p.path } diff --git a/java/sdk.go b/java/sdk.go index 988610f5b..0959be7de 100644 --- a/java/sdk.go +++ b/java/sdk.go @@ -28,7 +28,7 @@ func init() { android.RegisterPreSingletonType("sdk", sdkSingletonFactory) } -const sdkSingletonKey = "sdkSingletonKey" +var sdkSingletonKey = android.NewOnceKey("sdkSingletonKey") type sdkContext interface { // sdkVersion eturns the sdk_version property of the current module, or an empty string if it is not set. diff --git a/java/sdk_library.go b/java/sdk_library.go index ca3131c2f..1b0fe75e0 100644 --- a/java/sdk_library.go +++ b/java/sdk_library.go @@ -627,8 +627,10 @@ func (module *sdkLibrary) ImplementationJars(ctx android.BaseContext, sdkVersion } } +var javaSdkLibrariesKey = android.NewOnceKey("javaSdkLibraries") + func javaSdkLibraries(config android.Config) *[]string { - return config.Once("javaSdkLibraries", func() interface{} { + return config.Once(javaSdkLibrariesKey, func() interface{} { return &[]string{} }).(*[]string) }