Merge "Add arch_variant support for diag"
diff --git a/Android.bp b/Android.bp
index e7a5de2..866ed25 100644
--- a/Android.bp
+++ b/Android.bp
@@ -75,6 +75,10 @@
product_available: true,
recovery_available: true,
native_bridge_supported: true,
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
arch: {
arm: {
diff --git a/android/Android.bp b/android/Android.bp
index 279d442..4bd272d 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -4,6 +4,7 @@
deps: [
"blueprint",
"blueprint-bootstrap",
+ "sbox_proto",
"soong",
"soong-android-soongconfig",
"soong-bazel",
@@ -23,6 +24,7 @@
"defaults.go",
"defs.go",
"depset.go",
+ "deptag.go",
"expand.go",
"filegroup.go",
"hooks.go",
@@ -72,6 +74,7 @@
"config_test.go",
"csuite_config_test.go",
"depset_test.go",
+ "deptag_test.go",
"expand_test.go",
"module_test.go",
"mutator_test.go",
diff --git a/android/androidmk.go b/android/androidmk.go
index 4adbb22..a670656 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -177,24 +177,96 @@
a.EntryMap[name] = append(a.EntryMap[name], value...)
}
-// Compute the list of Make strings to declare phone goals and dist-for-goals
-// calls from the module's dist and dists properties.
-func (a *AndroidMkEntries) GetDistForGoals(mod blueprint.Module) []string {
+// AddCompatibilityTestSuites adds the supplied test suites to the EntryMap, with special handling
+// for partial MTS test suites.
+func (a *AndroidMkEntries) AddCompatibilityTestSuites(suites ...string) {
+ // MTS supports a full test suite and partial per-module MTS test suites, with naming mts-${MODULE}.
+ // To reduce repetition, if we find a partial MTS test suite without an full MTS test suite,
+ // we add the full test suite to our list.
+ if PrefixInList(suites, "mts-") && !InList("mts", suites) {
+ suites = append(suites, "mts")
+ }
+ a.AddStrings("LOCAL_COMPATIBILITY_SUITE", suites...)
+}
+
+// The contributions to the dist.
+type distContributions struct {
+ // List of goals and the dist copy instructions.
+ copiesForGoals []*copiesForGoals
+}
+
+// getCopiesForGoals returns a copiesForGoals into which copy instructions that
+// must be processed when building one or more of those goals can be added.
+func (d *distContributions) getCopiesForGoals(goals string) *copiesForGoals {
+ copiesForGoals := &copiesForGoals{goals: goals}
+ d.copiesForGoals = append(d.copiesForGoals, copiesForGoals)
+ return copiesForGoals
+}
+
+// Associates a list of dist copy instructions with a set of goals for which they
+// should be run.
+type copiesForGoals struct {
+ // goals are a space separated list of build targets that will trigger the
+ // copy instructions.
+ goals string
+
+ // A list of instructions to copy a module's output files to somewhere in the
+ // dist directory.
+ copies []distCopy
+}
+
+// Adds a copy instruction.
+func (d *copiesForGoals) addCopyInstruction(from Path, dest string) {
+ d.copies = append(d.copies, distCopy{from, dest})
+}
+
+// Instruction on a path that must be copied into the dist.
+type distCopy struct {
+ // The path to copy from.
+ from Path
+
+ // The destination within the dist directory to copy to.
+ dest string
+}
+
+// Compute the contributions that the module makes to the dist.
+func (a *AndroidMkEntries) getDistContributions(mod blueprint.Module) *distContributions {
amod := mod.(Module).base()
name := amod.BaseModuleName()
- var ret []string
+ // Collate the set of associated tag/paths available for copying to the dist.
+ // Start with an empty (nil) set.
var availableTaggedDists TaggedDistFiles
+ // Then merge in any that are provided explicitly by the module.
if a.DistFiles != nil {
- availableTaggedDists = a.DistFiles
- } else if a.OutputFile.Valid() {
- availableTaggedDists = MakeDefaultDistFiles(a.OutputFile.Path())
- } else {
+ // Merge the DistFiles into the set.
+ availableTaggedDists = availableTaggedDists.merge(a.DistFiles)
+ }
+
+ // If no paths have been provided for the DefaultDistTag and the output file is
+ // valid then add that as the default dist path.
+ if _, ok := availableTaggedDists[DefaultDistTag]; !ok && a.OutputFile.Valid() {
+ availableTaggedDists = availableTaggedDists.addPathsForTag(DefaultDistTag, a.OutputFile.Path())
+ }
+
+ // If the distFiles created by GenerateTaggedDistFiles contains paths for the
+ // DefaultDistTag then that takes priority so delete any existing paths.
+ if _, ok := amod.distFiles[DefaultDistTag]; ok {
+ delete(availableTaggedDists, DefaultDistTag)
+ }
+
+ // Finally, merge the distFiles created by GenerateTaggedDistFiles.
+ availableTaggedDists = availableTaggedDists.merge(amod.distFiles)
+
+ if len(availableTaggedDists) == 0 {
// Nothing dist-able for this module.
return nil
}
+ // Collate the contributions this module makes to the dist.
+ distContributions := &distContributions{}
+
// Iterate over this module's dist structs, merged from the dist and dists properties.
for _, dist := range amod.Dists() {
// Get the list of goals this dist should be enabled for. e.g. sdk, droidcore
@@ -204,7 +276,7 @@
var tag string
if dist.Tag == nil {
// If the dist struct does not specify a tag, use the default output files tag.
- tag = ""
+ tag = DefaultDistTag
} else {
tag = *dist.Tag
}
@@ -218,15 +290,15 @@
}
if len(tagPaths) > 1 && (dist.Dest != nil || dist.Suffix != nil) {
- errorMessage := "Cannot apply dest/suffix for more than one dist " +
- "file for %s goals in module %s. The list of dist files, " +
+ errorMessage := "%s: Cannot apply dest/suffix for more than one dist " +
+ "file for %q goals tag %q in module %s. The list of dist files, " +
"which should have a single element, is:\n%s"
- panic(fmt.Errorf(errorMessage, goals, name, tagPaths))
+ panic(fmt.Errorf(errorMessage, mod, goals, tag, name, tagPaths))
}
- ret = append(ret, fmt.Sprintf(".PHONY: %s\n", goals))
+ copiesForGoals := distContributions.getCopiesForGoals(goals)
- // Create dist-for-goals calls for each path in the dist'd files.
+ // Iterate over each path adding a copy instruction to copiesForGoals
for _, path := range tagPaths {
// It's possible that the Path is nil from errant modules. Be defensive here.
if path == nil {
@@ -261,15 +333,41 @@
}
}
+ copiesForGoals.addCopyInstruction(path, dest)
+ }
+ }
+
+ return distContributions
+}
+
+// generateDistContributionsForMake generates make rules that will generate the
+// dist according to the instructions in the supplied distContribution.
+func generateDistContributionsForMake(distContributions *distContributions) []string {
+ var ret []string
+ for _, d := range distContributions.copiesForGoals {
+ ret = append(ret, fmt.Sprintf(".PHONY: %s\n", d.goals))
+ // Create dist-for-goals calls for each of the copy instructions.
+ for _, c := range d.copies {
ret = append(
ret,
- fmt.Sprintf("$(call dist-for-goals,%s,%s:%s)\n", goals, path.String(), dest))
+ fmt.Sprintf("$(call dist-for-goals,%s,%s:%s)\n", d.goals, c.from.String(), c.dest))
}
}
return ret
}
+// Compute the list of Make strings to declare phony goals and dist-for-goals
+// calls from the module's dist and dists properties.
+func (a *AndroidMkEntries) GetDistForGoals(mod blueprint.Module) []string {
+ distContributions := a.getDistContributions(mod)
+ if distContributions == nil {
+ return nil
+ }
+
+ return generateDistContributionsForMake(distContributions)
+}
+
func (a *AndroidMkEntries) fillInEntries(config Config, bpPath string, mod blueprint.Module) {
a.EntryMap = make(map[string][]string)
amod := mod.(Module).base()
diff --git a/android/androidmk_test.go b/android/androidmk_test.go
index bb03378..347b92e 100644
--- a/android/androidmk_test.go
+++ b/android/androidmk_test.go
@@ -18,17 +18,77 @@
"fmt"
"io"
"reflect"
+ "strings"
"testing"
+
+ "github.com/google/blueprint/proptools"
)
type customModule struct {
ModuleBase
- data AndroidMkData
- distFiles TaggedDistFiles
+
+ properties struct {
+ Default_dist_files *string
+ Dist_output_file *bool
+ }
+
+ data AndroidMkData
+ distFiles TaggedDistFiles
+ outputFile OptionalPath
+
+ // The paths that will be used as the default dist paths if no tag is
+ // specified.
+ defaultDistPaths Paths
}
+const (
+ defaultDistFiles_None = "none"
+ defaultDistFiles_Default = "default"
+ defaultDistFiles_Tagged = "tagged"
+)
+
func (m *customModule) GenerateAndroidBuildActions(ctx ModuleContext) {
- m.distFiles = m.GenerateTaggedDistFiles(ctx)
+
+ // If the dist_output_file: true then create an output file that is stored in
+ // the OutputFile property of the AndroidMkEntry.
+ if proptools.BoolDefault(m.properties.Dist_output_file, true) {
+ path := PathForTesting("dist-output-file.out")
+ m.outputFile = OptionalPathForPath(path)
+
+ // Previous code would prioritize the DistFiles property over the OutputFile
+ // property in AndroidMkEntry when determining the default dist paths.
+ // Setting this first allows it to be overridden based on the
+ // default_dist_files setting replicating that previous behavior.
+ m.defaultDistPaths = Paths{path}
+ }
+
+ // Based on the setting of the default_dist_files property possibly create a
+ // TaggedDistFiles structure that will be stored in the DistFiles property of
+ // the AndroidMkEntry.
+ defaultDistFiles := proptools.StringDefault(m.properties.Default_dist_files, defaultDistFiles_Tagged)
+ switch defaultDistFiles {
+ case defaultDistFiles_None:
+ // Do nothing
+
+ case defaultDistFiles_Default:
+ path := PathForTesting("default-dist.out")
+ m.defaultDistPaths = Paths{path}
+ m.distFiles = MakeDefaultDistFiles(path)
+
+ case defaultDistFiles_Tagged:
+ // Module types that set AndroidMkEntry.DistFiles to the result of calling
+ // GenerateTaggedDistFiles(ctx) relied on no tag being treated as "" which
+ // meant that the default dist paths would be whatever was returned by
+ // OutputFiles(""). In order to preserve that behavior when treating no tag
+ // as being equal to DefaultDistTag this ensures that
+ // OutputFiles(DefaultDistTag) will return the same as OutputFiles("").
+ m.defaultDistPaths = PathsForTesting("one.out")
+
+ // This must be called after setting defaultDistPaths/outputFile as
+ // GenerateTaggedDistFiles calls into OutputFiles(tag) which may use those
+ // fields.
+ m.distFiles = m.GenerateTaggedDistFiles(ctx)
+ }
}
func (m *customModule) AndroidMk() AndroidMkData {
@@ -41,6 +101,12 @@
func (m *customModule) OutputFiles(tag string) (Paths, error) {
switch tag {
+ case DefaultDistTag:
+ if m.defaultDistPaths != nil {
+ return m.defaultDistPaths, nil
+ } else {
+ return nil, fmt.Errorf("default dist tag is not available")
+ }
case "":
return PathsForTesting("one.out"), nil
case ".multiple":
@@ -55,28 +121,26 @@
func (m *customModule) AndroidMkEntries() []AndroidMkEntries {
return []AndroidMkEntries{
{
- Class: "CUSTOM_MODULE",
- DistFiles: m.distFiles,
+ Class: "CUSTOM_MODULE",
+ DistFiles: m.distFiles,
+ OutputFile: m.outputFile,
},
}
}
func customModuleFactory() Module {
module := &customModule{}
+
+ module.AddProperties(&module.properties)
+
InitAndroidModule(module)
return module
}
-func TestAndroidMkSingleton_PassesUpdatedAndroidMkDataToCustomCallback(t *testing.T) {
- bp := `
- custom {
- name: "foo",
- required: ["bar"],
- host_required: ["baz"],
- target_required: ["qux"],
- }
- `
-
+// buildConfigAndCustomModuleFoo creates a config object, processes the supplied
+// bp module and then returns the config and the custom module called "foo".
+func buildConfigAndCustomModuleFoo(t *testing.T, bp string) (Config, *customModule) {
+ t.Helper()
config := TestConfig(buildDir, nil, bp, nil)
config.katiEnabled = true // Enable androidmk Singleton
@@ -90,7 +154,21 @@
_, errs = ctx.PrepareBuildActions(config)
FailIfErrored(t, errs)
- m := ctx.ModuleForTests("foo", "").Module().(*customModule)
+ module := ctx.ModuleForTests("foo", "").Module().(*customModule)
+ return config, module
+}
+
+func TestAndroidMkSingleton_PassesUpdatedAndroidMkDataToCustomCallback(t *testing.T) {
+ bp := `
+ custom {
+ name: "foo",
+ required: ["bar"],
+ host_required: ["baz"],
+ target_required: ["qux"],
+ }
+ `
+
+ _, m := buildConfigAndCustomModuleFoo(t, bp)
assertEqual := func(expected interface{}, actual interface{}) {
if !reflect.DeepEqual(expected, actual) {
@@ -102,101 +180,29 @@
assertEqual([]string{"qux"}, m.data.Target_required)
}
-func TestGetDistForGoals(t *testing.T) {
- testCases := []struct {
- bp string
- expectedAndroidMkLines []string
- }{
- {
- bp: `
- custom {
- name: "foo",
- dist: {
- targets: ["my_goal"]
- }
- }
- `,
- expectedAndroidMkLines: []string{
- ".PHONY: my_goal\n",
- "$(call dist-for-goals,my_goal,one.out:one.out)\n",
- },
- },
- {
- bp: `
- custom {
- name: "foo",
- dist: {
- targets: ["my_goal"],
- tag: ".another-tag",
- }
- }
- `,
- expectedAndroidMkLines: []string{
- ".PHONY: my_goal\n",
- "$(call dist-for-goals,my_goal,another.out:another.out)\n",
- },
- },
- {
- bp: `
- custom {
- name: "foo",
- dists: [
- {
- targets: ["my_goal"],
- tag: ".another-tag",
- },
- ],
- }
- `,
- expectedAndroidMkLines: []string{
- ".PHONY: my_goal\n",
- "$(call dist-for-goals,my_goal,another.out:another.out)\n",
- },
- },
- {
- bp: `
- custom {
- name: "foo",
- dists: [
- {
- targets: ["my_goal"],
- },
- {
- targets: ["my_second_goal", "my_third_goal"],
- },
- ],
- }
- `,
- expectedAndroidMkLines: []string{
- ".PHONY: my_goal\n",
- "$(call dist-for-goals,my_goal,one.out:one.out)\n",
- ".PHONY: my_second_goal my_third_goal\n",
- "$(call dist-for-goals,my_second_goal my_third_goal,one.out:one.out)\n",
- },
- },
- {
- bp: `
- custom {
- name: "foo",
- dist: {
- targets: ["my_goal"],
+func TestGenerateDistContributionsForMake(t *testing.T) {
+ dc := &distContributions{
+ copiesForGoals: []*copiesForGoals{
+ {
+ goals: "my_goal",
+ copies: []distCopy{
+ distCopyForTest("one.out", "one.out"),
+ distCopyForTest("two.out", "other.out"),
},
- dists: [
- {
- targets: ["my_second_goal", "my_third_goal"],
- },
- ],
- }
- `,
- expectedAndroidMkLines: []string{
- ".PHONY: my_second_goal my_third_goal\n",
- "$(call dist-for-goals,my_second_goal my_third_goal,one.out:one.out)\n",
- ".PHONY: my_goal\n",
- "$(call dist-for-goals,my_goal,one.out:one.out)\n",
},
},
- {
- bp: `
+ }
+
+ makeOutput := generateDistContributionsForMake(dc)
+
+ assertStringEquals(t, `.PHONY: my_goal
+$(call dist-for-goals,my_goal,one.out:one.out)
+$(call dist-for-goals,my_goal,two.out:other.out)
+`, strings.Join(makeOutput, ""))
+}
+
+func TestGetDistForGoals(t *testing.T) {
+ bp := `
custom {
name: "foo",
dist: {
@@ -228,64 +234,522 @@
},
],
}
- `,
- expectedAndroidMkLines: []string{
- ".PHONY: my_second_goal\n",
- "$(call dist-for-goals,my_second_goal,two.out:two.out)\n",
- "$(call dist-for-goals,my_second_goal,three/four.out:four.out)\n",
- ".PHONY: my_third_goal\n",
- "$(call dist-for-goals,my_third_goal,one.out:test/dir/one.out)\n",
- ".PHONY: my_fourth_goal\n",
- "$(call dist-for-goals,my_fourth_goal,one.out:one.suffix.out)\n",
- ".PHONY: my_fifth_goal\n",
- "$(call dist-for-goals,my_fifth_goal,one.out:new-name)\n",
- ".PHONY: my_sixth_goal\n",
- "$(call dist-for-goals,my_sixth_goal,one.out:some/dir/new-name.suffix)\n",
- ".PHONY: my_goal my_other_goal\n",
- "$(call dist-for-goals,my_goal my_other_goal,two.out:two.out)\n",
- "$(call dist-for-goals,my_goal my_other_goal,three/four.out:four.out)\n",
- },
- },
+ `
+
+ expectedAndroidMkLines := []string{
+ ".PHONY: my_second_goal\n",
+ "$(call dist-for-goals,my_second_goal,two.out:two.out)\n",
+ "$(call dist-for-goals,my_second_goal,three/four.out:four.out)\n",
+ ".PHONY: my_third_goal\n",
+ "$(call dist-for-goals,my_third_goal,one.out:test/dir/one.out)\n",
+ ".PHONY: my_fourth_goal\n",
+ "$(call dist-for-goals,my_fourth_goal,one.out:one.suffix.out)\n",
+ ".PHONY: my_fifth_goal\n",
+ "$(call dist-for-goals,my_fifth_goal,one.out:new-name)\n",
+ ".PHONY: my_sixth_goal\n",
+ "$(call dist-for-goals,my_sixth_goal,one.out:some/dir/new-name.suffix)\n",
+ ".PHONY: my_goal my_other_goal\n",
+ "$(call dist-for-goals,my_goal my_other_goal,two.out:two.out)\n",
+ "$(call dist-for-goals,my_goal my_other_goal,three/four.out:four.out)\n",
}
- for _, testCase := range testCases {
- config := TestConfig(buildDir, nil, testCase.bp, nil)
- config.katiEnabled = true // Enable androidmk Singleton
+ config, module := buildConfigAndCustomModuleFoo(t, bp)
+ entries := AndroidMkEntriesForTest(t, config, "", module)
+ if len(entries) != 1 {
+ t.Errorf("Expected a single AndroidMk entry, got %d", len(entries))
+ }
+ androidMkLines := entries[0].GetDistForGoals(module)
- ctx := NewTestContext(config)
- ctx.RegisterSingletonType("androidmk", AndroidMkSingleton)
- ctx.RegisterModuleType("custom", customModuleFactory)
- ctx.Register()
-
- _, errs := ctx.ParseFileList(".", []string{"Android.bp"})
- FailIfErrored(t, errs)
- _, errs = ctx.PrepareBuildActions(config)
- FailIfErrored(t, errs)
-
- module := ctx.ModuleForTests("foo", "").Module().(*customModule)
- entries := AndroidMkEntriesForTest(t, config, "", module)
- if len(entries) != 1 {
- t.Errorf("Expected a single AndroidMk entry, got %d", len(entries))
- }
- androidMkLines := entries[0].GetDistForGoals(module)
-
- if len(androidMkLines) != len(testCase.expectedAndroidMkLines) {
+ if len(androidMkLines) != len(expectedAndroidMkLines) {
+ t.Errorf(
+ "Expected %d AndroidMk lines, got %d:\n%v",
+ len(expectedAndroidMkLines),
+ len(androidMkLines),
+ androidMkLines,
+ )
+ }
+ for idx, line := range androidMkLines {
+ expectedLine := expectedAndroidMkLines[idx]
+ if line != expectedLine {
t.Errorf(
- "Expected %d AndroidMk lines, got %d:\n%v",
- len(testCase.expectedAndroidMkLines),
- len(androidMkLines),
- androidMkLines,
+ "Expected AndroidMk line to be '%s', got '%s'",
+ expectedLine,
+ line,
)
}
- for idx, line := range androidMkLines {
- expectedLine := testCase.expectedAndroidMkLines[idx]
- if line != expectedLine {
- t.Errorf(
- "Expected AndroidMk line to be '%s', got '%s'",
- line,
- expectedLine,
- )
+ }
+}
+
+func distCopyForTest(from, to string) distCopy {
+ return distCopy{PathForTesting(from), to}
+}
+
+func TestGetDistContributions(t *testing.T) {
+ compareContributions := func(d1 *distContributions, d2 *distContributions) error {
+ if d1 == nil || d2 == nil {
+ if d1 != d2 {
+ return fmt.Errorf("pointer mismatch, expected both to be nil but they were %p and %p", d1, d2)
+ } else {
+ return nil
}
}
+ if expected, actual := len(d1.copiesForGoals), len(d2.copiesForGoals); expected != actual {
+ return fmt.Errorf("length mismatch, expected %d found %d", expected, actual)
+ }
+
+ for i, copies1 := range d1.copiesForGoals {
+ copies2 := d2.copiesForGoals[i]
+ if expected, actual := copies1.goals, copies2.goals; expected != actual {
+ return fmt.Errorf("goals mismatch at position %d: expected %q found %q", i, expected, actual)
+ }
+
+ if expected, actual := len(copies1.copies), len(copies2.copies); expected != actual {
+ return fmt.Errorf("length mismatch in copy instructions at position %d, expected %d found %d", i, expected, actual)
+ }
+
+ for j, c1 := range copies1.copies {
+ c2 := copies2.copies[j]
+ if expected, actual := NormalizePathForTesting(c1.from), NormalizePathForTesting(c2.from); expected != actual {
+ return fmt.Errorf("paths mismatch at position %d.%d: expected %q found %q", i, j, expected, actual)
+ }
+
+ if expected, actual := c1.dest, c2.dest; expected != actual {
+ return fmt.Errorf("dest mismatch at position %d.%d: expected %q found %q", i, j, expected, actual)
+ }
+ }
+ }
+
+ return nil
}
+
+ formatContributions := func(d *distContributions) string {
+ buf := &strings.Builder{}
+ if d == nil {
+ fmt.Fprint(buf, "nil")
+ } else {
+ for _, copiesForGoals := range d.copiesForGoals {
+ fmt.Fprintf(buf, " Goals: %q {\n", copiesForGoals.goals)
+ for _, c := range copiesForGoals.copies {
+ fmt.Fprintf(buf, " %s -> %s\n", NormalizePathForTesting(c.from), c.dest)
+ }
+ fmt.Fprint(buf, " }\n")
+ }
+ }
+ return buf.String()
+ }
+
+ testHelper := func(t *testing.T, name, bp string, expectedContributions *distContributions) {
+ t.Helper()
+ t.Run(name, func(t *testing.T) {
+ t.Helper()
+
+ config, module := buildConfigAndCustomModuleFoo(t, bp)
+ entries := AndroidMkEntriesForTest(t, config, "", module)
+ if len(entries) != 1 {
+ t.Errorf("Expected a single AndroidMk entry, got %d", len(entries))
+ }
+ distContributions := entries[0].getDistContributions(module)
+
+ if err := compareContributions(expectedContributions, distContributions); err != nil {
+ t.Errorf("%s\nExpected Contributions\n%sActualContributions\n%s",
+ err,
+ formatContributions(expectedContributions),
+ formatContributions(distContributions))
+ }
+ })
+ }
+
+ testHelper(t, "dist-without-tag", `
+ custom {
+ name: "foo",
+ dist: {
+ targets: ["my_goal"]
+ }
+ }
+`,
+ &distContributions{
+ copiesForGoals: []*copiesForGoals{
+ {
+ goals: "my_goal",
+ copies: []distCopy{
+ distCopyForTest("one.out", "one.out"),
+ },
+ },
+ },
+ })
+
+ testHelper(t, "dist-with-tag", `
+ custom {
+ name: "foo",
+ dist: {
+ targets: ["my_goal"],
+ tag: ".another-tag",
+ }
+ }
+`,
+ &distContributions{
+ copiesForGoals: []*copiesForGoals{
+ {
+ goals: "my_goal",
+ copies: []distCopy{
+ distCopyForTest("another.out", "another.out"),
+ },
+ },
+ },
+ })
+
+ testHelper(t, "dists-with-tag", `
+ custom {
+ name: "foo",
+ dists: [
+ {
+ targets: ["my_goal"],
+ tag: ".another-tag",
+ },
+ ],
+ }
+`,
+ &distContributions{
+ copiesForGoals: []*copiesForGoals{
+ {
+ goals: "my_goal",
+ copies: []distCopy{
+ distCopyForTest("another.out", "another.out"),
+ },
+ },
+ },
+ })
+
+ testHelper(t, "multiple-dists-with-and-without-tag", `
+ custom {
+ name: "foo",
+ dists: [
+ {
+ targets: ["my_goal"],
+ },
+ {
+ targets: ["my_second_goal", "my_third_goal"],
+ },
+ ],
+ }
+`,
+ &distContributions{
+ copiesForGoals: []*copiesForGoals{
+ {
+ goals: "my_goal",
+ copies: []distCopy{
+ distCopyForTest("one.out", "one.out"),
+ },
+ },
+ {
+ goals: "my_second_goal my_third_goal",
+ copies: []distCopy{
+ distCopyForTest("one.out", "one.out"),
+ },
+ },
+ },
+ })
+
+ testHelper(t, "dist-plus-dists-without-tags", `
+ custom {
+ name: "foo",
+ dist: {
+ targets: ["my_goal"],
+ },
+ dists: [
+ {
+ targets: ["my_second_goal", "my_third_goal"],
+ },
+ ],
+ }
+`,
+ &distContributions{
+ copiesForGoals: []*copiesForGoals{
+ {
+ goals: "my_second_goal my_third_goal",
+ copies: []distCopy{
+ distCopyForTest("one.out", "one.out"),
+ },
+ },
+ {
+ goals: "my_goal",
+ copies: []distCopy{
+ distCopyForTest("one.out", "one.out"),
+ },
+ },
+ },
+ })
+
+ testHelper(t, "dist-plus-dists-with-tags", `
+ custom {
+ name: "foo",
+ dist: {
+ targets: ["my_goal", "my_other_goal"],
+ tag: ".multiple",
+ },
+ dists: [
+ {
+ targets: ["my_second_goal"],
+ tag: ".multiple",
+ },
+ {
+ targets: ["my_third_goal"],
+ dir: "test/dir",
+ },
+ {
+ targets: ["my_fourth_goal"],
+ suffix: ".suffix",
+ },
+ {
+ targets: ["my_fifth_goal"],
+ dest: "new-name",
+ },
+ {
+ targets: ["my_sixth_goal"],
+ dest: "new-name",
+ dir: "some/dir",
+ suffix: ".suffix",
+ },
+ ],
+ }
+`,
+ &distContributions{
+ copiesForGoals: []*copiesForGoals{
+ {
+ goals: "my_second_goal",
+ copies: []distCopy{
+ distCopyForTest("two.out", "two.out"),
+ distCopyForTest("three/four.out", "four.out"),
+ },
+ },
+ {
+ goals: "my_third_goal",
+ copies: []distCopy{
+ distCopyForTest("one.out", "test/dir/one.out"),
+ },
+ },
+ {
+ goals: "my_fourth_goal",
+ copies: []distCopy{
+ distCopyForTest("one.out", "one.suffix.out"),
+ },
+ },
+ {
+ goals: "my_fifth_goal",
+ copies: []distCopy{
+ distCopyForTest("one.out", "new-name"),
+ },
+ },
+ {
+ goals: "my_sixth_goal",
+ copies: []distCopy{
+ distCopyForTest("one.out", "some/dir/new-name.suffix"),
+ },
+ },
+ {
+ goals: "my_goal my_other_goal",
+ copies: []distCopy{
+ distCopyForTest("two.out", "two.out"),
+ distCopyForTest("three/four.out", "four.out"),
+ },
+ },
+ },
+ })
+
+ // The above test the default values of default_dist_files and use_output_file.
+
+ // The following tests explicitly test the different combinations of those settings.
+ testHelper(t, "tagged-dist-files-no-output", `
+ custom {
+ name: "foo",
+ default_dist_files: "tagged",
+ dist_output_file: false,
+ dists: [
+ {
+ targets: ["my_goal"],
+ },
+ {
+ targets: ["my_goal"],
+ tag: ".multiple",
+ },
+ ],
+ }
+`, &distContributions{
+ copiesForGoals: []*copiesForGoals{
+ {
+ goals: "my_goal",
+ copies: []distCopy{
+ distCopyForTest("one.out", "one.out"),
+ },
+ },
+ {
+ goals: "my_goal",
+ copies: []distCopy{
+ distCopyForTest("two.out", "two.out"),
+ distCopyForTest("three/four.out", "four.out"),
+ },
+ },
+ },
+ })
+
+ testHelper(t, "default-dist-files-no-output", `
+ custom {
+ name: "foo",
+ default_dist_files: "default",
+ dist_output_file: false,
+ dists: [
+ {
+ targets: ["my_goal"],
+ },
+ {
+ targets: ["my_goal"],
+ tag: ".multiple",
+ },
+ ],
+ }
+`, &distContributions{
+ copiesForGoals: []*copiesForGoals{
+ {
+ goals: "my_goal",
+ copies: []distCopy{
+ distCopyForTest("default-dist.out", "default-dist.out"),
+ },
+ },
+ {
+ goals: "my_goal",
+ copies: []distCopy{
+ distCopyForTest("two.out", "two.out"),
+ distCopyForTest("three/four.out", "four.out"),
+ },
+ },
+ },
+ })
+
+ testHelper(t, "no-dist-files-no-output", `
+ custom {
+ name: "foo",
+ default_dist_files: "none",
+ dist_output_file: false,
+ dists: [
+ // The following is silently ignored because there is not default file
+ // in either the dist files or the output file.
+ {
+ targets: ["my_goal"],
+ },
+ {
+ targets: ["my_goal"],
+ tag: ".multiple",
+ },
+ ],
+ }
+`, &distContributions{
+ copiesForGoals: []*copiesForGoals{
+ {
+ goals: "my_goal",
+ copies: []distCopy{
+ distCopyForTest("two.out", "two.out"),
+ distCopyForTest("three/four.out", "four.out"),
+ },
+ },
+ },
+ })
+
+ testHelper(t, "tagged-dist-files-default-output", `
+ custom {
+ name: "foo",
+ default_dist_files: "tagged",
+ dist_output_file: true,
+ dists: [
+ {
+ targets: ["my_goal"],
+ },
+ {
+ targets: ["my_goal"],
+ tag: ".multiple",
+ },
+ ],
+ }
+`, &distContributions{
+ copiesForGoals: []*copiesForGoals{
+ {
+ goals: "my_goal",
+ copies: []distCopy{
+ distCopyForTest("one.out", "one.out"),
+ },
+ },
+ {
+ goals: "my_goal",
+ copies: []distCopy{
+ distCopyForTest("two.out", "two.out"),
+ distCopyForTest("three/four.out", "four.out"),
+ },
+ },
+ },
+ })
+
+ testHelper(t, "default-dist-files-default-output", `
+ custom {
+ name: "foo",
+ default_dist_files: "default",
+ dist_output_file: true,
+ dists: [
+ {
+ targets: ["my_goal"],
+ },
+ {
+ targets: ["my_goal"],
+ tag: ".multiple",
+ },
+ ],
+ }
+`, &distContributions{
+ copiesForGoals: []*copiesForGoals{
+ {
+ goals: "my_goal",
+ copies: []distCopy{
+ distCopyForTest("default-dist.out", "default-dist.out"),
+ },
+ },
+ {
+ goals: "my_goal",
+ copies: []distCopy{
+ distCopyForTest("two.out", "two.out"),
+ distCopyForTest("three/four.out", "four.out"),
+ },
+ },
+ },
+ })
+
+ testHelper(t, "no-dist-files-default-output", `
+ custom {
+ name: "foo",
+ default_dist_files: "none",
+ dist_output_file: true,
+ dists: [
+ {
+ targets: ["my_goal"],
+ },
+ {
+ targets: ["my_goal"],
+ tag: ".multiple",
+ },
+ ],
+ }
+`, &distContributions{
+ copiesForGoals: []*copiesForGoals{
+ {
+ goals: "my_goal",
+ copies: []distCopy{
+ distCopyForTest("dist-output-file.out", "dist-output-file.out"),
+ },
+ },
+ {
+ goals: "my_goal",
+ copies: []distCopy{
+ distCopyForTest("two.out", "two.out"),
+ distCopyForTest("three/four.out", "four.out"),
+ },
+ },
+ },
+ })
}
diff --git a/android/arch.go b/android/arch.go
index eb651e6..df407d4 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -271,7 +271,7 @@
if _, found := commonTargetMap[name]; found {
panic(fmt.Errorf("Found Os type duplicate during OsType registration: %q", name))
} else {
- commonTargetMap[name] = Target{Os: os, Arch: Arch{ArchType: Common}}
+ commonTargetMap[name] = Target{Os: os, Arch: CommonArch}
}
osArchTypeMap[os] = archTypes
@@ -341,6 +341,10 @@
// CommonOS is a pseudo OSType for a common OS variant, which is OsType agnostic and which
// has dependencies on all the OS variants.
CommonOS = newOsType("common_os", Generic, false)
+
+ // CommonArch is the Arch for all modules that are os-specific but not arch specific,
+ // for example most Java modules.
+ CommonArch = Arch{ArchType: Common}
)
// Target specifies the OS and architecture that a module is being compiled for.
@@ -511,9 +515,6 @@
// Identifies the dependency from CommonOS variant to the os specific variants.
var commonOsToOsSpecificVariantTag = archDepTag{name: "common os to os specific"}
-// Identifies the dependency from arch variant to the common variant for a "common_first" multilib.
-var firstArchToCommonArchDepTag = archDepTag{name: "first arch to common arch"}
-
// Get the OsType specific variants for the current CommonOS variant.
//
// The returned list will only contain enabled OsType specific variants of the
@@ -667,12 +668,6 @@
addTargetProperties(m, targets[i], multiTargets, i == 0)
m.base().setArchProperties(mctx)
}
-
- if multilib == "common_first" && len(modules) >= 2 {
- for i := range modules[1:] {
- mctx.AddInterVariantDependency(firstArchToCommonArchDepTag, modules[i+1], modules[0])
- }
- }
}
// addTargetProperties annotates a variant with the Target is is being compiled for, the list
diff --git a/android/config.go b/android/config.go
index b481cac..57ab70b 100644
--- a/android/config.go
+++ b/android/config.go
@@ -14,6 +14,9 @@
package android
+// This is the primary location to write and read all configuration values and
+// product variables necessary for soong_build's operation.
+
import (
"encoding/json"
"fmt"
@@ -32,20 +35,31 @@
"android/soong/android/soongconfig"
)
+// Bool re-exports proptools.Bool for the android package.
var Bool = proptools.Bool
+
+// String re-exports proptools.String for the android package.
var String = proptools.String
+
+// StringDefault re-exports proptools.StringDefault for the android package.
var StringDefault = proptools.StringDefault
+// FutureApiLevelInt is a placeholder constant for unreleased API levels.
const FutureApiLevelInt = 10000
+// FutureApiLevel represents unreleased API levels.
var FutureApiLevel = ApiLevel{
value: "current",
number: FutureApiLevelInt,
isPreview: true,
}
-// The configuration file name
+// configFileName is the name the file containing FileConfigurableOptions from
+// soong_ui for the soong_build primary builder.
const configFileName = "soong.config"
+
+// productVariablesFileName contain the product configuration variables from soong_ui for the
+// soong_build primary builder and Kati.
const productVariablesFileName = "soong.variables"
// A FileConfigurableOptions contains options which can be configured by the
@@ -56,6 +70,8 @@
Host_bionic_arm64 *bool `json:",omitempty"`
}
+// SetDefaultConfig resets the receiving FileConfigurableOptions to default
+// values.
func (f *FileConfigurableOptions) SetDefaultConfig() {
*f = FileConfigurableOptions{}
}
@@ -65,29 +81,38 @@
*config
}
+// BuildDir returns the build output directory for the configuration.
func (c Config) BuildDir() string {
return c.buildDir
}
-// A DeviceConfig object represents the configuration for a particular device being built. For
-// now there will only be one of these, but in the future there may be multiple devices being
-// built
+// A DeviceConfig object represents the configuration for a particular device
+// being built. For now there will only be one of these, but in the future there
+// may be multiple devices being built.
type DeviceConfig struct {
*deviceConfig
}
+// VendorConfig represents the configuration for vendor-specific behavior.
type VendorConfig soongconfig.SoongConfig
+// Definition of general build configuration for soong_build. Some of these
+// configuration values are generated from soong_ui for soong_build,
+// communicated over JSON files like soong.config or soong.variables.
type config struct {
+ // Options configurable with soong.confg
FileConfigurableOptions
+
+ // Options configurable with soong.variables
productVariables productVariables
// Only available on configs created by TestConfig
TestProductVariables *productVariables
+ // A specialized context object for Bazel/Soong mixed builds and migration
+ // purposes.
BazelContext BazelContext
- PrimaryBuilder string
ConfigFileName string
ProductVariablesFileName string
@@ -97,8 +122,8 @@
AndroidCommonTarget Target // the Target for common modules for the Android device
AndroidFirstDeviceTarget Target // the first Target for modules for the Android device
- // multilibConflicts for an ArchType is true if there is earlier configured device architecture with the same
- // multilib value.
+ // multilibConflicts for an ArchType is true if there is earlier configured
+ // device architecture with the same multilib value.
multilibConflicts map[ArchType]bool
deviceConfig *deviceConfig
@@ -128,6 +153,8 @@
// in tests when a path doesn't exist.
testAllowNonExistentPaths bool
+ // The list of files that when changed, must invalidate soong_build to
+ // regenerate build.ninja.
ninjaFileDepsSet sync.Map
OncePer
@@ -151,7 +178,8 @@
return loadFromConfigFile(&config.productVariables, absolutePath(config.ProductVariablesFileName))
}
-// loads configuration options from a JSON file in the cwd.
+// loadFromConfigFile loads and decodes configuration options from a JSON file
+// in the current working directory.
func loadFromConfigFile(configurable jsonConfigurable, filename string) error {
// Try to open the file
configFileReader, err := os.Open(filename)
@@ -191,7 +219,7 @@
f, err := ioutil.TempFile(filepath.Dir(filename), "config")
if err != nil {
- return fmt.Errorf("cannot create empty config file %s: %s\n", filename, err.Error())
+ return fmt.Errorf("cannot create empty config file %s: %s", filename, err.Error())
}
defer os.Remove(f.Name())
defer f.Close()
@@ -223,7 +251,7 @@
}
}
-// TestConfig returns a Config object suitable for using for tests
+// TestConfig returns a Config object for testing.
func TestConfig(buildDir string, env map[string]string, bp string, fs map[string][]byte) Config {
envCopy := make(map[string]string)
for k, v := range env {
@@ -269,6 +297,9 @@
return Config{config}
}
+// TestArchConfigNativeBridge returns a Config object suitable for using
+// for tests that need to run the arch mutator for native bridge supported
+// archs.
func TestArchConfigNativeBridge(buildDir string, env map[string]string, bp string, fs map[string][]byte) Config {
testConfig := TestArchConfig(buildDir, env, bp, fs)
config := testConfig.config
@@ -283,6 +314,8 @@
return testConfig
}
+// TestArchConfigFuchsia returns a Config object suitable for using for
+// tests that need to run the arch mutator for the Fuchsia arch.
func TestArchConfigFuchsia(buildDir string, env map[string]string, bp string, fs map[string][]byte) Config {
testConfig := TestConfig(buildDir, env, bp, fs)
config := testConfig.config
@@ -299,7 +332,8 @@
return testConfig
}
-// TestConfig returns a Config object suitable for using for tests that need to run the arch mutator
+// TestArchConfig returns a Config object suitable for using for tests that
+// need to run the arch mutator.
func TestArchConfig(buildDir string, env map[string]string, bp string, fs map[string][]byte) Config {
testConfig := TestConfig(buildDir, env, bp, fs)
config := testConfig.config
@@ -331,10 +365,10 @@
return testConfig
}
-// Returns a config object which is "reset" for another bootstrap run.
-// Only per-run data is reset. Data which needs to persist across multiple
-// runs in the same program execution is carried over (such as Bazel context
-// or environment deps).
+// ConfigForAdditionalRun is a config object which is "reset" for another
+// bootstrap run. Only per-run data is reset. Data which needs to persist across
+// multiple runs in the same program execution is carried over (such as Bazel
+// context or environment deps).
func ConfigForAdditionalRun(c Config) (Config, error) {
newConfig, err := NewConfig(c.srcDir, c.buildDir, c.moduleListFile)
if err != nil {
@@ -345,10 +379,10 @@
return newConfig, nil
}
-// New creates a new Config object. The srcDir argument specifies the path to
-// the root source directory. It also loads the config file, if found.
+// NewConfig creates a new Config object. The srcDir argument specifies the path
+// to the root source directory. It also loads the config file, if found.
func NewConfig(srcDir, buildDir string, moduleListFile string) (Config, error) {
- // Make a config with default options
+ // Make a config with default options.
config := &config{
ConfigFileName: filepath.Join(buildDir, configFileName),
ProductVariablesFileName: filepath.Join(buildDir, productVariablesFileName),
@@ -394,6 +428,8 @@
config.katiEnabled = true
}
+ // Sets up the map of target OSes to the finer grained compilation targets
+ // that are configured from the product variables.
targets, err := decodeTargetProductVariables(config)
if err != nil {
return Config{}, err
@@ -427,9 +463,14 @@
multilib[target.Arch.ArchType.Multilib] = true
}
+ // Map of OS to compilation targets.
config.Targets = targets
+
+ // Compilation targets for host tools.
config.BuildOSTarget = config.Targets[BuildOs][0]
config.BuildOSCommonTarget = getCommonTargets(config.Targets[BuildOs])[0]
+
+ // Compilation targets for Android.
if len(config.Targets[Android]) > 0 {
config.AndroidCommonTarget = getCommonTargets(config.Targets[Android])[0]
config.AndroidFirstDeviceTarget = firstTarget(config.Targets[Android], "lib64", "lib32")[0]
@@ -444,13 +485,9 @@
Bool(config.productVariables.ClangCoverage))
config.BazelContext, err = NewBazelContext(config)
- if err != nil {
- return Config{}, err
- }
- return Config{config}, nil
-}
-var TestConfigOsFs = map[string][]byte{}
+ return Config{config}, err
+}
// mockFileSystem replaces all reads with accesses to the provided map of
// filenames to contents stored as a byte slice.
@@ -486,12 +523,15 @@
return c.stopBefore
}
+// SetStopBefore configures soong_build to exit earlier at a specific point.
func (c *config) SetStopBefore(stopBefore bootstrap.StopBefore) {
c.stopBefore = stopBefore
}
var _ bootstrap.ConfigStopBefore = (*config)(nil)
+// BlueprintToolLocation returns the directory containing build system tools
+// from Blueprint, like soong_zip and merge_zips.
func (c *config) BlueprintToolLocation() string {
return filepath.Join(c.buildDir, "host", c.PrebuiltOS(), "bin")
}
@@ -534,7 +574,7 @@
"for the full list of allowed host tools on your system.", name))
}
-// PrebuiltOS returns the name of the host OS used in prebuilts directories
+// PrebuiltOS returns the name of the host OS used in prebuilts directories.
func (c *config) PrebuiltOS() string {
switch runtime.GOOS {
case "linux":
@@ -551,10 +591,14 @@
return fmt.Sprintf("%s/prebuilts/go/%s", c.srcDir, c.PrebuiltOS())
}
+// PrebuiltBuildTool returns the path to a tool in the prebuilts directory containing
+// checked-in tools, like Kati, Ninja or Toybox, for the current host OS.
func (c *config) PrebuiltBuildTool(ctx PathContext, tool string) Path {
return PathForSource(ctx, "prebuilts/build-tools", c.PrebuiltOS(), "bin", tool)
}
+// CpPreserveSymlinksFlags returns the host-specific flag for the cp(1) command
+// to preserve symlinks.
func (c *config) CpPreserveSymlinksFlags() string {
switch runtime.GOOS {
case "darwin":
@@ -602,6 +646,8 @@
return value == "0" || value == "n" || value == "no" || value == "off" || value == "false"
}
+// EnvDeps returns the environment variables this build depends on. The first
+// call to this function blocks future reads from the environment.
func (c *config) EnvDeps() map[string]string {
c.envLock.Lock()
defer c.envLock.Unlock()
@@ -617,11 +663,18 @@
return String(c.productVariables.BuildId)
}
+// BuildNumberFile returns the path to a text file containing metadata
+// representing the current build's number.
+//
+// Rules that want to reference the build number should read from this file
+// without depending on it. They will run whenever their other dependencies
+// require them to run and get the current build number. This ensures they don't
+// rebuild on every incremental build when the build number changes.
func (c *config) BuildNumberFile(ctx PathContext) Path {
return PathForOutput(ctx, String(c.productVariables.BuildNumberFile))
}
-// DeviceName returns the name of the current device target
+// DeviceName returns the name of the current device target.
// TODO: take an AndroidModuleContext to select the device name for multi-device builds
func (c *config) DeviceName() string {
return *c.productVariables.DeviceName
@@ -693,19 +746,20 @@
return append(levels, c.PreviewApiLevels()...)
}
+// DefaultAppTargetSdk returns the API level that platform apps are targeting.
+// This converts a codename to the exact ApiLevel it represents.
func (c *config) DefaultAppTargetSdk(ctx EarlyModuleContext) ApiLevel {
if Bool(c.productVariables.Platform_sdk_final) {
return c.PlatformSdkVersion()
- } else {
- codename := c.PlatformSdkCodename()
- if codename == "" {
- return NoneApiLevel
- }
- if codename == "REL" {
- panic("Platform_sdk_codename should not be REL when Platform_sdk_final is true")
- }
- return ApiLevelOrPanic(ctx, codename)
}
+ codename := c.PlatformSdkCodename()
+ if codename == "" {
+ return NoneApiLevel
+ }
+ if codename == "REL" {
+ panic("Platform_sdk_codename should not be REL when Platform_sdk_final is true")
+ }
+ return ApiLevelOrPanic(ctx, codename)
}
func (c *config) AppsDefaultVersionName() string {
@@ -737,19 +791,17 @@
defaultCert := String(c.productVariables.DefaultAppCertificate)
if defaultCert != "" {
return PathForSource(ctx, filepath.Dir(defaultCert))
- } else {
- return PathForSource(ctx, "build/make/target/product/security")
}
+ return PathForSource(ctx, "build/make/target/product/security")
}
func (c *config) DefaultAppCertificate(ctx PathContext) (pem, key SourcePath) {
defaultCert := String(c.productVariables.DefaultAppCertificate)
if defaultCert != "" {
return PathForSource(ctx, defaultCert+".x509.pem"), PathForSource(ctx, defaultCert+".pk8")
- } else {
- defaultDir := c.DefaultAppCertificateDir(ctx)
- return defaultDir.Join(ctx, "testkey.x509.pem"), defaultDir.Join(ctx, "testkey.pk8")
}
+ defaultDir := c.DefaultAppCertificateDir(ctx)
+ return defaultDir.Join(ctx, "testkey.x509.pem"), defaultDir.Join(ctx, "testkey.pk8")
}
func (c *config) ApexKeyDir(ctx ModuleContext) SourcePath {
@@ -759,12 +811,14 @@
// When defaultCert is unset or is set to the testkeys path, use the APEX keys
// that is under the module dir
return pathForModuleSrc(ctx)
- } else {
- // If not, APEX keys are under the specified directory
- return PathForSource(ctx, filepath.Dir(defaultCert))
}
+ // If not, APEX keys are under the specified directory
+ return PathForSource(ctx, filepath.Dir(defaultCert))
}
+// AllowMissingDependencies configures Blueprint/Soong to not fail when modules
+// are configured to depend on non-existent modules. Note that this does not
+// affect missing input dependencies at the Ninja level.
func (c *config) AllowMissingDependencies() bool {
return Bool(c.productVariables.Allow_missing_dependencies)
}
@@ -834,9 +888,8 @@
func (c *config) EnableCFI() bool {
if c.productVariables.EnableCFI == nil {
return true
- } else {
- return *c.productVariables.EnableCFI
}
+ return *c.productVariables.EnableCFI
}
func (c *config) DisableScudo() bool {
@@ -881,11 +934,13 @@
return c.IsEnvTrue("RUN_ERROR_PRONE")
}
+// XrefCorpusName returns the Kythe cross-reference corpus name.
func (c *config) XrefCorpusName() string {
return c.Getenv("XREF_CORPUS")
}
-// Returns Compilation Unit encoding to use. Can be 'json' (default), 'proto' or 'all'.
+// XrefCuEncoding returns the compilation unit encoding to use for Kythe code
+// xrefs. Can be 'json' (default), 'proto' or 'all'.
func (c *config) XrefCuEncoding() string {
if enc := c.Getenv("KYTHE_KZIP_ENCODING"); enc != "" {
return enc
@@ -920,6 +975,10 @@
return Bool(c.productVariables.ArtUseReadBarrier)
}
+// Enforce Runtime Resource Overlays for a module. RROs supersede static RROs,
+// but some modules still depend on it.
+//
+// More info: https://source.android.com/devices/architecture/rros
func (c *config) EnforceRROForModule(name string) bool {
enforceList := c.productVariables.EnforceRROTargets
// TODO(b/150820813) Some modules depend on static overlay, remove this after eliminating the dependency.
@@ -966,6 +1025,9 @@
return c.productVariables.ModulesLoadedByPrivilegedModules
}
+// DexpreoptGlobalConfigPath returns the path to the dexpreopt.config file in
+// the output directory, if it was created during the product configuration
+// phase by Kati.
func (c *config) DexpreoptGlobalConfigPath(ctx PathContext) OptionalPath {
if c.productVariables.DexpreoptGlobalConfig == nil {
return OptionalPathForPath(nil)
@@ -974,6 +1036,12 @@
pathForBuildToolDep(ctx, *c.productVariables.DexpreoptGlobalConfig))
}
+// DexpreoptGlobalConfig returns the raw byte contents of the dexpreopt global
+// configuration. Since the configuration file was created by Kati during
+// product configuration (externally of soong_build), it's not tracked, so we
+// also manually add a Ninja file dependency on the configuration file to the
+// rule that creates the main build.ninja file. This ensures that build.ninja is
+// regenerated correctly if dexpreopt.config changes.
func (c *config) DexpreoptGlobalConfig(ctx PathContext) ([]byte, error) {
path := c.DexpreoptGlobalConfigPath(ctx)
if !path.Valid() {
@@ -1253,6 +1321,14 @@
return Bool(c.productVariables.EnforceProductPartitionInterface)
}
+func (c *config) EnforceInterPartitionJavaSdkLibrary() bool {
+ return Bool(c.productVariables.EnforceInterPartitionJavaSdkLibrary)
+}
+
+func (c *config) InterPartitionJavaLibraryAllowList() []string {
+ return c.productVariables.InterPartitionJavaLibraryAllowList
+}
+
func (c *config) InstallExtraFlattenedApexes() bool {
return Bool(c.productVariables.InstallExtraFlattenedApexes)
}
@@ -1332,26 +1408,31 @@
// - "system_ext:foo"
//
type ConfiguredJarList struct {
- apexes []string // A list of apex components.
- jars []string // A list of jar components.
+ // A list of apex components, which can be an apex name,
+ // or special names like "platform" or "system_ext".
+ apexes []string
+
+ // A list of jar module name components.
+ jars []string
}
-// The length of the list.
+// Len returns the length of the list of jars.
func (l *ConfiguredJarList) Len() int {
return len(l.jars)
}
-// Jar component of idx-th pair on the list.
+// Jar returns the idx-th jar component of (apex, jar) pairs.
func (l *ConfiguredJarList) Jar(idx int) string {
return l.jars[idx]
}
-// Apex component of idx-th pair on the list.
+// Apex returns the idx-th apex component of (apex, jar) pairs.
func (l *ConfiguredJarList) Apex(idx int) string {
return l.apexes[idx]
}
-// If the list contains a pair with the given jar.
+// ContainsJar returns true if the (apex, jar) pairs contains a pair with the
+// given jar module name.
func (l *ConfiguredJarList) ContainsJar(jar string) bool {
return InList(jar, l.jars)
}
@@ -1366,7 +1447,8 @@
return false
}
-// Index of the first pair with the given jar on the list, or -1 if none.
+// IndexOfJar returns the first pair with the given jar name on the list, or -1
+// if not found.
func (l *ConfiguredJarList) IndexOfJar(jar string) int {
return IndexList(jar, l.jars)
}
@@ -1394,7 +1476,7 @@
return ConfiguredJarList{apexes, jars}
}
-// Filter out sublist.
+// RemoveList filters out a list of (apex, jar) pairs from the receiving list of pairs.
func (l *ConfiguredJarList) RemoveList(list ConfiguredJarList) ConfiguredJarList {
apexes := make([]string, 0, l.Len())
jars := make([]string, 0, l.Len())
@@ -1410,12 +1492,14 @@
return ConfiguredJarList{apexes, jars}
}
-// A copy of the list of strings containing jar components.
+// CopyOfJars returns a copy of the list of strings containing jar module name
+// components.
func (l *ConfiguredJarList) CopyOfJars() []string {
return CopyOf(l.jars)
}
-// A copy of the list of strings with colon-separated (apex, jar) pairs.
+// CopyOfApexJarPairs returns a copy of the list of strings with colon-separated
+// (apex, jar) pairs.
func (l *ConfiguredJarList) CopyOfApexJarPairs() []string {
pairs := make([]string, 0, l.Len())
@@ -1427,7 +1511,7 @@
return pairs
}
-// A list of build paths based on the given directory prefix.
+// BuildPaths returns a list of build paths based on the given directory prefix.
func (l *ConfiguredJarList) BuildPaths(ctx PathContext, dir OutputPath) WritablePaths {
paths := make(WritablePaths, l.Len())
for i, jar := range l.jars {
@@ -1436,7 +1520,8 @@
return paths
}
-// Called when loading configuration from JSON into a configuration structure.
+// UnmarshalJSON converts JSON configuration from raw bytes into a
+// ConfiguredJarList structure.
func (l *ConfiguredJarList) UnmarshalJSON(b []byte) error {
// Try and unmarshal into a []string each item of which contains a pair
// <apex>:<jar>.
@@ -1456,16 +1541,19 @@
return nil
}
+// ModuleStem hardcodes the stem of framework-minus-apex to return "framework".
+//
+// TODO(b/139391334): hard coded until we find a good way to query the stem of a
+// module before any other mutators are run.
func ModuleStem(module string) string {
- // b/139391334: the stem of framework-minus-apex is framework. This is hard coded here until we
- // find a good way to query the stem of a module before any other mutators are run.
if module == "framework-minus-apex" {
return "framework"
}
return module
}
-// A list of on-device paths.
+// DevicePaths computes the on-device paths for the list of (apex, jar) pairs,
+// based on the operating system.
func (l *ConfiguredJarList) DevicePaths(cfg Config, ostype OsType) []string {
paths := make([]string, l.Len())
for i, jar := range l.jars {
@@ -1526,6 +1614,8 @@
}
}
+// CreateTestConfiguredJarList is a function to create ConfiguredJarList for
+// tests.
func CreateTestConfiguredJarList(list []string) ConfiguredJarList {
apexes, jars, err := splitListOfPairsIntoPairOfLists(list)
if err != nil {
@@ -1535,6 +1625,7 @@
return ConfiguredJarList{apexes, jars}
}
+// EmptyConfiguredJarList returns an empty jar list.
func EmptyConfiguredJarList() ConfiguredJarList {
return ConfiguredJarList{}
}
@@ -1544,8 +1635,7 @@
func (c *config) BootJars() []string {
return c.Once(earlyBootJarsKey, func() interface{} {
list := c.productVariables.BootJars.CopyOfJars()
- list = append(list, c.productVariables.UpdatableBootJars.CopyOfJars()...)
- return list
+ return append(list, c.productVariables.UpdatableBootJars.CopyOfJars()...)
}).([]string)
}
diff --git a/android/csuite_config.go b/android/csuite_config.go
index 15c518a..bf24d98 100644
--- a/android/csuite_config.go
+++ b/android/csuite_config.go
@@ -14,11 +14,6 @@
package android
-import (
- "fmt"
- "io"
-)
-
func init() {
RegisterModuleType("csuite_config", CSuiteConfigFactory)
}
@@ -38,22 +33,21 @@
me.OutputFilePath = PathForModuleOut(ctx, me.BaseModuleName()).OutputPath
}
-func (me *CSuiteConfig) AndroidMk() AndroidMkData {
- androidMkData := AndroidMkData{
+func (me *CSuiteConfig) AndroidMkEntries() []AndroidMkEntries {
+ androidMkEntries := AndroidMkEntries{
Class: "FAKE",
Include: "$(BUILD_SYSTEM)/suite_host_config.mk",
OutputFile: OptionalPathForPath(me.OutputFilePath),
}
- androidMkData.Extra = []AndroidMkExtraFunc{
- func(w io.Writer, outputFile Path) {
+ androidMkEntries.ExtraEntries = []AndroidMkExtraEntriesFunc{
+ func(entries *AndroidMkEntries) {
if me.properties.Test_config != nil {
- fmt.Fprintf(w, "LOCAL_TEST_CONFIG := %s\n",
- *me.properties.Test_config)
+ entries.SetString("LOCAL_TEST_CONFIG", *me.properties.Test_config)
}
- fmt.Fprintln(w, "LOCAL_COMPATIBILITY_SUITE := csuite")
+ entries.AddCompatibilityTestSuites("csuite")
},
}
- return androidMkData
+ return []AndroidMkEntries{androidMkEntries}
}
func InitCSuiteConfigModule(me *CSuiteConfig) {
diff --git a/android/deptag.go b/android/deptag.go
new file mode 100644
index 0000000..be5c35c
--- /dev/null
+++ b/android/deptag.go
@@ -0,0 +1,45 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import "github.com/google/blueprint"
+
+// Dependency tags can implement this interface and return true from InstallDepNeeded to annotate
+// that the installed files of the parent should depend on the installed files of the child.
+type InstallNeededDependencyTag interface {
+ // If InstallDepNeeded returns true then the installed files of the parent will depend on the
+ // installed files of the child.
+ InstallDepNeeded() bool
+}
+
+// Dependency tags can embed this struct to annotate that the installed files of the parent should
+// depend on the installed files of the child.
+type InstallAlwaysNeededDependencyTag struct{}
+
+func (i InstallAlwaysNeededDependencyTag) InstallDepNeeded() bool {
+ return true
+}
+
+var _ InstallNeededDependencyTag = InstallAlwaysNeededDependencyTag{}
+
+// IsInstallDepNeeded returns true if the dependency tag implements the InstallNeededDependencyTag
+// interface and the InstallDepNeeded returns true, meaning that the installed files of the parent
+// should depend on the installed files of the child.
+func IsInstallDepNeeded(tag blueprint.DependencyTag) bool {
+ if i, ok := tag.(InstallNeededDependencyTag); ok {
+ return i.InstallDepNeeded()
+ }
+ return false
+}
diff --git a/android/deptag_test.go b/android/deptag_test.go
new file mode 100644
index 0000000..bdd449e
--- /dev/null
+++ b/android/deptag_test.go
@@ -0,0 +1,135 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import (
+ "testing"
+
+ "github.com/google/blueprint"
+)
+
+type testInstallDependencyTagModule struct {
+ ModuleBase
+ Properties struct {
+ Install_deps []string
+ Deps []string
+ }
+}
+
+func (t *testInstallDependencyTagModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+ outputFile := PathForModuleOut(ctx, "out")
+ ctx.Build(pctx, BuildParams{
+ Rule: Touch,
+ Output: outputFile,
+ })
+ ctx.InstallFile(PathForModuleInstall(ctx), ctx.ModuleName(), outputFile)
+}
+
+var testInstallDependencyTagAlwaysDepTag = struct {
+ blueprint.DependencyTag
+ InstallAlwaysNeededDependencyTag
+}{}
+
+var testInstallDependencyTagNeverDepTag = struct {
+ blueprint.DependencyTag
+}{}
+
+func (t *testInstallDependencyTagModule) DepsMutator(ctx BottomUpMutatorContext) {
+ ctx.AddVariationDependencies(nil, testInstallDependencyTagAlwaysDepTag, t.Properties.Install_deps...)
+ ctx.AddVariationDependencies(nil, testInstallDependencyTagNeverDepTag, t.Properties.Deps...)
+}
+
+func testInstallDependencyTagModuleFactory() Module {
+ module := &testInstallDependencyTagModule{}
+ InitAndroidArchModule(module, HostAndDeviceDefault, MultilibCommon)
+ module.AddProperties(&module.Properties)
+ return module
+}
+
+func TestInstallDependencyTag(t *testing.T) {
+ bp := `
+ test_module {
+ name: "foo",
+ deps: ["dep"],
+ install_deps: ["install_dep"],
+ }
+
+ test_module {
+ name: "install_dep",
+ install_deps: ["transitive"],
+ }
+
+ test_module {
+ name: "transitive",
+ }
+
+ test_module {
+ name: "dep",
+ }
+ `
+
+ config := TestArchConfig(buildDir, nil, bp, nil)
+ ctx := NewTestArchContext(config)
+
+ ctx.RegisterModuleType("test_module", testInstallDependencyTagModuleFactory)
+
+ ctx.Register()
+ _, errs := ctx.ParseFileList(".", []string{"Android.bp"})
+ FailIfErrored(t, errs)
+ _, errs = ctx.PrepareBuildActions(config)
+ FailIfErrored(t, errs)
+
+ hostFoo := ctx.ModuleForTests("foo", config.BuildOSCommonTarget.String()).Description("install")
+ hostInstallDep := ctx.ModuleForTests("install_dep", config.BuildOSCommonTarget.String()).Description("install")
+ hostTransitive := ctx.ModuleForTests("transitive", config.BuildOSCommonTarget.String()).Description("install")
+ hostDep := ctx.ModuleForTests("dep", config.BuildOSCommonTarget.String()).Description("install")
+
+ if g, w := hostFoo.Implicits.Strings(), hostInstallDep.Output.String(); !InList(w, g) {
+ t.Errorf("expected host dependency %q, got %q", w, g)
+ }
+
+ if g, w := hostFoo.Implicits.Strings(), hostTransitive.Output.String(); !InList(w, g) {
+ t.Errorf("expected host dependency %q, got %q", w, g)
+ }
+
+ if g, w := hostInstallDep.Implicits.Strings(), hostTransitive.Output.String(); !InList(w, g) {
+ t.Errorf("expected host dependency %q, got %q", w, g)
+ }
+
+ if g, w := hostFoo.Implicits.Strings(), hostDep.Output.String(); InList(w, g) {
+ t.Errorf("expected no host dependency %q, got %q", w, g)
+ }
+
+ deviceFoo := ctx.ModuleForTests("foo", "android_common").Description("install")
+ deviceInstallDep := ctx.ModuleForTests("install_dep", "android_common").Description("install")
+ deviceTransitive := ctx.ModuleForTests("transitive", "android_common").Description("install")
+ deviceDep := ctx.ModuleForTests("dep", "android_common").Description("install")
+
+ if g, w := deviceFoo.OrderOnly.Strings(), deviceInstallDep.Output.String(); !InList(w, g) {
+ t.Errorf("expected device dependency %q, got %q", w, g)
+ }
+
+ if g, w := deviceFoo.OrderOnly.Strings(), deviceTransitive.Output.String(); !InList(w, g) {
+ t.Errorf("expected device dependency %q, got %q", w, g)
+ }
+
+ if g, w := deviceInstallDep.OrderOnly.Strings(), deviceTransitive.Output.String(); !InList(w, g) {
+ t.Errorf("expected device dependency %q, got %q", w, g)
+ }
+
+ if g, w := deviceFoo.OrderOnly.Strings(), deviceDep.Output.String(); InList(w, g) {
+ t.Errorf("expected no device dependency %q, got %q", w, g)
+ }
+}
diff --git a/android/module.go b/android/module.go
index 7a05096..d680baa 100644
--- a/android/module.go
+++ b/android/module.go
@@ -503,8 +503,12 @@
// A suffix to add to the artifact file name (before any extension).
Suffix *string `android:"arch_variant"`
- // A string tag to select the OutputFiles associated with the tag. Defaults to the
- // the empty "" string.
+ // A string tag to select the OutputFiles associated with the tag.
+ //
+ // If no tag is specified then it will select the default dist paths provided
+ // by the module type. If a tag of "" is specified then it will return the
+ // default output files provided by the modules, i.e. the result of calling
+ // OutputFiles("").
Tag *string `android:"arch_variant"`
}
@@ -733,9 +737,45 @@
Dists []Dist `android:"arch_variant"`
}
+// The key to use in TaggedDistFiles when a Dist structure does not specify a
+// tag property. This intentionally does not use "" as the default because that
+// would mean that an empty tag would have a different meaning when used in a dist
+// structure that when used to reference a specific set of output paths using the
+// :module{tag} syntax, which passes tag to the OutputFiles(tag) method.
+const DefaultDistTag = "<default-dist-tag>"
+
// A map of OutputFile tag keys to Paths, for disting purposes.
type TaggedDistFiles map[string]Paths
+// addPathsForTag adds a mapping from the tag to the paths. If the map is nil
+// then it will create a map, update it and then return it. If a mapping already
+// exists for the tag then the paths are appended to the end of the current list
+// of paths, ignoring any duplicates.
+func (t TaggedDistFiles) addPathsForTag(tag string, paths ...Path) TaggedDistFiles {
+ if t == nil {
+ t = make(TaggedDistFiles)
+ }
+
+ for _, distFile := range paths {
+ if distFile != nil && !t[tag].containsPath(distFile) {
+ t[tag] = append(t[tag], distFile)
+ }
+ }
+
+ return t
+}
+
+// merge merges the entries from the other TaggedDistFiles object into this one.
+// If the TaggedDistFiles is nil then it will create a new instance, merge the
+// other into it, and then return it.
+func (t TaggedDistFiles) merge(other TaggedDistFiles) TaggedDistFiles {
+ for tag, paths := range other {
+ t = t.addPathsForTag(tag, paths...)
+ }
+
+ return t
+}
+
func MakeDefaultDistFiles(paths ...Path) TaggedDistFiles {
for _, path := range paths {
if path == nil {
@@ -744,7 +784,7 @@
}
// The default OutputFile tag is the empty "" string.
- return TaggedDistFiles{"": paths}
+ return TaggedDistFiles{DefaultDistTag: paths}
}
type hostAndDeviceProperties struct {
@@ -970,6 +1010,9 @@
noticeFiles Paths
phonies map[string]Paths
+ // The files to copy to the dist as explicitly specified in the .bp file.
+ distFiles TaggedDistFiles
+
// Used by buildTargetSingleton to create checkbuild and per-directory build targets
// Only set on the final variant of each module
installTarget WritablePath
@@ -1071,23 +1114,30 @@
}
func (m *ModuleBase) GenerateTaggedDistFiles(ctx BaseModuleContext) TaggedDistFiles {
- distFiles := make(TaggedDistFiles)
+ var distFiles TaggedDistFiles
for _, dist := range m.Dists() {
- var tag string
- var distFilesForTag Paths
- if dist.Tag == nil {
- tag = ""
- } else {
- tag = *dist.Tag
- }
- distFilesForTag, err := m.base().module.(OutputFileProducer).OutputFiles(tag)
- if err != nil {
- ctx.PropertyErrorf("dist.tag", "%s", err.Error())
- }
- for _, distFile := range distFilesForTag {
- if distFile != nil && !distFiles[tag].containsPath(distFile) {
- distFiles[tag] = append(distFiles[tag], distFile)
+ // If no tag is specified then it means to use the default dist paths so use
+ // the special tag name which represents that.
+ tag := proptools.StringDefault(dist.Tag, DefaultDistTag)
+
+ if outputFileProducer, ok := m.module.(OutputFileProducer); ok {
+ // Call the OutputFiles(tag) method to get the paths associated with the tag.
+ distFilesForTag, err := outputFileProducer.OutputFiles(tag)
+
+ // If the tag was not supported and is not DefaultDistTag then it is an error.
+ // Failing to find paths for DefaultDistTag is not an error. It just means
+ // that the module type requires the legacy behavior.
+ if err != nil && tag != DefaultDistTag {
+ ctx.PropertyErrorf("dist.tag", "%s", err.Error())
}
+
+ distFiles = distFiles.addPathsForTag(tag, distFilesForTag...)
+ } else if tag != DefaultDistTag {
+ // If the tag was specified then it is an error if the module does not
+ // implement OutputFileProducer because there is no other way of accessing
+ // the paths for the specified tag.
+ ctx.PropertyErrorf("dist.tag",
+ "tag %s not supported because the module does not implement OutputFileProducer", tag)
}
}
@@ -1286,14 +1336,18 @@
return m.commonProperties.NamespaceExportedToMake
}
+// computeInstallDeps finds the installed paths of all dependencies that have a dependency
+// tag that is annotated as needing installation via the IsInstallDepNeeded method.
func (m *ModuleBase) computeInstallDeps(ctx blueprint.ModuleContext) InstallPaths {
-
var result InstallPaths
- // TODO(ccross): we need to use WalkDeps and have some way to know which dependencies require installation
- ctx.VisitDepsDepthFirst(func(m blueprint.Module) {
- if a, ok := m.(Module); ok {
- result = append(result, a.FilesToInstall()...)
+ ctx.WalkDeps(func(child, parent blueprint.Module) bool {
+ if a, ok := child.(Module); ok {
+ if IsInstallDepNeeded(ctx.OtherModuleDependencyTag(child)) {
+ result = append(result, a.FilesToInstall()...)
+ return true
+ }
}
+ return false
})
return result
@@ -1575,22 +1629,9 @@
ctx.Variable(pctx, "moduleDescSuffix", s)
// Some common property checks for properties that will be used later in androidmk.go
- if m.distProperties.Dist.Dest != nil {
- _, err := validateSafePath(*m.distProperties.Dist.Dest)
- if err != nil {
- ctx.PropertyErrorf("dist.dest", "%s", err.Error())
- }
- }
- if m.distProperties.Dist.Dir != nil {
- _, err := validateSafePath(*m.distProperties.Dist.Dir)
- if err != nil {
- ctx.PropertyErrorf("dist.dir", "%s", err.Error())
- }
- }
- if m.distProperties.Dist.Suffix != nil {
- if strings.Contains(*m.distProperties.Dist.Suffix, "/") {
- ctx.PropertyErrorf("dist.suffix", "Suffix may not contain a '/' character.")
- }
+ checkDistProperties(ctx, "dist", &m.distProperties.Dist)
+ for i, _ := range m.distProperties.Dists {
+ checkDistProperties(ctx, fmt.Sprintf("dists[%d]", i), &m.distProperties.Dists[i])
}
if m.Enabled() {
@@ -1627,6 +1668,15 @@
return
}
+ // Create the set of tagged dist files after calling GenerateAndroidBuildActions
+ // as GenerateTaggedDistFiles() calls OutputFiles(tag) and so relies on the
+ // output paths being set which must be done before or during
+ // GenerateAndroidBuildActions.
+ m.distFiles = m.GenerateTaggedDistFiles(ctx)
+ if ctx.Failed() {
+ return
+ }
+
m.installFiles = append(m.installFiles, ctx.installFiles...)
m.checkbuildFiles = append(m.checkbuildFiles, ctx.checkbuildFiles...)
m.packagingSpecs = append(m.packagingSpecs, ctx.packagingSpecs...)
@@ -1655,6 +1705,32 @@
m.variables = ctx.variables
}
+// Check the supplied dist structure to make sure that it is valid.
+//
+// property - the base property, e.g. dist or dists[1], which is combined with the
+// name of the nested property to produce the full property, e.g. dist.dest or
+// dists[1].dir.
+func checkDistProperties(ctx *moduleContext, property string, dist *Dist) {
+ if dist.Dest != nil {
+ _, err := validateSafePath(*dist.Dest)
+ if err != nil {
+ ctx.PropertyErrorf(property+".dest", "%s", err.Error())
+ }
+ }
+ if dist.Dir != nil {
+ _, err := validateSafePath(*dist.Dir)
+ if err != nil {
+ ctx.PropertyErrorf(property+".dir", "%s", err.Error())
+ }
+ }
+ if dist.Suffix != nil {
+ if strings.Contains(*dist.Suffix, "/") {
+ ctx.PropertyErrorf(property+".suffix", "Suffix may not contain a '/' character.")
+ }
+ }
+
+}
+
type earlyModuleContext struct {
blueprint.EarlyModuleContext
diff --git a/android/module_test.go b/android/module_test.go
index 6cc1813..e3cc613 100644
--- a/android/module_test.go
+++ b/android/module_test.go
@@ -232,3 +232,51 @@
t.Errorf("Expected build params to fail validation: %+v", bparams)
}
}
+
+func TestDistErrorChecking(t *testing.T) {
+ bp := `
+ deps {
+ name: "foo",
+ dist: {
+ dest: "../invalid-dest",
+ dir: "../invalid-dir",
+ suffix: "invalid/suffix",
+ },
+ dists: [
+ {
+ dest: "../invalid-dest0",
+ dir: "../invalid-dir0",
+ suffix: "invalid/suffix0",
+ },
+ {
+ dest: "../invalid-dest1",
+ dir: "../invalid-dir1",
+ suffix: "invalid/suffix1",
+ },
+ ],
+ }
+ `
+
+ config := TestConfig(buildDir, nil, bp, nil)
+
+ ctx := NewTestContext(config)
+ ctx.RegisterModuleType("deps", depsModuleFactory)
+ ctx.Register()
+
+ _, errs := ctx.ParseFileList(".", []string{"Android.bp"})
+ FailIfErrored(t, errs)
+ _, errs = ctx.PrepareBuildActions(config)
+
+ expectedErrs := []string{
+ "\\QAndroid.bp:5:13: module \"foo\": dist.dest: Path is outside directory: ../invalid-dest\\E",
+ "\\QAndroid.bp:6:12: module \"foo\": dist.dir: Path is outside directory: ../invalid-dir\\E",
+ "\\QAndroid.bp:7:15: module \"foo\": dist.suffix: Suffix may not contain a '/' character.\\E",
+ "\\QAndroid.bp:11:15: module \"foo\": dists[0].dest: Path is outside directory: ../invalid-dest0\\E",
+ "\\QAndroid.bp:12:14: module \"foo\": dists[0].dir: Path is outside directory: ../invalid-dir0\\E",
+ "\\QAndroid.bp:13:17: module \"foo\": dists[0].suffix: Suffix may not contain a '/' character.\\E",
+ "\\QAndroid.bp:16:15: module \"foo\": dists[1].dest: Path is outside directory: ../invalid-dest1\\E",
+ "\\QAndroid.bp:17:14: module \"foo\": dists[1].dir: Path is outside directory: ../invalid-dir1\\E",
+ "\\QAndroid.bp:18:17: module \"foo\": dists[1].suffix: Suffix may not contain a '/' character.\\E",
+ }
+ CheckErrorsAgainstExpectations(t, errs, expectedErrs)
+}
diff --git a/android/packaging.go b/android/packaging.go
index 512e4ba..3e42060 100644
--- a/android/packaging.go
+++ b/android/packaging.go
@@ -21,10 +21,10 @@
"github.com/google/blueprint"
)
-// PackagingSpec abstracts a request to place a built artifact at a certain path in a package.
-// A package can be the traditional <partition>.img, but isn't limited to those. Other examples could
-// be a new filesystem image that is a subset of system.img (e.g. for an Android-like mini OS running
-// on a VM), or a zip archive for some of the host tools.
+// PackagingSpec abstracts a request to place a built artifact at a certain path in a package. A
+// package can be the traditional <partition>.img, but isn't limited to those. Other examples could
+// be a new filesystem image that is a subset of system.img (e.g. for an Android-like mini OS
+// running on a VM), or a zip archive for some of the host tools.
type PackagingSpec struct {
// Path relative to the root of the package
relPathInPackage string
@@ -40,15 +40,25 @@
executable bool
}
+// Get file name of installed package
+func (p *PackagingSpec) FileName() string {
+ if p.relPathInPackage != "" {
+ return filepath.Base(p.relPathInPackage)
+ }
+
+ return ""
+}
+
type PackageModule interface {
Module
packagingBase() *PackagingBase
// AddDeps adds dependencies to the `deps` modules. This should be called in DepsMutator.
- AddDeps(ctx BottomUpMutatorContext)
+ // When adding the dependencies, depTag is used as the tag.
+ AddDeps(ctx BottomUpMutatorContext, depTag blueprint.DependencyTag)
// CopyDepsToZip zips the built artifacts of the dependencies into the given zip file and
- // returns zip entries in it. This is expected to be called in GenerateAndroidBuildActions,
+ // returns zip entries in it. This is expected to be called in GenerateAndroidBuildActions,
// followed by a build rule that unzips it and creates the final output (img, zip, tar.gz,
// etc.) from the extracted files
CopyDepsToZip(ctx ModuleContext, zipOut OutputPath) []string
@@ -59,9 +69,9 @@
type PackagingBase struct {
properties PackagingProperties
- // Allows this module to skip missing dependencies. In most cases, this
- // is not required, but for rare cases like when there's a dependency
- // to a module which exists in certain repo checkouts, this is needed.
+ // Allows this module to skip missing dependencies. In most cases, this is not required, but
+ // for rare cases like when there's a dependency to a module which exists in certain repo
+ // checkouts, this is needed.
IgnoreMissingDependencies bool
}
@@ -82,10 +92,6 @@
Multilib packagingMultilibProperties `android:"arch_variant"`
}
-type packagingDependencyTag struct{ blueprint.BaseDependencyTag }
-
-var depTag = packagingDependencyTag{}
-
func InitPackageModule(p PackageModule) {
base := p.packagingBase()
p.AddProperties(&base.properties)
@@ -95,10 +101,10 @@
return p
}
-// From deps and multilib.*.deps, select the dependencies that are for the given arch
-// deps is for the current archicture when this module is not configured for multi target.
-// When configured for multi target, deps is selected for each of the targets and is NOT
-// selected for the current architecture which would be Common.
+// From deps and multilib.*.deps, select the dependencies that are for the given arch deps is for
+// the current archicture when this module is not configured for multi target. When configured for
+// multi target, deps is selected for each of the targets and is NOT selected for the current
+// architecture which would be Common.
func (p *PackagingBase) getDepsForArch(ctx BaseModuleContext, arch ArchType) []string {
var ret []string
if arch == ctx.Target().Arch.ArchType && len(ctx.MultiTargets()) == 0 {
@@ -134,7 +140,7 @@
}
// See PackageModule.AddDeps
-func (p *PackagingBase) AddDeps(ctx BottomUpMutatorContext) {
+func (p *PackagingBase) AddDeps(ctx BottomUpMutatorContext, depTag blueprint.DependencyTag) {
for _, t := range p.getSupportedTargets(ctx) {
for _, dep := range p.getDepsForArch(ctx, t.Arch.ArchType) {
if p.IgnoreMissingDependencies && !ctx.OtherModuleExists(dep) {
@@ -147,15 +153,9 @@
// See PackageModule.CopyDepsToZip
func (p *PackagingBase) CopyDepsToZip(ctx ModuleContext, zipOut OutputPath) (entries []string) {
- var supportedArches []string
- for _, t := range p.getSupportedTargets(ctx) {
- supportedArches = append(supportedArches, t.Arch.ArchType.String())
- }
m := make(map[string]PackagingSpec)
ctx.WalkDeps(func(child Module, parent Module) bool {
- // Don't track modules with unsupported arch
- // TODO(jiyong): remove this when aosp/1501613 lands.
- if !InList(child.Target().Arch.ArchType.String(), supportedArches) {
+ if !IsInstallDepNeeded(ctx.OtherModuleDependencyTag(child)) {
return false
}
for _, ps := range child.PackagingSpecs() {
diff --git a/android/packaging_test.go b/android/packaging_test.go
index 7710c7f..7269bfb 100644
--- a/android/packaging_test.go
+++ b/android/packaging_test.go
@@ -17,6 +17,8 @@
import (
"reflect"
"testing"
+
+ "github.com/google/blueprint"
)
// Module to be packaged
@@ -27,6 +29,12 @@
}
}
+// dep tag used in this test. All dependencies are considered as installable.
+type installDepTag struct {
+ blueprint.BaseDependencyTag
+ InstallAlwaysNeededDependencyTag
+}
+
func componentTestModuleFactory() Module {
m := &componentTestModule{}
m.AddProperties(&m.props)
@@ -35,7 +43,7 @@
}
func (m *componentTestModule) DepsMutator(ctx BottomUpMutatorContext) {
- ctx.AddDependency(ctx.Module(), nil, m.props.Deps...)
+ ctx.AddDependency(ctx.Module(), installDepTag{}, m.props.Deps...)
}
func (m *componentTestModule) GenerateAndroidBuildActions(ctx ModuleContext) {
@@ -61,7 +69,7 @@
}
func (m *packageTestModule) DepsMutator(ctx BottomUpMutatorContext) {
- m.AddDeps(ctx)
+ m.AddDeps(ctx, installDepTag{})
}
func (m *packageTestModule) GenerateAndroidBuildActions(ctx ModuleContext) {
diff --git a/android/paths.go b/android/paths.go
index a62c9e3..b7117a3 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -665,7 +665,7 @@
return Paths(ret)
}
-// WritablePaths is a slice of WritablePaths, used for multiple outputs.
+// WritablePaths is a slice of WritablePath, used for multiple outputs.
type WritablePaths []WritablePath
// Strings returns the string forms of the writable paths.
diff --git a/android/rule_builder.go b/android/rule_builder.go
index 86418b2..3efe9f8 100644
--- a/android/rule_builder.go
+++ b/android/rule_builder.go
@@ -20,27 +20,33 @@
"path/filepath"
"sort"
"strings"
+ "testing"
+ "github.com/golang/protobuf/proto"
"github.com/google/blueprint"
"github.com/google/blueprint/proptools"
+ "android/soong/cmd/sbox/sbox_proto"
"android/soong/shared"
)
-const sboxOutDir = "__SBOX_OUT_DIR__"
+const sboxSandboxBaseDir = "__SBOX_SANDBOX_DIR__"
+const sboxOutSubDir = "out"
+const sboxOutDir = sboxSandboxBaseDir + "/" + sboxOutSubDir
// RuleBuilder provides an alternative to ModuleContext.Rule and ModuleContext.Build to add a command line to the build
// graph.
type RuleBuilder struct {
- commands []*RuleBuilderCommand
- installs RuleBuilderInstalls
- temporariesSet map[WritablePath]bool
- restat bool
- sbox bool
- highmem bool
- remoteable RemoteRuleSupports
- sboxOutDir WritablePath
- missingDeps []string
+ commands []*RuleBuilderCommand
+ installs RuleBuilderInstalls
+ temporariesSet map[WritablePath]bool
+ restat bool
+ sbox bool
+ highmem bool
+ remoteable RemoteRuleSupports
+ sboxOutDir WritablePath
+ sboxManifestPath WritablePath
+ missingDeps []string
}
// NewRuleBuilder returns a newly created RuleBuilder.
@@ -106,12 +112,14 @@
return r
}
-// Sbox marks the rule as needing to be wrapped by sbox. The WritablePath should point to the output
-// directory that sbox will wipe. It should not be written to by any other rule. sbox will ensure
-// that all outputs have been written, and will discard any output files that were not specified.
+// Sbox marks the rule as needing to be wrapped by sbox. The outputDir should point to the output
+// directory that sbox will wipe. It should not be written to by any other rule. manifestPath should
+// point to a location where sbox's manifest will be written and must be outside outputDir. sbox
+// will ensure that all outputs have been written, and will discard any output files that were not
+// specified.
//
// Sbox is not compatible with Restat()
-func (r *RuleBuilder) Sbox(outputDir WritablePath) *RuleBuilder {
+func (r *RuleBuilder) Sbox(outputDir WritablePath, manifestPath WritablePath) *RuleBuilder {
if r.sbox {
panic("Sbox() may not be called more than once")
}
@@ -123,6 +131,7 @@
}
r.sbox = true
r.sboxOutDir = outputDir
+ r.sboxManifestPath = manifestPath
return r
}
@@ -420,7 +429,8 @@
r.depFileMergerCmd(ctx, depFiles)
if r.sbox {
- // Check for Rel() errors, as all depfiles should be in the output dir
+ // Check for Rel() errors, as all depfiles should be in the output dir. Errors
+ // will be reported to the ctx.
for _, path := range depFiles[1:] {
Rel(ctx, r.sboxOutDir.String(), path.String())
}
@@ -443,34 +453,60 @@
commandString := strings.Join(commands, " && ")
if r.sbox {
- sboxOutputs := make([]string, len(outputs))
- for i, output := range outputs {
- sboxOutputs[i] = filepath.Join(sboxOutDir, Rel(ctx, r.sboxOutDir.String(), output.String()))
- }
-
- commandString = proptools.ShellEscape(commandString)
- if !strings.HasPrefix(commandString, `'`) {
- commandString = `'` + commandString + `'`
- }
-
- sboxCmd := &RuleBuilderCommand{}
- sboxCmd.BuiltTool(ctx, "sbox").
- Flag("-c").Text(commandString).
- Flag("--sandbox-path").Text(shared.TempDirForOutDir(PathForOutput(ctx).String())).
- Flag("--output-root").Text(r.sboxOutDir.String())
+ // If running the command inside sbox, write the rule data out to an sbox
+ // manifest.textproto.
+ manifest := sbox_proto.Manifest{}
+ command := sbox_proto.Command{}
+ manifest.Commands = append(manifest.Commands, &command)
+ command.Command = proto.String(commandString)
if depFile != nil {
- sboxCmd.Flag("--depfile-out").Text(depFile.String())
+ manifest.OutputDepfile = proto.String(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))
+ // Add copy rules to the manifest to copy each output file from the sbox directory.
+ // to the output directory.
+ sboxOutputs := make([]string, len(outputs))
+ for i, output := range outputs {
+ rel := Rel(ctx, r.sboxOutDir.String(), output.String())
+ sboxOutputs[i] = filepath.Join(sboxOutDir, rel)
+ command.CopyAfter = append(command.CopyAfter, &sbox_proto.Copy{
+ From: proto.String(filepath.Join(sboxOutSubDir, rel)),
+ To: proto.String(output.String()),
+ })
+ }
- sboxCmd.Flags(sboxOutputs)
+ // Add a hash of the list of input files to the manifest so that the textproto file
+ // changes when the list of input files changes and causes the sbox rule that
+ // depends on it to rerun.
+ command.InputHash = proto.String(hashSrcFiles(inputs))
+ // Verify that the manifest textproto is not inside the sbox output directory, otherwise
+ // it will get deleted when the sbox rule clears its output directory.
+ _, manifestInOutDir := MaybeRel(ctx, r.sboxOutDir.String(), r.sboxManifestPath.String())
+ if manifestInOutDir {
+ ReportPathErrorf(ctx, "sbox rule %q manifestPath %q must not be in outputDir %q",
+ name, r.sboxManifestPath.String(), r.sboxOutDir.String())
+ }
+
+ // Create a rule to write the manifest as a the textproto.
+ WriteFileRule(ctx, r.sboxManifestPath, proto.MarshalTextString(&manifest))
+
+ // Generate a new string to use as the command line of the sbox rule. This uses
+ // a RuleBuilderCommand as a convenience method of building the command line, then
+ // converts it to a string to replace commandString.
+ sboxCmd := &RuleBuilderCommand{}
+ sboxCmd.Text("rm -rf").Output(r.sboxOutDir)
+ sboxCmd.Text("&&")
+ sboxCmd.BuiltTool(ctx, "sbox").
+ Flag("--sandbox-path").Text(shared.TempDirForOutDir(PathForOutput(ctx).String())).
+ Flag("--manifest").Input(r.sboxManifestPath)
+
+ // Replace the command string, and add the sbox tool and manifest textproto to the
+ // dependencies of the final sbox rule.
commandString = sboxCmd.buf.String()
tools = append(tools, sboxCmd.tools...)
+ inputs = append(inputs, sboxCmd.inputs...)
} 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
@@ -890,6 +926,19 @@
return ninjaEscapeExceptForSpans(c.String(), c.unescapedSpans)
}
+// RuleBuilderSboxProtoForTests takes the BuildParams for the manifest passed to RuleBuilder.Sbox()
+// and returns sbox testproto generated by the RuleBuilder.
+func RuleBuilderSboxProtoForTests(t *testing.T, params TestingBuildParams) *sbox_proto.Manifest {
+ t.Helper()
+ content := ContentFromFileRuleForTests(t, params)
+ manifest := sbox_proto.Manifest{}
+ err := proto.UnmarshalText(content, &manifest)
+ if err != nil {
+ t.Fatalf("failed to unmarshal manifest: %s", err.Error())
+ }
+ return &manifest
+}
+
func ninjaEscapeExceptForSpans(s string, spans [][2]int) string {
if len(spans) == 0 {
return proptools.NinjaEscape(s)
diff --git a/android/rule_builder_test.go b/android/rule_builder_test.go
index c1d5521..dc360c3 100644
--- a/android/rule_builder_test.go
+++ b/android/rule_builder_test.go
@@ -395,16 +395,17 @@
})
t.Run("sbox", func(t *testing.T) {
- rule := NewRuleBuilder().Sbox(PathForOutput(ctx))
+ rule := NewRuleBuilder().Sbox(PathForOutput(ctx, ""),
+ PathForOutput(ctx, "sbox.textproto"))
addCommands(rule)
wantCommands := []string{
- "__SBOX_OUT_DIR__/DepFile Flag FlagWithArg=arg FlagWithDepFile=__SBOX_OUT_DIR__/depfile FlagWithInput=input FlagWithOutput=__SBOX_OUT_DIR__/output Input __SBOX_OUT_DIR__/Output __SBOX_OUT_DIR__/SymlinkOutput Text Tool after command2 old cmd",
- "command2 __SBOX_OUT_DIR__/depfile2 input2 __SBOX_OUT_DIR__/output2 tool2",
- "command3 input3 __SBOX_OUT_DIR__/output2 __SBOX_OUT_DIR__/output3",
+ "__SBOX_SANDBOX_DIR__/out/DepFile Flag FlagWithArg=arg FlagWithDepFile=__SBOX_SANDBOX_DIR__/out/depfile FlagWithInput=input FlagWithOutput=__SBOX_SANDBOX_DIR__/out/output Input __SBOX_SANDBOX_DIR__/out/Output __SBOX_SANDBOX_DIR__/out/SymlinkOutput Text Tool after command2 old cmd",
+ "command2 __SBOX_SANDBOX_DIR__/out/depfile2 input2 __SBOX_SANDBOX_DIR__/out/output2 tool2",
+ "command3 input3 __SBOX_SANDBOX_DIR__/out/output2 __SBOX_SANDBOX_DIR__/out/output3",
}
- wantDepMergerCommand := "out/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer __SBOX_OUT_DIR__/DepFile __SBOX_OUT_DIR__/depfile __SBOX_OUT_DIR__/ImplicitDepFile __SBOX_OUT_DIR__/depfile2"
+ wantDepMergerCommand := "out/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer __SBOX_SANDBOX_DIR__/out/DepFile __SBOX_SANDBOX_DIR__/out/depfile __SBOX_SANDBOX_DIR__/out/ImplicitDepFile __SBOX_SANDBOX_DIR__/out/depfile2"
if g, w := rule.Commands(), wantCommands; !reflect.DeepEqual(g, w) {
t.Errorf("\nwant rule.Commands() = %#v\n got %#v", w, g)
@@ -451,11 +452,12 @@
func (t *testRuleBuilderModule) GenerateAndroidBuildActions(ctx ModuleContext) {
in := PathsForSource(ctx, t.properties.Srcs)
- out := PathForModuleOut(ctx, ctx.ModuleName())
- outDep := PathForModuleOut(ctx, ctx.ModuleName()+".d")
- outDir := PathForModuleOut(ctx)
+ out := PathForModuleOut(ctx, "gen", ctx.ModuleName())
+ outDep := PathForModuleOut(ctx, "gen", ctx.ModuleName()+".d")
+ outDir := PathForModuleOut(ctx, "gen")
+ manifestPath := PathForModuleOut(ctx, "sbox.textproto")
- testRuleBuilder_Build(ctx, in, out, outDep, outDir, t.properties.Restat, t.properties.Sbox)
+ testRuleBuilder_Build(ctx, in, out, outDep, outDir, manifestPath, t.properties.Restat, t.properties.Sbox)
}
type testRuleBuilderSingleton struct{}
@@ -466,17 +468,18 @@
func (t *testRuleBuilderSingleton) GenerateBuildActions(ctx SingletonContext) {
in := PathForSource(ctx, "bar")
- out := PathForOutput(ctx, "baz")
- outDep := PathForOutput(ctx, "baz.d")
- outDir := PathForOutput(ctx)
- testRuleBuilder_Build(ctx, Paths{in}, out, outDep, outDir, true, false)
+ out := PathForOutput(ctx, "singleton/gen/baz")
+ outDep := PathForOutput(ctx, "singleton/gen/baz.d")
+ outDir := PathForOutput(ctx, "singleton/gen")
+ manifestPath := PathForOutput(ctx, "singleton/sbox.textproto")
+ testRuleBuilder_Build(ctx, Paths{in}, out, outDep, outDir, manifestPath, true, false)
}
-func testRuleBuilder_Build(ctx BuilderContext, in Paths, out, outDep, outDir WritablePath, restat, sbox bool) {
+func testRuleBuilder_Build(ctx BuilderContext, in Paths, out, outDep, outDir, manifestPath WritablePath, restat, sbox bool) {
rule := NewRuleBuilder()
if sbox {
- rule.Sbox(outDir)
+ rule.Sbox(outDir, manifestPath)
}
rule.Command().Tool(PathForSource(ctx, "cp")).Inputs(in).Output(out).ImplicitDepFile(outDep)
@@ -518,10 +521,10 @@
_, errs = ctx.PrepareBuildActions(config)
FailIfErrored(t, errs)
- check := func(t *testing.T, params TestingBuildParams, wantCommand, wantOutput, wantDepfile string, wantRestat bool, extraCmdDeps []string) {
+ check := func(t *testing.T, params TestingBuildParams, wantCommand, wantOutput, wantDepfile string, wantRestat bool, extraImplicits, extraCmdDeps []string) {
t.Helper()
command := params.RuleParams.Command
- re := regexp.MustCompile(" (# hash of input list:|--input-hash) [a-z0-9]*")
+ re := regexp.MustCompile(" # hash of input list: [a-z0-9]*$")
command = re.ReplaceAllLiteralString(command, "")
if command != wantCommand {
t.Errorf("\nwant RuleParams.Command = %q\n got %q", wantCommand, params.RuleParams.Command)
@@ -536,7 +539,8 @@
t.Errorf("want RuleParams.Restat = %v, got %v", wantRestat, params.RuleParams.Restat)
}
- if len(params.Implicits) != 1 || params.Implicits[0].String() != "bar" {
+ wantImplicits := append([]string{"bar"}, extraImplicits...)
+ if !reflect.DeepEqual(params.Implicits.Strings(), wantImplicits) {
t.Errorf("want Implicits = [%q], got %q", "bar", params.Implicits.Strings())
}
@@ -558,27 +562,29 @@
}
t.Run("module", func(t *testing.T) {
- outFile := filepath.Join(buildDir, ".intermediates", "foo", "foo")
+ outFile := filepath.Join(buildDir, ".intermediates", "foo", "gen", "foo")
check(t, ctx.ModuleForTests("foo", "").Rule("rule"),
"cp bar "+outFile,
- outFile, outFile+".d", true, nil)
+ outFile, outFile+".d", true, nil, nil)
})
t.Run("sbox", func(t *testing.T) {
outDir := filepath.Join(buildDir, ".intermediates", "foo_sbox")
- outFile := filepath.Join(outDir, "foo_sbox")
- depFile := filepath.Join(outDir, "foo_sbox.d")
+ outFile := filepath.Join(outDir, "gen/foo_sbox")
+ depFile := filepath.Join(outDir, "gen/foo_sbox.d")
+ manifest := filepath.Join(outDir, "sbox.textproto")
sbox := filepath.Join(buildDir, "host", config.PrebuiltOS(), "bin/sbox")
sandboxPath := shared.TempDirForOutDir(buildDir)
- cmd := sbox + ` -c 'cp bar __SBOX_OUT_DIR__/foo_sbox' --sandbox-path ` + sandboxPath + " --output-root " + outDir + " --depfile-out " + depFile + " __SBOX_OUT_DIR__/foo_sbox"
+ cmd := `rm -rf ` + outDir + `/gen && ` +
+ sbox + ` --sandbox-path ` + sandboxPath + ` --manifest ` + manifest
- check(t, ctx.ModuleForTests("foo_sbox", "").Rule("rule"),
- cmd, outFile, depFile, false, []string{sbox})
+ check(t, ctx.ModuleForTests("foo_sbox", "").Output("gen/foo_sbox"),
+ cmd, outFile, depFile, false, []string{manifest}, []string{sbox})
})
t.Run("singleton", func(t *testing.T) {
- outFile := filepath.Join(buildDir, "baz")
+ outFile := filepath.Join(buildDir, "singleton/gen/baz")
check(t, ctx.SingletonForTests("rule_builder_test").Rule("rule"),
- "cp bar "+outFile, outFile, outFile+".d", true, nil)
+ "cp bar "+outFile, outFile, outFile+".d", true, nil, nil)
})
}
@@ -715,14 +721,16 @@
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)
+ manifest := RuleBuilderSboxProtoForTests(t, gen.Output("sbox.textproto"))
+ hash := manifest.Commands[0].GetInputHash()
+
+ if g, w := hash, test.expectedHash; g != w {
+ t.Errorf("Expected has %q, got %q", w, g)
}
})
t.Run("", func(t *testing.T) {
gen := ctx.ModuleForTests(test.name+"", "")
- command := gen.Output(test.name).RuleParams.Command
+ command := gen.Output("gen/" + 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)
}
diff --git a/android/util.go b/android/util.go
index 65c5f1b..8e4c0f4 100644
--- a/android/util.go
+++ b/android/util.go
@@ -29,56 +29,44 @@
return append([]string(nil), s...)
}
+// JoinWithPrefix prepends the prefix to each string in the list and
+// returns them joined together with " " as separator.
func JoinWithPrefix(strs []string, prefix string) string {
if len(strs) == 0 {
return ""
}
- if len(strs) == 1 {
- return prefix + strs[0]
+ var buf strings.Builder
+ buf.WriteString(prefix)
+ buf.WriteString(strs[0])
+ for i := 1; i < len(strs); i++ {
+ buf.WriteString(" ")
+ buf.WriteString(prefix)
+ buf.WriteString(strs[i])
}
-
- n := len(" ") * (len(strs) - 1)
- for _, s := range strs {
- n += len(prefix) + len(s)
- }
-
- ret := make([]byte, 0, n)
- for i, s := range strs {
- if i != 0 {
- ret = append(ret, ' ')
- }
- ret = append(ret, prefix...)
- ret = append(ret, s...)
- }
- return string(ret)
+ return buf.String()
}
+// JoinWithSuffix appends the suffix to each string in the list and
+// returns them joined together with given separator.
func JoinWithSuffix(strs []string, suffix string, separator string) string {
if len(strs) == 0 {
return ""
}
- if len(strs) == 1 {
- return strs[0] + suffix
+ var buf strings.Builder
+ buf.WriteString(strs[0])
+ buf.WriteString(suffix)
+ for i := 1; i < len(strs); i++ {
+ buf.WriteString(separator)
+ buf.WriteString(strs[i])
+ buf.WriteString(suffix)
}
-
- n := len(" ") * (len(strs) - 1)
- for _, s := range strs {
- n += len(suffix) + len(s)
- }
-
- ret := make([]byte, 0, n)
- for i, s := range strs {
- if i != 0 {
- ret = append(ret, separator...)
- }
- ret = append(ret, s...)
- ret = append(ret, suffix...)
- }
- return string(ret)
+ return buf.String()
}
+// SortedIntKeys returns the keys of the given integer-keyed map in the ascending order
+// TODO(asmundak): once Go has generics, combine this with SortedStringKeys below.
func SortedIntKeys(m interface{}) []int {
v := reflect.ValueOf(m)
if v.Kind() != reflect.Map {
@@ -93,6 +81,7 @@
return s
}
+// SorterStringKeys returns the keys of the given string-keyed map in the ascending order
func SortedStringKeys(m interface{}) []string {
v := reflect.ValueOf(m)
if v.Kind() != reflect.Map {
@@ -107,6 +96,7 @@
return s
}
+// SortedStringMapValues returns the values of the string-values map in the ascending order
func SortedStringMapValues(m interface{}) []string {
v := reflect.ValueOf(m)
if v.Kind() != reflect.Map {
@@ -121,6 +111,7 @@
return s
}
+// IndexList returns the index of the first occurrence of the given string in the list or -1
func IndexList(s string, list []string) int {
for i, l := range list {
if l == s {
@@ -131,6 +122,7 @@
return -1
}
+// InList checks if the string belongs to the list
func InList(s string, list []string) bool {
return IndexList(s, list) != -1
}
@@ -176,7 +168,10 @@
return -1
}
+// FilterList divides the string list into two lists: one with the strings belonging
+// to the given filter list, and the other with the remaining ones
func FilterList(list []string, filter []string) (remainder []string, filtered []string) {
+ // InList is O(n). May be worth using more efficient lookup for longer lists.
for _, l := range list {
if InList(l, filter) {
filtered = append(filtered, l)
@@ -188,6 +183,8 @@
return
}
+// RemoveListFromList removes the strings belonging to the filter list from the
+// given list and returns the result
func RemoveListFromList(list []string, filter_out []string) (result []string) {
result = make([]string, 0, len(list))
for _, l := range list {
@@ -198,20 +195,18 @@
return
}
+// RemoveFromList removes given string from the string list.
func RemoveFromList(s string, list []string) (bool, []string) {
- i := IndexList(s, list)
- if i == -1 {
- return false, list
- }
-
- result := make([]string, 0, len(list)-1)
- result = append(result, list[:i]...)
- for _, l := range list[i+1:] {
- if l != s {
- result = append(result, l)
+ result := make([]string, 0, len(list))
+ var removed bool
+ for _, item := range list {
+ if item != s {
+ result = append(result, item)
+ } else {
+ removed = true
}
}
- return true, result
+ return removed, result
}
// FirstUniqueStrings returns all unique elements of a slice of strings, keeping the first copy of
@@ -317,11 +312,10 @@
return s[1], s[2], true
}
+// GetNumericSdkVersion removes the first occurrence of system_ in a string,
+// which is assumed to be something like "system_1.2.3"
func GetNumericSdkVersion(v string) string {
- if strings.Contains(v, "system_") {
- return strings.Replace(v, "system_", "", 1)
- }
- return v
+ return strings.Replace(v, "system_", "", 1)
}
// copied from build/kati/strutil.go
@@ -334,17 +328,17 @@
return str
}
in := str
- trimed := str
+ trimmed := str
if ps[0] != "" {
- trimed = strings.TrimPrefix(in, ps[0])
- if trimed == in {
+ trimmed = strings.TrimPrefix(in, ps[0])
+ if trimmed == in {
return str
}
}
- in = trimed
+ in = trimmed
if ps[1] != "" {
- trimed = strings.TrimSuffix(in, ps[1])
- if trimed == in {
+ trimmed = strings.TrimSuffix(in, ps[1])
+ if trimmed == in {
return str
}
}
@@ -353,7 +347,7 @@
if len(rs) != 2 {
return repl
}
- return rs[0] + trimed + rs[1]
+ return rs[0] + trimmed + rs[1]
}
// copied from build/kati/strutil.go
@@ -424,13 +418,15 @@
return ret
}
+// CheckDuplicate checks if there are duplicates in given string list.
+// If there are, it returns first such duplicate and true.
func CheckDuplicate(values []string) (duplicate string, found bool) {
seen := make(map[string]string)
for _, v := range values {
if duplicate, found = seen[v]; found {
- return
+ return duplicate, true
}
seen[v] = v
}
- return
+ return "", false
}
diff --git a/android/variable.go b/android/variable.go
index a9a9c87..0df5272 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -347,6 +347,9 @@
EnforceProductPartitionInterface *bool `json:",omitempty"`
+ EnforceInterPartitionJavaSdkLibrary *bool `json:",omitempty"`
+ InterPartitionJavaLibraryAllowList []string `json:",omitempty"`
+
InstallExtraFlattenedApexes *bool `json:",omitempty"`
BoardUsesRecoveryAsBoot *bool `json:",omitempty"`
diff --git a/apex/Android.bp b/apex/Android.bp
index 1a5f683..9e8c30d 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -9,6 +9,7 @@
"soong-cc",
"soong-java",
"soong-python",
+ "soong-rust",
"soong-sh",
],
srcs: [
diff --git a/apex/androidmk.go b/apex/androidmk.go
index fe89b73..da38c2a 100644
--- a/apex/androidmk.go
+++ b/apex/androidmk.go
@@ -334,7 +334,6 @@
func (a *apexBundle) androidMkForType() android.AndroidMkData {
return android.AndroidMkData{
- DistFiles: a.distFiles,
Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
moduleNames := []string{}
apexType := a.properties.ApexType
diff --git a/apex/apex.go b/apex/apex.go
index a645b06..f127757 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -32,6 +32,7 @@
prebuilt_etc "android/soong/etc"
"android/soong/java"
"android/soong/python"
+ "android/soong/rust"
"android/soong/sh"
)
@@ -179,6 +180,9 @@
// List of JNI libraries that are embedded inside this APEX.
Jni_libs []string
+ // List of rust dyn libraries
+ Rust_dyn_libs []string
+
// List of native executables that are embedded inside this APEX.
Binaries []string
@@ -349,8 +353,6 @@
lintReports android.Paths
prebuiltFileToDelete string
-
- distFiles android.TaggedDistFiles
}
// apexFileClass represents a type of file that can be included in APEX.
@@ -513,13 +515,15 @@
func addDependenciesForNativeModules(ctx android.BottomUpMutatorContext, nativeModules ApexNativeDependencies, target android.Target, imageVariation string) {
binVariations := target.Variations()
libVariations := append(target.Variations(), blueprint.Variation{Mutator: "link", Variation: "shared"})
+ rustLibVariations := append(target.Variations(), blueprint.Variation{Mutator: "rust_libraries", Variation: "dylib"})
if ctx.Device() {
binVariations = append(binVariations, blueprint.Variation{Mutator: "image", Variation: imageVariation})
libVariations = append(libVariations,
blueprint.Variation{Mutator: "image", Variation: imageVariation},
- blueprint.Variation{Mutator: "version", Variation: ""}, // "" is the non-stub variant
- )
+ blueprint.Variation{Mutator: "version", Variation: ""}) // "" is the non-stub variant
+ rustLibVariations = append(rustLibVariations,
+ blueprint.Variation{Mutator: "image", Variation: imageVariation})
}
// Use *FarVariation* to be able to depend on modules having conflicting variations with
@@ -529,6 +533,7 @@
ctx.AddFarVariationDependencies(binVariations, testTag, nativeModules.Tests...)
ctx.AddFarVariationDependencies(libVariations, jniLibTag, nativeModules.Jni_libs...)
ctx.AddFarVariationDependencies(libVariations, sharedLibTag, nativeModules.Native_shared_libs...)
+ ctx.AddFarVariationDependencies(rustLibVariations, sharedLibTag, nativeModules.Rust_dyn_libs...)
}
func (a *apexBundle) combineProperties(ctx android.BottomUpMutatorContext) {
@@ -1096,7 +1101,8 @@
// Implements android.OutputFileProducer
func (a *apexBundle) OutputFiles(tag string) (android.Paths, error) {
switch tag {
- case "":
+ case "", android.DefaultDistTag:
+ // This is the default dist path.
return android.Paths{a.outputFile}, nil
default:
return nil, fmt.Errorf("unsupported module reference tag %q", tag)
@@ -1264,6 +1270,35 @@
return af
}
+func apexFileForRustExecutable(ctx android.BaseModuleContext, rustm *rust.Module) apexFile {
+ dirInApex := "bin"
+ if rustm.Target().NativeBridge == android.NativeBridgeEnabled {
+ dirInApex = filepath.Join(dirInApex, rustm.Target().NativeBridgeRelativePath)
+ }
+ fileToCopy := rustm.OutputFile().Path()
+ androidMkModuleName := rustm.BaseModuleName() + rustm.Properties.SubName
+ af := newApexFile(ctx, fileToCopy, androidMkModuleName, dirInApex, nativeExecutable, rustm)
+ return af
+}
+
+func apexFileForRustLibrary(ctx android.BaseModuleContext, rustm *rust.Module) apexFile {
+ // Decide the APEX-local directory by the multilib of the library
+ // In the future, we may query this to the module.
+ var dirInApex string
+ switch rustm.Arch().ArchType.Multilib {
+ case "lib32":
+ dirInApex = "lib"
+ case "lib64":
+ dirInApex = "lib64"
+ }
+ if rustm.Target().NativeBridge == android.NativeBridgeEnabled {
+ dirInApex = filepath.Join(dirInApex, rustm.Target().NativeBridgeRelativePath)
+ }
+ fileToCopy := rustm.OutputFile().Path()
+ androidMkModuleName := rustm.BaseModuleName() + rustm.Properties.SubName
+ return newApexFile(ctx, fileToCopy, androidMkModuleName, dirInApex, nativeSharedLib, rustm)
+}
+
func apexFileForPyBinary(ctx android.BaseModuleContext, py *python.Module) apexFile {
dirInApex := "bin"
fileToCopy := py.HostToolPath().Path()
@@ -1501,8 +1536,11 @@
filesInfo = append(filesInfo, apexFileForPyBinary(ctx, py))
} else if gb, ok := child.(bootstrap.GoBinaryTool); ok && a.Host() {
filesInfo = append(filesInfo, apexFileForGoBinary(ctx, depName, gb))
+ } else if rust, ok := child.(*rust.Module); ok {
+ filesInfo = append(filesInfo, apexFileForRustExecutable(ctx, rust))
+ return true // track transitive dependencies
} else {
- ctx.PropertyErrorf("binaries", "%q is neither cc_binary, (embedded) py_binary, (host) blueprint_go_binary, (host) bootstrap_go_binary, nor sh_binary", depName)
+ ctx.PropertyErrorf("binaries", "%q is neither cc_binary, rust_binary, (embedded) py_binary, (host) blueprint_go_binary, (host) bootstrap_go_binary, nor sh_binary", depName)
}
case javaLibTag:
switch child.(type) {
@@ -1663,6 +1701,13 @@
if prebuilt, ok := child.(prebuilt_etc.PrebuiltEtcModule); ok {
filesInfo = append(filesInfo, apexFileForPrebuiltEtc(ctx, prebuilt, depName))
}
+ } else if rust.IsDylibDepTag(depTag) {
+ if rustm, ok := child.(*rust.Module); ok && rustm.IsInstallableToApex() {
+ af := apexFileForRustLibrary(ctx, rustm)
+ af.transitiveDep = true
+ filesInfo = append(filesInfo, af)
+ return true // track transitive dependencies
+ }
} else if _, ok := depTag.(android.CopyDirectlyInAnyApexTag); ok {
// nothing
} else if am.CanHaveApexVariants() && am.IsInstallableToApex() {
@@ -1786,14 +1831,11 @@
a.linkToSystemLib = false
}
- a.setCertificateAndPrivateKey(ctx)
-
a.compatSymlinks = makeCompatSymlinks(a.BaseModuleName(), ctx)
////////////////////////////////////////////////////////////////////////////////////////////
// 4) generate the build rules to create the APEX. This is done in builder.go.
a.buildManifest(ctx, provideNativeLibs, requireNativeLibs)
- a.buildFileContexts(ctx)
if a.properties.ApexType == flattenedApex {
a.buildFlattenedApex(ctx)
} else {
@@ -1801,7 +1843,25 @@
}
a.buildApexDependencyInfo(ctx)
a.buildLintReports(ctx)
- a.distFiles = a.GenerateTaggedDistFiles(ctx)
+
+ // Append meta-files to the filesInfo list so that they are reflected in Android.mk as well.
+ if a.installable() {
+ // For flattened APEX, make sure that APEX manifest and apex_pubkey are also copied
+ // along with other ordinary files. (Note that this is done by apexer for
+ // non-flattened APEXes)
+ a.filesInfo = append(a.filesInfo, newApexFile(ctx, a.manifestPbOut, "apex_manifest.pb", ".", etc, nil))
+
+ // Place the public key as apex_pubkey. This is also done by apexer for
+ // non-flattened APEXes case.
+ // TODO(jiyong): Why do we need this CP rule?
+ copiedPubkey := android.PathForModuleOut(ctx, "apex_pubkey")
+ ctx.Build(pctx, android.BuildParams{
+ Rule: android.Cp,
+ Input: a.public_key_file,
+ Output: copiedPubkey,
+ })
+ a.filesInfo = append(a.filesInfo, newApexFile(ctx, copiedPubkey, "apex_pubkey", ".", etc, nil))
+ }
}
///////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 33e5077..a94e3b4 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -32,6 +32,7 @@
"android/soong/dexpreopt"
prebuilt_etc "android/soong/etc"
"android/soong/java"
+ "android/soong/rust"
"android/soong/sh"
)
@@ -136,6 +137,8 @@
bp = bp + cc.GatherRequiredDepsForTest(android.Android)
+ bp = bp + rust.GatherRequiredDepsForTest()
+
bp = bp + java.GatherRequiredDepsForTest()
fs := map[string][]byte{
@@ -185,6 +188,7 @@
"bar/baz": nil,
"testdata/baz": nil,
"AppSet.apks": nil,
+ "foo.rs": nil,
}
cc.GatherRequiredFilesForTest(fs)
@@ -241,6 +245,7 @@
ctx.PostDepsMutators(android.RegisterVisibilityRuleEnforcer)
cc.RegisterRequiredBuildComponentsForTest(ctx)
+ rust.RegisterRequiredBuildComponentsForTest(ctx)
ctx.RegisterModuleType("cc_test", cc.TestFactory)
ctx.RegisterModuleType("vndk_prebuilt_shared", cc.VndkPrebuiltSharedFactory)
@@ -349,10 +354,12 @@
manifest: ":myapex.manifest",
androidManifest: ":myapex.androidmanifest",
key: "myapex.key",
+ binaries: ["foo.rust"],
native_shared_libs: ["mylib"],
+ rust_dyn_libs: ["libfoo.dylib.rust"],
multilib: {
both: {
- binaries: ["foo",],
+ binaries: ["foo"],
}
},
java_libs: [
@@ -415,6 +422,28 @@
apex_available: [ "myapex", "com.android.gki.*" ],
}
+ rust_binary {
+ name: "foo.rust",
+ srcs: ["foo.rs"],
+ rlibs: ["libfoo.rlib.rust"],
+ dylibs: ["libfoo.dylib.rust"],
+ apex_available: ["myapex"],
+ }
+
+ rust_library_rlib {
+ name: "libfoo.rlib.rust",
+ srcs: ["foo.rs"],
+ crate_name: "foo",
+ apex_available: ["myapex"],
+ }
+
+ rust_library_dylib {
+ name: "libfoo.dylib.rust",
+ srcs: ["foo.rs"],
+ crate_name: "foo",
+ apex_available: ["myapex"],
+ }
+
apex {
name: "com.android.gki.fake",
binaries: ["foo"],
@@ -529,16 +558,20 @@
ensureListContains(t, ctx.ModuleVariantsForTests("mylib"), "android_arm64_armv8-a_shared_apex10000")
ensureListContains(t, ctx.ModuleVariantsForTests("myjar"), "android_common_apex10000")
ensureListContains(t, ctx.ModuleVariantsForTests("myjar_dex"), "android_common_apex10000")
+ ensureListContains(t, ctx.ModuleVariantsForTests("foo.rust"), "android_arm64_armv8-a_apex10000")
// Ensure that apex variant is created for the indirect dep
ensureListContains(t, ctx.ModuleVariantsForTests("mylib2"), "android_arm64_armv8-a_shared_apex10000")
ensureListContains(t, ctx.ModuleVariantsForTests("myotherjar"), "android_common_apex10000")
+ ensureListContains(t, ctx.ModuleVariantsForTests("libfoo.rlib.rust"), "android_arm64_armv8-a_rlib_dylib-std_apex10000")
+ ensureListContains(t, ctx.ModuleVariantsForTests("libfoo.dylib.rust"), "android_arm64_armv8-a_dylib_apex10000")
// Ensure that both direct and indirect deps are copied into apex
ensureContains(t, copyCmds, "image.apex/lib64/mylib.so")
ensureContains(t, copyCmds, "image.apex/lib64/mylib2.so")
ensureContains(t, copyCmds, "image.apex/javalib/myjar_stem.jar")
ensureContains(t, copyCmds, "image.apex/javalib/myjar_dex.jar")
+ ensureContains(t, copyCmds, "image.apex/lib64/libfoo.dylib.rust.dylib.so")
// .. but not for java libs
ensureNotContains(t, copyCmds, "image.apex/javalib/myotherjar.jar")
ensureNotContains(t, copyCmds, "image.apex/javalib/msharedjar.jar")
diff --git a/apex/builder.go b/apex/builder.go
index 6f27dd1..b858135 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -40,7 +40,7 @@
pctx.Import("android/soong/java")
pctx.HostBinToolVariable("apexer", "apexer")
// ART minimal builds (using the master-art manifest) do not have the "frameworks/base"
- // projects, and hence cannot built 'aapt2'. Use the SDK prebuilt instead.
+ // projects, and hence cannot build 'aapt2'. Use the SDK prebuilt instead.
hostBinToolVariableWithPrebuilt := func(name, prebuiltDir, tool string) {
pctx.VariableFunc(name, func(ctx android.PackageVarContext) string {
if !ctx.Config().FrameworksBaseDirExists(ctx) {
@@ -175,24 +175,29 @@
`exit 1); touch ${out}`,
Description: "Diff ${image_content_file} and ${allowed_files_file}",
}, "image_content_file", "allowed_files_file", "apex_module_name")
+
+ // Don't add more rules here. Consider using android.NewRuleBuilder instead.
)
+// buildManifest creates buile rules to modify the input apex_manifest.json to add information
+// gathered by the build system such as provided/required native libraries. Two output files having
+// different formats are generated. a.manifestJsonOut is JSON format for Q devices, and
+// a.manifest.PbOut is protobuf format for R+ devices.
+// TODO(jiyong): make this to return paths instead of directly storing the paths to apexBundle
func (a *apexBundle) buildManifest(ctx android.ModuleContext, provideNativeLibs, requireNativeLibs []string) {
- manifestSrc := android.PathForModuleSrc(ctx, proptools.StringDefault(a.properties.Manifest, "apex_manifest.json"))
+ src := android.PathForModuleSrc(ctx, proptools.StringDefault(a.properties.Manifest, "apex_manifest.json"))
- manifestJsonFullOut := android.PathForModuleOut(ctx, "apex_manifest_full.json")
-
- // put dependency({provide|require}NativeLibs) in apex_manifest.json
+ // Put dependency({provide|require}NativeLibs) in apex_manifest.json
provideNativeLibs = android.SortedUniqueStrings(provideNativeLibs)
requireNativeLibs = android.SortedUniqueStrings(android.RemoveListFromList(requireNativeLibs, provideNativeLibs))
- // apex name can be overridden
+ // APEX name can be overridden
optCommands := []string{}
if a.properties.Apex_name != nil {
optCommands = append(optCommands, "-v name "+*a.properties.Apex_name)
}
- // collect jniLibs. Notice that a.filesInfo is already sorted
+ // Collect jniLibs. Notice that a.filesInfo is already sorted
var jniLibs []string
for _, fi := range a.filesInfo {
if fi.isJniLib && !android.InList(fi.stem(), jniLibs) {
@@ -203,9 +208,10 @@
optCommands = append(optCommands, "-a jniLibs "+strings.Join(jniLibs, " "))
}
+ manifestJsonFullOut := android.PathForModuleOut(ctx, "apex_manifest_full.json")
ctx.Build(pctx, android.BuildParams{
Rule: apexManifestRule,
- Input: manifestSrc,
+ Input: src,
Output: manifestJsonFullOut,
Args: map[string]string{
"provideNativeLibs": strings.Join(provideNativeLibs, " "),
@@ -214,10 +220,10 @@
},
})
+ // b/143654022 Q apexd can't understand newly added keys in apex_manifest.json prepare
+ // stripped-down version so that APEX modules built from R+ can be installed to Q
minSdkVersion := a.minSdkVersion(ctx)
if minSdkVersion.EqualTo(android.SdkVersion_Android10) {
- // b/143654022 Q apexd can't understand newly added keys in apex_manifest.json
- // prepare stripped-down version so that APEX modules built from R+ can be installed to Q
a.manifestJsonOut = android.PathForModuleOut(ctx, "apex_manifest.json")
ctx.Build(pctx, android.BuildParams{
Rule: stripApexManifestRule,
@@ -226,7 +232,7 @@
})
}
- // from R+, protobuf binary format (.pb) is the standard format for apex_manifest
+ // From R+, protobuf binary format (.pb) is the standard format for apex_manifest
a.manifestPbOut = android.PathForModuleOut(ctx, "apex_manifest.pb")
ctx.Build(pctx, android.BuildParams{
Rule: pbApexManifestRule,
@@ -235,10 +241,11 @@
})
}
-func (a *apexBundle) buildFileContexts(ctx android.ModuleContext) {
- if a.properties.ApexType == zipApex {
- return
- }
+// buildFileContexts create build rules to append an entry for apex_manifest.pb to the file_contexts
+// file for this APEX which is either from /systme/sepolicy/apex/<apexname>-file_contexts or from
+// the file_contexts property of this APEX. This is to make sure that the manifest file is correctly
+// labeled as system_file.
+func (a *apexBundle) buildFileContexts(ctx android.ModuleContext) android.OutputPath {
var fileContexts android.Path
if a.properties.File_contexts == nil {
fileContexts = android.PathForSource(ctx, "system/sepolicy/apex", ctx.ModuleName()+"-file_contexts")
@@ -248,18 +255,17 @@
if a.Platform() {
if matched, err := path.Match("system/sepolicy/**/*", fileContexts.String()); err != nil || !matched {
ctx.PropertyErrorf("file_contexts", "should be under system/sepolicy, but %q", fileContexts)
- return
}
}
if !android.ExistentPathForSource(ctx, fileContexts.String()).Valid() {
- ctx.PropertyErrorf("file_contexts", "cannot find file_contexts file: %q", a.fileContexts)
- return
+ ctx.PropertyErrorf("file_contexts", "cannot find file_contexts file: %q", fileContexts.String())
}
output := android.PathForModuleOut(ctx, "file_contexts")
rule := android.NewRuleBuilder()
- if a.properties.ApexType == imageApex {
+ switch a.properties.ApexType {
+ case imageApex:
// remove old file
rule.Command().Text("rm").FlagWithOutput("-f ", output)
// copy file_contexts
@@ -269,7 +275,7 @@
// force-label /apex_manifest.pb and / as system_file so that apexd can read them
rule.Command().Text("echo").Flag("/apex_manifest\\\\.pb u:object_r:system_file:s0").Text(">>").Output(output)
rule.Command().Text("echo").Flag("/ u:object_r:system_file:s0").Text(">>").Output(output)
- } else {
+ case flattenedApex:
// For flattened apexes, install path should be prepended.
// File_contexts file should be emiited to make via LOCAL_FILE_CONTEXTS
// so that it can be merged into file_contexts.bin
@@ -284,13 +290,16 @@
// force-label /apex_manifest.pb and / as system_file so that apexd can read them
rule.Command().Text("echo").Flag(apexPath + `/apex_manifest\\.pb u:object_r:system_file:s0`).Text(">>").Output(output)
rule.Command().Text("echo").Flag(apexPath + "/ u:object_r:system_file:s0").Text(">>").Output(output)
+ default:
+ panic(fmt.Errorf("unsupported type %v", a.properties.ApexType))
}
rule.Build(pctx, ctx, "file_contexts."+a.Name(), "Generate file_contexts")
-
- a.fileContexts = output.OutputPath
+ return output.OutputPath
}
+// buildNoticeFiles creates a buile rule for aggregating notice files from the modules that
+// contributes to this APEX. The notice files are merged into a big notice file.
func (a *apexBundle) buildNoticeFiles(ctx android.ModuleContext, apexFileName string) android.NoticeOutputs {
var noticeFiles android.Paths
@@ -299,13 +308,11 @@
// As soon as the dependency graph crosses the APEX boundary, don't go further.
return false
}
-
- notices := to.NoticeFiles()
- noticeFiles = append(noticeFiles, notices...)
-
+ noticeFiles = append(noticeFiles, to.NoticeFiles()...)
return true
})
+ // TODO(jiyong): why do we need this? WalkPayloadDeps should have already covered this.
for _, fi := range a.filesInfo {
noticeFiles = append(noticeFiles, fi.noticeFiles...)
}
@@ -317,6 +324,9 @@
return android.BuildNoticeOutput(ctx, a.installDir, apexFileName, android.SortedUniquePaths(noticeFiles))
}
+// buildInstalledFilesFile creates a build rule for the installed-files.txt file where the list of
+// files included in this APEX is shown. The text file is dist'ed so that people can see what's
+// included in the APEX without actually downloading and extracting it.
func (a *apexBundle) buildInstalledFilesFile(ctx android.ModuleContext, builtApex android.Path, imageDir android.Path) android.OutputPath {
output := android.PathForModuleOut(ctx, "installed-files.txt")
rule := android.NewRuleBuilder()
@@ -330,6 +340,8 @@
return output.OutputPath
}
+// buildBundleConfig creates a build rule for the bundle config file that will control the bundle
+// creation process.
func (a *apexBundle) buildBundleConfig(ctx android.ModuleContext) android.OutputPath {
output := android.PathForModuleOut(ctx, "bundle_config.json")
@@ -351,8 +363,8 @@
"apex_manifest.*",
}
- // collect the manifest names and paths of android apps
- // if their manifest names are overridden
+ // Collect the manifest names and paths of android apps if their manifest names are
+ // overridden.
for _, fi := range a.filesInfo {
if fi.class != app && fi.class != appSet {
continue
@@ -378,30 +390,31 @@
return output.OutputPath
}
+// buildUnflattendApex creates build rules to build an APEX using apexer.
func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext) {
- var abis []string
- for _, target := range ctx.MultiTargets() {
- if len(target.Arch.Abi) > 0 {
- abis = append(abis, target.Arch.Abi[0])
- }
- }
-
- abis = android.FirstUniqueStrings(abis)
-
apexType := a.properties.ApexType
suffix := apexType.suffix()
- var implicitInputs []android.Path
- unsignedOutputFile := android.PathForModuleOut(ctx, a.Name()+suffix+".unsigned")
- // TODO(jiyong): construct the copy rules using RuleBuilder
+ ////////////////////////////////////////////////////////////////////////////////////////////
+ // Step 1: copy built files to appropriate directories under the image directory
+
+ imageDir := android.PathForModuleOut(ctx, "image"+suffix)
+
+ // TODO(jiyong): use the RuleBuilder
var copyCommands []string
+ var implicitInputs []android.Path
for _, fi := range a.filesInfo {
- destPath := android.PathForModuleOut(ctx, "image"+suffix, fi.path()).String()
+ destPath := imageDir.Join(ctx, fi.path()).String()
+
+ // Prepare the destination path
destPathDir := filepath.Dir(destPath)
if fi.class == appSet {
copyCommands = append(copyCommands, "rm -rf "+destPathDir)
}
copyCommands = append(copyCommands, "mkdir -p "+destPathDir)
+
+ // Copy the built file to the directory. But if the symlink optimization is turned
+ // on, place a symlink to the corresponding file in /system partition instead.
if a.linkToSystemLib && fi.transitiveDep && fi.availableToPlatform() {
// TODO(jiyong): pathOnDevice should come from fi.module, not being calculated here
pathOnDevice := filepath.Join("/system", fi.path())
@@ -415,11 +428,15 @@
}
implicitInputs = append(implicitInputs, fi.builtFile)
}
- // create additional symlinks pointing the file inside the APEX
+
+ // Create additional symlinks pointing the file inside the APEX (if any). Note that
+ // this is independent from the symlink optimization.
for _, symlinkPath := range fi.symlinkPaths() {
- symlinkDest := android.PathForModuleOut(ctx, "image"+suffix, symlinkPath).String()
+ symlinkDest := imageDir.Join(ctx, symlinkPath).String()
copyCommands = append(copyCommands, "ln -sfn "+filepath.Base(destPath)+" "+symlinkDest)
}
+
+ // Copy the test files (if any)
for _, d := range fi.dataPaths {
// TODO(eakammer): This is now the third repetition of ~this logic for test paths, refactoring should be possible
relPath := d.SrcPath.Rel()
@@ -428,28 +445,37 @@
panic(fmt.Errorf("path %q does not end with %q", dataPath, relPath))
}
- dataDest := android.PathForModuleOut(ctx, "image"+suffix, fi.apexRelativePath(relPath), d.RelativeInstallPath).String()
+ dataDest := imageDir.Join(ctx, fi.apexRelativePath(relPath), d.RelativeInstallPath).String()
copyCommands = append(copyCommands, "cp -f "+d.SrcPath.String()+" "+dataDest)
implicitInputs = append(implicitInputs, d.SrcPath)
}
}
-
- // TODO(jiyong): use RuleBuilder
- var emitCommands []string
- imageContentFile := android.PathForModuleOut(ctx, "content.txt")
- emitCommands = append(emitCommands, "echo ./apex_manifest.pb >> "+imageContentFile.String())
- minSdkVersion := a.minSdkVersion(ctx)
- if minSdkVersion.EqualTo(android.SdkVersion_Android10) {
- emitCommands = append(emitCommands, "echo ./apex_manifest.json >> "+imageContentFile.String())
- }
- for _, fi := range a.filesInfo {
- emitCommands = append(emitCommands, "echo './"+fi.path()+"' >> "+imageContentFile.String())
- }
- emitCommands = append(emitCommands, "sort -o "+imageContentFile.String()+" "+imageContentFile.String())
implicitInputs = append(implicitInputs, a.manifestPbOut)
+ ////////////////////////////////////////////////////////////////////////////////////////////
+ // Step 1.a: Write the list of files in this APEX to a txt file and compare it against
+ // the allowed list given via the allowed_files property. Build fails when the two lists
+ // differ.
+ //
+ // TODO(jiyong): consider removing this. Nobody other than com.android.apex.cts.shim.* seems
+ // to be using this at this moment. Furthermore, this looks very similar to what
+ // buildInstalledFilesFile does. At least, move this to somewhere else so that this doesn't
+ // hurt readability.
+ // TODO(jiyong): use RuleBuilder
if a.overridableProperties.Allowed_files != nil {
+ // Build content.txt
+ var emitCommands []string
+ imageContentFile := android.PathForModuleOut(ctx, "content.txt")
+ emitCommands = append(emitCommands, "echo ./apex_manifest.pb >> "+imageContentFile.String())
+ minSdkVersion := a.minSdkVersion(ctx)
+ if minSdkVersion.EqualTo(android.SdkVersion_Android10) {
+ emitCommands = append(emitCommands, "echo ./apex_manifest.json >> "+imageContentFile.String())
+ }
+ for _, fi := range a.filesInfo {
+ emitCommands = append(emitCommands, "echo './"+fi.path()+"' >> "+imageContentFile.String())
+ }
+ emitCommands = append(emitCommands, "sort -o "+imageContentFile.String()+" "+imageContentFile.String())
ctx.Build(pctx, android.BuildParams{
Rule: emitApexContentRule,
Implicits: implicitInputs,
@@ -460,8 +486,9 @@
},
})
implicitInputs = append(implicitInputs, imageContentFile)
- allowedFilesFile := android.PathForModuleSrc(ctx, proptools.String(a.overridableProperties.Allowed_files))
+ // Compare content.txt against allowed_files.
+ allowedFilesFile := android.PathForModuleSrc(ctx, proptools.String(a.overridableProperties.Allowed_files))
phonyOutput := android.PathForModuleOut(ctx, a.Name()+"-diff-phony-output")
ctx.Build(pctx, android.BuildParams{
Rule: diffApexContentRule,
@@ -474,16 +501,19 @@
"apex_module_name": a.Name(),
},
})
-
implicitInputs = append(implicitInputs, phonyOutput)
}
+ unsignedOutputFile := android.PathForModuleOut(ctx, a.Name()+suffix+".unsigned")
outHostBinDir := android.PathForOutput(ctx, "host", ctx.Config().PrebuiltOS(), "bin").String()
prebuiltSdkToolsBinDir := filepath.Join("prebuilts", "sdk", "tools", runtime.GOOS, "bin")
- imageDir := android.PathForModuleOut(ctx, "image"+suffix)
if apexType == imageApex {
- // files and dirs that will be created in APEX
+ ////////////////////////////////////////////////////////////////////////////////////
+ // Step 2: create canned_fs_config which encodes filemode,uid,gid of each files
+ // in this APEX. The file will be used by apexer in later steps.
+ // TODO(jiyong): make this as a function
+ // TODO(jiyong): use the RuleBuilder
var readOnlyPaths = []string{"apex_manifest.json", "apex_manifest.pb"}
var executablePaths []string // this also includes dirs
var extractedAppSetPaths android.Paths
@@ -528,11 +558,17 @@
"apk_paths": strings.Join(extractedAppSetDirs, " "),
},
})
+ implicitInputs = append(implicitInputs, cannedFsConfig)
+ ////////////////////////////////////////////////////////////////////////////////////
+ // Step 3: Prepare option flags for apexer and invoke it to create an unsigned APEX.
+ // TODO(jiyong): use the RuleBuilder
optFlags := []string{}
- // Additional implicit inputs.
- implicitInputs = append(implicitInputs, cannedFsConfig, a.fileContexts, a.private_key_file, a.public_key_file)
+ fileContexts := a.buildFileContexts(ctx)
+ implicitInputs = append(implicitInputs, fileContexts)
+
+ implicitInputs = append(implicitInputs, a.private_key_file, a.public_key_file)
optFlags = append(optFlags, "--pubkey "+a.public_key_file.String())
manifestPackageName := a.getOverrideManifestPackageName(ctx)
@@ -546,15 +582,18 @@
optFlags = append(optFlags, "--android_manifest "+androidManifestFile.String())
}
+ // Determine target/min sdk version from the context
+ // TODO(jiyong): make this as a function
moduleMinSdkVersion := a.minSdkVersion(ctx)
minSdkVersion := moduleMinSdkVersion.String()
- // bundletool doesn't understand what "current" is. We need to transform it to codename
+ // bundletool doesn't understand what "current" is. We need to transform it to
+ // codename
if moduleMinSdkVersion.IsCurrent() {
minSdkVersion = ctx.Config().DefaultAppTargetSdk(ctx).String()
}
- // apex module doesn't have a concept of target_sdk_version, hence for the time being
- // targetSdkVersion == default targetSdkVersion of the branch.
+ // apex module doesn't have a concept of target_sdk_version, hence for the time
+ // being targetSdkVersion == default targetSdkVersion of the branch.
targetSdkVersion := strconv.Itoa(ctx.Config().DefaultAppTargetSdk(ctx).FinalOrFutureInt())
if java.UseApiFingerprint(ctx) {
@@ -595,8 +634,8 @@
}
if a.properties.Apex_name != nil {
- // If apex_name is set, apexer can skip checking if key name matches with apex name.
- // Note that apex_manifest is also mended.
+ // If apex_name is set, apexer can skip checking if key name matches with
+ // apex name. Note that apex_manifest is also mended.
optFlags = append(optFlags, "--do_not_check_keyname")
}
@@ -617,13 +656,14 @@
"image_dir": imageDir.String(),
"copy_commands": strings.Join(copyCommands, " && "),
"manifest": a.manifestPbOut.String(),
- "file_contexts": a.fileContexts.String(),
+ "file_contexts": fileContexts.String(),
"canned_fs_config": cannedFsConfig.String(),
"key": a.private_key_file.String(),
"opt_flags": strings.Join(optFlags, " "),
},
})
+ // TODO(jiyong): make the two rules below as separate functions
apexProtoFile := android.PathForModuleOut(ctx, a.Name()+".pb"+suffix)
bundleModuleFile := android.PathForModuleOut(ctx, a.Name()+suffix+"-base.zip")
a.bundleModuleFile = bundleModuleFile
@@ -637,6 +677,15 @@
bundleConfig := a.buildBundleConfig(ctx)
+ var abis []string
+ for _, target := range ctx.MultiTargets() {
+ if len(target.Arch.Abi) > 0 {
+ abis = append(abis, target.Arch.Abi[0])
+ }
+ }
+
+ abis = android.FirstUniqueStrings(abis)
+
ctx.Build(pctx, android.BuildParams{
Rule: apexBundleRule,
Input: apexProtoFile,
@@ -648,7 +697,7 @@
"config": bundleConfig.String(),
},
})
- } else {
+ } else { // zipApex
ctx.Build(pctx, android.BuildParams{
Rule: zipApexRule,
Implicits: implicitInputs,
@@ -663,16 +712,17 @@
})
}
+ ////////////////////////////////////////////////////////////////////////////////////
+ // Step 4: Sign the APEX using signapk
a.outputFile = android.PathForModuleOut(ctx, a.Name()+suffix)
+
+ pem, key := a.getCertificateAndPrivateKey(ctx)
rule := java.Signapk
args := map[string]string{
- "certificates": a.container_certificate_file.String() + " " + a.container_private_key_file.String(),
+ "certificates": pem.String() + " " + key.String(),
"flags": "-a 4096", //alignment
}
- implicits := android.Paths{
- a.container_certificate_file,
- a.container_private_key_file,
- }
+ implicits := android.Paths{pem, key}
if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_SIGNAPK") {
rule = java.SignapkRE
args["implicits"] = strings.Join(implicits.Strings(), ",")
@@ -691,7 +741,6 @@
if a.installable() {
ctx.InstallFile(a.installDir, a.Name()+suffix, a.outputFile)
}
- a.buildFilesInfo(ctx)
// installed-files.txt is dist'ed
a.installedFilesFile = a.buildInstalledFilesFile(ctx, a.outputFile, imageDir)
@@ -706,58 +755,50 @@
return true
}
+// buildFlattenedApex creates rules for a flattened APEX. Flattened APEX actually doesn't have a
+// single output file. It is a phony target for all the files under /system/apex/<name> directory.
+// This function creates the installation rules for the files.
func (a *apexBundle) buildFlattenedApex(ctx android.ModuleContext) {
- // Temporarily wrap the original `ctx` into a `flattenedApexContext` to have it
- // reply true to `InstallBypassMake()` (thus making the call
- // `android.PathForModuleInstall` below use `android.pathForInstallInMakeDir`
- // instead of `android.PathForOutput`) to return the correct path to the flattened
- // APEX (as its contents is installed by Make, not Soong).
- factx := flattenedApexContext{ctx}
- a.outputFile = android.PathForModuleInstall(&factx, "apex", a.Name())
- a.buildFilesInfo(ctx)
-}
-
-func (a *apexBundle) setCertificateAndPrivateKey(ctx android.ModuleContext) {
- if a.container_certificate_file == nil {
- cert := String(a.properties.Certificate)
- if cert == "" {
- pem, key := ctx.Config().DefaultAppCertificate(ctx)
- a.container_certificate_file = pem
- a.container_private_key_file = key
- } else {
- defaultDir := ctx.Config().DefaultAppCertificateDir(ctx)
- a.container_certificate_file = defaultDir.Join(ctx, cert+".x509.pem")
- a.container_private_key_file = defaultDir.Join(ctx, cert+".pk8")
- }
- }
-}
-
-func (a *apexBundle) buildFilesInfo(ctx android.ModuleContext) {
+ bundleName := a.Name()
if a.installable() {
- // For flattened APEX, do nothing but make sure that APEX manifest and apex_pubkey are also copied along
- // with other ordinary files.
- a.filesInfo = append(a.filesInfo, newApexFile(ctx, a.manifestPbOut, "apex_manifest.pb", ".", etc, nil))
-
- // rename to apex_pubkey
- copiedPubkey := android.PathForModuleOut(ctx, "apex_pubkey")
- ctx.Build(pctx, android.BuildParams{
- Rule: android.Cp,
- Input: a.public_key_file,
- Output: copiedPubkey,
- })
- a.filesInfo = append(a.filesInfo, newApexFile(ctx, copiedPubkey, "apex_pubkey", ".", etc, nil))
-
- if a.properties.ApexType == flattenedApex {
- apexBundleName := a.Name()
- for _, fi := range a.filesInfo {
- dir := filepath.Join("apex", apexBundleName, fi.installDir)
- target := ctx.InstallFile(android.PathForModuleInstall(ctx, dir), fi.stem(), fi.builtFile)
- for _, sym := range fi.symlinks {
- ctx.InstallSymlink(android.PathForModuleInstall(ctx, dir), sym, target)
- }
+ for _, fi := range a.filesInfo {
+ dir := filepath.Join("apex", bundleName, fi.installDir)
+ target := ctx.InstallFile(android.PathForModuleInstall(ctx, dir), fi.stem(), fi.builtFile)
+ for _, sym := range fi.symlinks {
+ ctx.InstallSymlink(android.PathForModuleInstall(ctx, dir), sym, target)
}
}
}
+
+ a.fileContexts = a.buildFileContexts(ctx)
+
+ // Temporarily wrap the original `ctx` into a `flattenedApexContext` to have it reply true
+ // to `InstallBypassMake()` (thus making the call `android.PathForModuleInstall` below use
+ // `android.pathForInstallInMakeDir` instead of `android.PathForOutput`) to return the
+ // correct path to the flattened APEX (as its contents is installed by Make, not Soong).
+ // TODO(jiyong): Why do we need to set outputFile for flattened APEX? We don't seem to use
+ // it and it actually points to a path that can never be built. Remove this.
+ factx := flattenedApexContext{ctx}
+ a.outputFile = android.PathForModuleInstall(&factx, "apex", bundleName)
+}
+
+// getCertificateAndPrivateKey retrieves the cert and the private key that will be used to sign
+// the zip container of this APEX. See the description of the 'certificate' property for how
+// the cert and the private key are found.
+func (a *apexBundle) getCertificateAndPrivateKey(ctx android.PathContext) (pem, key android.Path) {
+ if a.container_certificate_file != nil {
+ return a.container_certificate_file, a.container_private_key_file
+ }
+
+ cert := String(a.properties.Certificate)
+ if cert == "" {
+ return ctx.Config().DefaultAppCertificate(ctx)
+ }
+
+ defaultDir := ctx.Config().DefaultAppCertificateDir(ctx)
+ pem = defaultDir.Join(ctx, cert+".x509.pem")
+ key = defaultDir.Join(ctx, cert+".pk8")
+ return pem, key
}
func (a *apexBundle) getOverrideManifestPackageName(ctx android.ModuleContext) string {
diff --git a/apex/key.go b/apex/key.go
index 43764da..d9e3c10 100644
--- a/apex/key.go
+++ b/apex/key.go
@@ -127,13 +127,14 @@
apexKeyMap := make(map[string]apexKeyEntry)
ctx.VisitAllModules(func(module android.Module) {
if m, ok := module.(*apexBundle); ok && m.Enabled() && m.installable() {
+ pem, key := m.getCertificateAndPrivateKey(ctx)
apexKeyMap[m.Name()] = apexKeyEntry{
name: m.Name() + ".apex",
presigned: false,
public_key: m.public_key_file.String(),
private_key: m.private_key_file.String(),
- container_certificate: m.container_certificate_file.String(),
- container_private_key: m.container_private_key_file.String(),
+ container_certificate: pem.String(),
+ container_private_key: key.String(),
partition: m.PartitionTag(ctx.DeviceConfig()),
}
}
diff --git a/cc/Android.bp b/cc/Android.bp
index ff2cdf3..88104e2 100644
--- a/cc/Android.bp
+++ b/cc/Android.bp
@@ -72,6 +72,8 @@
"vendor_public_library.go",
"testing.go",
+
+ "stub_library.go",
],
testSrcs: [
"cc_test.go",
diff --git a/cc/androidmk.go b/cc/androidmk.go
index d32e4de..320e69b 100644
--- a/cc/androidmk.go
+++ b/cc/androidmk.go
@@ -334,8 +334,7 @@
entries.Class = "NATIVE_TESTS"
entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
if len(benchmark.Properties.Test_suites) > 0 {
- entries.SetString("LOCAL_COMPATIBILITY_SUITE",
- strings.Join(benchmark.Properties.Test_suites, " "))
+ entries.AddCompatibilityTestSuites(benchmark.Properties.Test_suites...)
}
if benchmark.testConfig != nil {
entries.SetString("LOCAL_FULL_TEST_CONFIG", benchmark.testConfig.String())
@@ -360,8 +359,7 @@
}
entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
if len(test.Properties.Test_suites) > 0 {
- entries.SetString("LOCAL_COMPATIBILITY_SUITE",
- strings.Join(test.Properties.Test_suites, " "))
+ entries.AddCompatibilityTestSuites(test.Properties.Test_suites...)
}
if test.testConfig != nil {
entries.SetString("LOCAL_FULL_TEST_CONFIG", test.testConfig.String())
diff --git a/cc/binary.go b/cc/binary.go
index da29412..fbd293e 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -42,6 +42,7 @@
// extension (if any) appended
Symlinks []string `android:"arch_variant"`
+ // override the dynamic linker
DynamicLinker string `blueprint:"mutated"`
// Names of modules to be overridden. Listed modules can only be other binaries
@@ -80,6 +81,7 @@
// Executables
//
+// binaryDecorator is a decorator containing information for C++ binary modules.
type binaryDecorator struct {
*baseLinker
*baseInstaller
@@ -105,11 +107,15 @@
// Location of the files that should be copied to dist dir when requested
distFiles android.TaggedDistFiles
+ // Action command lines to run directly after the binary is installed. For example,
+ // may be used to symlink runtime dependencies (such as bionic) alongside installation.
post_install_cmds []string
}
var _ linker = (*binaryDecorator)(nil)
+// linkerProps returns the list of individual properties objects relevant
+// for this binary.
func (binary *binaryDecorator) linkerProps() []interface{} {
return append(binary.baseLinker.linkerProps(),
&binary.Properties,
@@ -117,6 +123,10 @@
}
+// getStemWithoutSuffix returns the main section of the name to use for the symlink of
+// the main output file of this binary module. This may be derived from the module name
+// or other property overrides.
+// For the full symlink name, the `Suffix` property of a binary module must be appended.
func (binary *binaryDecorator) getStemWithoutSuffix(ctx BaseModuleContext) string {
stem := ctx.baseModuleName()
if String(binary.Properties.Stem) != "" {
@@ -126,10 +136,14 @@
return stem
}
+// getStem returns the full name to use for the symlink of the main output file of this binary
+// module. This may be derived from the module name and/or other property overrides.
func (binary *binaryDecorator) getStem(ctx BaseModuleContext) string {
return binary.getStemWithoutSuffix(ctx) + String(binary.Properties.Suffix)
}
+// linkerDeps augments and returns the given `deps` to contain dependencies on
+// modules common to most binaries, such as bionic libraries.
func (binary *binaryDecorator) linkerDeps(ctx DepsContext, deps Deps) Deps {
deps = binary.baseLinker.linkerDeps(ctx, deps)
if ctx.toolchain().Bionic() {
@@ -155,6 +169,13 @@
deps.LateStaticLibs = append(groupLibs, deps.LateStaticLibs...)
}
+ // Embed the linker into host bionic binaries. This is needed to support host bionic,
+ // as the linux kernel requires that the ELF interpreter referenced by PT_INTERP be
+ // either an absolute path, or relative from CWD. To work around this, we extract
+ // the load sections from the runtime linker ELF binary and embed them into each host
+ // bionic binary, omitting the PT_INTERP declaration. The kernel will treat it as a static
+ // binary, and then we use a special entry point to fix up the arguments passed by
+ // the kernel before jumping to the embedded linker.
if ctx.Os() == android.LinuxBionic && !binary.static() {
deps.DynamicLinker = "linker"
deps.LinkerFlagsFile = "host_bionic_linker_flags"
@@ -170,9 +191,13 @@
}
func (binary *binaryDecorator) isDependencyRoot() bool {
+ // Binaries are always the dependency root.
return true
}
+// NewBinary builds and returns a new Module corresponding to a C++ binary.
+// Individual module implementations which comprise a C++ binary should call this function,
+// set some fields on the result, and then call the Init function.
func NewBinary(hod android.HostOrDeviceSupported) (*Module, *binaryDecorator) {
module := newModule(hod, android.MultilibFirst)
binary := &binaryDecorator{
@@ -190,11 +215,15 @@
return module, binary
}
+// linkerInit initializes dynamic properties of the linker (such as runpath) based
+// on properties of this binary.
func (binary *binaryDecorator) linkerInit(ctx BaseModuleContext) {
binary.baseLinker.linkerInit(ctx)
if !ctx.toolchain().Bionic() {
if ctx.Os() == android.Linux {
+ // Unless explicitly specified otherwise, host static binaries are built with -static
+ // if HostStaticBinaries is true for the product configuration.
if binary.Properties.Static_executable == nil && ctx.Config().HostStaticBinaries() {
binary.Properties.Static_executable = BoolPtr(true)
}
@@ -217,9 +246,13 @@
return true
}
+// linkerFlags returns a Flags object containing linker flags that are defined
+// by this binary, or that are implied by attributes of this binary. These flags are
+// combined with the given flags.
func (binary *binaryDecorator) linkerFlags(ctx ModuleContext, flags Flags) Flags {
flags = binary.baseLinker.linkerFlags(ctx, flags)
+ // Passing -pie to clang for Windows binaries causes a warning that -pie is unused.
if ctx.Host() && !ctx.Windows() && !binary.static() {
if !ctx.Config().IsEnvTrue("DISABLE_HOST_PIE") {
flags.Global.LdFlags = append(flags.Global.LdFlags, "-pie")
@@ -248,7 +281,7 @@
"-Bstatic",
"-Wl,--gc-sections",
)
- } else {
+ } else { // not static
if flags.DynamicLinker == "" {
if binary.Properties.DynamicLinker != "" {
flags.DynamicLinker = binary.Properties.DynamicLinker
@@ -288,7 +321,7 @@
"-Wl,-z,nocopyreloc",
)
}
- } else {
+ } else { // not bionic
if binary.static() {
flags.Global.LdFlags = append(flags.Global.LdFlags, "-static")
}
@@ -300,6 +333,9 @@
return flags
}
+// link registers actions to link this binary, and sets various fields
+// on this binary to reflect information that should be exported up the build
+// tree (for example, exported flags and include paths).
func (binary *binaryDecorator) link(ctx ModuleContext,
flags Flags, deps PathDeps, objs Objects) android.Path {
@@ -309,6 +345,7 @@
var linkerDeps android.Paths
+ // Add flags from linker flags file.
if deps.LinkerFlagsFile.Valid() {
flags.Local.LdFlags = append(flags.Local.LdFlags, "$$(cat "+deps.LinkerFlagsFile.String()+")")
linkerDeps = append(linkerDeps, deps.LinkerFlagsFile.Path())
@@ -342,12 +379,15 @@
outputFile = maybeInjectBoringSSLHash(ctx, outputFile, binary.Properties.Inject_bssl_hash, fileName)
+ // If use_version_lib is true, make an android::build::GetBuildNumber() function available.
if Bool(binary.baseLinker.Properties.Use_version_lib) {
if ctx.Host() {
versionedOutputFile := outputFile
outputFile = android.PathForModuleOut(ctx, "unversioned", fileName)
binary.injectVersionSymbol(ctx, outputFile, versionedOutputFile)
} else {
+ // When dist'ing a library or binary that has use_version_lib set, always
+ // distribute the stamped version, even for the device.
versionedOutputFile := android.PathForModuleOut(ctx, "versioned", fileName)
binary.distFiles = android.MakeDefaultDistFiles(versionedOutputFile)
@@ -361,6 +401,7 @@
}
}
+ // Handle host bionic linker symbols.
if ctx.Os() == android.LinuxBionic && !binary.static() {
injectedOutputFile := outputFile
outputFile = android.PathForModuleOut(ctx, "prelinker", fileName)
@@ -386,6 +427,7 @@
linkerDeps = append(linkerDeps, objs.tidyFiles...)
linkerDeps = append(linkerDeps, flags.LdFlagsDeps...)
+ // Register link action.
TransformObjToDynamicBinary(ctx, objs.objFiles, sharedLibs, deps.StaticLibs,
deps.LateStaticLibs, deps.WholeStaticLibs, linkerDeps, deps.CrtBegin, deps.CrtEnd, true,
builderFlags, outputFile, nil)
@@ -406,6 +448,7 @@
ctx.PropertyErrorf("symlink_preferred_arch", "must also specify suffix")
}
if ctx.TargetPrimary() {
+ // Install a symlink to the preferred architecture
symlinkName := binary.getStemWithoutSuffix(ctx)
binary.symlinks = append(binary.symlinks, symlinkName)
binary.preferredArchSymlink = symlinkName
diff --git a/cc/cc.go b/cc/cc.go
index bd6e5d5..1a7ccf2 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -551,7 +551,15 @@
return d.Kind == staticLibraryDependency
}
-// dependencyTag is used for tagging miscellanous dependency types that don't fit into
+// InstallDepNeeded returns true for shared libraries so that shared library dependencies of
+// binaries or other shared libraries are installed as dependencies.
+func (d libraryDependencyTag) InstallDepNeeded() bool {
+ return d.shared()
+}
+
+var _ android.InstallNeededDependencyTag = libraryDependencyTag{}
+
+// dependencyTag is used for tagging miscellaneous dependency types that don't fit into
// libraryDependencyTag. Each tag object is created globally and reused for multiple
// dependencies (although since the object contains no references, assigning a tag to a
// variable and modifying it will not modify the original). Users can compare the tag
@@ -561,18 +569,27 @@
name string
}
+// installDependencyTag is used for tagging miscellaneous dependency types that don't fit into
+// libraryDependencyTag, but where the dependency needs to be installed when the parent is
+// installed.
+type installDependencyTag struct {
+ blueprint.BaseDependencyTag
+ android.InstallAlwaysNeededDependencyTag
+ name string
+}
+
var (
genSourceDepTag = dependencyTag{name: "gen source"}
genHeaderDepTag = dependencyTag{name: "gen header"}
genHeaderExportDepTag = dependencyTag{name: "gen header export"}
objDepTag = dependencyTag{name: "obj"}
linkerFlagsDepTag = dependencyTag{name: "linker flags file"}
- dynamicLinkerDepTag = dependencyTag{name: "dynamic linker"}
+ dynamicLinkerDepTag = installDependencyTag{name: "dynamic linker"}
reuseObjTag = dependencyTag{name: "reuse objects"}
staticVariantTag = dependencyTag{name: "static variant"}
vndkExtDepTag = dependencyTag{name: "vndk extends"}
dataLibDepTag = dependencyTag{name: "data lib"}
- runtimeDepTag = dependencyTag{name: "runtime lib"}
+ runtimeDepTag = installDependencyTag{name: "runtime lib"}
testPerSrcDepTag = dependencyTag{name: "test_per_src"}
stubImplDepTag = dependencyTag{name: "stub_impl"}
)
@@ -599,8 +616,7 @@
}
func IsRuntimeDepTag(depTag blueprint.DependencyTag) bool {
- ccDepTag, ok := depTag.(dependencyTag)
- return ok && ccDepTag == runtimeDepTag
+ return depTag == runtimeDepTag
}
func IsTestPerSrcDepTag(depTag blueprint.DependencyTag) bool {
@@ -1841,6 +1857,11 @@
return
}
+ // sysprop_library has to support both C++ and Java. So sysprop_library internally creates one
+ // C++ implementation library and one Java implementation library. When a module links against
+ // sysprop_library, the C++ implementation library has to be linked. syspropImplLibraries is a
+ // map from sysprop_library to implementation library; it will be used in whole_static_libs,
+ // static_libs, and shared_libs.
syspropImplLibraries := syspropImplLibraries(actx.Config())
vendorSnapshotStaticLibs := vendorSnapshotStaticLibs(actx.Config())
diff --git a/cc/cc_test.go b/cc/cc_test.go
index f5ce867..7c98585 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -4069,3 +4069,98 @@
}
}
+
+func TestInstallSharedLibs(t *testing.T) {
+ bp := `
+ cc_binary {
+ name: "bin",
+ host_supported: true,
+ shared_libs: ["libshared"],
+ runtime_libs: ["libruntime"],
+ srcs: [":gen"],
+ }
+
+ cc_library_shared {
+ name: "libshared",
+ host_supported: true,
+ shared_libs: ["libtransitive"],
+ }
+
+ cc_library_shared {
+ name: "libtransitive",
+ host_supported: true,
+ }
+
+ cc_library_shared {
+ name: "libruntime",
+ host_supported: true,
+ }
+
+ cc_binary_host {
+ name: "tool",
+ srcs: ["foo.cpp"],
+ }
+
+ genrule {
+ name: "gen",
+ tools: ["tool"],
+ out: ["gen.cpp"],
+ cmd: "$(location tool) $(out)",
+ }
+ `
+
+ config := TestConfig(buildDir, android.Android, nil, bp, nil)
+ ctx := testCcWithConfig(t, config)
+
+ hostBin := ctx.ModuleForTests("bin", config.BuildOSTarget.String()).Description("install")
+ hostShared := ctx.ModuleForTests("libshared", config.BuildOSTarget.String()+"_shared").Description("install")
+ hostRuntime := ctx.ModuleForTests("libruntime", config.BuildOSTarget.String()+"_shared").Description("install")
+ hostTransitive := ctx.ModuleForTests("libtransitive", config.BuildOSTarget.String()+"_shared").Description("install")
+ hostTool := ctx.ModuleForTests("tool", config.BuildOSTarget.String()).Description("install")
+
+ if g, w := hostBin.Implicits.Strings(), hostShared.Output.String(); !android.InList(w, g) {
+ t.Errorf("expected host bin dependency %q, got %q", w, g)
+ }
+
+ if g, w := hostBin.Implicits.Strings(), hostTransitive.Output.String(); !android.InList(w, g) {
+ t.Errorf("expected host bin dependency %q, got %q", w, g)
+ }
+
+ if g, w := hostShared.Implicits.Strings(), hostTransitive.Output.String(); !android.InList(w, g) {
+ t.Errorf("expected host bin dependency %q, got %q", w, g)
+ }
+
+ if g, w := hostBin.Implicits.Strings(), hostRuntime.Output.String(); !android.InList(w, g) {
+ t.Errorf("expected host bin dependency %q, got %q", w, g)
+ }
+
+ if g, w := hostBin.Implicits.Strings(), hostTool.Output.String(); android.InList(w, g) {
+ t.Errorf("expected no host bin dependency %q, got %q", w, g)
+ }
+
+ deviceBin := ctx.ModuleForTests("bin", "android_arm64_armv8-a").Description("install")
+ deviceShared := ctx.ModuleForTests("libshared", "android_arm64_armv8-a_shared").Description("install")
+ deviceTransitive := ctx.ModuleForTests("libtransitive", "android_arm64_armv8-a_shared").Description("install")
+ deviceRuntime := ctx.ModuleForTests("libruntime", "android_arm64_armv8-a_shared").Description("install")
+
+ if g, w := deviceBin.OrderOnly.Strings(), deviceShared.Output.String(); !android.InList(w, g) {
+ t.Errorf("expected device bin dependency %q, got %q", w, g)
+ }
+
+ if g, w := deviceBin.OrderOnly.Strings(), deviceTransitive.Output.String(); !android.InList(w, g) {
+ t.Errorf("expected device bin dependency %q, got %q", w, g)
+ }
+
+ if g, w := deviceShared.OrderOnly.Strings(), deviceTransitive.Output.String(); !android.InList(w, g) {
+ t.Errorf("expected device bin dependency %q, got %q", w, g)
+ }
+
+ if g, w := deviceBin.OrderOnly.Strings(), deviceRuntime.Output.String(); !android.InList(w, g) {
+ t.Errorf("expected device bin dependency %q, got %q", w, g)
+ }
+
+ if g, w := deviceBin.OrderOnly.Strings(), hostTool.Output.String(); android.InList(w, g) {
+ t.Errorf("expected no device bin dependency %q, got %q", w, g)
+ }
+
+}
diff --git a/cc/gen.go b/cc/gen.go
index 134d6d9..5895b31 100644
--- a/cc/gen.go
+++ b/cc/gen.go
@@ -232,7 +232,8 @@
var yaccRule_ *android.RuleBuilder
yaccRule := func() *android.RuleBuilder {
if yaccRule_ == nil {
- yaccRule_ = android.NewRuleBuilder().Sbox(android.PathForModuleGen(ctx, "yacc"))
+ yaccRule_ = android.NewRuleBuilder().Sbox(android.PathForModuleGen(ctx, "yacc"),
+ android.PathForModuleGen(ctx, "yacc.sbox.textproto"))
}
return yaccRule_
}
@@ -261,7 +262,8 @@
deps = append(deps, headerFile)
case ".aidl":
if aidlRule == nil {
- aidlRule = android.NewRuleBuilder().Sbox(android.PathForModuleGen(ctx, "aidl"))
+ aidlRule = android.NewRuleBuilder().Sbox(android.PathForModuleGen(ctx, "aidl"),
+ android.PathForModuleGen(ctx, "aidl.sbox.textproto"))
}
cppFile := android.GenPathWithExt(ctx, "aidl", srcFile, "cpp")
depFile := android.GenPathWithExt(ctx, "aidl", srcFile, "cpp.d")
diff --git a/cc/gen_test.go b/cc/gen_test.go
index 4b9a36e..41ef95c 100644
--- a/cc/gen_test.go
+++ b/cc/gen_test.go
@@ -18,6 +18,8 @@
"path/filepath"
"strings"
"testing"
+
+ "android/soong/android"
)
func TestGen(t *testing.T) {
@@ -56,13 +58,14 @@
}`)
aidl := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Rule("aidl")
+ aidlManifest := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Output("aidl.sbox.textproto")
libfoo := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Module().(*Module)
if !inList("-I"+filepath.Dir(aidl.Output.String()), libfoo.flags.Local.CommonFlags) {
t.Errorf("missing aidl includes in global flags")
}
- aidlCommand := aidl.RuleParams.Command
+ aidlCommand := android.RuleBuilderSboxProtoForTests(t, aidlManifest).Commands[0].GetCommand()
if !strings.Contains(aidlCommand, "-Isub") {
t.Errorf("aidl command for c.aidl should contain \"-Isub\", but was %q", aidlCommand)
}
diff --git a/cc/library.go b/cc/library.go
index 2127c08..7ae75f2 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -1202,15 +1202,21 @@
}
}
+ // If the library is sysprop_library, expose either public or internal header selectively.
if library.baseCompiler.hasSrcExt(".sysprop") {
dir := android.PathForModuleGen(ctx, "sysprop", "include")
if library.Properties.Sysprop.Platform != nil {
- isProduct := ctx.ProductSpecific() && !ctx.useVndk()
- isVendor := ctx.useVndk()
+ isClientProduct := ctx.ProductSpecific() && !ctx.useVndk()
+ isClientVendor := ctx.useVndk()
isOwnerPlatform := Bool(library.Properties.Sysprop.Platform)
+ // If the owner is different from the user, expose public header. That is,
+ // 1) if the user is product (as owner can only be platform / vendor)
+ // 2) if one is platform and the other is vendor
+ // Exceptions are ramdisk and recovery. They are not enforced at all. So
+ // they always use internal header.
if !ctx.inRamdisk() && !ctx.inVendorRamdisk() && !ctx.inRecovery() &&
- (isProduct || (isOwnerPlatform == isVendor)) {
+ (isClientProduct || (isOwnerPlatform == isClientVendor)) {
dir = android.PathForModuleGen(ctx, "sysprop/public", "include")
}
}
diff --git a/cc/linker.go b/cc/linker.go
index cbf8898..9d4a643 100644
--- a/cc/linker.go
+++ b/cc/linker.go
@@ -211,6 +211,7 @@
linker.Properties.Ldflags = append(linker.Properties.Ldflags, flags...)
}
+// linkerInit initializes dynamic properties of the linker (such as runpath).
func (linker *baseLinker) linkerInit(ctx BaseModuleContext) {
if ctx.toolchain().Is64Bit() {
linker.dynamicProperties.RunPaths = append(linker.dynamicProperties.RunPaths, "../lib64", "lib64")
diff --git a/cc/proto.go b/cc/proto.go
index ae988ec..9c102a2 100644
--- a/cc/proto.go
+++ b/cc/proto.go
@@ -130,6 +130,8 @@
flags.protoC = true
flags.protoOptionsFile = true
flags.proto.OutTypeFlag = "--nanopb_out"
+ // Disable nanopb timestamps to support remote caching.
+ flags.proto.OutParams = append(flags.proto.OutParams, "-T")
plugin = "protoc-gen-nanopb"
case "full":
flags.proto.OutTypeFlag = "--cpp_out"
diff --git a/cc/stub_library.go b/cc/stub_library.go
new file mode 100644
index 0000000..76d236c
--- /dev/null
+++ b/cc/stub_library.go
@@ -0,0 +1,82 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package cc
+
+import (
+ "strings"
+
+ "android/soong/android"
+)
+
+func init() {
+ // Use singleton type to gather all generated soong modules.
+ android.RegisterSingletonType("stublibraries", stubLibrariesSingleton)
+}
+
+type stubLibraries struct {
+ stubLibraryMap map[string]bool
+}
+
+// Check if the module defines stub, or itself is stub
+func isStubTarget(m *Module) bool {
+ if m.IsStubs() || m.HasStubsVariants() {
+ return true
+ }
+
+ // Library which defines LLNDK Stub is also Stub target.
+ // Pure LLNDK Stub target would not contain any packaging
+ // with target file path.
+ if library, ok := m.linker.(*libraryDecorator); ok {
+ if library.Properties.Llndk_stubs != nil {
+ return true
+ }
+ }
+
+ return false
+}
+
+// Get target file name to be installed from this module
+func getInstalledFileName(m *Module) string {
+ for _, ps := range m.PackagingSpecs() {
+ if name := ps.FileName(); name != "" {
+ return name
+ }
+ }
+ return ""
+}
+
+func (s *stubLibraries) GenerateBuildActions(ctx android.SingletonContext) {
+ // Visit all generated soong modules and store stub library file names.
+ ctx.VisitAllModules(func(module android.Module) {
+ if m, ok := module.(*Module); ok {
+ if isStubTarget(m) {
+ if name := getInstalledFileName(m); name != "" {
+ s.stubLibraryMap[name] = true
+ }
+ }
+ }
+ })
+}
+
+func stubLibrariesSingleton() android.Singleton {
+ return &stubLibraries{
+ stubLibraryMap: make(map[string]bool),
+ }
+}
+
+func (s *stubLibraries) MakeVars(ctx android.MakeVarsContext) {
+ // Convert stub library file names into Makefile variable.
+ ctx.Strict("STUB_LIBRARIES", strings.Join(android.SortedStringKeys(s.stubLibraryMap), " "))
+}
diff --git a/cc/sysprop.go b/cc/sysprop.go
index 6cac7fb..f578b50 100644
--- a/cc/sysprop.go
+++ b/cc/sysprop.go
@@ -14,6 +14,25 @@
package cc
+// This file contains a map to redirect dependencies towards sysprop_library.
+// As sysprop_library has to support both Java and C++, sysprop_library internally
+// generates cc_library and java_library. For example, the following sysprop_library
+//
+// sysprop_library {
+// name: "foo",
+// }
+//
+// will internally generate with prefix "lib"
+//
+// cc_library {
+// name: "libfoo",
+// }
+//
+// When a cc module links against "foo", build system will redirect the
+// dependency to "libfoo". To do that, SyspropMutator gathers all sysprop_library,
+// records their cc implementation library names to a map. The map will be used in
+// cc.Module.DepsMutator.
+
import (
"sync"
@@ -22,7 +41,7 @@
type syspropLibraryInterface interface {
BaseModuleName() string
- CcModuleName() string
+ CcImplementationModuleName() string
}
var (
@@ -43,6 +62,8 @@
syspropImplLibrariesLock.Lock()
defer syspropImplLibrariesLock.Unlock()
- syspropImplLibraries[m.BaseModuleName()] = m.CcModuleName()
+ // BaseModuleName is the name of sysprop_library
+ // CcImplementationModuleName is the name of cc_library generated by sysprop_library
+ syspropImplLibraries[m.BaseModuleName()] = m.CcImplementationModuleName()
}
}
diff --git a/cc/testing.go b/cc/testing.go
index 7161313..95a93a0 100644
--- a/cc/testing.go
+++ b/cc/testing.go
@@ -16,6 +16,7 @@
import (
"android/soong/android"
+ "android/soong/genrule"
)
func RegisterRequiredBuildComponentsForTest(ctx android.RegistrationContext) {
@@ -24,6 +25,7 @@
RegisterBinaryBuildComponents(ctx)
RegisterLibraryBuildComponents(ctx)
RegisterLibraryHeadersBuildComponents(ctx)
+ genrule.RegisterGenruleBuildComponents(ctx)
ctx.RegisterModuleType("toolchain_library", ToolchainLibraryFactory)
ctx.RegisterModuleType("llndk_library", LlndkLibraryFactory)
@@ -164,6 +166,10 @@
product_available: true,
recovery_available: true,
src: "",
+ apex_available: [
+ "//apex_available:platform",
+ "//apex_available:anyapex",
+ ],
}
toolchain_library {
diff --git a/cmd/sbox/Android.bp b/cmd/sbox/Android.bp
index 6fa304e..f5e87c0 100644
--- a/cmd/sbox/Android.bp
+++ b/cmd/sbox/Android.bp
@@ -14,8 +14,20 @@
blueprint_go_binary {
name: "sbox",
- deps: ["soong-makedeps"],
+ deps: [
+ "sbox_proto",
+ "soong-makedeps",
+ ],
srcs: [
"sbox.go",
],
}
+
+bootstrap_go_package {
+ name: "sbox_proto",
+ pkgPath: "android/soong/cmd/sbox/sbox_proto",
+ deps: ["golang-protobuf-proto"],
+ srcs: [
+ "sbox_proto/sbox.pb.go",
+ ],
+}
diff --git a/cmd/sbox/sbox.go b/cmd/sbox/sbox.go
index 65a34fd..a4f57ea 100644
--- a/cmd/sbox/sbox.go
+++ b/cmd/sbox/sbox.go
@@ -16,44 +16,44 @@
import (
"bytes"
+ "crypto/sha1"
+ "encoding/hex"
"errors"
"flag"
"fmt"
+ "io"
"io/ioutil"
"os"
"os/exec"
- "path"
"path/filepath"
+ "strconv"
"strings"
"time"
+ "android/soong/cmd/sbox/sbox_proto"
"android/soong/makedeps"
+
+ "github.com/golang/protobuf/proto"
)
var (
sandboxesRoot string
- rawCommand string
- outputRoot string
+ manifestFile string
keepOutDir bool
- depfileOut string
- inputHash string
+)
+
+const (
+ depFilePlaceholder = "__SBOX_DEPFILE__"
+ sandboxDirPlaceholder = "__SBOX_SANDBOX_DIR__"
)
func init() {
flag.StringVar(&sandboxesRoot, "sandbox-path", "",
"root of temp directory to put the sandbox into")
- flag.StringVar(&rawCommand, "c", "",
- "command to run")
- flag.StringVar(&outputRoot, "output-root", "",
- "root of directory to copy outputs into")
+ flag.StringVar(&manifestFile, "manifest", "",
+ "textproto manifest describing the sandboxed command(s)")
flag.BoolVar(&keepOutDir, "keep-out-dir", false,
"whether to keep the sandbox directory when done")
-
- flag.StringVar(&depfileOut, "depfile-out", "",
- "file path of the depfile to generate. This value will replace '__SBOX_DEPFILE__' in the command and will be treated as an output but won't be added to __SBOX_OUT_FILES__")
-
- flag.StringVar(&inputHash, "input-hash", "",
- "This option is ignored. Typical usage is to supply a hash of the list of input names so that the module will be rebuilt if the list (and thus the hash) changes.")
}
func usageViolation(violation string) {
@@ -62,11 +62,7 @@
}
fmt.Fprintf(os.Stderr,
- "Usage: sbox -c <commandToRun> --sandbox-path <sandboxPath> --output-root <outputRoot> [--depfile-out depFile] [--input-hash hash] <outputFile> [<outputFile>...]\n"+
- "\n"+
- "Deletes <outputRoot>,"+
- "runs <commandToRun>,"+
- "and moves each <outputFile> out of <sandboxPath> and into <outputRoot>\n")
+ "Usage: sbox --manifest <manifest> --sandbox-path <sandboxPath>\n")
flag.PrintDefaults()
@@ -103,8 +99,8 @@
}
func run() error {
- if rawCommand == "" {
- usageViolation("-c <commandToRun> is required and must be non-empty")
+ if manifestFile == "" {
+ usageViolation("--manifest <manifest> is required and must be non-empty")
}
if sandboxesRoot == "" {
// In practice, the value of sandboxesRoot will mostly likely be at a fixed location relative to OUT_DIR,
@@ -114,61 +110,43 @@
// and by passing it as a parameter we don't need to duplicate its value
usageViolation("--sandbox-path <sandboxPath> is required and must be non-empty")
}
- if len(outputRoot) == 0 {
- usageViolation("--output-root <outputRoot> is required and must be non-empty")
+
+ manifest, err := readManifest(manifestFile)
+
+ if len(manifest.Commands) == 0 {
+ return fmt.Errorf("at least one commands entry is required in %q", manifestFile)
}
- // the contents of the __SBOX_OUT_FILES__ variable
- outputsVarEntries := flag.Args()
- if len(outputsVarEntries) == 0 {
- usageViolation("at least one output file must be given")
+ // setup sandbox directory
+ err = os.MkdirAll(sandboxesRoot, 0777)
+ if err != nil {
+ return fmt.Errorf("failed to create %q: %w", sandboxesRoot, err)
}
- // all outputs
- var allOutputs []string
+ // This tool assumes that there are no two concurrent runs with the same
+ // manifestFile. It should therefore be safe to use the hash of the
+ // manifestFile as the temporary directory name. We do this because it
+ // makes the temporary directory name deterministic. There are some
+ // tools that embed the name of the temporary output in the output, and
+ // they otherwise cause non-determinism, which then poisons actions
+ // depending on this one.
+ hash := sha1.New()
+ hash.Write([]byte(manifestFile))
+ tempDir := filepath.Join(sandboxesRoot, "sbox", hex.EncodeToString(hash.Sum(nil)))
- // setup directories
- err := os.MkdirAll(sandboxesRoot, 0777)
+ err = os.RemoveAll(tempDir)
if err != nil {
return err
}
- err = os.RemoveAll(outputRoot)
+ err = os.MkdirAll(tempDir, 0777)
if err != nil {
- return err
- }
- err = os.MkdirAll(outputRoot, 0777)
- if err != nil {
- return err
- }
-
- tempDir, err := ioutil.TempDir(sandboxesRoot, "sbox")
-
- for i, filePath := range outputsVarEntries {
- if !strings.HasPrefix(filePath, "__SBOX_OUT_DIR__/") {
- return fmt.Errorf("output files must start with `__SBOX_OUT_DIR__/`")
- }
- outputsVarEntries[i] = strings.TrimPrefix(filePath, "__SBOX_OUT_DIR__/")
- }
-
- allOutputs = append([]string(nil), outputsVarEntries...)
-
- if depfileOut != "" {
- sandboxedDepfile, err := filepath.Rel(outputRoot, depfileOut)
- if err != nil {
- return err
- }
- allOutputs = append(allOutputs, sandboxedDepfile)
- rawCommand = strings.Replace(rawCommand, "__SBOX_DEPFILE__", filepath.Join(tempDir, sandboxedDepfile), -1)
-
- }
-
- if err != nil {
- return fmt.Errorf("Failed to create temp dir: %s", err)
+ return fmt.Errorf("failed to create temporary dir in %q: %w", sandboxesRoot, err)
}
// In the common case, the following line of code is what removes the sandbox
// If a fatal error occurs (such as if our Go process is killed unexpectedly),
- // then at the beginning of the next build, Soong will retry the cleanup
+ // then at the beginning of the next build, Soong will wipe the temporary
+ // directory.
defer func() {
// in some cases we decline to remove the temp dir, to facilitate debugging
if !keepOutDir {
@@ -176,27 +154,95 @@
}
}()
- if strings.Contains(rawCommand, "__SBOX_OUT_DIR__") {
- rawCommand = strings.Replace(rawCommand, "__SBOX_OUT_DIR__", tempDir, -1)
- }
+ // If there is more than one command in the manifest use a separate directory for each one.
+ useSubDir := len(manifest.Commands) > 1
+ var commandDepFiles []string
- if strings.Contains(rawCommand, "__SBOX_OUT_FILES__") {
- // expands into a space-separated list of output files to be generated into the sandbox directory
- tempOutPaths := []string{}
- for _, outputPath := range outputsVarEntries {
- tempOutPath := path.Join(tempDir, outputPath)
- tempOutPaths = append(tempOutPaths, tempOutPath)
+ for i, command := range manifest.Commands {
+ localTempDir := tempDir
+ if useSubDir {
+ localTempDir = filepath.Join(localTempDir, strconv.Itoa(i))
}
- pathsText := strings.Join(tempOutPaths, " ")
- rawCommand = strings.Replace(rawCommand, "__SBOX_OUT_FILES__", pathsText, -1)
- }
-
- for _, filePath := range allOutputs {
- dir := path.Join(tempDir, filepath.Dir(filePath))
- err = os.MkdirAll(dir, 0777)
+ depFile, err := runCommand(command, localTempDir)
if err != nil {
+ // Running the command failed, keep the temporary output directory around in
+ // case a user wants to inspect it for debugging purposes. Soong will delete
+ // it at the beginning of the next build anyway.
+ keepOutDir = true
return err
}
+ if depFile != "" {
+ commandDepFiles = append(commandDepFiles, depFile)
+ }
+ }
+
+ outputDepFile := manifest.GetOutputDepfile()
+ if len(commandDepFiles) > 0 && outputDepFile == "" {
+ return fmt.Errorf("Sandboxed commands used %s but output depfile is not set in manifest file",
+ depFilePlaceholder)
+ }
+
+ if outputDepFile != "" {
+ // Merge the depfiles from each command in the manifest to a single output depfile.
+ err = rewriteDepFiles(commandDepFiles, outputDepFile)
+ if err != nil {
+ return fmt.Errorf("failed merging depfiles: %w", err)
+ }
+ }
+
+ return nil
+}
+
+// readManifest reads an sbox manifest from a textproto file.
+func readManifest(file string) (*sbox_proto.Manifest, error) {
+ manifestData, err := ioutil.ReadFile(file)
+ if err != nil {
+ return nil, fmt.Errorf("error reading manifest %q: %w", file, err)
+ }
+
+ manifest := sbox_proto.Manifest{}
+
+ err = proto.UnmarshalText(string(manifestData), &manifest)
+ if err != nil {
+ return nil, fmt.Errorf("error parsing manifest %q: %w", file, err)
+ }
+
+ return &manifest, nil
+}
+
+// runCommand runs a single command from a manifest. If the command references the
+// __SBOX_DEPFILE__ placeholder it returns the name of the depfile that was used.
+func runCommand(command *sbox_proto.Command, tempDir string) (depFile string, err error) {
+ rawCommand := command.GetCommand()
+ if rawCommand == "" {
+ return "", fmt.Errorf("command is required")
+ }
+
+ err = os.MkdirAll(tempDir, 0777)
+ if err != nil {
+ return "", fmt.Errorf("failed to create %q: %w", tempDir, err)
+ }
+
+ // Copy in any files specified by the manifest.
+ err = copyFiles(command.CopyBefore, "", tempDir)
+ if err != nil {
+ return "", err
+ }
+
+ if strings.Contains(rawCommand, depFilePlaceholder) {
+ depFile = filepath.Join(tempDir, "deps.d")
+ rawCommand = strings.Replace(rawCommand, depFilePlaceholder, depFile, -1)
+ }
+
+ if strings.Contains(rawCommand, sandboxDirPlaceholder) {
+ rawCommand = strings.Replace(rawCommand, sandboxDirPlaceholder, tempDir, -1)
+ }
+
+ // Emulate ninja's behavior of creating the directories for any output files before
+ // running the command.
+ err = makeOutputDirs(command.CopyAfter, tempDir)
+ if err != nil {
+ return "", err
}
commandDescription := rawCommand
@@ -205,27 +251,20 @@
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
+
+ if command.GetChdir() {
+ cmd.Dir = tempDir
+ }
err = cmd.Run()
if exit, ok := err.(*exec.ExitError); ok && !exit.Success() {
- return fmt.Errorf("sbox command (%s) failed with err %#v\n", commandDescription, err.Error())
+ return "", fmt.Errorf("sbox command failed with err:\n%s\n%w\n", commandDescription, err)
} else if err != nil {
- return err
+ return "", err
}
- // validate that all files are created properly
- var missingOutputErrors []string
- for _, filePath := range allOutputs {
- tempPath := filepath.Join(tempDir, filePath)
- fileInfo, err := os.Stat(tempPath)
- if err != nil {
- missingOutputErrors = append(missingOutputErrors, fmt.Sprintf("%s: does not exist", filePath))
- continue
- }
- if fileInfo.IsDir() {
- missingOutputErrors = append(missingOutputErrors, fmt.Sprintf("%s: not a file", filePath))
- }
- }
+ missingOutputErrors := validateOutputFiles(command.CopyAfter, tempDir)
+
if len(missingOutputErrors) > 0 {
// find all created files for making a more informative error message
createdFiles := findAllFilesUnder(tempDir)
@@ -236,7 +275,7 @@
errorMessage += "in sandbox " + tempDir + ",\n"
errorMessage += fmt.Sprintf("failed to create %v files:\n", len(missingOutputErrors))
for _, missingOutputError := range missingOutputErrors {
- errorMessage += " " + missingOutputError + "\n"
+ errorMessage += " " + missingOutputError.Error() + "\n"
}
if len(createdFiles) < 1 {
errorMessage += "created 0 files."
@@ -253,19 +292,120 @@
}
}
- // Keep the temporary output directory around in case a user wants to inspect it for debugging purposes.
- // Soong will delete it later anyway.
- keepOutDir = true
- return errors.New(errorMessage)
+ return "", errors.New(errorMessage)
}
// the created files match the declared files; now move them
- for _, filePath := range allOutputs {
- tempPath := filepath.Join(tempDir, filePath)
- destPath := filePath
- if len(outputRoot) != 0 {
- destPath = filepath.Join(outputRoot, filePath)
+ err = moveFiles(command.CopyAfter, tempDir, "")
+
+ return depFile, nil
+}
+
+// makeOutputDirs creates directories in the sandbox dir for every file that has a rule to be copied
+// out of the sandbox. This emulate's Ninja's behavior of creating directories for output files
+// so that the tools don't have to.
+func makeOutputDirs(copies []*sbox_proto.Copy, sandboxDir string) error {
+ for _, copyPair := range copies {
+ dir := joinPath(sandboxDir, filepath.Dir(copyPair.GetFrom()))
+ err := os.MkdirAll(dir, 0777)
+ if err != nil {
+ return err
}
- err := os.MkdirAll(filepath.Dir(destPath), 0777)
+ }
+ return nil
+}
+
+// validateOutputFiles verifies that all files that have a rule to be copied out of the sandbox
+// were created by the command.
+func validateOutputFiles(copies []*sbox_proto.Copy, sandboxDir string) []error {
+ var missingOutputErrors []error
+ for _, copyPair := range copies {
+ fromPath := joinPath(sandboxDir, copyPair.GetFrom())
+ fileInfo, err := os.Stat(fromPath)
+ if err != nil {
+ missingOutputErrors = append(missingOutputErrors, fmt.Errorf("%s: does not exist", fromPath))
+ continue
+ }
+ if fileInfo.IsDir() {
+ missingOutputErrors = append(missingOutputErrors, fmt.Errorf("%s: not a file", fromPath))
+ }
+ }
+ return missingOutputErrors
+}
+
+// copyFiles copies files in or out of the sandbox.
+func copyFiles(copies []*sbox_proto.Copy, fromDir, toDir string) error {
+ for _, copyPair := range copies {
+ fromPath := joinPath(fromDir, copyPair.GetFrom())
+ toPath := joinPath(toDir, copyPair.GetTo())
+ err := copyOneFile(fromPath, toPath)
+ if err != nil {
+ return fmt.Errorf("error copying %q to %q: %w", fromPath, toPath, err)
+ }
+ }
+ return nil
+}
+
+// copyOneFile copies a file.
+func copyOneFile(from string, to string) error {
+ err := os.MkdirAll(filepath.Dir(to), 0777)
+ if err != nil {
+ return err
+ }
+
+ stat, err := os.Stat(from)
+ if err != nil {
+ return err
+ }
+
+ perm := stat.Mode()
+
+ in, err := os.Open(from)
+ if err != nil {
+ return err
+ }
+ defer in.Close()
+
+ out, err := os.Create(to)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ out.Close()
+ if err != nil {
+ os.Remove(to)
+ }
+ }()
+
+ _, err = io.Copy(out, in)
+ if err != nil {
+ return err
+ }
+
+ if err = out.Close(); err != nil {
+ return err
+ }
+
+ if err = os.Chmod(to, perm); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// moveFiles moves files specified by a set of copy rules. It uses os.Rename, so it is restricted
+// to moving files where the source and destination are in the same filesystem. This is OK for
+// sbox because the temporary directory is inside the out directory. It updates the timestamp
+// of the new file.
+func moveFiles(copies []*sbox_proto.Copy, fromDir, toDir string) error {
+ for _, copyPair := range copies {
+ fromPath := joinPath(fromDir, copyPair.GetFrom())
+ toPath := joinPath(toDir, copyPair.GetTo())
+ err := os.MkdirAll(filepath.Dir(toPath), 0777)
+ if err != nil {
+ return err
+ }
+
+ err = os.Rename(fromPath, toPath)
if err != nil {
return err
}
@@ -273,37 +413,53 @@
// Update the timestamp of the output file in case the tool wrote an old timestamp (for example, tar can extract
// files with old timestamps).
now := time.Now()
- err = os.Chtimes(tempPath, now, now)
- if err != nil {
- return err
- }
-
- err = os.Rename(tempPath, destPath)
+ err = os.Chtimes(toPath, now, now)
if err != nil {
return err
}
}
-
- // Rewrite the depfile so that it doesn't include the (randomized) sandbox directory
- if depfileOut != "" {
- in, err := ioutil.ReadFile(depfileOut)
- if err != nil {
- return err
- }
-
- deps, err := makedeps.Parse(depfileOut, bytes.NewBuffer(in))
- if err != nil {
- return err
- }
-
- deps.Output = "outputfile"
-
- err = ioutil.WriteFile(depfileOut, deps.Print(), 0666)
- if err != nil {
- return err
- }
- }
-
- // TODO(jeffrygaston) if a process creates more output files than it declares, should there be a warning?
return nil
}
+
+// Rewrite one or more depfiles so that it doesn't include the (randomized) sandbox directory
+// to an output file.
+func rewriteDepFiles(ins []string, out string) error {
+ var mergedDeps []string
+ for _, in := range ins {
+ data, err := ioutil.ReadFile(in)
+ if err != nil {
+ return err
+ }
+
+ deps, err := makedeps.Parse(in, bytes.NewBuffer(data))
+ if err != nil {
+ return err
+ }
+ mergedDeps = append(mergedDeps, deps.Inputs...)
+ }
+
+ deps := makedeps.Deps{
+ // Ninja doesn't care what the output file is, so we can use any string here.
+ Output: "outputfile",
+ Inputs: mergedDeps,
+ }
+
+ // Make the directory for the output depfile in case it is in a different directory
+ // than any of the output files.
+ outDir := filepath.Dir(out)
+ err := os.MkdirAll(outDir, 0777)
+ if err != nil {
+ return fmt.Errorf("failed to create %q: %w", outDir, err)
+ }
+
+ return ioutil.WriteFile(out, deps.Print(), 0666)
+}
+
+// joinPath wraps filepath.Join but returns file without appending to dir if file is
+// absolute.
+func joinPath(dir, file string) string {
+ if filepath.IsAbs(file) {
+ return file
+ }
+ return filepath.Join(dir, file)
+}
diff --git a/cmd/sbox/sbox_proto/sbox.pb.go b/cmd/sbox/sbox_proto/sbox.pb.go
new file mode 100644
index 0000000..6584bdf
--- /dev/null
+++ b/cmd/sbox/sbox_proto/sbox.pb.go
@@ -0,0 +1,233 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: sbox.proto
+
+package sbox_proto
+
+import (
+ fmt "fmt"
+ proto "github.com/golang/protobuf/proto"
+ math "math"
+)
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
+
+// A set of commands to run in a sandbox.
+type Manifest struct {
+ // A list of commands to run in the sandbox.
+ Commands []*Command `protobuf:"bytes,1,rep,name=commands" json:"commands,omitempty"`
+ // If set, GCC-style dependency files from any command that references __SBOX_DEPFILE__ will be
+ // merged into the given output file relative to the $PWD when sbox was started.
+ OutputDepfile *string `protobuf:"bytes,2,opt,name=output_depfile,json=outputDepfile" json:"output_depfile,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Manifest) Reset() { *m = Manifest{} }
+func (m *Manifest) String() string { return proto.CompactTextString(m) }
+func (*Manifest) ProtoMessage() {}
+func (*Manifest) Descriptor() ([]byte, []int) {
+ return fileDescriptor_9d0425bf0de86ed1, []int{0}
+}
+
+func (m *Manifest) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Manifest.Unmarshal(m, b)
+}
+func (m *Manifest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Manifest.Marshal(b, m, deterministic)
+}
+func (m *Manifest) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Manifest.Merge(m, src)
+}
+func (m *Manifest) XXX_Size() int {
+ return xxx_messageInfo_Manifest.Size(m)
+}
+func (m *Manifest) XXX_DiscardUnknown() {
+ xxx_messageInfo_Manifest.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Manifest proto.InternalMessageInfo
+
+func (m *Manifest) GetCommands() []*Command {
+ if m != nil {
+ return m.Commands
+ }
+ return nil
+}
+
+func (m *Manifest) GetOutputDepfile() string {
+ if m != nil && m.OutputDepfile != nil {
+ return *m.OutputDepfile
+ }
+ return ""
+}
+
+// SandboxManifest describes a command to run in the sandbox.
+type Command struct {
+ // A list of copy rules to run before the sandboxed command. The from field is relative to the
+ // $PWD when sbox was run, the to field is relative to the top of the temporary sandbox directory.
+ CopyBefore []*Copy `protobuf:"bytes,1,rep,name=copy_before,json=copyBefore" json:"copy_before,omitempty"`
+ // If true, change the working directory to the top of the temporary sandbox directory before
+ // running the command. If false, leave the working directory where it was when sbox was started.
+ Chdir *bool `protobuf:"varint,2,opt,name=chdir" json:"chdir,omitempty"`
+ // The command to run.
+ Command *string `protobuf:"bytes,3,req,name=command" json:"command,omitempty"`
+ // A list of copy rules to run after the sandboxed command. The from field is relative to the
+ // top of the temporary sandbox directory, the to field is relative to the $PWD when sbox was run.
+ CopyAfter []*Copy `protobuf:"bytes,4,rep,name=copy_after,json=copyAfter" json:"copy_after,omitempty"`
+ // An optional hash of the input files to ensure the textproto files and the sbox rule reruns
+ // when the lists of inputs changes, even if the inputs are not on the command line.
+ InputHash *string `protobuf:"bytes,5,opt,name=input_hash,json=inputHash" json:"input_hash,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Command) Reset() { *m = Command{} }
+func (m *Command) String() string { return proto.CompactTextString(m) }
+func (*Command) ProtoMessage() {}
+func (*Command) Descriptor() ([]byte, []int) {
+ return fileDescriptor_9d0425bf0de86ed1, []int{1}
+}
+
+func (m *Command) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Command.Unmarshal(m, b)
+}
+func (m *Command) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Command.Marshal(b, m, deterministic)
+}
+func (m *Command) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Command.Merge(m, src)
+}
+func (m *Command) XXX_Size() int {
+ return xxx_messageInfo_Command.Size(m)
+}
+func (m *Command) XXX_DiscardUnknown() {
+ xxx_messageInfo_Command.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Command proto.InternalMessageInfo
+
+func (m *Command) GetCopyBefore() []*Copy {
+ if m != nil {
+ return m.CopyBefore
+ }
+ return nil
+}
+
+func (m *Command) GetChdir() bool {
+ if m != nil && m.Chdir != nil {
+ return *m.Chdir
+ }
+ return false
+}
+
+func (m *Command) GetCommand() string {
+ if m != nil && m.Command != nil {
+ return *m.Command
+ }
+ return ""
+}
+
+func (m *Command) GetCopyAfter() []*Copy {
+ if m != nil {
+ return m.CopyAfter
+ }
+ return nil
+}
+
+func (m *Command) GetInputHash() string {
+ if m != nil && m.InputHash != nil {
+ return *m.InputHash
+ }
+ return ""
+}
+
+// Copy describes a from-to pair of files to copy. The paths may be relative, the root that they
+// are relative to is specific to the context the Copy is used in and will be different for
+// from and to.
+type Copy struct {
+ From *string `protobuf:"bytes,1,req,name=from" json:"from,omitempty"`
+ To *string `protobuf:"bytes,2,req,name=to" json:"to,omitempty"`
+ XXX_NoUnkeyedLiteral struct{} `json:"-"`
+ XXX_unrecognized []byte `json:"-"`
+ XXX_sizecache int32 `json:"-"`
+}
+
+func (m *Copy) Reset() { *m = Copy{} }
+func (m *Copy) String() string { return proto.CompactTextString(m) }
+func (*Copy) ProtoMessage() {}
+func (*Copy) Descriptor() ([]byte, []int) {
+ return fileDescriptor_9d0425bf0de86ed1, []int{2}
+}
+
+func (m *Copy) XXX_Unmarshal(b []byte) error {
+ return xxx_messageInfo_Copy.Unmarshal(m, b)
+}
+func (m *Copy) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+ return xxx_messageInfo_Copy.Marshal(b, m, deterministic)
+}
+func (m *Copy) XXX_Merge(src proto.Message) {
+ xxx_messageInfo_Copy.Merge(m, src)
+}
+func (m *Copy) XXX_Size() int {
+ return xxx_messageInfo_Copy.Size(m)
+}
+func (m *Copy) XXX_DiscardUnknown() {
+ xxx_messageInfo_Copy.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_Copy proto.InternalMessageInfo
+
+func (m *Copy) GetFrom() string {
+ if m != nil && m.From != nil {
+ return *m.From
+ }
+ return ""
+}
+
+func (m *Copy) GetTo() string {
+ if m != nil && m.To != nil {
+ return *m.To
+ }
+ return ""
+}
+
+func init() {
+ proto.RegisterType((*Manifest)(nil), "sbox.Manifest")
+ proto.RegisterType((*Command)(nil), "sbox.Command")
+ proto.RegisterType((*Copy)(nil), "sbox.Copy")
+}
+
+func init() {
+ proto.RegisterFile("sbox.proto", fileDescriptor_9d0425bf0de86ed1)
+}
+
+var fileDescriptor_9d0425bf0de86ed1 = []byte{
+ // 252 bytes of a gzipped FileDescriptorProto
+ 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x90, 0x41, 0x4b, 0xc3, 0x40,
+ 0x10, 0x85, 0x49, 0x9a, 0xd2, 0x66, 0x6a, 0x7b, 0x18, 0x3c, 0xec, 0x45, 0x08, 0x01, 0x21, 0x55,
+ 0xe8, 0xc1, 0x7f, 0x60, 0xf5, 0xe0, 0xc5, 0xcb, 0x1e, 0x45, 0x08, 0xdb, 0x64, 0x97, 0x04, 0x4c,
+ 0x66, 0xd9, 0xdd, 0x82, 0xfd, 0x57, 0xfe, 0x44, 0xd9, 0x49, 0xea, 0xc5, 0xdb, 0xcc, 0xfb, 0x78,
+ 0xf3, 0x1e, 0x03, 0xe0, 0x4f, 0xf4, 0x7d, 0xb0, 0x8e, 0x02, 0x61, 0x16, 0xe7, 0xf2, 0x13, 0xd6,
+ 0xef, 0x6a, 0xec, 0x8d, 0xf6, 0x01, 0xf7, 0xb0, 0x6e, 0x68, 0x18, 0xd4, 0xd8, 0x7a, 0x91, 0x14,
+ 0x8b, 0x6a, 0xf3, 0xb4, 0x3d, 0xb0, 0xe1, 0x65, 0x52, 0xe5, 0x1f, 0xc6, 0x7b, 0xd8, 0xd1, 0x39,
+ 0xd8, 0x73, 0xa8, 0x5b, 0x6d, 0x4d, 0xff, 0xa5, 0x45, 0x5a, 0x24, 0x55, 0x2e, 0xb7, 0x93, 0xfa,
+ 0x3a, 0x89, 0xe5, 0x4f, 0x02, 0xab, 0xd9, 0x8c, 0x8f, 0xb0, 0x69, 0xc8, 0x5e, 0xea, 0x93, 0x36,
+ 0xe4, 0xf4, 0x1c, 0x00, 0xd7, 0x00, 0x7b, 0x91, 0x10, 0xf1, 0x91, 0x29, 0xde, 0xc2, 0xb2, 0xe9,
+ 0xda, 0xde, 0xf1, 0xd9, 0xb5, 0x9c, 0x16, 0x14, 0xb0, 0x9a, 0x1b, 0x88, 0x45, 0x91, 0x56, 0xb9,
+ 0xbc, 0xae, 0xb8, 0x07, 0x76, 0xd7, 0xca, 0x04, 0xed, 0x44, 0xf6, 0xef, 0x76, 0x1e, 0xe9, 0x73,
+ 0x84, 0x78, 0x07, 0xd0, 0x8f, 0xb1, 0x79, 0xa7, 0x7c, 0x27, 0x96, 0x5c, 0x3b, 0x67, 0xe5, 0x4d,
+ 0xf9, 0xae, 0x7c, 0x80, 0x2c, 0x3a, 0x10, 0x21, 0x33, 0x8e, 0x06, 0x91, 0x70, 0x10, 0xcf, 0xb8,
+ 0x83, 0x34, 0x90, 0x48, 0x59, 0x49, 0x03, 0x1d, 0x6f, 0x3e, 0xf8, 0xa1, 0x35, 0x3f, 0xf4, 0x37,
+ 0x00, 0x00, 0xff, 0xff, 0x95, 0x4d, 0xee, 0x7d, 0x5d, 0x01, 0x00, 0x00,
+}
diff --git a/cmd/sbox/sbox_proto/sbox.proto b/cmd/sbox/sbox_proto/sbox.proto
new file mode 100644
index 0000000..ab95545
--- /dev/null
+++ b/cmd/sbox/sbox_proto/sbox.proto
@@ -0,0 +1,58 @@
+// Copyright 2020 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+syntax = "proto2";
+
+package sbox;
+option go_package = "sbox_proto";
+
+// A set of commands to run in a sandbox.
+message Manifest {
+ // A list of commands to run in the sandbox.
+ repeated Command commands = 1;
+
+ // If set, GCC-style dependency files from any command that references __SBOX_DEPFILE__ will be
+ // merged into the given output file relative to the $PWD when sbox was started.
+ optional string output_depfile = 2;
+}
+
+// SandboxManifest describes a command to run in the sandbox.
+message Command {
+ // A list of copy rules to run before the sandboxed command. The from field is relative to the
+ // $PWD when sbox was run, the to field is relative to the top of the temporary sandbox directory.
+ repeated Copy copy_before = 1;
+
+ // If true, change the working directory to the top of the temporary sandbox directory before
+ // running the command. If false, leave the working directory where it was when sbox was started.
+ optional bool chdir = 2;
+
+ // The command to run.
+ required string command = 3;
+
+ // A list of copy rules to run after the sandboxed command. The from field is relative to the
+ // top of the temporary sandbox directory, the to field is relative to the $PWD when sbox was run.
+ repeated Copy copy_after = 4;
+
+ // An optional hash of the input files to ensure the textproto files and the sbox rule reruns
+ // when the lists of inputs changes, even if the inputs are not on the command line.
+ optional string input_hash = 5;
+}
+
+// Copy describes a from-to pair of files to copy. The paths may be relative, the root that they
+// are relative to is specific to the context the Copy is used in and will be different for
+// from and to.
+message Copy {
+ required string from = 1;
+ required string to = 2;
+}
\ No newline at end of file
diff --git a/cmd/soong_build/queryview_test.go b/cmd/soong_build/queryview_test.go
index 525802a..9471a91 100644
--- a/cmd/soong_build/queryview_test.go
+++ b/cmd/soong_build/queryview_test.go
@@ -53,6 +53,12 @@
android.ModuleBase
}
+// OutputFiles is needed because some instances of this module use dist with a
+// tag property which requires the module implements OutputFileProducer.
+func (m *customModule) OutputFiles(tag string) (android.Paths, error) {
+ return android.PathsForTesting("path" + tag), nil
+}
+
func (m *customModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
// nothing for now.
}
diff --git a/dexpreopt/class_loader_context.go b/dexpreopt/class_loader_context.go
index b910a70..3759217 100644
--- a/dexpreopt/class_loader_context.go
+++ b/dexpreopt/class_loader_context.go
@@ -22,17 +22,214 @@
"android/soong/android"
)
-// These libs are added as <uses-library> dependencies for apps if the targetSdkVersion in the
-// app manifest is less than the specified version. This is needed because these libraries haven't
-// existed prior to certain SDK version, but classes in them were in bootclasspath jars, etc.
-// Some of the compatibility libraries are optional (their <uses-library> tag has "required=false"),
-// so that if this library is missing this in not a build or run-time error.
+// This comment describes the following:
+// 1. the concept of class loader context (CLC) and its relation to classpath
+// 2. how PackageManager constructs CLC from shared libraries and their dependencies
+// 3. build-time vs. run-time CLC and why this matters for dexpreopt
+// 4. manifest fixer: a tool that adds missing <uses-library> tags to the manifests
+// 5. build system support for CLC
+//
+// 1. Class loader context
+// -----------------------
+//
+// Java libraries and apps that have run-time dependency on other libraries should list the used
+// libraries in their manifest (AndroidManifest.xml file). Each used library should be specified in
+// a <uses-library> tag that has the library name and an optional attribute specifying if the
+// library is optional or required. Required libraries are necessary for the library/app to run (it
+// will fail at runtime if the library cannot be loaded), and optional libraries are used only if
+// they are present (if not, the library/app can run without them).
+//
+// The libraries listed in <uses-library> tags are in the classpath of a library/app.
+//
+// Besides libraries, an app may also use another APK (for example in the case of split APKs), or
+// anything that gets added by the app dynamically. In general, it is impossible to know at build
+// time what the app may use at runtime. In the build system we focus on the known part: libraries.
+//
+// Class loader context (CLC) is a tree-like structure that describes class loader hierarchy. The
+// build system uses CLC in a more narrow sense: it is a tree of libraries that represents
+// transitive closure of all <uses-library> dependencies of a library/app. The top-level elements of
+// a CLC are the direct <uses-library> dependencies specified in the manifest (aka. classpath). Each
+// node of a CLC tree is a <uses-library> which may have its own <uses-library> sub-nodes.
+//
+// Because <uses-library> dependencies are, in general, a graph and not necessarily a tree, CLC may
+// contain subtrees for the same library multiple times. In other words, CLC is the dependency graph
+// "unfolded" to a tree. The duplication is only on a logical level, and the actual underlying class
+// loaders are not duplicated (at runtime there is a single class loader instance for each library).
+//
+// Example: A has <uses-library> tags B, C and D; C has <uses-library tags> B and D;
+// D has <uses-library> E; B and E have no <uses-library> dependencies. The CLC is:
+// A
+// ├── B
+// ├── C
+// │ ├── B
+// │ └── D
+// │ └── E
+// └── D
+// └── E
+//
+// CLC defines the lookup order of libraries when resolving Java classes used by the library/app.
+// The lookup order is important because libraries may contain duplicate classes, and the class is
+// resolved to the first match.
+//
+// 2. PackageManager and "shared" libraries
+// ----------------------------------------
+//
+// In order to load an APK at runtime, PackageManager (in frameworks/base) creates a CLC. It adds
+// the libraries listed in the <uses-library> tags in the app's manifest as top-level CLC elements.
+// For each of the used libraries PackageManager gets all its <uses-library> dependencies (specified
+// as tags in the manifest of that library) and adds a nested CLC for each dependency. This process
+// continues recursively until all leaf nodes of the constructed CLC tree are libraries that have no
+// <uses-library> dependencies.
+//
+// PackageManager is aware only of "shared" libraries. The definition of "shared" here differs from
+// its usual meaning (as in shared vs. static). In Android, Java "shared" libraries are those listed
+// in /system/etc/permissions/platform.xml file. This file is installed on device. Each entry in it
+// contains the name of a "shared" library, a path to its DEX jar file and a list of dependencies
+// (other "shared" libraries that this one uses at runtime and specifies them in <uses-library> tags
+// in its manifest).
+//
+// In other words, there are two sources of information that allow PackageManager to construct CLC
+// at runtime: <uses-library> tags in the manifests and "shared" library dependencies in
+// /system/etc/permissions/platform.xml.
+//
+// 3. Build-time and run-time CLC and dexpreopt
+// --------------------------------------------
+//
+// CLC is needed not only when loading a library/app, but also when compiling it. Compilation may
+// happen either on device (known as "dexopt") or during the build (known as "dexpreopt"). Since
+// dexopt takes place on device, it has the same information as PackageManager (manifests and
+// shared library dependencies). Dexpreopt, on the other hand, takes place on host and in a totally
+// different environment, and it has to get the same information from the build system (see the
+// section about build system support below).
+//
+// Thus, the build-time CLC used by dexpreopt and the run-time CLC used by PackageManager are
+// the same thing, but computed in two different ways.
+//
+// It is important that build-time and run-time CLCs coincide, otherwise the AOT-compiled code
+// created by dexpreopt will be rejected. In order to check the equality of build-time and
+// run-time CLCs, the dex2oat compiler records build-time CLC in the *.odex files (in the
+// "classpath" field of the OAT file header). To find the stored CLC, use the following command:
+// `oatdump --oat-file=<FILE> | grep '^classpath = '`.
+//
+// Mismatch between build-time and run-time CLC is reported in logcat during boot (search with
+// `logcat | grep -E 'ClassLoaderContext [a-z ]+ mismatch'`. Mismatch is bad for performance, as it
+// forces the library/app to either be dexopted, or to run without any optimizations (e.g. the app's
+// code may need to be extracted in memory from the APK, a very expensive operation).
+//
+// A <uses-library> can be either optional or required. From dexpreopt standpoint, required library
+// must be present at build time (its absence is a build error). An optional library may be either
+// present or absent at build time: if present, it will be added to the CLC, passed to dex2oat and
+// recorded in the *.odex file; otherwise, if the library is absent, it will be skipped and not
+// added to CLC. If there is a mismatch between built-time and run-time status (optional library is
+// present in one case, but not the other), then the build-time and run-time CLCs won't match and
+// the compiled code will be rejected. It is unknown at build time if the library will be present at
+// runtime, therefore either including or excluding it may cause CLC mismatch.
+//
+// 4. Manifest fixer
+// -----------------
+//
+// Sometimes <uses-library> tags are missing from the source manifest of a library/app. This may
+// happen for example if one of the transitive dependencies of the library/app starts using another
+// <uses-library>, and the library/app's manifest isn't updated to include it.
+//
+// Soong can compute some of the missing <uses-library> tags for a given library/app automatically
+// as SDK libraries in the transitive dependency closure of the library/app. The closure is needed
+// because a library/app may depend on a static library that may in turn depend on an SDK library,
+// (possibly transitively via another library).
+//
+// Not all <uses-library> tags can be computed in this way, because some of the <uses-library>
+// dependencies are not SDK libraries, or they are not reachable via transitive dependency closure.
+// But when possible, allowing Soong to calculate the manifest entries is less prone to errors and
+// simplifies maintenance. For example, consider a situation when many apps use some static library
+// that adds a new <uses-library> dependency -- all the apps will have to be updated. That is
+// difficult to maintain.
+//
+// Soong computes the libraries that need to be in the manifest as the top-level libraries in CLC.
+// These libraries are passed to the manifest_fixer.
+//
+// All libraries added to the manifest should be "shared" libraries, so that PackageManager can look
+// up their dependencies and reconstruct the nested subcontexts at runtime. There is no build check
+// to ensure this, it is an assumption.
+//
+// 5. Build system support
+// -----------------------
+//
+// In order to construct CLC for dexpreopt and manifest_fixer, the build system needs to know all
+// <uses-library> dependencies of the dexpreopted library/app (including transitive dependencies).
+// For each <uses-librarry> dependency it needs to know the following information:
+//
+// - the real name of the <uses-library> (it may be different from the module name)
+// - build-time (on host) and run-time (on device) paths to the DEX jar file of the library
+// - whether this library is optional or required
+// - all <uses-library> dependencies
+//
+// Since the build system doesn't have access to the manifest contents (it cannot read manifests at
+// the time of build rule generation), it is necessary to copy this information to the Android.bp
+// and Android.mk files. For blueprints, the relevant properties are `uses_libs` and
+// `optional_uses_libs`. For makefiles, relevant variables are `LOCAL_USES_LIBRARIES` and
+// `LOCAL_OPTIONAL_USES_LIBRARIES`. It is preferable to avoid specifying these properties explicilty
+// when they can be computed automatically by Soong (as the transitive closure of SDK library
+// dependencies).
+//
+// Some of the Java libraries that are used as <uses-library> are not SDK libraries (they are
+// defined as `java_library` rather than `java_sdk_library` in the Android.bp files). In order for
+// the build system to handle them automatically like SDK libraries, it is possible to set a
+// property `provides_uses_lib` or variable `LOCAL_PROVIDES_USES_LIBRARY` on the blueprint/makefile
+// module of such library. This property can also be used to specify real library name in cases
+// when it differs from the module name.
+//
+// Because the information from the manifests has to be duplicated in the Android.bp/Android.mk
+// files, there is a danger that it may get out of sync. To guard against that, the build system
+// generates a rule that checks the metadata in the build files against the contents of a manifest
+// (verify_uses_libraries). The manifest can be available as a source file, or as part of a prebuilt
+// APK. Note that reading the manifests at the Ninja stage of the build is fine, unlike the build
+// rule generation phase.
+//
+// ClassLoaderContext is a structure that represents CLC.
+//
+type ClassLoaderContext struct {
+ // The name of the library.
+ Name string
+
+ // On-host build path to the library dex file (used in dex2oat argument --class-loader-context).
+ Host android.Path
+
+ // On-device install path (used in dex2oat argument --stored-class-loader-context).
+ Device string
+
+ // Nested sub-CLC for dependencies.
+ Subcontexts []*ClassLoaderContext
+}
+
+// ClassLoaderContextMap is a map from SDK version to CLC. There is a special entry with key
+// AnySdkVersion that stores unconditional CLC that is added regardless of the target SDK version.
+//
+// Conditional CLC is for compatibility libraries which didn't exist prior to a certain SDK version
+// (say, N), but classes in them were in the bootclasspath jars, etc., and in version N they have
+// been separated into a standalone <uses-library>. Compatibility libraries should only be in the
+// CLC if the library/app that uses them has `targetSdkVersion` less than N in the manifest.
+//
+// Currently only apps (but not libraries) use conditional CLC.
+//
+// Target SDK version information is unavailable to the build system at rule generation time, so
+// the build system doesn't know whether conditional CLC is needed for a given app or not. So it
+// generates a build rule that includes conditional CLC for all versions, extracts the target SDK
+// version from the manifest, and filters the CLCs based on that version. Exact final CLC that is
+// passed to dex2oat is unknown to the build system, and gets known only at Ninja stage.
+//
+type ClassLoaderContextMap map[int][]*ClassLoaderContext
+
+// Compatibility libraries. Some are optional, and some are required: this is the default that
+// affects how they are handled by the Soong logic that automatically adds implicit SDK libraries
+// to the manifest_fixer, but an explicit `uses_libs`/`optional_uses_libs` can override this.
var OrgApacheHttpLegacy = "org.apache.http.legacy"
var AndroidTestBase = "android.test.base"
var AndroidTestMock = "android.test.mock"
var AndroidHidlBase = "android.hidl.base-V1.0-java"
var AndroidHidlManager = "android.hidl.manager-V1.0-java"
+// Compatibility libraries grouped by version/optionality (for convenience, to avoid repeating the
+// same lists in multiple places).
var OptionalCompatUsesLibs28 = []string{
OrgApacheHttpLegacy,
}
@@ -55,37 +252,9 @@
// last). We use the converntional "current" SDK level (10000), but any big number would do as well.
const AnySdkVersion int = android.FutureApiLevelInt
-// ClassLoaderContext is a tree of libraries used by the dexpreopted module with their dependencies.
-// The context is used by dex2oat to compile the module and recorded in the AOT-compiled files, so
-// that it can be checked agains the run-time class loader context on device. If there is a mismatch
-// at runtime, AOT-compiled code is rejected.
-type ClassLoaderContext struct {
- // The name of the library (same as the name of the module that contains it).
- Name string
-
- // On-host build path to the library dex file (used in dex2oat argument --class-loader-context).
- Host android.Path
-
- // On-device install path (used in dex2oat argument --stored-class-loader-context).
- Device string
-
- // Nested class loader subcontexts for dependencies.
- Subcontexts []*ClassLoaderContext
-
- // If the library is a shared library. This affects which elements of class loader context are
- // added as <uses-library> tags by the manifest_fixer (dependencies of shared libraries aren't).
- IsSharedLibrary bool
-}
-
-// ClassLoaderContextMap is a map from SDK version to a class loader context.
-// There is a special entry with key AnySdkVersion that stores unconditional class loader context.
-// Other entries store conditional contexts that should be added for some apps that have
-// targetSdkVersion in the manifest lower than the key SDK version.
-type ClassLoaderContextMap map[int][]*ClassLoaderContext
-
// Add class loader context for the given library to the map entry for the given SDK version.
func (clcMap ClassLoaderContextMap) addContext(ctx android.ModuleInstallPathContext, sdkVer int, lib string,
- shared bool, hostPath, installPath android.Path, strict bool, nestedClcMap ClassLoaderContextMap) error {
+ hostPath, installPath android.Path, strict bool, nestedClcMap ClassLoaderContextMap) error {
// If missing dependencies are allowed, the build shouldn't fail when a <uses-library> is
// not found. However, this is likely to result is disabling dexpreopt, as it won't be
@@ -132,20 +301,19 @@
}
clcMap[sdkVer] = append(clcMap[sdkVer], &ClassLoaderContext{
- Name: lib,
- Host: hostPath,
- Device: devicePath,
- Subcontexts: subcontexts,
- IsSharedLibrary: shared,
+ Name: lib,
+ Host: hostPath,
+ Device: devicePath,
+ Subcontexts: subcontexts,
})
return nil
}
// Wrapper around addContext that reports errors.
func (clcMap ClassLoaderContextMap) addContextOrReportError(ctx android.ModuleInstallPathContext, sdkVer int, lib string,
- shared bool, hostPath, installPath android.Path, strict bool, nestedClcMap ClassLoaderContextMap) {
+ hostPath, installPath android.Path, strict bool, nestedClcMap ClassLoaderContextMap) {
- err := clcMap.addContext(ctx, sdkVer, lib, shared, hostPath, installPath, strict, nestedClcMap)
+ err := clcMap.addContext(ctx, sdkVer, lib, hostPath, installPath, strict, nestedClcMap)
if err != nil {
ctx.ModuleErrorf(err.Error())
}
@@ -153,25 +321,25 @@
// Add class loader context. Fail on unknown build/install paths.
func (clcMap ClassLoaderContextMap) AddContext(ctx android.ModuleInstallPathContext, lib string,
- shared bool, hostPath, installPath android.Path) {
+ hostPath, installPath android.Path) {
- clcMap.addContextOrReportError(ctx, AnySdkVersion, lib, shared, hostPath, installPath, true, nil)
+ clcMap.addContextOrReportError(ctx, AnySdkVersion, lib, hostPath, installPath, true, nil)
}
// Add class loader context if the library exists. Don't fail on unknown build/install paths.
func (clcMap ClassLoaderContextMap) MaybeAddContext(ctx android.ModuleInstallPathContext, lib *string,
- shared bool, hostPath, installPath android.Path) {
+ hostPath, installPath android.Path) {
if lib != nil {
- clcMap.addContextOrReportError(ctx, AnySdkVersion, *lib, shared, hostPath, installPath, false, nil)
+ clcMap.addContextOrReportError(ctx, AnySdkVersion, *lib, hostPath, installPath, false, nil)
}
}
// Add class loader context for the given SDK version. Fail on unknown build/install paths.
func (clcMap ClassLoaderContextMap) AddContextForSdk(ctx android.ModuleInstallPathContext, sdkVer int,
- lib string, shared bool, hostPath, installPath android.Path, nestedClcMap ClassLoaderContextMap) {
+ lib string, hostPath, installPath android.Path, nestedClcMap ClassLoaderContextMap) {
- clcMap.addContextOrReportError(ctx, sdkVer, lib, shared, hostPath, installPath, true, nestedClcMap)
+ clcMap.addContextOrReportError(ctx, sdkVer, lib, hostPath, installPath, true, nestedClcMap)
}
// Merge the other class loader context map into this one, do not override existing entries.
@@ -208,26 +376,15 @@
}
}
-// List of libraries in the unconditional class loader context, excluding dependencies of shared
-// libraries. These libraries should be in the <uses-library> tags in the manifest. Some of them may
-// be present in the original manifest, others are added by the manifest_fixer.
+// Returns top-level libraries in the CLC (conditional CLC, i.e. compatibility libraries are not
+// included). This is the list of libraries that should be in the <uses-library> tags in the
+// manifest. Some of them may be present in the source manifest, others are added by manifest_fixer.
func (clcMap ClassLoaderContextMap) UsesLibs() (ulibs []string) {
if clcMap != nil {
- // compatibility libraries (those in conditional context) are not added to <uses-library> tags
- ulibs = usesLibsRec(clcMap[AnySdkVersion])
- ulibs = android.FirstUniqueStrings(ulibs)
- }
- return ulibs
-}
-
-func usesLibsRec(clcs []*ClassLoaderContext) (ulibs []string) {
- for _, clc := range clcs {
- ulibs = append(ulibs, clc.Name)
- // <uses-library> tags in the manifest should not include dependencies of shared libraries,
- // because PackageManager already tracks all such dependencies and automatically adds their
- // class loader contexts as subcontext of the shared library.
- if !clc.IsSharedLibrary {
- ulibs = append(ulibs, usesLibsRec(clc.Subcontexts)...)
+ clcs := clcMap[AnySdkVersion]
+ ulibs = make([]string, 0, len(clcs))
+ for _, clc := range clcs {
+ ulibs = append(ulibs, clc.Name)
}
}
return ulibs
@@ -273,6 +430,7 @@
return true, nil
}
+// Helper function for validateClassLoaderContext() that handles recursion.
func validateClassLoaderContextRec(sdkVer int, clcs []*ClassLoaderContext) (bool, error) {
for _, clc := range clcs {
if clc.Host == nil || clc.Device == UnknownInstallLibraryPath {
@@ -312,6 +470,7 @@
return clcStr, android.FirstUniquePaths(paths)
}
+// Helper function for ComputeClassLoaderContext() that handles recursion.
func computeClassLoaderContextRec(clcs []*ClassLoaderContext) (string, string, android.Paths) {
var paths android.Paths
var clcsHost, clcsTarget []string
@@ -336,7 +495,7 @@
return clcHost, clcTarget, paths
}
-// Paths to a <uses-library> on host and on device.
+// JSON representation of <uses-library> paths on host and on device.
type jsonLibraryPath struct {
Host string
Device string
diff --git a/dexpreopt/class_loader_context_test.go b/dexpreopt/class_loader_context_test.go
index abfca27..df68563 100644
--- a/dexpreopt/class_loader_context_test.go
+++ b/dexpreopt/class_loader_context_test.go
@@ -25,11 +25,6 @@
"android/soong/android"
)
-const (
- shared = true // dependencies are not added to uses libs
- nonshared = false // dependencies are added to uses libs
-)
-
func TestCLC(t *testing.T) {
// Construct class loader context with the following structure:
// .
@@ -55,36 +50,36 @@
m := make(ClassLoaderContextMap)
- m.AddContext(ctx, "a", nonshared, buildPath(ctx, "a"), installPath(ctx, "a"))
- m.AddContext(ctx, "b", shared, buildPath(ctx, "b"), installPath(ctx, "b"))
+ m.AddContext(ctx, "a", buildPath(ctx, "a"), installPath(ctx, "a"))
+ m.AddContext(ctx, "b", buildPath(ctx, "b"), installPath(ctx, "b"))
// "Maybe" variant in the good case: add as usual.
c := "c"
- m.MaybeAddContext(ctx, &c, nonshared, buildPath(ctx, "c"), installPath(ctx, "c"))
+ m.MaybeAddContext(ctx, &c, buildPath(ctx, "c"), installPath(ctx, "c"))
// "Maybe" variant in the bad case: don't add library with unknown name, keep going.
- m.MaybeAddContext(ctx, nil, nonshared, nil, nil)
+ m.MaybeAddContext(ctx, nil, nil, nil)
// Add some libraries with nested subcontexts.
m1 := make(ClassLoaderContextMap)
- m1.AddContext(ctx, "a1", nonshared, buildPath(ctx, "a1"), installPath(ctx, "a1"))
- m1.AddContext(ctx, "b1", shared, buildPath(ctx, "b1"), installPath(ctx, "b1"))
+ m1.AddContext(ctx, "a1", buildPath(ctx, "a1"), installPath(ctx, "a1"))
+ m1.AddContext(ctx, "b1", buildPath(ctx, "b1"), installPath(ctx, "b1"))
m2 := make(ClassLoaderContextMap)
- m2.AddContext(ctx, "a2", nonshared, buildPath(ctx, "a2"), installPath(ctx, "a2"))
- m2.AddContext(ctx, "b2", shared, buildPath(ctx, "b2"), installPath(ctx, "b2"))
- m2.AddContextForSdk(ctx, AnySdkVersion, "c2", shared, buildPath(ctx, "c2"), installPath(ctx, "c2"), m1)
+ m2.AddContext(ctx, "a2", buildPath(ctx, "a2"), installPath(ctx, "a2"))
+ m2.AddContext(ctx, "b2", buildPath(ctx, "b2"), installPath(ctx, "b2"))
+ m2.AddContextForSdk(ctx, AnySdkVersion, "c2", buildPath(ctx, "c2"), installPath(ctx, "c2"), m1)
m3 := make(ClassLoaderContextMap)
- m3.AddContext(ctx, "a3", nonshared, buildPath(ctx, "a3"), installPath(ctx, "a3"))
- m3.AddContext(ctx, "b3", shared, buildPath(ctx, "b3"), installPath(ctx, "b3"))
+ m3.AddContext(ctx, "a3", buildPath(ctx, "a3"), installPath(ctx, "a3"))
+ m3.AddContext(ctx, "b3", buildPath(ctx, "b3"), installPath(ctx, "b3"))
- m.AddContextForSdk(ctx, AnySdkVersion, "d", nonshared, buildPath(ctx, "d"), installPath(ctx, "d"), m2)
+ m.AddContextForSdk(ctx, AnySdkVersion, "d", buildPath(ctx, "d"), installPath(ctx, "d"), m2)
// When the same library is both in conditional and unconditional context, it should be removed
// from conditional context.
- m.AddContextForSdk(ctx, 42, "f", nonshared, buildPath(ctx, "f"), installPath(ctx, "f"), nil)
- m.AddContextForSdk(ctx, AnySdkVersion, "f", nonshared, buildPath(ctx, "f"), installPath(ctx, "f"), nil)
+ m.AddContextForSdk(ctx, 42, "f", buildPath(ctx, "f"), installPath(ctx, "f"), nil)
+ m.AddContextForSdk(ctx, AnySdkVersion, "f", buildPath(ctx, "f"), installPath(ctx, "f"), nil)
// Merge map with implicit root library that is among toplevel contexts => does nothing.
m.AddContextMap(m1, "c")
@@ -93,12 +88,12 @@
m.AddContextMap(m3, "m_g")
// Compatibility libraries with unknown install paths get default paths.
- m.AddContextForSdk(ctx, 29, AndroidHidlManager, nonshared, buildPath(ctx, AndroidHidlManager), nil, nil)
- m.AddContextForSdk(ctx, 29, AndroidHidlBase, nonshared, buildPath(ctx, AndroidHidlBase), nil, nil)
+ m.AddContextForSdk(ctx, 29, AndroidHidlManager, buildPath(ctx, AndroidHidlManager), nil, nil)
+ m.AddContextForSdk(ctx, 29, AndroidHidlBase, buildPath(ctx, AndroidHidlBase), nil, nil)
// Add "android.test.mock" to conditional CLC, observe that is gets removed because it is only
// needed as a compatibility library if "android.test.runner" is in CLC as well.
- m.AddContextForSdk(ctx, 30, AndroidTestMock, nonshared, buildPath(ctx, AndroidTestMock), nil, nil)
+ m.AddContextForSdk(ctx, 30, AndroidTestMock, buildPath(ctx, AndroidTestMock), nil, nil)
valid, validationError := validateClassLoaderContext(m)
@@ -158,7 +153,7 @@
// Test for libraries that are added by the manifest_fixer.
t.Run("uses libs", func(t *testing.T) {
- wantUsesLibs := []string{"a", "b", "c", "d", "a2", "b2", "c2", "f", "a3", "b3"}
+ wantUsesLibs := []string{"a", "b", "c", "d", "f", "a3", "b3"}
if !reflect.DeepEqual(wantUsesLibs, haveUsesLibs) {
t.Errorf("\nwant uses libs: %s\nhave uses libs: %s", wantUsesLibs, haveUsesLibs)
}
@@ -169,7 +164,7 @@
func TestCLCUnknownBuildPath(t *testing.T) {
ctx := testContext()
m := make(ClassLoaderContextMap)
- err := m.addContext(ctx, AnySdkVersion, "a", nonshared, nil, nil, true, nil)
+ err := m.addContext(ctx, AnySdkVersion, "a", nil, nil, true, nil)
checkError(t, err, "unknown build path to <uses-library> \"a\"")
}
@@ -177,7 +172,7 @@
func TestCLCUnknownInstallPath(t *testing.T) {
ctx := testContext()
m := make(ClassLoaderContextMap)
- err := m.addContext(ctx, AnySdkVersion, "a", nonshared, buildPath(ctx, "a"), nil, true, nil)
+ err := m.addContext(ctx, AnySdkVersion, "a", buildPath(ctx, "a"), nil, true, nil)
checkError(t, err, "unknown install path to <uses-library> \"a\"")
}
@@ -186,7 +181,7 @@
m := make(ClassLoaderContextMap)
a := "a"
- m.MaybeAddContext(ctx, &a, nonshared, nil, nil)
+ m.MaybeAddContext(ctx, &a, nil, nil)
// The library should be added to <uses-library> tags by the manifest_fixer.
t.Run("maybe add", func(t *testing.T) {
@@ -208,9 +203,9 @@
func TestCLCNestedConditional(t *testing.T) {
ctx := testContext()
m1 := make(ClassLoaderContextMap)
- m1.AddContextForSdk(ctx, 42, "a", nonshared, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
+ m1.AddContextForSdk(ctx, 42, "a", buildPath(ctx, "a"), installPath(ctx, "a"), nil)
m := make(ClassLoaderContextMap)
- err := m.addContext(ctx, AnySdkVersion, "b", nonshared, buildPath(ctx, "b"), installPath(ctx, "b"), true, m1)
+ err := m.addContext(ctx, AnySdkVersion, "b", buildPath(ctx, "b"), installPath(ctx, "b"), true, m1)
checkError(t, err, "nested class loader context shouldn't have conditional part")
}
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index a1605b4..ac33b76 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -18,6 +18,8 @@
"fmt"
"android/soong/android"
+
+ "github.com/google/blueprint"
)
func init() {
@@ -27,8 +29,16 @@
type filesystem struct {
android.ModuleBase
android.PackagingBase
+
+ output android.OutputPath
+ installDir android.InstallPath
}
+// android_filesystem packages a set of modules and their transitive dependencies into a filesystem
+// image. The filesystem images are expected to be mounted in the target device, which means the
+// modules in the filesystem image are built for the target device (i.e. Android, not Linux host).
+// The modules are placed in the filesystem image just like they are installed to the ordinary
+// partitions like system.img. For example, cc_library modules are placed under ./lib[64] directory.
func filesystemFactory() android.Module {
module := &filesystem{}
android.InitPackageModule(module)
@@ -36,8 +46,14 @@
return module
}
+var dependencyTag = struct{ blueprint.BaseDependencyTag }{}
+
func (f *filesystem) DepsMutator(ctx android.BottomUpMutatorContext) {
- f.AddDeps(ctx)
+ f.AddDeps(ctx, dependencyTag)
+}
+
+func (f *filesystem) installFileName() string {
+ return f.BaseModuleName() + ".img"
}
var pctx = android.NewPackageContext("android/soong/filesystem")
@@ -64,13 +80,32 @@
Text(">").Output(propFile).
Implicit(mkuserimg)
- image := android.PathForModuleOut(ctx, "filesystem.img").OutputPath
+ f.output = android.PathForModuleOut(ctx, "filesystem.img").OutputPath
builder.Command().BuiltTool(ctx, "build_image").
Text(rootDir.String()). // input directory
Input(propFile).
- Output(image).
+ Output(f.output).
Text(rootDir.String()) // directory where to find fs_config_files|dirs
// rootDir is not deleted. Might be useful for quick inspection.
builder.Build(pctx, ctx, "build_filesystem_image", fmt.Sprintf("Creating filesystem %s", f.BaseModuleName()))
+
+ f.installDir = android.PathForModuleInstall(ctx, "etc")
+ ctx.InstallFile(f.installDir, f.installFileName(), f.output)
+}
+
+var _ android.AndroidMkEntriesProvider = (*filesystem)(nil)
+
+// Implements android.AndroidMkEntriesProvider
+func (f *filesystem) AndroidMkEntries() []android.AndroidMkEntries {
+ return []android.AndroidMkEntries{android.AndroidMkEntries{
+ Class: "ETC",
+ OutputFile: android.OptionalPathForPath(f.output),
+ ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+ func(entries *android.AndroidMkEntries) {
+ entries.SetString("LOCAL_MODULE_PATH", f.installDir.ToMakePath().String())
+ entries.SetString("LOCAL_INSTALLED_MODULE_STEM", f.installFileName())
+ },
+ },
+ }}
}
diff --git a/genrule/Android.bp b/genrule/Android.bp
index 9690007..0e27d4e 100644
--- a/genrule/Android.bp
+++ b/genrule/Android.bp
@@ -4,6 +4,7 @@
deps: [
"blueprint",
"blueprint-pathtools",
+ "sbox_proto",
"soong",
"soong-android",
"soong-bazel",
diff --git a/genrule/genrule.go b/genrule/genrule.go
index 0743e67..e5ee3fc 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -12,6 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+// A genrule module takes a list of source files ("srcs" property), an optional
+// list of tools ("tools" property), and a command line ("cmd" property), to
+// generate output files ("out" property).
+
package genrule
import (
@@ -30,10 +34,10 @@
)
func init() {
- registerGenruleBuildComponents(android.InitRegistrationContext)
+ RegisterGenruleBuildComponents(android.InitRegistrationContext)
}
-func registerGenruleBuildComponents(ctx android.RegistrationContext) {
+func RegisterGenruleBuildComponents(ctx android.RegistrationContext) {
ctx.RegisterModuleType("genrule_defaults", defaultsFactory)
ctx.RegisterModuleType("gensrcs", GenSrcsFactory)
@@ -47,6 +51,8 @@
var (
pctx = android.NewPackageContext("android/soong/genrule")
+ // Used by gensrcs when there is more than 1 shard to merge the outputs
+ // of each shard into a zip file.
gensrcsMerge = pctx.AndroidStaticRule("gensrcsMerge", blueprint.RuleParams{
Command: "${soongZip} -o ${tmpZip} @${tmpZip}.rsp && ${zipSync} -d ${genDir} ${tmpZip}",
CommandDeps: []string{"${soongZip}", "${zipSync}"},
@@ -115,6 +121,7 @@
// Properties for Bazel migration purposes.
bazel.Properties
}
+
type Module struct {
android.ModuleBase
android.DefaultableModuleBase
@@ -127,6 +134,9 @@
properties generatorProperties
+ // For the different tasks that genrule and gensrc generate. genrule will
+ // generate 1 task, and gensrc will generate 1 or more tasks based on the
+ // number of shards the input files are sharded into.
taskGenerator taskFunc
deps android.Paths
@@ -148,14 +158,17 @@
type taskFunc func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask
type generateTask struct {
- in android.Paths
- out android.WritablePaths
- depFile android.WritablePath
- copyTo android.WritablePaths
- genDir android.WritablePath
- cmd string
- shard int
- shards int
+ in android.Paths
+ out android.WritablePaths
+ depFile android.WritablePath
+ copyTo android.WritablePaths // For gensrcs to set on gensrcsMerge rule.
+ genDir android.WritablePath
+ extraTools android.Paths // dependencies on tools used by the generator
+
+ cmd string
+ // For gensrsc sharding.
+ shard int
+ shards int
}
func (g *Module) GeneratedSourceFiles() android.Paths {
@@ -200,6 +213,7 @@
}
return ok
}
+
func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
g.subName = ctx.ModuleSubDir()
@@ -321,6 +335,7 @@
var outputFiles android.WritablePaths
var zipArgs strings.Builder
+ // Generate tasks, either from genrule or gensrcs.
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")
@@ -411,23 +426,29 @@
}
g.rawCommands = append(g.rawCommands, rawCommand)
- // Pick a unique rule name and the user-visible description.
+ // Pick a unique path outside the task.genDir for the sbox manifest textproto,
+ // a unique rule name, and the user-visible description.
+ manifestName := "genrule.sbox.textproto"
desc := "generate"
name := "generator"
if task.shards > 0 {
+ manifestName = "genrule_" + strconv.Itoa(task.shard) + ".sbox.textproto"
desc += " " + strconv.Itoa(task.shard)
name += strconv.Itoa(task.shard)
} else if len(task.out) == 1 {
desc += " " + task.out[0].Base()
}
+ manifestPath := android.PathForModuleOut(ctx, manifestName)
+
// Use a RuleBuilder to create a rule that runs the command inside an sbox sandbox.
- rule := android.NewRuleBuilder().Sbox(task.genDir)
+ rule := android.NewRuleBuilder().Sbox(task.genDir, manifestPath)
cmd := rule.Command()
cmd.Text(rawCommand)
cmd.ImplicitOutputs(task.out)
cmd.Implicits(task.in)
cmd.Implicits(g.deps)
+ cmd.Implicits(task.extraTools)
if Bool(g.properties.Depfile) {
cmd.ImplicitDepFile(task.depFile)
}
@@ -455,9 +476,10 @@
// 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,
- Outputs: outputFiles,
+ Rule: gensrcsMerge,
+ Implicits: copyFrom,
+ Outputs: outputFiles,
+ Description: "merge shards",
Args: map[string]string{
"zipArgs": zipArgs.String(),
"tmpZip": android.PathForModuleGen(ctx, g.subDir+".zip").String(),
@@ -558,49 +580,67 @@
func NewGenSrcs() *Module {
properties := &genSrcsProperties{}
+ // finalSubDir is the name of the subdirectory that output files will be generated into.
+ // It is used so that per-shard directories can be placed alongside it an then finally
+ // merged into it.
+ const finalSubDir = "gensrcs"
+
taskGenerator := func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask {
- genDir := android.PathForModuleGen(ctx, "gensrcs")
shardSize := defaultShardSize
if s := properties.Shard_size; s != nil {
shardSize = int(*s)
}
+ // gensrcs rules can easily hit command line limits by repeating the command for
+ // every input file. Shard the input files into groups.
shards := android.ShardPaths(srcFiles, shardSize)
var generateTasks []generateTask
for i, shard := range shards {
var commands []string
var outFiles android.WritablePaths
+ var commandDepFiles []string
var copyTo android.WritablePaths
- var shardDir android.WritablePath
- var depFile android.WritablePath
+ // When sharding is enabled (i.e. len(shards) > 1), the sbox rules for each
+ // shard will be write to their own directories and then be merged together
+ // into finalSubDir. If sharding is not enabled (i.e. len(shards) == 1),
+ // the sbox rule will write directly to finalSubDir.
+ genSubDir := finalSubDir
if len(shards) > 1 {
- shardDir = android.PathForModuleGen(ctx, strconv.Itoa(i))
- } else {
- shardDir = genDir
+ genSubDir = strconv.Itoa(i)
}
- for j, in := range shard {
- outFile := android.GenPathWithExt(ctx, "gensrcs", in, String(properties.Output_extension))
- if j == 0 {
- depFile = outFile.ReplaceExtension(ctx, "d")
- }
+ genDir := android.PathForModuleGen(ctx, genSubDir)
+ for _, in := range shard {
+ outFile := android.GenPathWithExt(ctx, finalSubDir, in, String(properties.Output_extension))
+
+ // If sharding is enabled, then outFile is the path to the output file in
+ // the shard directory, and copyTo is the path to the output file in the
+ // final directory.
if len(shards) > 1 {
- shardFile := android.GenPathWithExt(ctx, strconv.Itoa(i), in, String(properties.Output_extension))
+ shardFile := android.GenPathWithExt(ctx, genSubDir, in, String(properties.Output_extension))
copyTo = append(copyTo, outFile)
outFile = shardFile
}
outFiles = append(outFiles, outFile)
+ // pre-expand the command line to replace $in and $out with references to
+ // a single input and output file.
command, err := android.Expand(rawCommand, func(name string) (string, error) {
switch name {
case "in":
return in.String(), nil
case "out":
- return android.SboxPathForOutput(outFile, shardDir), nil
+ return android.SboxPathForOutput(outFile, genDir), nil
+ case "depfile":
+ // Generate a depfile for each output file. Store the list for
+ // later in order to combine them all into a single depfile.
+ depFile := android.SboxPathForOutput(outFile.ReplaceExtension(ctx, "d"), genDir)
+ commandDepFiles = append(commandDepFiles, depFile)
+ return depFile, nil
default:
return "$(" + name + ")", nil
}
@@ -615,15 +655,29 @@
}
fullCommand := strings.Join(commands, " && ")
+ var outputDepfile android.WritablePath
+ var extraTools android.Paths
+ if len(commandDepFiles) > 0 {
+ // Each command wrote to a depfile, but ninja can only handle one
+ // depfile per rule. Use the dep_fixer tool at the end of the
+ // command to combine all the depfiles into a single output depfile.
+ outputDepfile = android.PathForModuleGen(ctx, genSubDir, "gensrcs.d")
+ depFixerTool := ctx.Config().HostToolPath(ctx, "dep_fixer")
+ fullCommand += fmt.Sprintf(" && %s -o $(depfile) %s",
+ depFixerTool.String(), strings.Join(commandDepFiles, " "))
+ extraTools = append(extraTools, depFixerTool)
+ }
+
generateTasks = append(generateTasks, generateTask{
- in: shard,
- out: outFiles,
- depFile: depFile,
- copyTo: copyTo,
- genDir: shardDir,
- cmd: fullCommand,
- shard: i,
- shards: len(shards),
+ in: shard,
+ out: outFiles,
+ depFile: outputDepfile,
+ copyTo: copyTo,
+ genDir: genDir,
+ cmd: fullCommand,
+ shard: i,
+ shards: len(shards),
+ extraTools: extraTools,
})
}
@@ -631,7 +685,7 @@
}
g := generatorFactory(taskGenerator, properties)
- g.subDir = "gensrcs"
+ g.subDir = finalSubDir
return g
}
diff --git a/genrule/genrule_test.go b/genrule/genrule_test.go
index 6779c73..8d3cfcb 100644
--- a/genrule/genrule_test.go
+++ b/genrule/genrule_test.go
@@ -57,7 +57,7 @@
ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
ctx.RegisterModuleType("tool", toolFactory)
- registerGenruleBuildComponents(ctx)
+ RegisterGenruleBuildComponents(ctx)
ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
ctx.Register()
@@ -141,7 +141,7 @@
out: ["out"],
cmd: "$(location) > $(out)",
`,
- expect: "out/tool > __SBOX_OUT_DIR__/out",
+ expect: "out/tool > __SBOX_SANDBOX_DIR__/out/out",
},
{
name: "empty location tool2",
@@ -150,7 +150,7 @@
out: ["out"],
cmd: "$(location) > $(out)",
`,
- expect: "out/tool > __SBOX_OUT_DIR__/out",
+ expect: "out/tool > __SBOX_SANDBOX_DIR__/out/out",
},
{
name: "empty location tool file",
@@ -159,7 +159,7 @@
out: ["out"],
cmd: "$(location) > $(out)",
`,
- expect: "tool_file1 > __SBOX_OUT_DIR__/out",
+ expect: "tool_file1 > __SBOX_SANDBOX_DIR__/out/out",
},
{
name: "empty location tool file fg",
@@ -168,7 +168,7 @@
out: ["out"],
cmd: "$(location) > $(out)",
`,
- expect: "tool_file1 > __SBOX_OUT_DIR__/out",
+ expect: "tool_file1 > __SBOX_SANDBOX_DIR__/out/out",
},
{
name: "empty location tool and tool file",
@@ -178,7 +178,7 @@
out: ["out"],
cmd: "$(location) > $(out)",
`,
- expect: "out/tool > __SBOX_OUT_DIR__/out",
+ expect: "out/tool > __SBOX_SANDBOX_DIR__/out/out",
},
{
name: "tool",
@@ -187,7 +187,7 @@
out: ["out"],
cmd: "$(location tool) > $(out)",
`,
- expect: "out/tool > __SBOX_OUT_DIR__/out",
+ expect: "out/tool > __SBOX_SANDBOX_DIR__/out/out",
},
{
name: "tool2",
@@ -196,7 +196,7 @@
out: ["out"],
cmd: "$(location :tool) > $(out)",
`,
- expect: "out/tool > __SBOX_OUT_DIR__/out",
+ expect: "out/tool > __SBOX_SANDBOX_DIR__/out/out",
},
{
name: "tool file",
@@ -205,7 +205,7 @@
out: ["out"],
cmd: "$(location tool_file1) > $(out)",
`,
- expect: "tool_file1 > __SBOX_OUT_DIR__/out",
+ expect: "tool_file1 > __SBOX_SANDBOX_DIR__/out/out",
},
{
name: "tool file fg",
@@ -214,7 +214,7 @@
out: ["out"],
cmd: "$(location :1tool_file) > $(out)",
`,
- expect: "tool_file1 > __SBOX_OUT_DIR__/out",
+ expect: "tool_file1 > __SBOX_SANDBOX_DIR__/out/out",
},
{
name: "tool files",
@@ -223,7 +223,7 @@
out: ["out"],
cmd: "$(locations :tool_files) > $(out)",
`,
- expect: "tool_file1 tool_file2 > __SBOX_OUT_DIR__/out",
+ expect: "tool_file1 tool_file2 > __SBOX_SANDBOX_DIR__/out/out",
},
{
name: "in1",
@@ -232,7 +232,7 @@
out: ["out"],
cmd: "cat $(in) > $(out)",
`,
- expect: "cat in1 > __SBOX_OUT_DIR__/out",
+ expect: "cat in1 > __SBOX_SANDBOX_DIR__/out/out",
},
{
name: "in1 fg",
@@ -241,7 +241,7 @@
out: ["out"],
cmd: "cat $(in) > $(out)",
`,
- expect: "cat in1 > __SBOX_OUT_DIR__/out",
+ expect: "cat in1 > __SBOX_SANDBOX_DIR__/out/out",
},
{
name: "ins",
@@ -250,7 +250,7 @@
out: ["out"],
cmd: "cat $(in) > $(out)",
`,
- expect: "cat in1 in2 > __SBOX_OUT_DIR__/out",
+ expect: "cat in1 in2 > __SBOX_SANDBOX_DIR__/out/out",
},
{
name: "ins fg",
@@ -259,7 +259,7 @@
out: ["out"],
cmd: "cat $(in) > $(out)",
`,
- expect: "cat in1 in2 > __SBOX_OUT_DIR__/out",
+ expect: "cat in1 in2 > __SBOX_SANDBOX_DIR__/out/out",
},
{
name: "location in1",
@@ -268,7 +268,7 @@
out: ["out"],
cmd: "cat $(location in1) > $(out)",
`,
- expect: "cat in1 > __SBOX_OUT_DIR__/out",
+ expect: "cat in1 > __SBOX_SANDBOX_DIR__/out/out",
},
{
name: "location in1 fg",
@@ -277,7 +277,7 @@
out: ["out"],
cmd: "cat $(location :1in) > $(out)",
`,
- expect: "cat in1 > __SBOX_OUT_DIR__/out",
+ expect: "cat in1 > __SBOX_SANDBOX_DIR__/out/out",
},
{
name: "location ins",
@@ -286,7 +286,7 @@
out: ["out"],
cmd: "cat $(location in1) > $(out)",
`,
- expect: "cat in1 > __SBOX_OUT_DIR__/out",
+ expect: "cat in1 > __SBOX_SANDBOX_DIR__/out/out",
},
{
name: "location ins fg",
@@ -295,7 +295,7 @@
out: ["out"],
cmd: "cat $(locations :ins) > $(out)",
`,
- expect: "cat in1 in2 > __SBOX_OUT_DIR__/out",
+ expect: "cat in1 in2 > __SBOX_SANDBOX_DIR__/out/out",
},
{
name: "outs",
@@ -303,7 +303,7 @@
out: ["out", "out2"],
cmd: "echo foo > $(out)",
`,
- expect: "echo foo > __SBOX_OUT_DIR__/out __SBOX_OUT_DIR__/out2",
+ expect: "echo foo > __SBOX_SANDBOX_DIR__/out/out __SBOX_SANDBOX_DIR__/out/out2",
},
{
name: "location out",
@@ -311,7 +311,7 @@
out: ["out", "out2"],
cmd: "echo foo > $(location out2)",
`,
- expect: "echo foo > __SBOX_OUT_DIR__/out2",
+ expect: "echo foo > __SBOX_SANDBOX_DIR__/out/out2",
},
{
name: "depfile",
@@ -320,7 +320,7 @@
depfile: true,
cmd: "echo foo > $(out) && touch $(depfile)",
`,
- expect: "echo foo > __SBOX_OUT_DIR__/out && touch __SBOX_DEPFILE__",
+ expect: "echo foo > __SBOX_SANDBOX_DIR__/out/out && touch __SBOX_DEPFILE__",
},
{
name: "gendir",
@@ -328,7 +328,7 @@
out: ["out"],
cmd: "echo foo > $(genDir)/foo && cp $(genDir)/foo $(out)",
`,
- expect: "echo foo > __SBOX_OUT_DIR__/foo && cp __SBOX_OUT_DIR__/foo __SBOX_OUT_DIR__/out",
+ expect: "echo foo > __SBOX_SANDBOX_DIR__/out/foo && cp __SBOX_SANDBOX_DIR__/out/foo __SBOX_SANDBOX_DIR__/out/out",
},
{
@@ -443,7 +443,7 @@
allowMissingDependencies: true,
- expect: "cat ***missing srcs :missing*** > __SBOX_OUT_DIR__/out",
+ expect: "cat ***missing srcs :missing*** > __SBOX_SANDBOX_DIR__/out/out",
},
{
name: "tool allow missing dependencies",
@@ -455,7 +455,7 @@
allowMissingDependencies: true,
- expect: "***missing tool :missing*** > __SBOX_OUT_DIR__/out",
+ expect: "***missing tool :missing*** > __SBOX_SANDBOX_DIR__/out/out",
},
}
@@ -570,20 +570,11 @@
for _, test := range testcases {
t.Run(test.name, func(t *testing.T) {
gen := ctx.ModuleForTests(test.name, "")
- command := gen.Rule("generator").RuleParams.Command
+ manifest := android.RuleBuilderSboxProtoForTests(t, gen.Output("genrule.sbox.textproto"))
+ hash := manifest.Commands[0].GetInputHash()
- if len(test.expectedHash) > 0 {
- // We add spaces before and after to make sure that
- // this option doesn't abutt another sbox option.
- expectedInputHashOption := " --input-hash " + test.expectedHash
-
- if !strings.Contains(command, expectedInputHashOption) {
- t.Errorf("Expected command \"%s\" to contain \"%s\"", command, expectedInputHashOption)
- }
- } else {
- if strings.Contains(command, "--input-hash") {
- t.Errorf("Unexpected \"--input-hash\" found in command: \"%s\"", command)
- }
+ if g, w := hash, test.expectedHash; g != w {
+ t.Errorf("Expected has %q, got %q", w, g)
}
})
}
@@ -609,7 +600,7 @@
cmd: "$(location) $(in) > $(out)",
`,
cmds: []string{
- "bash -c 'out/tool in1.txt > __SBOX_OUT_DIR__/in1.h' && bash -c 'out/tool in2.txt > __SBOX_OUT_DIR__/in2.h'",
+ "bash -c 'out/tool in1.txt > __SBOX_SANDBOX_DIR__/out/in1.h' && bash -c 'out/tool in2.txt > __SBOX_SANDBOX_DIR__/out/in2.h'",
},
deps: []string{buildDir + "/.intermediates/gen/gen/gensrcs/in1.h", buildDir + "/.intermediates/gen/gen/gensrcs/in2.h"},
files: []string{buildDir + "/.intermediates/gen/gen/gensrcs/in1.h", buildDir + "/.intermediates/gen/gen/gensrcs/in2.h"},
@@ -623,8 +614,8 @@
shard_size: 2,
`,
cmds: []string{
- "bash -c 'out/tool in1.txt > __SBOX_OUT_DIR__/in1.h' && bash -c 'out/tool in2.txt > __SBOX_OUT_DIR__/in2.h'",
- "bash -c 'out/tool in3.txt > __SBOX_OUT_DIR__/in3.h'",
+ "bash -c 'out/tool in1.txt > __SBOX_SANDBOX_DIR__/out/in1.h' && bash -c 'out/tool in2.txt > __SBOX_SANDBOX_DIR__/out/in2.h'",
+ "bash -c 'out/tool in3.txt > __SBOX_SANDBOX_DIR__/out/in3.h'",
},
deps: []string{buildDir + "/.intermediates/gen/gen/gensrcs/in1.h", buildDir + "/.intermediates/gen/gen/gensrcs/in2.h", buildDir + "/.intermediates/gen/gen/gensrcs/in3.h"},
files: []string{buildDir + "/.intermediates/gen/gen/gensrcs/in1.h", buildDir + "/.intermediates/gen/gen/gensrcs/in2.h", buildDir + "/.intermediates/gen/gen/gensrcs/in3.h"},
@@ -710,7 +701,7 @@
}
gen := ctx.ModuleForTests("gen", "").Module().(*Module)
- expectedCmd := "cp in1 __SBOX_OUT_DIR__/out"
+ expectedCmd := "cp in1 __SBOX_SANDBOX_DIR__/out/out"
if gen.rawCommands[0] != expectedCmd {
t.Errorf("Expected cmd: %q, actual: %q", expectedCmd, gen.rawCommands[0])
}
diff --git a/java/Android.bp b/java/Android.bp
index 9e8dc78..39502b3 100644
--- a/java/Android.bp
+++ b/java/Android.bp
@@ -48,6 +48,7 @@
"robolectric.go",
"sdk.go",
"sdk_library.go",
+ "sdk_library_external.go",
"support_libraries.go",
"sysprop.go",
"system_modules.go",
diff --git a/java/aar.go b/java/aar.go
index 799e763..1940d7f 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -426,7 +426,7 @@
// (including the java_sdk_library) itself then append any implicit sdk library
// names to the list of sdk libraries to be added to the manifest.
if component, ok := module.(SdkLibraryComponentDependency); ok {
- classLoaderContexts.MaybeAddContext(ctx, component.OptionalImplicitSdkLibrary(), true,
+ classLoaderContexts.MaybeAddContext(ctx, component.OptionalImplicitSdkLibrary(),
component.DexJarBuildPath(), component.DexJarInstallPath())
}
diff --git a/java/androidmk.go b/java/androidmk.go
index 386a97f..fc573c8 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -85,7 +85,6 @@
} else {
entriesList = append(entriesList, android.AndroidMkEntries{
Class: "JAVA_LIBRARIES",
- DistFiles: library.distFiles,
OutputFile: android.OptionalPathForPath(library.outputFile),
Include: "$(BUILD_SYSTEM)/soong_java_prebuilt.mk",
ExtraEntries: []android.AndroidMkExtraEntriesFunc{
@@ -140,9 +139,9 @@
func testSuiteComponent(entries *android.AndroidMkEntries, test_suites []string) {
entries.SetString("LOCAL_MODULE_TAGS", "tests")
if len(test_suites) > 0 {
- entries.AddStrings("LOCAL_COMPATIBILITY_SUITE", test_suites...)
+ entries.AddCompatibilityTestSuites(test_suites...)
} else {
- entries.SetString("LOCAL_COMPATIBILITY_SUITE", "null-suite")
+ entries.AddCompatibilityTestSuites("null-suite")
}
}
@@ -524,17 +523,12 @@
// Note that dstubs.apiFile can be also be nil if WITHOUT_CHECKS_API is true.
// TODO(b/146727827): Revert when we do not need to generate stubs and API separately.
- var distFiles android.TaggedDistFiles
- if dstubs.apiFile != nil {
- distFiles = android.MakeDefaultDistFiles(dstubs.apiFile)
- }
outputFile := android.OptionalPathForPath(dstubs.stubsSrcJar)
if !outputFile.Valid() {
outputFile = android.OptionalPathForPath(dstubs.apiFile)
}
return []android.AndroidMkEntries{android.AndroidMkEntries{
Class: "JAVA_LIBRARIES",
- DistFiles: distFiles,
OutputFile: outputFile,
Include: "$(BUILD_SYSTEM)/soong_droiddoc_prebuilt.mk",
ExtraEntries: []android.AndroidMkExtraEntriesFunc{
diff --git a/java/androidmk_test.go b/java/androidmk_test.go
index 075b7aa..233e9d5 100644
--- a/java/androidmk_test.go
+++ b/java/androidmk_test.go
@@ -16,7 +16,6 @@
import (
"reflect"
- "strings"
"testing"
"android/soong/android"
@@ -135,182 +134,6 @@
}
}
-func TestDistWithTag(t *testing.T) {
- ctx, config := testJava(t, `
- java_library {
- name: "foo_without_tag",
- srcs: ["a.java"],
- compile_dex: true,
- dist: {
- targets: ["hi"],
- },
- }
- java_library {
- name: "foo_with_tag",
- srcs: ["a.java"],
- compile_dex: true,
- dist: {
- targets: ["hi"],
- tag: ".jar",
- },
- }
- `)
-
- withoutTagEntries := android.AndroidMkEntriesForTest(t, config, "", ctx.ModuleForTests("foo_without_tag", "android_common").Module())
- withTagEntries := android.AndroidMkEntriesForTest(t, config, "", ctx.ModuleForTests("foo_with_tag", "android_common").Module())
-
- if len(withoutTagEntries) != 2 || len(withTagEntries) != 2 {
- t.Errorf("two mk entries per module expected, got %d and %d", len(withoutTagEntries), len(withTagEntries))
- }
- if len(withTagEntries[0].DistFiles[".jar"]) != 1 ||
- !strings.Contains(withTagEntries[0].DistFiles[".jar"][0].String(), "/javac/foo_with_tag.jar") {
- t.Errorf("expected DistFiles to contain classes.jar, got %v", withTagEntries[0].DistFiles)
- }
- if len(withoutTagEntries[0].DistFiles[".jar"]) > 0 {
- t.Errorf("did not expect explicit DistFile for .jar tag, got %v", withoutTagEntries[0].DistFiles[".jar"])
- }
-}
-
-func TestDistWithDest(t *testing.T) {
- ctx, config := testJava(t, `
- java_library {
- name: "foo",
- srcs: ["a.java"],
- compile_dex: true,
- dist: {
- targets: ["my_goal"],
- dest: "my/custom/dest/dir",
- },
- }
- `)
-
- module := ctx.ModuleForTests("foo", "android_common").Module()
- entries := android.AndroidMkEntriesForTest(t, config, "", module)
- if len(entries) != 2 {
- t.Errorf("Expected 2 AndroidMk entries, got %d", len(entries))
- }
-
- distStrings := entries[0].GetDistForGoals(module)
-
- if len(distStrings) != 2 {
- t.Errorf("Expected 2 entries for dist: PHONY and dist-for-goals, but got %q", distStrings)
- }
-
- if distStrings[0] != ".PHONY: my_goal\n" {
- t.Errorf("Expected .PHONY entry to declare my_goal, but got: %s", distStrings[0])
- }
-
- if !strings.Contains(distStrings[1], "$(call dist-for-goals,my_goal") ||
- !strings.Contains(distStrings[1], ".intermediates/foo/android_common/dex/foo.jar:my/custom/dest/dir") {
- t.Errorf(
- "Expected dist-for-goals entry to contain my_goal and new dest dir, but got: %s", distStrings[1])
- }
-}
-
-func TestDistsWithAllProperties(t *testing.T) {
- ctx, config := testJava(t, `
- java_library {
- name: "foo",
- srcs: ["a.java"],
- compile_dex: true,
- dist: {
- targets: ["baz"],
- },
- dists: [
- {
- targets: ["bar"],
- tag: ".jar",
- dest: "bar.jar",
- dir: "bar/dir",
- suffix: ".qux",
- },
- ]
- }
- `)
-
- module := ctx.ModuleForTests("foo", "android_common").Module()
- entries := android.AndroidMkEntriesForTest(t, config, "", module)
- if len(entries) != 2 {
- t.Errorf("Expected 2 AndroidMk entries, got %d", len(entries))
- }
-
- distStrings := entries[0].GetDistForGoals(module)
-
- if len(distStrings) != 4 {
- t.Errorf("Expected 4 entries for dist: PHONY and dist-for-goals, but got %d", len(distStrings))
- }
-
- if distStrings[0] != ".PHONY: bar\n" {
- t.Errorf("Expected .PHONY entry to declare bar, but got: %s", distStrings[0])
- }
-
- if !strings.Contains(distStrings[1], "$(call dist-for-goals,bar") ||
- !strings.Contains(
- distStrings[1],
- ".intermediates/foo/android_common/javac/foo.jar:bar/dir/bar.qux.jar") {
- t.Errorf(
- "Expected dist-for-goals entry to contain bar and new dest dir, but got: %s", distStrings[1])
- }
-
- if distStrings[2] != ".PHONY: baz\n" {
- t.Errorf("Expected .PHONY entry to declare baz, but got: %s", distStrings[2])
- }
-
- if !strings.Contains(distStrings[3], "$(call dist-for-goals,baz") ||
- !strings.Contains(distStrings[3], ".intermediates/foo/android_common/dex/foo.jar:foo.jar") {
- t.Errorf(
- "Expected dist-for-goals entry to contain my_other_goal and new dest dir, but got: %s",
- distStrings[3])
- }
-}
-
-func TestDistsWithTag(t *testing.T) {
- ctx, config := testJava(t, `
- java_library {
- name: "foo_without_tag",
- srcs: ["a.java"],
- compile_dex: true,
- dists: [
- {
- targets: ["hi"],
- },
- ],
- }
- java_library {
- name: "foo_with_tag",
- srcs: ["a.java"],
- compile_dex: true,
- dists: [
- {
- targets: ["hi"],
- tag: ".jar",
- },
- ],
- }
- `)
-
- moduleWithoutTag := ctx.ModuleForTests("foo_without_tag", "android_common").Module()
- moduleWithTag := ctx.ModuleForTests("foo_with_tag", "android_common").Module()
-
- withoutTagEntries := android.AndroidMkEntriesForTest(t, config, "", moduleWithoutTag)
- withTagEntries := android.AndroidMkEntriesForTest(t, config, "", moduleWithTag)
-
- if len(withoutTagEntries) != 2 || len(withTagEntries) != 2 {
- t.Errorf("two mk entries per module expected, got %d and %d", len(withoutTagEntries), len(withTagEntries))
- }
-
- distFilesWithoutTag := withoutTagEntries[0].DistFiles
- distFilesWithTag := withTagEntries[0].DistFiles
-
- if len(distFilesWithTag[".jar"]) != 1 ||
- !strings.Contains(distFilesWithTag[".jar"][0].String(), "/javac/foo_with_tag.jar") {
- t.Errorf("expected foo_with_tag's .jar-tagged DistFiles to contain classes.jar, got %v", distFilesWithTag[".jar"])
- }
- if len(distFilesWithoutTag[".jar"]) > 0 {
- t.Errorf("did not expect foo_without_tag's .jar-tagged DistFiles to contain files, but got %v", distFilesWithoutTag[".jar"])
- }
-}
-
func TestJavaSdkLibrary_RequireXmlPermissionFile(t *testing.T) {
ctx, config := testJava(t, `
java_sdk_library {
diff --git a/java/app.go b/java/app.go
index 3446739..3384bbd 100755
--- a/java/app.go
+++ b/java/app.go
@@ -1981,7 +1981,7 @@
if tag, ok := ctx.OtherModuleDependencyTag(m).(usesLibraryDependencyTag); ok {
dep := ctx.OtherModuleName(m)
if lib, ok := m.(Dependency); ok {
- clcMap.AddContextForSdk(ctx, tag.sdkVersion, dep, isSharedSdkLibrary(m),
+ clcMap.AddContextForSdk(ctx, tag.sdkVersion, dep,
lib.DexJarBuildPath(), lib.DexJarInstallPath(), lib.ClassLoaderContexts())
} else if ctx.Config().AllowMissingDependencies() {
ctx.AddMissingDependencies([]string{dep})
@@ -1995,11 +1995,6 @@
return clcMap
}
-func isSharedSdkLibrary(m android.Module) bool {
- lib, ok := m.(SdkLibraryDependency)
- return ok && lib.IsSharedLibrary()
-}
-
// enforceUsesLibraries returns true of <uses-library> tags should be checked against uses_libs and optional_uses_libs
// properties. Defaults to true if either of uses_libs or optional_uses_libs is specified. Will default to true
// unconditionally in the future.
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index f9975ba..5df5845 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -25,11 +25,177 @@
"github.com/google/blueprint/proptools"
)
+// This comment describes:
+// 1. ART boot images in general (their types, structure, file layout, etc.)
+// 2. build system support for boot images
+//
+// 1. ART boot images
+// ------------------
+//
+// A boot image in ART is a set of files that contain AOT-compiled native code and a heap snapshot
+// of AOT-initialized classes for the bootclasspath Java libraries. A boot image is compiled from a
+// set of DEX jars by the dex2oat compiler. A boot image is used for two purposes: 1) it is
+// installed on device and loaded at runtime, and 2) other Java libraries and apps are compiled
+// against it (compilation may take place either on host, known as "dexpreopt", or on device, known
+// as "dexopt").
+//
+// A boot image is not a single file, but a collection of interrelated files. Each boot image has a
+// number of components that correspond to the Java libraries that constitute it. For each component
+// there are multiple files:
+// - *.oat or *.odex file with native code (architecture-specific, one per instruction set)
+// - *.art file with pre-initialized Java classes (architecture-specific, one per instruction set)
+// - *.vdex file with verification metadata for the DEX bytecode (architecture independent)
+//
+// *.vdex files for the boot images do not contain the DEX bytecode itself, because the
+// bootclasspath DEX files are stored on disk in uncompressed and aligned form. Consequently a boot
+// image is not self-contained and cannot be used without its DEX files. To simplify the management
+// of boot image files, ART uses a certain naming scheme and associates the following metadata with
+// each boot image:
+// - A stem, which is a symbolic name that is prepended to boot image file names.
+// - A location (on-device path to the boot image files).
+// - A list of boot image locations (on-device paths to dependency boot images).
+// - A set of DEX locations (on-device paths to the DEX files, one location for one DEX file used
+// to compile the boot image).
+//
+// There are two kinds of boot images:
+// - primary boot images
+// - boot image extensions
+//
+// 1.1. Primary boot images
+// ------------------------
+//
+// A primary boot image is compiled for a core subset of bootclasspath Java libraries. It does not
+// depend on any other images, and other boot images may depend on it.
+//
+// For example, assuming that the stem is "boot", the location is /apex/com.android.art/javalib/,
+// the set of core bootclasspath libraries is A B C, and the boot image is compiled for ARM targets
+// (32 and 64 bits), it will have three components with the following files:
+// - /apex/com.android.art/javalib/{arm,arm64}/boot.{art,oat,vdex}
+// - /apex/com.android.art/javalib/{arm,arm64}/boot-B.{art,oat,vdex}
+// - /apex/com.android.art/javalib/{arm,arm64}/boot-C.{art,oat,vdex}
+//
+// The files of the first component are special: they do not have the component name appended after
+// the stem. This naming convention dates back to the times when the boot image was not split into
+// components, and there were just boot.oat and boot.art. The decision to split was motivated by
+// licensing reasons for one of the bootclasspath libraries.
+//
+// As of November 2020 the only primary boot image in Android is the image in the ART APEX
+// com.android.art. The primary ART boot image contains the Core libraries that are part of the ART
+// module. When the ART module gets updated, the primary boot image will be updated with it, and all
+// dependent images will get invalidated (the checksum of the primary image stored in dependent
+// images will not match), unless they are updated in sync with the ART module.
+//
+// 1.2. Boot image extensions
+// --------------------------
+//
+// A boot image extension is compiled for a subset of bootclasspath Java libraries (in particular,
+// this subset does not include the Core bootclasspath libraries that go into the primary boot
+// image). A boot image extension depends on the primary boot image and optionally some other boot
+// image extensions. Other images may depend on it. In other words, boot image extensions can form
+// acyclic dependency graphs.
+//
+// The motivation for boot image extensions comes from the Mainline project. Consider a situation
+// when the list of bootclasspath libraries is A B C, and both A and B are parts of the Android
+// platform, but C is part of an updatable APEX com.android.C. When the APEX is updated, the Java
+// code for C might have changed compared to the code that was used to compile the boot image.
+// Consequently, the whole boot image is obsolete and invalidated (even though the code for A and B
+// that does not depend on C is up to date). To avoid this, the original monolithic boot image is
+// split in two parts: the primary boot image that contains A B, and the boot image extension that
+// contains C and depends on the primary boot image (extends it).
+//
+// For example, assuming that the stem is "boot", the location is /system/framework, the set of
+// bootclasspath libraries is D E (where D is part of the platform and is located in
+// /system/framework, and E is part of a non-updatable APEX com.android.E and is located in
+// /apex/com.android.E/javalib), and the boot image is compiled for ARM targets (32 and 64 bits),
+// it will have two components with the following files:
+// - /system/framework/{arm,arm64}/boot-D.{art,oat,vdex}
+// - /system/framework/{arm,arm64}/boot-E.{art,oat,vdex}
+//
+// As of November 2020 the only boot image extension in Android is the Framework boot image
+// extension. It extends the primary ART boot image and contains Framework libraries and other
+// bootclasspath libraries from the platform and non-updatable APEXes that are not included in the
+// ART image. The Framework boot image extension is updated together with the platform. In the
+// future other boot image extensions may be added for some updatable modules.
+//
+//
+// 2. Build system support for boot images
+// ---------------------------------------
+//
+// The primary ART boot image needs to be compiled with one dex2oat invocation that depends on DEX
+// jars for the core libraries. Framework boot image extension needs to be compiled with one dex2oat
+// invocation that depends on the primary ART boot image and all bootclasspath DEX jars except the
+// Core libraries.
+//
+// 2.1. Libraries that go in the boot images
+// -----------------------------------------
+//
+// The contents of each boot image are determined by the PRODUCT variables. The primary ART APEX
+// boot image contains libraries listed in the ART_APEX_JARS variable in the AOSP makefiles. The
+// Framework boot image extension contains libraries specified in the PRODUCT_BOOT_JARS and
+// PRODUCT_BOOT_JARS_EXTRA variables. The AOSP makefiles specify some common Framework libraries,
+// but more product-specific libraries can be added in the product makefiles.
+//
+// Each component of the PRODUCT_BOOT_JARS and PRODUCT_BOOT_JARS_EXTRA variables is either a simple
+// name (if the library is a part of the Platform), or a colon-separated pair <apex, name> (if the
+// library is a part of a non-updatable APEX).
+//
+// A related variable PRODUCT_UPDATABLE_BOOT_JARS contains bootclasspath libraries that are in
+// updatable APEXes. They are not included in the boot image.
+//
+// One exception to the above rules are "coverage" builds (a special build flavor which requires
+// setting environment variable EMMA_INSTRUMENT_FRAMEWORK=true). In coverage builds the Java code in
+// boot image libraries is instrumented, which means that the instrumentation library (jacocoagent)
+// needs to be added to the list of bootclasspath DEX jars.
+//
+// In general, there is a requirement that the source code for a boot image library must be
+// available at build time (e.g. it cannot be a stub that has a separate implementation library).
+//
+// 2.2. Static configs
+// -------------------
+//
+// Because boot images are used to dexpreopt other Java modules, the paths to boot image files must
+// be known by the time dexpreopt build rules for the dependent modules are generated. Boot image
+// configs are constructed very early during the build, before build rule generation. The configs
+// provide predefined paths to boot image files (these paths depend only on static build
+// configuration, such as PRODUCT variables, and use hard-coded directory names).
+//
+// 2.3. Singleton
+// --------------
+//
+// Build rules for the boot images are generated with a Soong singleton. Because a singleton has no
+// dependencies on other modules, it has to find the modules for the DEX jars using VisitAllModules.
+// Soong loops through all modules and compares each module against a list of bootclasspath library
+// names. Then it generates build rules that copy DEX jars from their intermediate module-specific
+// locations to the hard-coded locations predefined in the boot image configs.
+//
+// It would be possible to use a module with proper dependencies instead, but that would require
+// changes in the way Soong generates variables for Make: a singleton can use one MakeVars() method
+// that writes variables to out/soong/make_vars-*.mk, which is included early by the main makefile,
+// but module(s) would have to use out/soong/Android-*.mk which has a group of LOCAL_* variables
+// for each module, and is included later.
+//
+// 2.4. Install rules
+// ------------------
+//
+// The primary boot image and the Framework extension are installed in different ways. The primary
+// boot image is part of the ART APEX: it is copied into the APEX intermediate files, packaged
+// together with other APEX contents, extracted and mounted on device. The Framework boot image
+// extension is installed by the rules defined in makefiles (make/core/dex_preopt_libart.mk). Soong
+// writes out a few DEXPREOPT_IMAGE_* variables for Make; these variables contain boot image names,
+// paths and so on.
+//
+// 2.5. JIT-Zygote configuration
+// -----------------------------
+//
+// One special configuration is JIT-Zygote build, when the primary ART image is used for compiling
+// apps instead of the Framework boot image extension (see DEXPREOPT_USE_ART_IMAGE and UseArtImage).
+//
+
func init() {
RegisterDexpreoptBootJarsComponents(android.InitRegistrationContext)
}
-// Target-independent description of pre-compiled boot image.
+// Target-independent description of a boot image.
type bootImageConfig struct {
// If this image is an extension, the image that it extends.
extends *bootImageConfig
@@ -66,7 +232,7 @@
variants []*bootImageVariant
}
-// Target-dependent description of pre-compiled boot image.
+// Target-dependent description of a boot image.
type bootImageVariant struct {
*bootImageConfig
@@ -90,6 +256,7 @@
unstrippedInstalls android.RuleBuilderInstalls
}
+// Get target-specific boot image variant for the given boot image config and target.
func (image bootImageConfig) getVariant(target android.Target) *bootImageVariant {
for _, variant := range image.variants {
if variant.target.Os == target.Os && variant.target.Arch.ArchType == target.Arch.ArchType {
@@ -99,7 +266,7 @@
return nil
}
-// Return any (the first) variant which is for the device (as opposed to for the host)
+// Return any (the first) variant which is for the device (as opposed to for the host).
func (image bootImageConfig) getAnyAndroidVariant() *bootImageVariant {
for _, variant := range image.variants {
if variant.target.Os == android.Android {
@@ -109,10 +276,12 @@
return nil
}
+// Return the name of a boot image module given a boot image config and a component (module) index.
+// A module name is a combination of the Java library name, and the boot image stem (that is stored
+// in the config).
func (image bootImageConfig) moduleName(ctx android.PathContext, idx int) string {
- // Dexpreopt on the boot class path produces multiple files. The first dex file
- // is converted into 'name'.art (to match the legacy assumption that 'name'.art
- // exists), and the rest are converted to 'name'-<jar>.art.
+ // The first module of the primary boot image is special: its module name has only the stem, but
+ // not the library name. All other module names are of the form <stem>-<library name>
m := image.modules.Jar(idx)
name := image.stem
if idx != 0 || image.extends != nil {
@@ -121,6 +290,7 @@
return name
}
+// Return the name of the first boot image module, or stem if the list of modules is empty.
func (image bootImageConfig) firstModuleNameOrStem(ctx android.PathContext) string {
if image.modules.Len() > 0 {
return image.moduleName(ctx, 0)
@@ -129,6 +299,8 @@
}
}
+// Return filenames for the given boot image component, given the output directory and a list of
+// extensions.
func (image bootImageConfig) moduleFiles(ctx android.PathContext, dir android.OutputPath, exts ...string) android.OutputPaths {
ret := make(android.OutputPaths, 0, image.modules.Len()*len(exts))
for i := 0; i < image.modules.Len(); i++ {
@@ -140,17 +312,26 @@
return ret
}
+// Return boot image locations (as a list of symbolic paths).
+//
// The image "location" is a symbolic path that, with multiarchitecture support, doesn't really
// exist on the device. Typically it is /apex/com.android.art/javalib/boot.art and should be the
// same for all supported architectures on the device. The concrete architecture specific files
// actually end up in architecture-specific sub-directory such as arm, arm64, x86, or x86_64.
//
-// For example a physical file
-// "/apex/com.android.art/javalib/x86/boot.art" has "image location"
-// "/apex/com.android.art/javalib/boot.art" (which is not an actual file).
+// For example a physical file /apex/com.android.art/javalib/x86/boot.art has "image location"
+// /apex/com.android.art/javalib/boot.art (which is not an actual file).
+//
+// For a primary boot image the list of locations has a single element.
+//
+// For a boot image extension the list of locations contains a location for all dependency images
+// (including the primary image) and the location of the extension itself. For example, for the
+// Framework boot image extension that depends on the primary ART boot image the list contains two
+// elements.
//
// The location is passed as an argument to the ART tools like dex2oat instead of the real path.
// ART tools will then reconstruct the architecture-specific real path.
+//
func (image *bootImageVariant) imageLocations() (imageLocations []string) {
if image.extends != nil {
imageLocations = image.extends.getVariant(image.target).imageLocations()
@@ -158,18 +339,6 @@
return append(imageLocations, dexpreopt.PathToLocation(image.images, image.target.Arch.ArchType))
}
-func concat(lists ...[]string) []string {
- var size int
- for _, l := range lists {
- size += len(l)
- }
- ret := make([]string, 0, size)
- for _, l := range lists {
- ret = append(ret, l...)
- }
- return ret
-}
-
func dexpreoptBootJarsFactory() android.Singleton {
return &dexpreoptBootJars{}
}
@@ -182,10 +351,21 @@
return dexpreopt.GetGlobalConfig(ctx).DisablePreopt
}
+// Singleton for generating boot image build rules.
type dexpreoptBootJars struct {
+ // Default boot image config (currently always the Framework boot image extension). It should be
+ // noted that JIT-Zygote builds use ART APEX image instead of the Framework boot image extension,
+ // but the switch is handled not here, but in the makefiles (triggered with
+ // DEXPREOPT_USE_ART_IMAGE=true).
defaultBootImage *bootImageConfig
- otherImages []*bootImageConfig
+ // Other boot image configs (currently the list contains only the primary ART APEX image. It
+ // used to contain an experimental JIT-Zygote image (now replaced with the ART APEX image). In
+ // the future other boot image extensions may be added.
+ otherImages []*bootImageConfig
+
+ // Build path to a config file that Soong writes for Make (to be used in makefiles that install
+ // the default boot image).
dexpreoptConfigForMake android.WritablePath
}
@@ -205,7 +385,7 @@
return files
}
-// dexpreoptBoot singleton rules
+// Generate build rules for boot images.
func (d *dexpreoptBootJars) GenerateBuildActions(ctx android.SingletonContext) {
if skipDexpreoptBootJars(ctx) {
return
@@ -334,9 +514,10 @@
}
}
- // The path to bootclasspath dex files needs to be known at module GenerateAndroidBuildAction time, before
- // the bootclasspath modules have been compiled. Copy the dex jars there so the module rules that have
- // already been set up can find them.
+ // The paths to bootclasspath DEX files need to be known at module GenerateAndroidBuildAction
+ // time, before the boot images are built (these paths are used in dexpreopt rule generation for
+ // Java libraries and apps). Generate rules that copy bootclasspath DEX jars to the predefined
+ // paths.
for i := range bootDexJars {
ctx.Build(pctx, android.BuildParams{
Rule: android.Cp,
@@ -371,6 +552,7 @@
return image
}
+// Generate boot image build rules for a specific target.
func buildBootImageVariant(ctx android.SingletonContext, image *bootImageVariant,
profile android.Path, missingDeps []string) android.WritablePaths {
@@ -428,12 +610,15 @@
}
if image.extends != nil {
+ // It is a boot image extension, so it needs the boot image it depends on (in this case the
+ // primary ART APEX image).
artImage := image.primaryImages
cmd.
Flag("--runtime-arg").FlagWithInputList("-Xbootclasspath:", image.dexPathsDeps.Paths(), ":").
Flag("--runtime-arg").FlagWithList("-Xbootclasspath-locations:", image.dexLocationsDeps, ":").
FlagWithArg("--boot-image=", dexpreopt.PathToLocation(artImage, arch)).Implicit(artImage)
} else {
+ // It is a primary image, so it needs a base address.
cmd.FlagWithArg("--base=", ctx.Config().LibartImgDeviceBaseAddress())
}
@@ -717,7 +902,9 @@
android.WriteFileRule(ctx, path, string(data))
}
-// Export paths for default boot image to Make
+// Define Make variables for boot image names, paths, etc. These variables are used in makefiles
+// (make/core/dex_preopt_libart.mk) to generate install rules that copy boot image files to the
+// correct output directories.
func (d *dexpreoptBootJars) MakeVars(ctx android.MakeVarsContext) {
if d.dexpreoptConfigForMake != nil {
ctx.Strict("DEX_PREOPT_CONFIG_FOR_MAKE", d.dexpreoptConfigForMake.String())
@@ -731,6 +918,11 @@
ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_LOCATIONS", strings.Join(image.getAnyAndroidVariant().dexLocationsDeps, " "))
var imageNames []string
+ // TODO: the primary ART boot image should not be exposed to Make, as it is installed in a
+ // different way as a part of the ART APEX. However, there is a special JIT-Zygote build
+ // configuration which uses the primary ART image instead of the Framework boot image
+ // extension, and it relies on the ART image being exposed to Make. To fix this, it is
+ // necessary to rework the logic in makefiles.
for _, current := range append(d.otherImages, image) {
imageNames = append(imageNames, current.name)
for _, variant := range current.variants {
diff --git a/java/droiddoc.go b/java/droiddoc.go
index cf7f4fb..64e8c59 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -1050,7 +1050,8 @@
return android.Paths{d.stubsSrcJar}, nil
case ".docs.zip":
return android.Paths{d.docZip}, nil
- case ".api.txt":
+ case ".api.txt", android.DefaultDistTag:
+ // This is the default dist path for dist properties that have no tag property.
return android.Paths{d.apiFilePath}, nil
case ".removed-api.txt":
return android.Paths{d.removedApiFilePath}, nil
@@ -1563,7 +1564,7 @@
` 1. You can add '@hide' javadoc comments (and remove @SystemApi/@TestApi/etc)\n`+
` to the new methods, etc. shown in the above diff.\n\n`+
` 2. You can update current.txt and/or removed.txt by executing the following command:\n`+
- ` make %s-update-current-api\n\n`+
+ ` m %s-update-current-api\n\n`+
` To submit the revised current.txt to the main Android repository,\n`+
` you will need approval.\n`+
`******************************\n`, ctx.ModuleName())
diff --git a/java/hiddenapi_singleton.go b/java/hiddenapi_singleton.go
index e57f323..8f0e09c 100644
--- a/java/hiddenapi_singleton.go
+++ b/java/hiddenapi_singleton.go
@@ -259,7 +259,7 @@
FlagWithInput("--blocked ",
android.PathForSource(ctx, "frameworks/base/config/hiddenapi-force-blocked.txt")).
FlagWithInput("--blocked ",
- android.PathForSource(ctx, "frameworks/base/config/hiddenapi-temp-blocklist.txt")).
+ android.PathForSource(ctx, "frameworks/base/config/hiddenapi-temp-blocklist.txt")).FlagWithArg("--tag ", "lo-prio").
FlagWithInput("--unsupported ", android.PathForSource(
ctx, "frameworks/base/config/hiddenapi-unsupported-packages.txt")).Flag("--packages ").
FlagWithOutput("--output ", tempPath)
diff --git a/java/java.go b/java/java.go
index 3c9c92d..9000f74 100644
--- a/java/java.go
+++ b/java/java.go
@@ -456,8 +456,6 @@
// list of the xref extraction files
kytheFiles android.Paths
- distFiles android.TaggedDistFiles
-
// Collect the module directory for IDE info in java/jdeps.go.
modulePaths []string
@@ -486,6 +484,8 @@
switch tag {
case "":
return append(android.Paths{j.outputFile}, j.extraOutputFiles...), nil
+ case android.DefaultDistTag:
+ return android.Paths{j.outputFile}, nil
case ".jar":
return android.Paths{j.implementationAndResourcesJar}, nil
case ".proguard_map":
@@ -556,6 +556,14 @@
name string
}
+// installDependencyTag is a dependency tag that is annotated to cause the installed files of the
+// dependency to be installed when the parent module is installed.
+type installDependencyTag struct {
+ blueprint.BaseDependencyTag
+ android.InstallAlwaysNeededDependencyTag
+ name string
+}
+
type usesLibraryDependencyTag struct {
dependencyTag
sdkVersion int // SDK version in which the library appared as a standalone library.
@@ -590,6 +598,8 @@
instrumentationForTag = dependencyTag{name: "instrumentation_for"}
extraLintCheckTag = dependencyTag{name: "extra-lint-check"}
jniLibTag = dependencyTag{name: "jnilib"}
+ jniInstallTag = installDependencyTag{name: "jni install"}
+ binaryInstallTag = installDependencyTag{name: "binary install"}
usesLibTag = makeUsesLibraryDependencyTag(dexpreopt.AnySdkVersion)
usesLibCompat28Tag = makeUsesLibraryDependencyTag(28)
usesLibCompat29Tag = makeUsesLibraryDependencyTag(29)
@@ -762,6 +772,37 @@
libDeps := ctx.AddVariationDependencies(nil, libTag, rewriteSyspropLibs(j.properties.Libs, "libs")...)
ctx.AddVariationDependencies(nil, staticLibTag, rewriteSyspropLibs(j.properties.Static_libs, "static_libs")...)
+ if ctx.DeviceConfig().VndkVersion() != "" && ctx.Config().EnforceInterPartitionJavaSdkLibrary() {
+ // Require java_sdk_library at inter-partition java dependency to ensure stable
+ // interface between partitions. If inter-partition java_library dependency is detected,
+ // raise build error because java_library doesn't have a stable interface.
+ //
+ // Inputs:
+ // PRODUCT_ENFORCE_INTER_PARTITION_JAVA_SDK_LIBRARY
+ // if true, enable enforcement
+ // PRODUCT_INTER_PARTITION_JAVA_LIBRARY_ALLOWLIST
+ // exception list of java_library names to allow inter-partition dependency
+ for idx, lib := range j.properties.Libs {
+ if libDeps[idx] == nil {
+ continue
+ }
+
+ if _, ok := syspropPublicStubs[lib]; ok {
+ continue
+ }
+
+ if javaDep, ok := libDeps[idx].(javaSdkLibraryEnforceContext); ok {
+ // java_sdk_library is always allowed at inter-partition dependency.
+ // So, skip check.
+ if _, ok := javaDep.(*SdkLibrary); ok {
+ continue
+ }
+
+ j.checkPartitionsForJavaDependency(ctx, "libs", javaDep)
+ }
+ }
+ }
+
// For library dependencies that are component libraries (like stubs), add the implementation
// as a dependency (dexpreopt needs to be against the implementation library, not stubs).
for _, dep := range libDeps {
@@ -1041,7 +1082,7 @@
case libTag:
deps.classpath = append(deps.classpath, dep.SdkHeaderJars(ctx, j.sdkVersion())...)
// names of sdk libs that are directly depended are exported
- j.classLoaderContexts.MaybeAddContext(ctx, dep.OptionalImplicitSdkLibrary(), true,
+ j.classLoaderContexts.MaybeAddContext(ctx, dep.OptionalImplicitSdkLibrary(),
dep.DexJarBuildPath(), dep.DexJarInstallPath())
case staticLibTag:
ctx.ModuleErrorf("dependency on java_sdk_library %q can only be in libs", otherName)
@@ -2095,15 +2136,13 @@
// add the name of that java_sdk_library to the exported sdk libs to make sure
// that, if necessary, a <uses-library> element for that java_sdk_library is
// added to the Android manifest.
- j.classLoaderContexts.MaybeAddContext(ctx, j.OptionalImplicitSdkLibrary(), true,
+ j.classLoaderContexts.MaybeAddContext(ctx, j.OptionalImplicitSdkLibrary(),
j.DexJarBuildPath(), j.DexJarInstallPath())
// A non-SDK library may provide a <uses-library> (the name may be different from the module name).
if lib := proptools.String(j.usesLibraryProperties.Provides_uses_lib); lib != "" {
- j.classLoaderContexts.AddContext(ctx, lib, true, j.DexJarBuildPath(), j.DexJarInstallPath())
+ j.classLoaderContexts.AddContext(ctx, lib, j.DexJarBuildPath(), j.DexJarInstallPath())
}
-
- j.distFiles = j.GenerateTaggedDistFiles(ctx)
}
func (j *Library) DepsMutator(ctx android.BottomUpMutatorContext) {
@@ -2586,9 +2625,12 @@
if ctx.Arch().ArchType == android.Common {
j.deps(ctx)
} else {
- // This dependency ensures the host installation rules will install the jni libraries
- // when the wrapper is installed.
- ctx.AddVariationDependencies(nil, jniLibTag, j.binaryProperties.Jni_libs...)
+ // These dependencies ensure the host installation rules will install the jar file and
+ // the jni libraries when the wrapper is installed.
+ ctx.AddVariationDependencies(nil, jniInstallTag, j.binaryProperties.Jni_libs...)
+ ctx.AddVariationDependencies(
+ []blueprint.Variation{{Mutator: "arch", Variation: android.CommonArch.String()}},
+ binaryInstallTag, ctx.ModuleName())
}
}
@@ -2780,8 +2822,7 @@
case libTag:
flags.classpath = append(flags.classpath, dep.SdkHeaderJars(ctx, j.sdkVersion())...)
// names of sdk libs that are directly depended are exported
- j.classLoaderContexts.AddContext(ctx, otherName, dep.IsSharedLibrary(),
- dep.DexJarBuildPath(), dep.DexJarInstallPath())
+ j.classLoaderContexts.AddContext(ctx, otherName, dep.DexJarBuildPath(), dep.DexJarInstallPath())
}
}
})
@@ -2796,7 +2837,7 @@
// add the name of that java_sdk_library to the exported sdk libs to make sure
// that, if necessary, a <uses-library> element for that java_sdk_library is
// added to the Android manifest.
- j.classLoaderContexts.MaybeAddContext(ctx, j.OptionalImplicitSdkLibrary(), true,
+ j.classLoaderContexts.MaybeAddContext(ctx, j.OptionalImplicitSdkLibrary(),
outputFile, installFile)
j.exportAidlIncludeDirs = android.PathsForModuleSrc(ctx, j.properties.Aidl.Export_include_dirs)
diff --git a/java/java_test.go b/java/java_test.go
index 3ab228b..f7cf03f 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -30,7 +30,6 @@
"android/soong/android"
"android/soong/cc"
"android/soong/dexpreopt"
- "android/soong/genrule"
"android/soong/python"
)
@@ -81,7 +80,6 @@
RegisterSystemModulesBuildComponents(ctx)
ctx.RegisterModuleType("java_plugin", PluginFactory)
ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
- ctx.RegisterModuleType("genrule", genrule.GenRuleFactory)
ctx.RegisterModuleType("python_binary_host", python.PythonBinaryHostFactory)
RegisterDocsBuildComponents(ctx)
RegisterStubsBuildComponents(ctx)
@@ -807,6 +805,165 @@
})
}
+func TestJavaSdkLibraryEnforce(t *testing.T) {
+ partitionToBpOption := func(partition string) string {
+ switch partition {
+ case "system":
+ return ""
+ case "vendor":
+ return "soc_specific: true,"
+ case "product":
+ return "product_specific: true,"
+ default:
+ panic("Invalid partition group name: " + partition)
+ }
+ }
+
+ type testConfigInfo struct {
+ libraryType string
+ fromPartition string
+ toPartition string
+ enforceVendorInterface bool
+ enforceProductInterface bool
+ enforceJavaSdkLibraryCheck bool
+ allowList []string
+ }
+
+ createTestConfig := func(info testConfigInfo) android.Config {
+ bpFileTemplate := `
+ java_library {
+ name: "foo",
+ srcs: ["foo.java"],
+ libs: ["bar"],
+ sdk_version: "current",
+ %s
+ }
+
+ %s {
+ name: "bar",
+ srcs: ["bar.java"],
+ sdk_version: "current",
+ %s
+ }
+ `
+
+ bpFile := fmt.Sprintf(bpFileTemplate,
+ partitionToBpOption(info.fromPartition),
+ info.libraryType,
+ partitionToBpOption(info.toPartition))
+
+ config := testConfig(nil, bpFile, nil)
+ configVariables := config.TestProductVariables
+
+ configVariables.EnforceProductPartitionInterface = proptools.BoolPtr(info.enforceProductInterface)
+ if info.enforceVendorInterface {
+ configVariables.DeviceVndkVersion = proptools.StringPtr("current")
+ }
+ configVariables.EnforceInterPartitionJavaSdkLibrary = proptools.BoolPtr(info.enforceJavaSdkLibraryCheck)
+ configVariables.InterPartitionJavaLibraryAllowList = info.allowList
+
+ return config
+ }
+
+ isValidDependency := func(configInfo testConfigInfo) bool {
+ if configInfo.enforceVendorInterface == false {
+ return true
+ }
+
+ if configInfo.enforceJavaSdkLibraryCheck == false {
+ return true
+ }
+
+ if inList("bar", configInfo.allowList) {
+ return true
+ }
+
+ if configInfo.libraryType == "java_library" {
+ if configInfo.fromPartition != configInfo.toPartition {
+ if !configInfo.enforceProductInterface &&
+ ((configInfo.fromPartition == "system" && configInfo.toPartition == "product") ||
+ (configInfo.fromPartition == "product" && configInfo.toPartition == "system")) {
+ return true
+ }
+ return false
+ }
+ }
+
+ return true
+ }
+
+ errorMessage := "is not allowed across the partitions"
+
+ allPartitionCombinations := func() [][2]string {
+ var result [][2]string
+ partitions := []string{"system", "vendor", "product"}
+
+ for _, fromPartition := range partitions {
+ for _, toPartition := range partitions {
+ result = append(result, [2]string{fromPartition, toPartition})
+ }
+ }
+
+ return result
+ }
+
+ allFlagCombinations := func() [][3]bool {
+ var result [][3]bool
+ flagValues := [2]bool{false, true}
+
+ for _, vendorInterface := range flagValues {
+ for _, productInterface := range flagValues {
+ for _, enableEnforce := range flagValues {
+ result = append(result, [3]bool{vendorInterface, productInterface, enableEnforce})
+ }
+ }
+ }
+
+ return result
+ }
+
+ for _, libraryType := range []string{"java_library", "java_sdk_library"} {
+ for _, partitionValues := range allPartitionCombinations() {
+ for _, flagValues := range allFlagCombinations() {
+ testInfo := testConfigInfo{
+ libraryType: libraryType,
+ fromPartition: partitionValues[0],
+ toPartition: partitionValues[1],
+ enforceVendorInterface: flagValues[0],
+ enforceProductInterface: flagValues[1],
+ enforceJavaSdkLibraryCheck: flagValues[2],
+ }
+
+ if isValidDependency(testInfo) {
+ testJavaWithConfig(t, createTestConfig(testInfo))
+ } else {
+ testJavaErrorWithConfig(t, errorMessage, createTestConfig(testInfo))
+ }
+ }
+ }
+ }
+
+ testJavaWithConfig(t, createTestConfig(testConfigInfo{
+ libraryType: "java_library",
+ fromPartition: "vendor",
+ toPartition: "system",
+ enforceVendorInterface: true,
+ enforceProductInterface: true,
+ enforceJavaSdkLibraryCheck: true,
+ allowList: []string{"bar"},
+ }))
+
+ testJavaErrorWithConfig(t, errorMessage, createTestConfig(testConfigInfo{
+ libraryType: "java_library",
+ fromPartition: "vendor",
+ toPartition: "system",
+ enforceVendorInterface: true,
+ enforceProductInterface: true,
+ enforceJavaSdkLibraryCheck: true,
+ allowList: []string{"foo"},
+ }))
+}
+
func TestDefaults(t *testing.T) {
ctx, _ := testJava(t, `
java_defaults {
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 603c808..32bc077 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -688,11 +688,6 @@
return c.namingScheme.apiModuleName(apiScope, c.moduleBase.BaseModuleName())
}
-// If the SDK library is a shared library.
-func (c *commonToSdkLibraryAndImport) IsSharedLibrary() bool {
- return c.sharedLibrary()
-}
-
// The component names for different outputs of the java_sdk_library.
//
// They are similar to the names used for the child modules it creates
@@ -938,9 +933,6 @@
// jars for the stubs. The latter should only be needed when generating JavaDoc as otherwise
// they are identical to the corresponding header jars.
SdkImplementationJars(ctx android.BaseModuleContext, sdkVersion sdkSpec) android.Paths
-
- // If the SDK library is a shared library.
- IsSharedLibrary() bool
}
type SdkLibrary struct {
@@ -1289,11 +1281,7 @@
Include_dirs []string
Local_include_dirs []string
}
- Dist struct {
- Targets []string
- Dest *string
- Dir *string
- }
+ Dists []android.Dist
}{}
// The stubs source processing uses the same compile time classpath when extracting the
@@ -1393,11 +1381,23 @@
}
}
- // Dist the api txt artifact for sdk builds.
if !Bool(module.sdkLibraryProperties.No_dist) {
- props.Dist.Targets = []string{"sdk", "win_sdk"}
- props.Dist.Dest = proptools.StringPtr(fmt.Sprintf("%v.txt", module.distStem()))
- props.Dist.Dir = proptools.StringPtr(path.Join(module.apiDistPath(apiScope), "api"))
+ // Dist the api txt and removed api txt artifacts for sdk builds.
+ distDir := proptools.StringPtr(path.Join(module.apiDistPath(apiScope), "api"))
+ for _, p := range []struct {
+ tag string
+ pattern string
+ }{
+ {tag: ".api.txt", pattern: "%s.txt"},
+ {tag: ".removed-api.txt", pattern: "%s-removed.txt"},
+ } {
+ props.Dists = append(props.Dists, android.Dist{
+ Targets: []string{"sdk", "win_sdk"},
+ Dir: distDir,
+ Dest: proptools.StringPtr(fmt.Sprintf(p.pattern, module.distStem())),
+ Tag: proptools.StringPtr(p.tag),
+ })
+ }
}
mctx.CreateModule(DroidstubsFactory, &props)
diff --git a/java/sdk_library_external.go b/java/sdk_library_external.go
new file mode 100644
index 0000000..2934936
--- /dev/null
+++ b/java/sdk_library_external.go
@@ -0,0 +1,109 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package java
+
+import (
+ "android/soong/android"
+)
+
+type partitionGroup int
+
+// Representation of partition group for checking inter-partition library dependencies.
+// Between system and system_ext, there are no restrictions of dependencies,
+// so we can treat these partitions as the same in terms of inter-partition dependency.
+// Same policy is applied between vendor and odm partiton.
+const (
+ partitionGroupNone partitionGroup = iota
+ // group for system, and system_ext partition
+ partitionGroupSystem
+ // group for vendor and odm partition
+ partitionGroupVendor
+ // product partition
+ partitionGroupProduct
+)
+
+func (g partitionGroup) String() string {
+ switch g {
+ case partitionGroupSystem:
+ return "system"
+ case partitionGroupVendor:
+ return "vendor"
+ case partitionGroupProduct:
+ return "product"
+ }
+
+ return ""
+}
+
+// Get partition group of java module that can be used at inter-partition dependency check.
+// We currently have three groups
+// (system, system_ext) => system partition group
+// (vendor, odm) => vendor partition group
+// (product) => product partition group
+func (j *Module) partitionGroup(ctx android.EarlyModuleContext) partitionGroup {
+ // system and system_ext partition can be treated as the same in terms of inter-partition dependency.
+ if j.Platform() || j.SystemExtSpecific() {
+ return partitionGroupSystem
+ }
+
+ // vendor and odm partition can be treated as the same in terms of inter-partition dependency.
+ if j.SocSpecific() || j.DeviceSpecific() {
+ return partitionGroupVendor
+ }
+
+ // product partition is independent.
+ if j.ProductSpecific() {
+ return partitionGroupProduct
+ }
+
+ panic("Cannot determine partition type")
+}
+
+func (j *Module) allowListedInterPartitionJavaLibrary(ctx android.EarlyModuleContext) bool {
+ return inList(j.Name(), ctx.Config().InterPartitionJavaLibraryAllowList())
+}
+
+type javaSdkLibraryEnforceContext interface {
+ Name() string
+ allowListedInterPartitionJavaLibrary(ctx android.EarlyModuleContext) bool
+ partitionGroup(ctx android.EarlyModuleContext) partitionGroup
+}
+
+var _ javaSdkLibraryEnforceContext = (*Module)(nil)
+
+func (j *Module) checkPartitionsForJavaDependency(ctx android.EarlyModuleContext, propName string, dep javaSdkLibraryEnforceContext) {
+ if dep.allowListedInterPartitionJavaLibrary(ctx) {
+ return
+ }
+
+ // If product interface is not enforced, skip check between system and product partition.
+ // But still need to check between product and vendor partition because product interface flag
+ // just represents enforcement between product and system, and vendor interface enforcement
+ // that is enforced here by precondition is representing enforcement between vendor and other partitions.
+ if !ctx.Config().EnforceProductPartitionInterface() {
+ productToSystem := j.partitionGroup(ctx) == partitionGroupProduct && dep.partitionGroup(ctx) == partitionGroupSystem
+ systemToProduct := j.partitionGroup(ctx) == partitionGroupSystem && dep.partitionGroup(ctx) == partitionGroupProduct
+
+ if productToSystem || systemToProduct {
+ return
+ }
+ }
+
+ // If module and dependency library is inter-partition
+ if j.partitionGroup(ctx) != dep.partitionGroup(ctx) {
+ errorFormat := "dependency on java_library (%q) is not allowed across the partitions (%s -> %s), use java_sdk_library instead"
+ ctx.PropertyErrorf(propName, errorFormat, dep.Name(), j.partitionGroup(ctx), dep.partitionGroup(ctx))
+ }
+}
diff --git a/java/sysprop.go b/java/sysprop.go
index 1a70499..e41aef6 100644
--- a/java/sysprop.go
+++ b/java/sysprop.go
@@ -14,6 +14,10 @@
package java
+// This file contains a map to redirect dependencies towards sysprop_library. If a sysprop_library
+// is owned by Platform, and the client module links against system API, the public stub of the
+// sysprop_library should be used. The map will contain public stub names of sysprop_libraries.
+
import (
"sync"
diff --git a/linkerconfig/linkerconfig.go b/linkerconfig/linkerconfig.go
index 1c44c74..623c9dd 100644
--- a/linkerconfig/linkerconfig.go
+++ b/linkerconfig/linkerconfig.go
@@ -102,6 +102,7 @@
entries.SetString("LOCAL_MODULE_PATH", l.installDirPath.ToMakePath().String())
entries.SetString("LOCAL_INSTALLED_MODULE_STEM", l.outputFilePath.Base())
entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", !installable)
+ entries.SetString("LINKER_CONFIG_PATH_"+l.Name(), l.OutputFile().String())
},
},
}}
diff --git a/python/androidmk.go b/python/androidmk.go
index 040b6be..60637d3 100644
--- a/python/androidmk.go
+++ b/python/androidmk.go
@@ -15,8 +15,6 @@
package python
import (
- "fmt"
- "io"
"path/filepath"
"strings"
@@ -24,86 +22,72 @@
)
type subAndroidMkProvider interface {
- AndroidMk(*Module, *android.AndroidMkData)
+ AndroidMk(*Module, *android.AndroidMkEntries)
}
-func (p *Module) subAndroidMk(data *android.AndroidMkData, obj interface{}) {
+func (p *Module) subAndroidMk(entries *android.AndroidMkEntries, obj interface{}) {
if p.subAndroidMkOnce == nil {
p.subAndroidMkOnce = make(map[subAndroidMkProvider]bool)
}
if androidmk, ok := obj.(subAndroidMkProvider); ok {
if !p.subAndroidMkOnce[androidmk] {
p.subAndroidMkOnce[androidmk] = true
- androidmk.AndroidMk(p, data)
+ androidmk.AndroidMk(p, entries)
}
}
}
-func (p *Module) AndroidMk() android.AndroidMkData {
- ret := android.AndroidMkData{OutputFile: p.installSource}
+func (p *Module) AndroidMkEntries() []android.AndroidMkEntries {
+ entries := android.AndroidMkEntries{OutputFile: p.installSource}
- p.subAndroidMk(&ret, p.installer)
+ p.subAndroidMk(&entries, p.installer)
- return ret
+ return []android.AndroidMkEntries{entries}
}
-func (p *binaryDecorator) AndroidMk(base *Module, ret *android.AndroidMkData) {
- ret.Class = "EXECUTABLES"
+func (p *binaryDecorator) AndroidMk(base *Module, entries *android.AndroidMkEntries) {
+ entries.Class = "EXECUTABLES"
- ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
- if len(p.binaryProperties.Test_suites) > 0 {
- fmt.Fprintln(w, "LOCAL_COMPATIBILITY_SUITE :=",
- strings.Join(p.binaryProperties.Test_suites, " "))
- }
+ entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
+ entries.AddCompatibilityTestSuites(p.binaryProperties.Test_suites...)
})
- base.subAndroidMk(ret, p.pythonInstaller)
+ base.subAndroidMk(entries, p.pythonInstaller)
}
-func (p *testDecorator) AndroidMk(base *Module, ret *android.AndroidMkData) {
- ret.Class = "NATIVE_TESTS"
+func (p *testDecorator) AndroidMk(base *Module, entries *android.AndroidMkEntries) {
+ entries.Class = "NATIVE_TESTS"
- ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
- if len(p.binaryDecorator.binaryProperties.Test_suites) > 0 {
- fmt.Fprintln(w, "LOCAL_COMPATIBILITY_SUITE :=",
- strings.Join(p.binaryDecorator.binaryProperties.Test_suites, " "))
- }
+ entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
+ entries.AddCompatibilityTestSuites(p.binaryDecorator.binaryProperties.Test_suites...)
if p.testConfig != nil {
- fmt.Fprintln(w, "LOCAL_FULL_TEST_CONFIG :=",
- p.testConfig.String())
+ entries.SetString("LOCAL_FULL_TEST_CONFIG", p.testConfig.String())
}
- if !BoolDefault(p.binaryProperties.Auto_gen_config, true) {
- fmt.Fprintln(w, "LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG := true")
- }
+ entries.SetBoolIfTrue("LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG", !BoolDefault(p.binaryProperties.Auto_gen_config, true))
- if len(p.data) > 0 {
- fmt.Fprintln(w, "LOCAL_TEST_DATA :=",
- strings.Join(android.AndroidMkDataPaths(p.data), " "))
- }
+ entries.AddStrings("LOCAL_TEST_DATA", android.AndroidMkDataPaths(p.data)...)
- if Bool(p.testProperties.Test_options.Unit_test) {
- fmt.Fprintln(w, "LOCAL_IS_UNIT_TEST := true")
- }
+ entries.SetBoolIfTrue("LOCAL_IS_UNIT_TEST", Bool(p.testProperties.Test_options.Unit_test))
})
- base.subAndroidMk(ret, p.binaryDecorator.pythonInstaller)
+ base.subAndroidMk(entries, p.binaryDecorator.pythonInstaller)
}
-func (installer *pythonInstaller) AndroidMk(base *Module, ret *android.AndroidMkData) {
+func (installer *pythonInstaller) AndroidMk(base *Module, entries *android.AndroidMkEntries) {
// Soong installation is only supported for host modules. Have Make
// installation trigger Soong installation.
if base.Target().Os.Class == android.Host {
- ret.OutputFile = android.OptionalPathForPath(installer.path)
+ entries.OutputFile = android.OptionalPathForPath(installer.path)
}
- ret.Required = append(ret.Required, "libc++")
- ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
+ entries.Required = append(entries.Required, "libc++")
+ entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
path, file := filepath.Split(installer.path.ToMakePath().String())
stem := strings.TrimSuffix(file, filepath.Ext(file))
- fmt.Fprintln(w, "LOCAL_MODULE_SUFFIX := "+filepath.Ext(file))
- fmt.Fprintln(w, "LOCAL_MODULE_PATH := "+path)
- fmt.Fprintln(w, "LOCAL_MODULE_STEM := "+stem)
- fmt.Fprintln(w, "LOCAL_SHARED_LIBRARIES := "+strings.Join(installer.androidMkSharedLibs, " "))
- fmt.Fprintln(w, "LOCAL_CHECK_ELF_FILES := false")
+ entries.SetString("LOCAL_MODULE_SUFFIX", filepath.Ext(file))
+ entries.SetString("LOCAL_MODULE_PATH", path)
+ entries.SetString("LOCAL_MODULE_STEM", stem)
+ entries.AddStrings("LOCAL_SHARED_LIBRARIES", installer.androidMkSharedLibs...)
+ entries.SetBool("LOCAL_CHECK_ELF_FILES", false)
})
}
diff --git a/python/python.go b/python/python.go
index d7b1bba..7e376c6 100644
--- a/python/python.go
+++ b/python/python.go
@@ -197,7 +197,7 @@
var _ PythonDependency = (*Module)(nil)
-var _ android.AndroidMkDataProvider = (*Module)(nil)
+var _ android.AndroidMkEntriesProvider = (*Module)(nil)
func (p *Module) Init() android.Module {
@@ -217,11 +217,17 @@
name string
}
+type installDependencyTag struct {
+ blueprint.BaseDependencyTag
+ android.InstallAlwaysNeededDependencyTag
+ name string
+}
+
var (
pythonLibTag = dependencyTag{name: "pythonLib"}
javaDataTag = dependencyTag{name: "javaData"}
launcherTag = dependencyTag{name: "launcher"}
- launcherSharedLibTag = dependencyTag{name: "launcherSharedLib"}
+ launcherSharedLibTag = installDependencyTag{name: "launcherSharedLib"}
pyIdentifierRegexp = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_-]*$`)
pyExt = ".py"
protoExt = ".proto"
@@ -336,6 +342,7 @@
// cannot read the property at this stage and it will be too late to add
// dependencies later.
ctx.AddFarVariationDependencies(ctx.Target().Variations(), launcherSharedLibTag, "libsqlite")
+ ctx.AddFarVariationDependencies(ctx.Target().Variations(), launcherSharedLibTag, "libc++")
if ctx.Target().Os.Bionic() {
ctx.AddFarVariationDependencies(ctx.Target().Variations(), launcherSharedLibTag,
diff --git a/rust/androidmk.go b/rust/androidmk.go
index f98360a..c181d67 100644
--- a/rust/androidmk.go
+++ b/rust/androidmk.go
@@ -15,10 +15,7 @@
package rust
import (
- "fmt"
- "io"
"path/filepath"
- "strings"
"android/soong/android"
)
@@ -26,14 +23,14 @@
type AndroidMkContext interface {
Name() string
Target() android.Target
- SubAndroidMk(*android.AndroidMkData, interface{})
+ SubAndroidMk(*android.AndroidMkEntries, interface{})
}
type SubAndroidMkProvider interface {
- AndroidMk(AndroidMkContext, *android.AndroidMkData)
+ AndroidMk(AndroidMkContext, *android.AndroidMkEntries)
}
-func (mod *Module) SubAndroidMk(data *android.AndroidMkData, obj interface{}) {
+func (mod *Module) SubAndroidMk(data *android.AndroidMkEntries, obj interface{}) {
if mod.subAndroidMkOnce == nil {
mod.subAndroidMkOnce = make(map[SubAndroidMkProvider]bool)
}
@@ -45,33 +42,22 @@
}
}
-func (mod *Module) AndroidMk() android.AndroidMkData {
- if mod.Properties.HideFromMake {
- return android.AndroidMkData{
- Disabled: true,
- }
+func (mod *Module) AndroidMkEntries() []android.AndroidMkEntries {
+ if mod.Properties.HideFromMake || mod.hideApexVariantFromMake {
+
+ return []android.AndroidMkEntries{android.AndroidMkEntries{Disabled: true}}
}
- ret := android.AndroidMkData{
+ ret := android.AndroidMkEntries{
OutputFile: mod.outputFile,
Include: "$(BUILD_SYSTEM)/soong_rust_prebuilt.mk",
- Extra: []android.AndroidMkExtraFunc{
- func(w io.Writer, outputFile android.Path) {
- if len(mod.Properties.AndroidMkRlibs) > 0 {
- fmt.Fprintln(w, "LOCAL_RLIB_LIBRARIES := "+strings.Join(mod.Properties.AndroidMkRlibs, " "))
- }
- if len(mod.Properties.AndroidMkDylibs) > 0 {
- fmt.Fprintln(w, "LOCAL_DYLIB_LIBRARIES := "+strings.Join(mod.Properties.AndroidMkDylibs, " "))
- }
- if len(mod.Properties.AndroidMkProcMacroLibs) > 0 {
- fmt.Fprintln(w, "LOCAL_PROC_MACRO_LIBRARIES := "+strings.Join(mod.Properties.AndroidMkProcMacroLibs, " "))
- }
- if len(mod.Properties.AndroidMkSharedLibs) > 0 {
- fmt.Fprintln(w, "LOCAL_SHARED_LIBRARIES := "+strings.Join(mod.Properties.AndroidMkSharedLibs, " "))
- }
- if len(mod.Properties.AndroidMkStaticLibs) > 0 {
- fmt.Fprintln(w, "LOCAL_STATIC_LIBRARIES := "+strings.Join(mod.Properties.AndroidMkStaticLibs, " "))
- }
+ ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+ func(entries *android.AndroidMkEntries) {
+ entries.AddStrings("LOCAL_RLIB_LIBRARIES", mod.Properties.AndroidMkRlibs...)
+ entries.AddStrings("LOCAL_DYLIB_LIBRARIES", mod.Properties.AndroidMkDylibs...)
+ entries.AddStrings("LOCAL_PROC_MACRO_LIBRARIES", mod.Properties.AndroidMkProcMacroLibs...)
+ entries.AddStrings("LOCAL_SHARED_LIBRARIES", mod.Properties.AndroidMkSharedLibs...)
+ entries.AddStrings("LOCAL_STATIC_LIBRARIES", mod.Properties.AndroidMkStaticLibs...)
},
},
}
@@ -84,10 +70,10 @@
}
ret.SubName += mod.Properties.SubName
- return ret
+ return []android.AndroidMkEntries{ret}
}
-func (binary *binaryDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (binary *binaryDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
ctx.SubAndroidMk(ret, binary.baseCompiler)
if binary.distFile.Valid() {
@@ -95,35 +81,26 @@
}
ret.Class = "EXECUTABLES"
- ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
- if binary.coverageOutputZipFile.Valid() {
- fmt.Fprintln(w, "LOCAL_PREBUILT_COVERAGE_ARCHIVE := "+binary.coverageOutputZipFile.String())
- }
+ ret.ExtraEntries = append(ret.ExtraEntries, func(entries *android.AndroidMkEntries) {
+ entries.SetOptionalPath("LOCAL_PREBUILT_COVERAGE_ARCHIVE", binary.coverageOutputZipFile)
})
}
-func (test *testDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (test *testDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
test.binaryDecorator.AndroidMk(ctx, ret)
ret.Class = "NATIVE_TESTS"
- ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
- if len(test.Properties.Test_suites) > 0 {
- fmt.Fprintln(w, "LOCAL_COMPATIBILITY_SUITE :=",
- strings.Join(test.Properties.Test_suites, " "))
- }
+ ret.ExtraEntries = append(ret.ExtraEntries, func(entries *android.AndroidMkEntries) {
+ entries.AddCompatibilityTestSuites(test.Properties.Test_suites...)
if test.testConfig != nil {
- fmt.Fprintln(w, "LOCAL_FULL_TEST_CONFIG :=", test.testConfig.String())
+ entries.SetString("LOCAL_FULL_TEST_CONFIG", test.testConfig.String())
}
- if !BoolDefault(test.Properties.Auto_gen_config, true) {
- fmt.Fprintln(w, "LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG := true")
- }
- if Bool(test.Properties.Test_options.Unit_test) {
- fmt.Fprintln(w, "LOCAL_IS_UNIT_TEST := true")
- }
+ entries.SetBoolIfTrue("LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG", !BoolDefault(test.Properties.Auto_gen_config, true))
+ entries.SetBoolIfTrue("LOCAL_IS_UNIT_TEST", Bool(test.Properties.Test_options.Unit_test))
})
// TODO(chh): add test data with androidMkWriteTestData(test.data, ctx, ret)
}
-func (library *libraryDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (library *libraryDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
ctx.SubAndroidMk(ret, library.baseCompiler)
if library.rlib() {
@@ -140,15 +117,12 @@
ret.DistFiles = android.MakeDefaultDistFiles(library.distFile.Path())
}
- ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
- if library.coverageOutputZipFile.Valid() {
- fmt.Fprintln(w, "LOCAL_PREBUILT_COVERAGE_ARCHIVE := "+library.coverageOutputZipFile.String())
- }
-
+ ret.ExtraEntries = append(ret.ExtraEntries, func(entries *android.AndroidMkEntries) {
+ entries.SetOptionalPath("LOCAL_PREBUILT_COVERAGE_ARCHIVE", library.coverageOutputZipFile)
})
}
-func (procMacro *procMacroDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (procMacro *procMacroDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
ctx.SubAndroidMk(ret, procMacro.baseCompiler)
ret.Class = "PROC_MACRO_LIBRARIES"
@@ -158,29 +132,29 @@
}
-func (sourceProvider *BaseSourceProvider) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (sourceProvider *BaseSourceProvider) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
outFile := sourceProvider.OutputFiles[0]
ret.Class = "ETC"
ret.OutputFile = android.OptionalPathForPath(outFile)
ret.SubName += sourceProvider.subName
- ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
+ ret.ExtraEntries = append(ret.ExtraEntries, func(entries *android.AndroidMkEntries) {
_, file := filepath.Split(outFile.String())
stem, suffix, _ := android.SplitFileExt(file)
- fmt.Fprintln(w, "LOCAL_MODULE_SUFFIX := "+suffix)
- fmt.Fprintln(w, "LOCAL_MODULE_STEM := "+stem)
- fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := true")
+ entries.SetString("LOCAL_MODULE_SUFFIX", suffix)
+ entries.SetString("LOCAL_MODULE_STEM", stem)
+ entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", true)
})
}
-func (bindgen *bindgenDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (bindgen *bindgenDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
ctx.SubAndroidMk(ret, bindgen.BaseSourceProvider)
}
-func (proto *protobufDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (proto *protobufDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
ctx.SubAndroidMk(ret, proto.BaseSourceProvider)
}
-func (compiler *baseCompiler) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkData) {
+func (compiler *baseCompiler) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
if compiler.path == (android.InstallPath{}) {
return
}
@@ -194,14 +168,12 @@
unstrippedOutputFile = ret.OutputFile
ret.OutputFile = compiler.strippedOutputFile
}
- ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) {
- if compiler.strippedOutputFile.Valid() {
- fmt.Fprintln(w, "LOCAL_SOONG_UNSTRIPPED_BINARY :=", unstrippedOutputFile)
- }
+ ret.ExtraEntries = append(ret.ExtraEntries, func(entries *android.AndroidMkEntries) {
+ entries.SetOptionalPath("LOCAL_SOONG_UNSTRIPPED_BINARY", unstrippedOutputFile)
path, file := filepath.Split(compiler.path.ToMakePath().String())
stem, suffix, _ := android.SplitFileExt(file)
- fmt.Fprintln(w, "LOCAL_MODULE_SUFFIX := "+suffix)
- fmt.Fprintln(w, "LOCAL_MODULE_PATH := "+path)
- fmt.Fprintln(w, "LOCAL_MODULE_STEM := "+stem)
+ entries.SetString("LOCAL_MODULE_SUFFIX", suffix)
+ entries.SetString("LOCAL_MODULE_PATH", path)
+ entries.SetString("LOCAL_MODULE_STEM", stem)
})
}
diff --git a/rust/clippy_test.go b/rust/clippy_test.go
index 132b7b8..e24f666 100644
--- a/rust/clippy_test.go
+++ b/rust/clippy_test.go
@@ -18,6 +18,7 @@
"testing"
"android/soong/android"
+ "android/soong/cc"
)
func TestClippy(t *testing.T) {
@@ -45,6 +46,7 @@
}`
bp = bp + GatherRequiredDepsForTest()
+ bp = bp + cc.GatherRequiredDepsForTest(android.NoOsType)
fs := map[string][]byte{
// Reuse the same blueprint file for subdirectories.
diff --git a/rust/compiler_test.go b/rust/compiler_test.go
index a6dba9e..2b40727 100644
--- a/rust/compiler_test.go
+++ b/rust/compiler_test.go
@@ -19,6 +19,7 @@
"testing"
"android/soong/android"
+ "android/soong/cc"
)
// Test that feature flags are being correctly generated.
@@ -132,6 +133,7 @@
}`
bp = bp + GatherRequiredDepsForTest()
+ bp = bp + cc.GatherRequiredDepsForTest(android.NoOsType)
fs := map[string][]byte{
// Reuse the same blueprint file for subdirectories.
diff --git a/rust/config/allowed_list.go b/rust/config/allowed_list.go
index 3ad32fa..e6643f5 100644
--- a/rust/config/allowed_list.go
+++ b/rust/config/allowed_list.go
@@ -6,6 +6,7 @@
// for an example.
// TODO(b/160223496): enable rustfmt globally.
RustAllowedPaths = []string{
+ "device/google/cuttlefish",
"external/adhd",
"external/crosvm",
"external/minijail",
diff --git a/rust/config/global.go b/rust/config/global.go
index 6e268a0..22d9567 100644
--- a/rust/config/global.go
+++ b/rust/config/global.go
@@ -24,7 +24,7 @@
var pctx = android.NewPackageContext("android/soong/rust/config")
var (
- RustDefaultVersion = "1.47.0"
+ RustDefaultVersion = "1.48.0"
RustDefaultBase = "prebuilts/rust/"
DefaultEdition = "2018"
Stdlibs = []string{
diff --git a/rust/library.go b/rust/library.go
index ae33f0f..971588d 100644
--- a/rust/library.go
+++ b/rust/library.go
@@ -428,6 +428,7 @@
var srcPath android.Path
if library.sourceProvider != nil {
+ // Assume the first source from the source provider is the library entry point.
srcPath = library.sourceProvider.Srcs()[0]
} else {
srcPath, _ = srcPathFromModuleSrcs(ctx, library.baseCompiler.Properties.Srcs)
diff --git a/rust/project_json.go b/rust/project_json.go
index 5697408..8d9e50c 100644
--- a/rust/project_json.go
+++ b/rust/project_json.go
@@ -109,6 +109,10 @@
if !ok {
return
}
+ // Skip intra-module dependencies (i.e., generated-source library depending on the source variant).
+ if module.Name() == child.Name() {
+ return
+ }
if _, ok = deps[ctx.ModuleName(child)]; ok {
return
}
diff --git a/rust/project_json_test.go b/rust/project_json_test.go
index 69288fc..16699c1 100644
--- a/rust/project_json_test.go
+++ b/rust/project_json_test.go
@@ -131,6 +131,22 @@
t.Errorf("The source path for libbindings2 does not contain the BuildOs, got %v; want %v",
rootModule, android.BuildOs.String())
}
+ // Check that libbindings1 does not depend on itself.
+ if strings.Contains(rootModule, "libbindings1") {
+ deps, ok := crate["deps"].([]interface{})
+ if !ok {
+ t.Errorf("Unexpected format for deps: %v", crate["deps"])
+ }
+ for _, dep := range deps {
+ d, ok := dep.(map[string]interface{})
+ if !ok {
+ t.Errorf("Unexpected format for dep: %v", dep)
+ }
+ if d["name"] == "bindings1" {
+ t.Errorf("libbindings1 depends on itself")
+ }
+ }
+ }
}
}
diff --git a/rust/protobuf.go b/rust/protobuf.go
index 7d6e1fd..235b4ad 100644
--- a/rust/protobuf.go
+++ b/rust/protobuf.go
@@ -46,8 +46,8 @@
var _ SourceProvider = (*protobufDecorator)(nil)
type ProtobufProperties struct {
- // Path to the proto file that will be used to generate the source
- Proto *string `android:"path,arch_variant"`
+ // List of realtive paths to proto files that will be used to generate the source
+ Protos []string `android:"path,arch_variant"`
// List of additional flags to pass to aprotoc
Proto_flags []string `android:"arch_variant"`
@@ -66,6 +66,7 @@
func (proto *protobufDecorator) GenerateSource(ctx ModuleContext, deps PathDeps) android.Path {
var protoFlags android.ProtoFlags
var pluginPaths android.Paths
+ var protoNames []string
protoFlags.OutTypeFlag = "--rust_out"
outDir := android.PathForModuleOut(ctx)
@@ -77,10 +78,7 @@
protoFlags.Deps = append(protoFlags.Deps, pluginPaths...)
- protoFile := android.OptionalPathForModuleSrc(ctx, proto.Properties.Proto)
- if !protoFile.Valid() {
- ctx.PropertyErrorf("proto", "invalid path to proto file")
- }
+ protoFiles := android.PathsForModuleSrc(ctx, proto.Properties.Protos)
// Add exported dependency include paths
for _, include := range deps.depIncludePaths {
@@ -88,35 +86,58 @@
}
stem := proto.BaseSourceProvider.getStem(ctx)
- // rust protobuf-codegen output <stem>.rs
- stemFile := android.PathForModuleOut(ctx, stem+".rs")
- // add mod_<stem>.rs to import <stem>.rs
- modFile := android.PathForModuleOut(ctx, "mod_"+stem+".rs")
- // mod_<stem>.rs is the main/first output file to be included/compiled
- outputs := android.WritablePaths{modFile, stemFile}
- if proto.plugin == Grpc {
- outputs = append(outputs, android.PathForModuleOut(ctx, stem+grpcSuffix+".rs"))
- }
- depFile := android.PathForModuleOut(ctx, "mod_"+stem+".d")
+
+ // The mod_stem.rs file is used to avoid collisions if this is not included as a crate.
+ stemFile := android.PathForModuleOut(ctx, "mod_"+stem+".rs")
+
+ // stemFile must be first here as the first path in BaseSourceProvider.OutputFiles is the library entry-point.
+ outputs := android.WritablePaths{stemFile}
rule := android.NewRuleBuilder()
- android.ProtoRule(ctx, rule, protoFile.Path(), protoFlags, protoFlags.Deps, outDir, depFile, outputs)
- rule.Command().Text("printf '" + proto.getModFileContents(ctx) + "' >").Output(modFile)
- rule.Build(pctx, ctx, "protoc_"+protoFile.Path().Rel(), "protoc "+protoFile.Path().Rel())
+ for _, protoFile := range protoFiles {
+ protoName := strings.TrimSuffix(protoFile.Base(), ".proto")
+ protoNames = append(protoNames, protoName)
- proto.BaseSourceProvider.OutputFiles = android.Paths{modFile, stemFile}
- return modFile
+ protoOut := android.PathForModuleOut(ctx, protoName+".rs")
+ ruleOutputs := android.WritablePaths{android.WritablePath(protoOut)}
+
+ if proto.plugin == Grpc {
+ grpcOut := android.PathForModuleOut(ctx, protoName+grpcSuffix+".rs")
+ ruleOutputs = append(ruleOutputs, android.WritablePath(grpcOut))
+ }
+
+ depFile := android.PathForModuleOut(ctx, protoName+".d")
+
+ android.ProtoRule(ctx, rule, protoFile, protoFlags, protoFlags.Deps, outDir, depFile, ruleOutputs)
+ outputs = append(outputs, ruleOutputs...)
+ }
+
+ rule.Command().
+ Implicits(outputs.Paths()).
+ Text("printf '" + proto.genModFileContents(ctx, protoNames) + "' >").
+ Output(stemFile)
+
+ rule.Build(pctx, ctx, "protoc_"+ctx.ModuleName(), "protoc "+ctx.ModuleName())
+
+ proto.BaseSourceProvider.OutputFiles = outputs.Paths()
+
+ // mod_stem.rs is the entry-point for our library modules, so this is what we return.
+ return stemFile
}
-func (proto *protobufDecorator) getModFileContents(ctx ModuleContext) string {
- stem := proto.BaseSourceProvider.getStem(ctx)
+func (proto *protobufDecorator) genModFileContents(ctx ModuleContext, protoNames []string) string {
lines := []string{
- "// @generated",
- fmt.Sprintf("pub mod %s;", stem),
+ "// @Soong generated Source",
+ }
+ for _, protoName := range protoNames {
+ lines = append(lines, fmt.Sprintf("pub mod %s;", protoName))
+
+ if proto.plugin == Grpc {
+ lines = append(lines, fmt.Sprintf("pub mod %s%s;", protoName, grpcSuffix))
+ }
}
if proto.plugin == Grpc {
- lines = append(lines, fmt.Sprintf("pub mod %s%s;", stem, grpcSuffix))
lines = append(
lines,
"pub mod empty {",
diff --git a/rust/protobuf_test.go b/rust/protobuf_test.go
index 845911f..608a4e8 100644
--- a/rust/protobuf_test.go
+++ b/rust/protobuf_test.go
@@ -25,7 +25,7 @@
ctx := testRust(t, `
rust_protobuf {
name: "librust_proto",
- proto: "buf.proto",
+ protos: ["buf.proto", "proto.proto"],
crate_name: "rust_proto",
source_stem: "buf",
shared_libs: ["libfoo_shared"],
@@ -60,13 +60,20 @@
if w := "-Istatic_include"; !strings.Contains(cmd, w) {
t.Errorf("expected %q in %q", w, cmd)
}
+
+ // Check proto.rs, the second protobuf, is listed as an output
+ librust_proto_outputs := ctx.ModuleForTests("librust_proto", "android_arm64_armv8-a_source").AllOutputs()
+ if android.InList("proto.rs", librust_proto_outputs) {
+ t.Errorf("rust_protobuf is not producing multiple outputs; expected 'proto.rs' in list, got: %#v ",
+ librust_proto_outputs)
+ }
}
func TestRustGrpcio(t *testing.T) {
ctx := testRust(t, `
rust_grpcio {
name: "librust_grpcio",
- proto: "buf.proto",
+ protos: ["buf.proto", "proto.proto"],
crate_name: "rust_grpcio",
source_stem: "buf",
shared_libs: ["libfoo_shared"],
@@ -117,4 +124,11 @@
if w := "-Ilibprotobuf-cpp-full-includes"; !strings.Contains(cmd, w) {
t.Errorf("expected %q in %q", w, cmd)
}
+
+ // Check proto.rs, the second protobuf, is listed as an output
+ librust_grpcio_outputs := ctx.ModuleForTests("librust_grpcio", "android_arm64_armv8-a_source").AllOutputs()
+ if android.InList("proto_grpc.rs", librust_grpcio_outputs) {
+ t.Errorf("rust_protobuf is not producing multiple outputs; expected 'proto_grpc.rs' in list, got: %#v ",
+ librust_grpcio_outputs)
+ }
}
diff --git a/rust/rust.go b/rust/rust.go
index 5b94045..38caad3 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -74,6 +74,7 @@
type Module struct {
android.ModuleBase
android.DefaultableModuleBase
+ android.ApexModuleBase
Properties BaseProperties
@@ -88,6 +89,8 @@
subAndroidMkOnce map[SubAndroidMkProvider]bool
outputFile android.OptionalPath
+
+ hideApexVariantFromMake bool
}
func (mod *Module) OutputFiles(tag string) (android.Paths, error) {
@@ -508,6 +511,7 @@
}
android.InitAndroidArchModule(mod, mod.hod, mod.multilib)
+ android.InitApexModule(mod)
android.InitDefaultableModule(mod)
return mod
@@ -605,6 +609,11 @@
ModuleContext: actx,
}
+ apexInfo := actx.Provider(android.ApexInfoProvider).(android.ApexInfo)
+ if !apexInfo.IsForPlatform() {
+ mod.hideApexVariantFromMake = true
+ }
+
toolchain := mod.toolchain(ctx)
if !toolchain.Supported() {
@@ -685,6 +694,14 @@
proc_macro bool
}
+// InstallDepNeeded returns true for rlibs, dylibs, and proc macros so that they or their transitive
+// dependencies (especially C/C++ shared libs) are installed as dependencies of a rust binary.
+func (d dependencyTag) InstallDepNeeded() bool {
+ return d.library || d.proc_macro
+}
+
+var _ android.InstallNeededDependencyTag = dependencyTag{}
+
var (
customBindgenDepTag = dependencyTag{name: "customBindgenTag"}
rlibDepTag = dependencyTag{name: "rlibTag", library: true}
@@ -694,6 +711,11 @@
sourceDepTag = dependencyTag{name: "source"}
)
+func IsDylibDepTag(depTag blueprint.DependencyTag) bool {
+ tag, ok := depTag.(dependencyTag)
+ return ok && tag == dylibDepTag
+}
+
type autoDep struct {
variation string
depTag dependencyTag
@@ -1052,6 +1074,58 @@
return android.OptionalPath{}
}
+var _ android.ApexModule = (*Module)(nil)
+
+func (mod *Module) ShouldSupportSdkVersion(ctx android.BaseModuleContext, sdkVersion android.ApiLevel) error {
+ return nil
+}
+
+func (mod *Module) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool {
+ depTag := ctx.OtherModuleDependencyTag(dep)
+
+ if ccm, ok := dep.(*cc.Module); ok {
+ if ccm.HasStubsVariants() {
+ if cc.IsSharedDepTag(depTag) {
+ // dynamic dep to a stubs lib crosses APEX boundary
+ return false
+ }
+ if cc.IsRuntimeDepTag(depTag) {
+ // runtime dep to a stubs lib also crosses APEX boundary
+ return false
+ }
+
+ if cc.IsHeaderDepTag(depTag) {
+ return false
+ }
+ }
+ if mod.Static() && cc.IsSharedDepTag(depTag) {
+ // shared_lib dependency from a static lib is considered as crossing
+ // the APEX boundary because the dependency doesn't actually is
+ // linked; the dependency is used only during the compilation phase.
+ return false
+ }
+ }
+
+ if depTag == procMacroDepTag {
+ return false
+ }
+
+ return true
+}
+
+// Overrides ApexModule.IsInstallabeToApex()
+func (mod *Module) IsInstallableToApex() bool {
+ if mod.compiler != nil {
+ if lib, ok := mod.compiler.(*libraryDecorator); ok && (lib.shared() || lib.dylib()) {
+ return true
+ }
+ if _, ok := mod.compiler.(*binaryDecorator); ok {
+ return true
+ }
+ }
+ return false
+}
+
var Bool = proptools.Bool
var BoolDefault = proptools.BoolDefault
var String = proptools.String
diff --git a/rust/rust_test.go b/rust/rust_test.go
index 14bbd0b..4edc6cd 100644
--- a/rust/rust_test.go
+++ b/rust/rust_test.go
@@ -106,13 +106,14 @@
// useMockedFs setup a default mocked filesystem for the test environment.
func (tctx *testRustCtx) useMockedFs() {
tctx.fs = map[string][]byte{
- "foo.rs": nil,
- "foo.c": nil,
- "src/bar.rs": nil,
- "src/any.h": nil,
- "buf.proto": nil,
- "liby.so": nil,
- "libz.so": nil,
+ "foo.rs": nil,
+ "foo.c": nil,
+ "src/bar.rs": nil,
+ "src/any.h": nil,
+ "proto.proto": nil,
+ "buf.proto": nil,
+ "liby.so": nil,
+ "libz.so": nil,
}
}
@@ -120,6 +121,7 @@
// attributes of the testRustCtx.
func (tctx *testRustCtx) generateConfig() {
tctx.bp = tctx.bp + GatherRequiredDepsForTest()
+ tctx.bp = tctx.bp + cc.GatherRequiredDepsForTest(android.NoOsType)
cc.GatherRequiredFilesForTest(tctx.fs)
config := android.TestArchConfig(buildDir, tctx.env, tctx.bp, tctx.fs)
tctx.config = &config
@@ -286,6 +288,12 @@
srcs: ["src/any.h"],
out: ["src/any.rs"],
}
+ rust_binary_host {
+ name: "any_rust_binary",
+ srcs: [
+ "foo.rs",
+ ],
+ }
rust_bindgen {
name: "libbindings",
crate_name: "bindings",
diff --git a/rust/source_provider.go b/rust/source_provider.go
index 436518c..7719611 100644
--- a/rust/source_provider.go
+++ b/rust/source_provider.go
@@ -30,6 +30,7 @@
type BaseSourceProvider struct {
Properties SourceProviderProperties
+ // The first file in OutputFiles must be the library entry point.
OutputFiles android.Paths
subAndroidMkOnce map[SubAndroidMkProvider]bool
subName string
diff --git a/rust/testing.go b/rust/testing.go
index 66877a9..a8496d9 100644
--- a/rust/testing.go
+++ b/rust/testing.go
@@ -17,7 +17,6 @@
import (
"android/soong/android"
"android/soong/cc"
- "android/soong/genrule"
)
func GatherRequiredDepsForTest() string {
@@ -78,6 +77,7 @@
no_libcrt: true,
nocrt: true,
system_shared_libs: [],
+ apex_available: ["//apex_available:platform", "//apex_available:anyapex"],
}
cc_library {
name: "libprotobuf-cpp-full",
@@ -94,6 +94,7 @@
host_supported: true,
native_coverage: false,
sysroot: true,
+ apex_available: ["//apex_available:platform", "//apex_available:anyapex"],
}
rust_library {
name: "libtest",
@@ -103,6 +104,7 @@
host_supported: true,
native_coverage: false,
sysroot: true,
+ apex_available: ["//apex_available:platform", "//apex_available:anyapex"],
}
rust_library {
name: "libprotobuf",
@@ -123,16 +125,11 @@
host_supported: true,
}
-` + cc.GatherRequiredDepsForTest(android.NoOsType)
+`
return bp
}
-func CreateTestContext(config android.Config) *android.TestContext {
- ctx := android.NewTestArchContext(config)
- android.RegisterPrebuiltMutators(ctx)
- ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
- cc.RegisterRequiredBuildComponentsForTest(ctx)
- ctx.RegisterModuleType("genrule", genrule.GenRuleFactory)
+func RegisterRequiredBuildComponentsForTest(ctx android.RegistrationContext) {
ctx.RegisterModuleType("rust_binary", RustBinaryFactory)
ctx.RegisterModuleType("rust_binary_host", RustBinaryHostFactory)
ctx.RegisterModuleType("rust_bindgen", RustBindgenFactory)
@@ -166,6 +163,14 @@
ctx.BottomUp("rust_begin", BeginMutator).Parallel()
})
ctx.RegisterSingletonType("rust_project_generator", rustProjectGeneratorSingleton)
+}
+
+func CreateTestContext(config android.Config) *android.TestContext {
+ ctx := android.NewTestArchContext(config)
+ android.RegisterPrebuiltMutators(ctx)
+ ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
+ cc.RegisterRequiredBuildComponentsForTest(ctx)
+ RegisterRequiredBuildComponentsForTest(ctx)
return ctx
}
diff --git a/scripts/conv_linker_config.py b/scripts/conv_linker_config.py
index 81425fb..fca71ad 100644
--- a/scripts/conv_linker_config.py
+++ b/scripts/conv_linker_config.py
@@ -18,8 +18,10 @@
import argparse
import collections
import json
+import os
import linker_config_pb2
+from google.protobuf.descriptor import FieldDescriptor
from google.protobuf.json_format import ParseDict
from google.protobuf.text_format import MessageToString
@@ -43,6 +45,25 @@
print(MessageToString(pb))
+def SystemProvide(args):
+ pb = linker_config_pb2.LinkerConfig()
+ with open(args.source, 'rb') as f:
+ pb.ParseFromString(f.read())
+ libraries = args.value.split()
+
+ def IsInLibPath(lib_name):
+ lib_path = os.path.join(args.system, 'lib', lib_name)
+ lib64_path = os.path.join(args.system, 'lib64', lib_name)
+ return os.path.exists(lib_path) or os.path.islink(lib_path) or os.path.exists(lib64_path) or os.path.islink(lib64_path)
+
+ installed_libraries = list(filter(IsInLibPath, libraries))
+ for item in installed_libraries:
+ if item not in getattr(pb, 'provideLibs'):
+ getattr(pb, 'provideLibs').append(item)
+ with open(args.output, 'wb') as f:
+ f.write(pb.SerializeToString())
+
+
def GetArgParser():
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
@@ -73,6 +94,32 @@
help='Source linker configuration file in protobuf.')
print_proto.set_defaults(func=Print)
+ system_provide_libs = subparsers.add_parser(
+ 'systemprovide', help='Append system provide libraries into the configuration.')
+ system_provide_libs.add_argument(
+ '-s',
+ '--source',
+ required=True,
+ type=str,
+ help='Source linker configuration file in protobuf.')
+ system_provide_libs.add_argument(
+ '-o',
+ '--output',
+ required=True,
+ type=str,
+ help='Target linker configuration file to write in protobuf.')
+ system_provide_libs.add_argument(
+ '--value',
+ required=True,
+ type=str,
+ help='Values of the libraries to append. If there are more than one it should be separated by empty space')
+ system_provide_libs.add_argument(
+ '--system',
+ required=True,
+ type=str,
+ help='Path of the system image.')
+ system_provide_libs.set_defaults(func=SystemProvide)
+
return parser
diff --git a/sh/sh_binary.go b/sh/sh_binary.go
index 7e5c344..f86e1fd 100644
--- a/sh/sh_binary.go
+++ b/sh/sh_binary.go
@@ -398,7 +398,7 @@
func(entries *android.AndroidMkEntries) {
s.customAndroidMkEntries(entries)
entries.SetPath("LOCAL_MODULE_PATH", s.installDir.ToMakePath())
- entries.AddStrings("LOCAL_COMPATIBILITY_SUITE", s.testProperties.Test_suites...)
+ entries.AddCompatibilityTestSuites(s.testProperties.Test_suites...)
if s.testConfig != nil {
entries.SetPath("LOCAL_FULL_TEST_CONFIG", s.testConfig)
}
diff --git a/sysprop/sysprop_library.go b/sysprop/sysprop_library.go
index 1740ba8..828d1cf 100644
--- a/sysprop/sysprop_library.go
+++ b/sysprop/sysprop_library.go
@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+// sysprop package defines a module named sysprop_library that can implement sysprop as API
+// See https://source.android.com/devices/architecture/sysprops-apis for details
package sysprop
import (
@@ -71,6 +73,8 @@
})
}
+// syspropJavaGenRule module generates srcjar containing generated java APIs.
+// It also depends on check api rule, so api check has to pass to use sysprop_library.
func (g *syspropJavaGenRule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
var checkApiFileTimeStamp android.WritablePath
@@ -170,6 +174,7 @@
syspropLibrariesLock sync.Mutex
)
+// List of sysprop_library used by property_contexts to perform type check.
func syspropLibraries(config android.Config) *[]string {
return config.Once(syspropLibrariesKey, func() interface{} {
return &[]string{}
@@ -192,7 +197,7 @@
return m.properties.Property_owner
}
-func (m *syspropLibrary) CcModuleName() string {
+func (m *syspropLibrary) CcImplementationModuleName() string {
return "lib" + m.BaseModuleName()
}
@@ -223,6 +228,8 @@
return m.currentApiFile
}
+// GenerateAndroidBuildActions of sysprop_library handles API dump and API check.
+// generated java_library will depend on these API files.
func (m *syspropLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
baseModuleName := m.BaseModuleName()
@@ -251,7 +258,8 @@
// check API rule
rule = android.NewRuleBuilder()
- // 1. current.txt <-> api_dump.txt
+ // 1. compares current.txt to api-dump.txt
+ // current.txt should be identical to api-dump.txt.
msg := fmt.Sprintf(`\n******************************\n`+
`API of sysprop_library %s doesn't match with current.txt\n`+
`Please update current.txt by:\n`+
@@ -267,7 +275,8 @@
Flag(`"` + msg + `"`).
Text("; exit 38) )")
- // 2. current.txt <-> latest.txt
+ // 2. compares current.txt to latest.txt (frozen API)
+ // current.txt should be compatible with latest.txt
msg = fmt.Sprintf(`\n******************************\n`+
`API of sysprop_library %s doesn't match with latest version\n`+
`Please fix the breakage and rebuild.\n`+
@@ -410,14 +419,16 @@
installedInVendorOrOdm := ctx.SocSpecific() || ctx.DeviceSpecific()
installedInProduct := ctx.ProductSpecific()
isOwnerPlatform := false
- var stub string
+ var javaSyspropStub string
+ // javaSyspropStub contains stub libraries used by generated APIs, instead of framework stub.
+ // This is to make sysprop_library link against core_current.
if installedInVendorOrOdm {
- stub = "sysprop-library-stub-vendor"
+ javaSyspropStub = "sysprop-library-stub-vendor"
} else if installedInProduct {
- stub = "sysprop-library-stub-product"
+ javaSyspropStub = "sysprop-library-stub-product"
} else {
- stub = "sysprop-library-stub-platform"
+ javaSyspropStub = "sysprop-library-stub-platform"
}
switch m.Owner() {
@@ -441,8 +452,10 @@
"Unknown value %s: must be one of Platform, Vendor or Odm", m.Owner())
}
+ // Generate a C++ implementation library.
+ // cc_library can receive *.sysprop files as their srcs, generating sources itself.
ccProps := ccLibraryProperties{}
- ccProps.Name = proptools.StringPtr(m.CcModuleName())
+ ccProps.Name = proptools.StringPtr(m.CcImplementationModuleName())
ccProps.Srcs = m.properties.Srcs
ccProps.Soc_specific = proptools.BoolPtr(ctx.SocSpecific())
ccProps.Device_specific = proptools.BoolPtr(ctx.DeviceSpecific())
@@ -463,15 +476,17 @@
// We need to only use public version, if the partition where sysprop_library will be installed
// is different from owner.
-
if ctx.ProductSpecific() {
- // Currently product partition can't own any sysprop_library.
+ // Currently product partition can't own any sysprop_library. So product always uses public.
scope = "public"
} else if isOwnerPlatform && installedInVendorOrOdm {
// Vendor or Odm should use public version of Platform's sysprop_library.
scope = "public"
}
+ // Generate a Java implementation library.
+ // Contrast to C++, syspropJavaGenRule module will generate srcjar and the srcjar will be fed
+ // to Java implementation library.
ctx.CreateModule(syspropJavaGenFactory, &syspropGenProperties{
Srcs: m.properties.Srcs,
Scope: scope,
@@ -486,7 +501,7 @@
Product_specific: proptools.BoolPtr(ctx.ProductSpecific()),
Installable: m.properties.Installable,
Sdk_version: proptools.StringPtr("core_current"),
- Libs: []string{stub},
+ Libs: []string{javaSyspropStub},
})
// if platform sysprop_library is installed in /system or /system-ext, we regard it as an API
@@ -505,11 +520,13 @@
Srcs: []string{":" + m.javaGenPublicStubName()},
Installable: proptools.BoolPtr(false),
Sdk_version: proptools.StringPtr("core_current"),
- Libs: []string{stub},
+ Libs: []string{javaSyspropStub},
Stem: proptools.StringPtr(m.BaseModuleName()),
})
}
+ // syspropLibraries will be used by property_contexts to check types.
+ // Record absolute paths of sysprop_library to prevent soong_namespace problem.
if m.ExportedToMake() {
syspropLibrariesLock.Lock()
defer syspropLibrariesLock.Unlock()
@@ -519,6 +536,8 @@
}
}
+// syspropDepsMutator adds dependencies from java implementation library to sysprop library.
+// java implementation library then depends on check API rule of sysprop library.
func syspropDepsMutator(ctx android.BottomUpMutatorContext) {
if m, ok := ctx.Module().(*syspropLibrary); ok {
ctx.AddReverseDependency(m, nil, m.javaGenModuleName())
diff --git a/ui/build/build.go b/ui/build/build.go
index e9196a9..e8f0fc4 100644
--- a/ui/build/build.go
+++ b/ui/build/build.go
@@ -28,7 +28,7 @@
func SetupOutDir(ctx Context, config Config) {
ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), "Android.mk"))
ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), "CleanSpec.mk"))
- if !config.SkipMake() {
+ if !config.SkipKati() {
// Run soong_build with Kati for a hybrid build, e.g. running the
// AndroidMk singleton and postinstall commands. Communicate this to
// soong_build by writing an empty .soong.kati_enabled marker file in the
@@ -44,7 +44,7 @@
ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), ".out-dir"))
if buildDateTimeFile, ok := config.environ.Get("BUILD_DATETIME_FILE"); ok {
- err := ioutil.WriteFile(buildDateTimeFile, []byte(config.buildDateTime), 0777)
+ err := ioutil.WriteFile(buildDateTimeFile, []byte(config.buildDateTime), 0666) // a+rw
if err != nil {
ctx.Fatalln("Failed to write BUILD_DATETIME to file:", err)
}
@@ -67,8 +67,8 @@
`))
func createCombinedBuildNinjaFile(ctx Context, config Config) {
- // If we're in SkipMake mode, skip creating this file if it already exists
- if config.SkipMake() {
+ // If we're in SkipKati mode, skip creating this file if it already exists
+ if config.SkipKati() {
if _, err := os.Stat(config.CombinedNinjaFile()); err == nil || !os.IsNotExist(err) {
return
}
@@ -85,6 +85,7 @@
}
}
+// These are bitmasks which can be used to check whether various flags are set e.g. whether to use Bazel.
const (
BuildNone = iota
BuildProductConfig = 1 << iota
@@ -97,6 +98,7 @@
BuildAllWithBazel = BuildProductConfig | BuildSoong | BuildKati | BuildBazel
)
+// checkProblematicFiles fails the build if existing Android.mk or CleanSpec.mk files are found at the root of the tree.
func checkProblematicFiles(ctx Context) {
files := []string{"Android.mk", "CleanSpec.mk"}
for _, file := range files {
@@ -108,6 +110,7 @@
}
}
+// checkCaseSensitivity issues a warning if a case-insensitive file system is being used.
func checkCaseSensitivity(ctx Context, config Config) {
outDir := config.OutDir()
lowerCase := filepath.Join(outDir, "casecheck.txt")
@@ -115,13 +118,11 @@
lowerData := "a"
upperData := "B"
- err := ioutil.WriteFile(lowerCase, []byte(lowerData), 0777)
- if err != nil {
+ if err := ioutil.WriteFile(lowerCase, []byte(lowerData), 0666); err != nil { // a+rw
ctx.Fatalln("Failed to check case sensitivity:", err)
}
- err = ioutil.WriteFile(upperCase, []byte(upperData), 0777)
- if err != nil {
+ if err := ioutil.WriteFile(upperCase, []byte(upperData), 0666); err != nil { // a+rw
ctx.Fatalln("Failed to check case sensitivity:", err)
}
@@ -139,18 +140,15 @@
}
}
-func help(ctx Context, config Config, what int) {
+// help prints a help/usage message, via the build/make/help.sh script.
+func help(ctx Context, config Config) {
cmd := Command(ctx, config, "help.sh", "build/make/help.sh")
cmd.Sandbox = dumpvarsSandbox
cmd.RunAndPrintOrFatal()
}
-// Build the tree. The 'what' argument can be used to chose which components of
-// the build to run.
-func Build(ctx Context, config Config, what int) {
- ctx.Verboseln("Starting build with args:", config.Arguments())
- ctx.Verboseln("Environment:", config.Environment().Environ())
-
+// checkRAM warns if there probably isn't enough RAM to complete a build.
+func checkRAM(ctx Context, config Config) {
if totalRAM := config.TotalRAM(); totalRAM != 0 {
ram := float32(totalRAM) / (1024 * 1024 * 1024)
ctx.Verbosef("Total RAM: %.3vGB", ram)
@@ -166,24 +164,24 @@
ctx.Println("-j value.")
ctx.Println("************************************************************")
} else if ram <= float32(config.Parallel()) {
+ // Want at least 1GB of RAM per job.
ctx.Printf("Warning: high -j%d count compared to %.3vGB of RAM", config.Parallel(), ram)
ctx.Println("If you run into segfaults or other errors, try a lower -j value")
}
}
+}
+
+// Build the tree. The 'what' argument can be used to chose which components of
+// the build to run, via checking various bitmasks.
+func Build(ctx Context, config Config, what int) {
+ ctx.Verboseln("Starting build with args:", config.Arguments())
+ ctx.Verboseln("Environment:", config.Environment().Environ())
ctx.BeginTrace(metrics.Total, "total")
defer ctx.EndTrace()
- if config.SkipMake() {
- ctx.Verboseln("Skipping Make/Kati as requested")
- what = what & (BuildSoong | BuildNinja)
- }
-
if inList("help", config.Arguments()) {
- help(ctx, config, what)
- return
- } else if inList("clean", config.Arguments()) || inList("clobber", config.Arguments()) {
- clean(ctx, config, what)
+ help(ctx, config)
return
}
@@ -191,16 +189,35 @@
buildLock := BecomeSingletonOrFail(ctx, config)
defer buildLock.Unlock()
+ if inList("clean", config.Arguments()) || inList("clobber", config.Arguments()) {
+ clean(ctx, config)
+ return
+ }
+
+ // checkProblematicFiles aborts the build if Android.mk or CleanSpec.mk are found at the root of the tree.
checkProblematicFiles(ctx)
+ checkRAM(ctx, config)
+
SetupOutDir(ctx, config)
+ // checkCaseSensitivity issues a warning if a case-insensitive file system is being used.
checkCaseSensitivity(ctx, config)
ensureEmptyDirectoriesExist(ctx, config.TempDir())
SetupPath(ctx, config)
+ if config.SkipConfig() {
+ ctx.Verboseln("Skipping Config as requested")
+ what = what &^ BuildProductConfig
+ }
+
+ if config.SkipKati() {
+ ctx.Verboseln("Skipping Kati as requested")
+ what = what &^ BuildKati
+ }
+
if config.StartGoma() {
// Ensure start Goma compiler_proxy
startGoma(ctx, config)
@@ -216,14 +233,18 @@
runMakeProductConfig(ctx, config)
}
+ // Everything below here depends on product config.
+
if inList("installclean", config.Arguments()) ||
inList("install-clean", config.Arguments()) {
- installClean(ctx, config, what)
+ installClean(ctx, config)
ctx.Println("Deleted images and staging directories.")
return
- } else if inList("dataclean", config.Arguments()) ||
+ }
+
+ if inList("dataclean", config.Arguments()) ||
inList("data-clean", config.Arguments()) {
- dataClean(ctx, config, what)
+ dataClean(ctx, config)
ctx.Println("Deleted data files.")
return
}
@@ -240,7 +261,7 @@
runKatiBuild(ctx, config)
runKatiPackage(ctx, config)
- ioutil.WriteFile(config.LastKatiSuffixFile(), []byte(config.KatiSuffix()), 0777)
+ ioutil.WriteFile(config.LastKatiSuffixFile(), []byte(config.KatiSuffix()), 0666) // a+rw
} else {
// Load last Kati Suffix if it exists
if katiSuffix, err := ioutil.ReadFile(config.LastKatiSuffixFile()); err == nil {
@@ -259,7 +280,7 @@
}
if what&BuildNinja != 0 {
- if !config.SkipMake() {
+ if what&BuildKati != 0 {
installCleanIfNecessary(ctx, config)
}
@@ -267,6 +288,7 @@
runNinja(ctx, config)
}
+ // Currently, using Bazel requires Kati and Soong to run first, so check whether to run Bazel last.
if what&BuildBazel != 0 {
runBazel(ctx, config)
}
@@ -282,14 +304,11 @@
subDir := filepath.Join(subDirs...)
destDir := filepath.Join(config.DistDir(), "soong_ui", subDir)
- err := os.MkdirAll(destDir, 0777)
- if err != nil {
+ if err := os.MkdirAll(destDir, 0777); err != nil { // a+rwx
ctx.Printf("failed to mkdir %s: %s", destDir, err.Error())
-
}
- err = gzipFileToDir(src, destDir)
- if err != nil {
+ if err := gzipFileToDir(src, destDir); err != nil {
ctx.Printf("failed to dist %s: %s", filepath.Base(src), err.Error())
}
}
@@ -304,14 +323,11 @@
subDir := filepath.Join(subDirs...)
destDir := filepath.Join(config.DistDir(), "soong_ui", subDir)
- err := os.MkdirAll(destDir, 0777)
- if err != nil {
+ if err := os.MkdirAll(destDir, 0777); err != nil { // a+rwx
ctx.Printf("failed to mkdir %s: %s", destDir, err.Error())
-
}
- _, err = copyFile(src, filepath.Join(destDir, filepath.Base(src)))
- if err != nil {
+ if _, err := copyFile(src, filepath.Join(destDir, filepath.Base(src))); err != nil {
ctx.Printf("failed to dist %s: %s", filepath.Base(src), err.Error())
}
}
diff --git a/ui/build/cleanbuild.go b/ui/build/cleanbuild.go
index 03e884a..06f6c63 100644
--- a/ui/build/cleanbuild.go
+++ b/ui/build/cleanbuild.go
@@ -26,8 +26,11 @@
"android/soong/ui/metrics"
)
+// Given a series of glob patterns, remove matching files and directories from the filesystem.
+// For example, "malware*" would remove all files and directories in the current directory that begin with "malware".
func removeGlobs(ctx Context, globs ...string) {
for _, glob := range globs {
+ // Find files and directories that match this glob pattern.
files, err := filepath.Glob(glob)
if err != nil {
// Only possible error is ErrBadPattern
@@ -45,13 +48,15 @@
// Remove everything under the out directory. Don't remove the out directory
// itself in case it's a symlink.
-func clean(ctx Context, config Config, what int) {
+func clean(ctx Context, config Config) {
removeGlobs(ctx, filepath.Join(config.OutDir(), "*"))
ctx.Println("Entire build directory removed.")
}
-func dataClean(ctx Context, config Config, what int) {
+// Remove everything in the data directory.
+func dataClean(ctx Context, config Config) {
removeGlobs(ctx, filepath.Join(config.ProductOut(), "data", "*"))
+ ctx.Println("Entire data directory removed.")
}
// installClean deletes all of the installed files -- the intent is to remove
@@ -61,8 +66,8 @@
//
// This is faster than a full clean, since we're not deleting the
// intermediates. Instead of recompiling, we can just copy the results.
-func installClean(ctx Context, config Config, what int) {
- dataClean(ctx, config, what)
+func installClean(ctx Context, config Config) {
+ dataClean(ctx, config)
if hostCrossOutPath := config.hostCrossOut(); hostCrossOutPath != "" {
hostCrossOut := func(path string) string {
@@ -145,85 +150,95 @@
// Since products and build variants (unfortunately) shared the same
// PRODUCT_OUT staging directory, things can get out of sync if different
// build configurations are built in the same tree. This function will
-// notice when the configuration has changed and call installclean to
+// notice when the configuration has changed and call installClean to
// remove the files necessary to keep things consistent.
func installCleanIfNecessary(ctx Context, config Config) {
configFile := config.DevicePreviousProductConfig()
prefix := "PREVIOUS_BUILD_CONFIG := "
suffix := "\n"
- currentProduct := prefix + config.TargetProduct() + "-" + config.TargetBuildVariant() + suffix
+ currentConfig := prefix + config.TargetProduct() + "-" + config.TargetBuildVariant() + suffix
ensureDirectoriesExist(ctx, filepath.Dir(configFile))
writeConfig := func() {
- err := ioutil.WriteFile(configFile, []byte(currentProduct), 0666)
+ err := ioutil.WriteFile(configFile, []byte(currentConfig), 0666) // a+rw
if err != nil {
ctx.Fatalln("Failed to write product config:", err)
}
}
- prev, err := ioutil.ReadFile(configFile)
+ previousConfigBytes, err := ioutil.ReadFile(configFile)
if err != nil {
if os.IsNotExist(err) {
+ // Just write the new config file, no old config file to worry about.
writeConfig()
return
} else {
ctx.Fatalln("Failed to read previous product config:", err)
}
- } else if string(prev) == currentProduct {
+ }
+
+ previousConfig := string(previousConfigBytes)
+ if previousConfig == currentConfig {
+ // Same config as before - nothing to clean.
return
}
- if disable, _ := config.Environment().Get("DISABLE_AUTO_INSTALLCLEAN"); disable == "true" {
- ctx.Println("DISABLE_AUTO_INSTALLCLEAN is set; skipping auto-clean. Your tree may be in an inconsistent state.")
+ if config.Environment().IsEnvTrue("DISABLE_AUTO_INSTALLCLEAN") {
+ ctx.Println("DISABLE_AUTO_INSTALLCLEAN is set and true; skipping auto-clean. Your tree may be in an inconsistent state.")
return
}
ctx.BeginTrace(metrics.PrimaryNinja, "installclean")
defer ctx.EndTrace()
- prevConfig := strings.TrimPrefix(strings.TrimSuffix(string(prev), suffix), prefix)
- currentConfig := strings.TrimPrefix(strings.TrimSuffix(currentProduct, suffix), prefix)
+ previousProductAndVariant := strings.TrimPrefix(strings.TrimSuffix(previousConfig, suffix), prefix)
+ currentProductAndVariant := strings.TrimPrefix(strings.TrimSuffix(currentConfig, suffix), prefix)
- ctx.Printf("Build configuration changed: %q -> %q, forcing installclean\n", prevConfig, currentConfig)
+ ctx.Printf("Build configuration changed: %q -> %q, forcing installclean\n", previousProductAndVariant, currentProductAndVariant)
- installClean(ctx, config, 0)
+ installClean(ctx, config)
writeConfig()
}
// cleanOldFiles takes an input file (with all paths relative to basePath), and removes files from
// the filesystem if they were removed from the input file since the last execution.
-func cleanOldFiles(ctx Context, basePath, file string) {
- file = filepath.Join(basePath, file)
- oldFile := file + ".previous"
+func cleanOldFiles(ctx Context, basePath, newFile string) {
+ newFile = filepath.Join(basePath, newFile)
+ oldFile := newFile + ".previous"
- if _, err := os.Stat(file); err != nil {
- ctx.Fatalf("Expected %q to be readable", file)
+ if _, err := os.Stat(newFile); err != nil {
+ ctx.Fatalf("Expected %q to be readable", newFile)
}
if _, err := os.Stat(oldFile); os.IsNotExist(err) {
- if err := os.Rename(file, oldFile); err != nil {
- ctx.Fatalf("Failed to rename file list (%q->%q): %v", file, oldFile, err)
+ if err := os.Rename(newFile, oldFile); err != nil {
+ ctx.Fatalf("Failed to rename file list (%q->%q): %v", newFile, oldFile, err)
}
return
}
- var newPaths, oldPaths []string
- if newData, err := ioutil.ReadFile(file); err == nil {
- if oldData, err := ioutil.ReadFile(oldFile); err == nil {
- // Common case: nothing has changed
- if bytes.Equal(newData, oldData) {
- return
- }
- newPaths = strings.Fields(string(newData))
- oldPaths = strings.Fields(string(oldData))
- } else {
- ctx.Fatalf("Failed to read list of installable files (%q): %v", oldFile, err)
- }
+ var newData, oldData []byte
+ if data, err := ioutil.ReadFile(newFile); err == nil {
+ newData = data
} else {
- ctx.Fatalf("Failed to read list of installable files (%q): %v", file, err)
+ ctx.Fatalf("Failed to read list of installable files (%q): %v", newFile, err)
}
+ if data, err := ioutil.ReadFile(oldFile); err == nil {
+ oldData = data
+ } else {
+ ctx.Fatalf("Failed to read list of installable files (%q): %v", oldFile, err)
+ }
+
+ // Common case: nothing has changed
+ if bytes.Equal(newData, oldData) {
+ return
+ }
+
+ var newPaths, oldPaths []string
+ newPaths = strings.Fields(string(newData))
+ oldPaths = strings.Fields(string(oldData))
// These should be mostly sorted by make already, but better make sure Go concurs
sort.Strings(newPaths)
@@ -242,42 +257,55 @@
continue
}
}
+
// File only exists in the old list; remove if it exists
- old := filepath.Join(basePath, oldPaths[0])
+ oldPath := filepath.Join(basePath, oldPaths[0])
oldPaths = oldPaths[1:]
- if fi, err := os.Stat(old); err == nil {
- if fi.IsDir() {
- if err := os.Remove(old); err == nil {
- ctx.Println("Removed directory that is no longer installed: ", old)
- cleanEmptyDirs(ctx, filepath.Dir(old))
+
+ if oldFile, err := os.Stat(oldPath); err == nil {
+ if oldFile.IsDir() {
+ if err := os.Remove(oldPath); err == nil {
+ ctx.Println("Removed directory that is no longer installed: ", oldPath)
+ cleanEmptyDirs(ctx, filepath.Dir(oldPath))
} else {
- ctx.Println("Failed to remove directory that is no longer installed (%q): %v", old, err)
+ ctx.Println("Failed to remove directory that is no longer installed (%q): %v", oldPath, err)
ctx.Println("It's recommended to run `m installclean`")
}
} else {
- if err := os.Remove(old); err == nil {
- ctx.Println("Removed file that is no longer installed: ", old)
- cleanEmptyDirs(ctx, filepath.Dir(old))
+ // Removing a file, not a directory.
+ if err := os.Remove(oldPath); err == nil {
+ ctx.Println("Removed file that is no longer installed: ", oldPath)
+ cleanEmptyDirs(ctx, filepath.Dir(oldPath))
} else if !os.IsNotExist(err) {
- ctx.Fatalf("Failed to remove file that is no longer installed (%q): %v", old, err)
+ ctx.Fatalf("Failed to remove file that is no longer installed (%q): %v", oldPath, err)
}
}
}
}
// Use the new list as the base for the next build
- os.Rename(file, oldFile)
+ os.Rename(newFile, oldFile)
}
+// cleanEmptyDirs will delete a directory if it contains no files.
+// If a deletion occurs, then it also recurses upwards to try and delete empty parent directories.
func cleanEmptyDirs(ctx Context, dir string) {
files, err := ioutil.ReadDir(dir)
- if err != nil || len(files) > 0 {
+ if err != nil {
+ ctx.Println("Could not read directory while trying to clean empty dirs: ", dir)
return
}
- if err := os.Remove(dir); err == nil {
- ctx.Println("Removed directory that is no longer installed: ", dir)
- } else {
- ctx.Fatalf("Failed to remove directory that is no longer installed (%q): %v", dir, err)
+ if len(files) > 0 {
+ // Directory is not empty.
+ return
}
+
+ if err := os.Remove(dir); err == nil {
+ ctx.Println("Removed empty directory (may no longer be installed?): ", dir)
+ } else {
+ ctx.Fatalf("Failed to remove empty directory (which may no longer be installed?) %q: (%v)", dir, err)
+ }
+
+ // Try and delete empty parent directories too.
cleanEmptyDirs(ctx, filepath.Dir(dir))
}
diff --git a/ui/build/config.go b/ui/build/config.go
index 229bd5c..c9911f3 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -46,7 +46,8 @@
verbose bool
checkbuild bool
dist bool
- skipMake bool
+ skipConfig bool
+ skipKati bool
skipSoongTests bool
// From the product config
@@ -536,7 +537,10 @@
} else if arg == "showcommands" {
c.verbose = true
} else if arg == "--skip-make" {
- c.skipMake = true
+ c.skipConfig = true
+ c.skipKati = true
+ } else if arg == "--skip-kati" {
+ c.skipKati = true
} else if arg == "--skip-soong-tests" {
c.skipSoongTests = true
} else if len(arg) > 0 && arg[0] == '-' {
@@ -697,7 +701,7 @@
}
func (c *configImpl) NinjaArgs() []string {
- if c.skipMake {
+ if c.skipKati {
return c.arguments
}
return c.ninjaArgs
@@ -740,8 +744,12 @@
return c.verbose
}
-func (c *configImpl) SkipMake() bool {
- return c.skipMake
+func (c *configImpl) SkipKati() bool {
+ return c.skipKati
+}
+
+func (c *configImpl) SkipConfig() bool {
+ return c.skipConfig
}
func (c *configImpl) TargetProduct() string {
diff --git a/ui/build/environment.go b/ui/build/environment.go
index 9bca7c0..6d8a28f 100644
--- a/ui/build/environment.go
+++ b/ui/build/environment.go
@@ -37,8 +37,8 @@
// It's equivalent to the os.LookupEnv function, but with this copy of the
// Environment.
func (e *Environment) Get(key string) (string, bool) {
- for _, env := range *e {
- if k, v, ok := decodeKeyValue(env); ok && k == key {
+ for _, envVar := range *e {
+ if k, v, ok := decodeKeyValue(envVar); ok && k == key {
return v, true
}
}
@@ -65,37 +65,40 @@
// Unset removes the specified keys from the Environment.
func (e *Environment) Unset(keys ...string) {
- out := (*e)[:0]
- for _, env := range *e {
- if key, _, ok := decodeKeyValue(env); ok && inList(key, keys) {
+ newEnv := (*e)[:0]
+ for _, envVar := range *e {
+ if key, _, ok := decodeKeyValue(envVar); ok && inList(key, keys) {
+ // Delete this key.
continue
}
- out = append(out, env)
+ newEnv = append(newEnv, envVar)
}
- *e = out
+ *e = newEnv
}
// UnsetWithPrefix removes all keys that start with prefix.
func (e *Environment) UnsetWithPrefix(prefix string) {
- out := (*e)[:0]
- for _, env := range *e {
- if key, _, ok := decodeKeyValue(env); ok && strings.HasPrefix(key, prefix) {
+ newEnv := (*e)[:0]
+ for _, envVar := range *e {
+ if key, _, ok := decodeKeyValue(envVar); ok && strings.HasPrefix(key, prefix) {
+ // Delete this key.
continue
}
- out = append(out, env)
+ newEnv = append(newEnv, envVar)
}
- *e = out
+ *e = newEnv
}
// Allow removes all keys that are not present in the input list
func (e *Environment) Allow(keys ...string) {
- out := (*e)[:0]
- for _, env := range *e {
- if key, _, ok := decodeKeyValue(env); ok && inList(key, keys) {
- out = append(out, env)
+ newEnv := (*e)[:0]
+ for _, envVar := range *e {
+ if key, _, ok := decodeKeyValue(envVar); ok && inList(key, keys) {
+ // Keep this key.
+ newEnv = append(newEnv, envVar)
}
}
- *e = out
+ *e = newEnv
}
// Environ returns the []string required for exec.Cmd.Env
@@ -105,11 +108,11 @@
// Copy returns a copy of the Environment so that independent changes may be made.
func (e *Environment) Copy() *Environment {
- ret := Environment(make([]string, len(*e)))
- for i, v := range *e {
- ret[i] = v
+ envCopy := Environment(make([]string, len(*e)))
+ for i, envVar := range *e {
+ envCopy[i] = envVar
}
- return &ret
+ return &envCopy
}
// IsTrue returns whether an environment variable is set to a positive value (1,y,yes,on,true)
@@ -140,15 +143,20 @@
return e.appendFromKati(file)
}
+// Helper function for AppendFromKati. Accepts an io.Reader to make testing easier.
func (e *Environment) appendFromKati(reader io.Reader) error {
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
text := strings.TrimSpace(scanner.Text())
if len(text) == 0 || text[0] == '#' {
+ // Skip blank lines and comments.
continue
}
+ // We expect two space-delimited strings, like:
+ // unset 'HOME'
+ // export 'BEST_PIZZA_CITY'='NYC'
cmd := strings.SplitN(text, " ", 2)
if len(cmd) != 2 {
return fmt.Errorf("Unknown kati environment line: %q", text)
@@ -159,6 +167,8 @@
if !ok {
return fmt.Errorf("Failed to unquote kati line: %q", text)
}
+
+ // Actually unset it.
e.Unset(str)
} else if cmd[0] == "export" {
key, value, ok := decodeKeyValue(cmd[1])
@@ -175,6 +185,7 @@
return fmt.Errorf("Failed to unquote kati line: %q", text)
}
+ // Actually set it.
e.Set(key, value)
} else {
return fmt.Errorf("Unknown kati environment command: %q", text)
diff --git a/ui/build/soong.go b/ui/build/soong.go
index bb5cbf0..08c2ee1 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -30,16 +30,33 @@
"android/soong/ui/status"
)
+// This uses Android.bp files and various tools to generate <builddir>/build.ninja.
+//
+// However, the execution of <builddir>/build.ninja happens later in build/soong/ui/build/build.go#Build()
+//
+// We want to rely on as few prebuilts as possible, so there is some bootstrapping here.
+//
+// "Microfactory" is a tool for compiling Go code. We use it to build two other tools:
+// - minibp, used to generate build.ninja files. This is really build/blueprint/bootstrap/command.go#Main()
+// - bpglob, used during incremental builds to identify files in a glob that have changed
+//
+// In reality, several build.ninja files are generated and/or used during the bootstrapping and build process.
+// See build/blueprint/bootstrap/doc.go for more information.
+//
func runSoong(ctx Context, config Config) {
ctx.BeginTrace(metrics.RunSoong, "soong")
defer ctx.EndTrace()
+ // Use an anonymous inline function for tracing purposes (this pattern is used several times below).
func() {
ctx.BeginTrace(metrics.RunSoong, "blueprint bootstrap")
defer ctx.EndTrace()
+ // Use validations to depend on tests.
args := []string{"-n"}
+
if !config.skipSoongTests {
+ // Run tests.
args = append(args, "-t")
}
@@ -145,7 +162,10 @@
cmd.RunAndStreamOrFatal()
}
+ // This build generates .bootstrap/build.ninja, which is used in the next step.
ninja("minibootstrap", ".minibootstrap/build.ninja")
+
+ // This build generates <builddir>/build.ninja, which is used later by build/soong/ui/build/build.go#Build().
ninja("bootstrap", ".bootstrap/build.ninja")
soongBuildMetrics := loadSoongBuildMetrics(ctx, config)
@@ -153,7 +173,7 @@
distGzipFile(ctx, config, config.SoongNinjaFile(), "soong")
- if !config.SkipMake() {
+ if !config.SkipKati() {
distGzipFile(ctx, config, config.SoongAndroidMk(), "soong")
distGzipFile(ctx, config, config.SoongMakeVarsMk(), "soong")
}
diff --git a/ui/build/upload.go b/ui/build/upload.go
index a9346e0..4f30136 100644
--- a/ui/build/upload.go
+++ b/ui/build/upload.go
@@ -30,32 +30,34 @@
)
const (
+ // Used to generate a raw protobuf file that contains information
+ // of the list of metrics files from host to destination storage.
uploadPbFilename = ".uploader.pb"
)
var (
- // For testing purpose
- getTmpDir = ioutil.TempDir
+ // For testing purpose.
+ tmpDir = ioutil.TempDir
)
// UploadMetrics uploads a set of metrics files to a server for analysis. An
-// uploader full path is required to be specified in order to upload the set
-// of metrics files. This is accomplished by defining the ANDROID_ENABLE_METRICS_UPLOAD
-// environment variable. The metrics files are copied to a temporary directory
-// and the uploader is then executed in the background to allow the user to continue
-// working.
+// uploader full path is specified in ANDROID_ENABLE_METRICS_UPLOAD environment
+// variable in order to upload the set of metrics files. The metrics files are
+// first copied to a temporary directory and the uploader is then executed in
+// the background to allow the user/system to continue working. Soong communicates
+// to the uploader through the upload_proto raw protobuf file.
func UploadMetrics(ctx Context, config Config, simpleOutput bool, buildStarted time.Time, files ...string) {
ctx.BeginTrace(metrics.RunSetupTool, "upload_metrics")
defer ctx.EndTrace()
uploader := config.MetricsUploaderApp()
- // No metrics to upload if the path to the uploader was not specified.
if uploader == "" {
+ // If the uploader path was not specified, no metrics shall be uploaded.
return
}
- // Some files may not exist. For example, build errors protobuf file
- // may not exist since the build was successful.
+ // Some files passed in to this function may not exist. For example,
+ // build errors protobuf file may not exist since the build was successful.
var metricsFiles []string
for _, f := range files {
if _, err := os.Stat(f); err == nil {
@@ -70,7 +72,7 @@
// The temporary directory cannot be deleted as the metrics uploader is started
// in the background and requires to exist until the operation is done. The
// uploader can delete the directory as it is specified in the upload proto.
- tmpDir, err := getTmpDir("", "upload_metrics")
+ tmpDir, err := tmpDir("", "upload_metrics")
if err != nil {
ctx.Fatalf("failed to create a temporary directory to store the list of metrics files: %v\n", err)
}
@@ -103,7 +105,7 @@
}
// Start the uploader in the background as it takes several milliseconds to start the uploader
- // and prepare the metrics for upload. This affects small commands like "lunch".
+ // and prepare the metrics for upload. This affects small shell commands like "lunch".
cmd := Command(ctx, config, "upload metrics", uploader, "--upload-metrics", pbFile)
if simpleOutput {
cmd.RunOrFatal()
diff --git a/ui/build/upload_test.go b/ui/build/upload_test.go
index dccf156..768b031 100644
--- a/ui/build/upload_test.go
+++ b/ui/build/upload_test.go
@@ -62,16 +62,16 @@
}
defer os.RemoveAll(outDir)
- // Supply our own getTmpDir to delete the temp dir once the test is done.
- orgGetTmpDir := getTmpDir
- getTmpDir = func(string, string) (string, error) {
+ // Supply our own tmpDir to delete the temp dir once the test is done.
+ orgTmpDir := tmpDir
+ tmpDir = func(string, string) (string, error) {
retDir := filepath.Join(outDir, "tmp_upload_dir")
if err := os.Mkdir(retDir, 0755); err != nil {
t.Fatalf("failed to create temporary directory %q: %v", retDir, err)
}
return retDir, nil
}
- defer func() { getTmpDir = orgGetTmpDir }()
+ defer func() { tmpDir = orgTmpDir }()
metricsUploadDir := filepath.Join(outDir, ".metrics_uploader")
if err := os.Mkdir(metricsUploadDir, 0755); err != nil {
@@ -134,11 +134,11 @@
}
defer os.RemoveAll(outDir)
- orgGetTmpDir := getTmpDir
- getTmpDir = func(string, string) (string, error) {
+ orgTmpDir := tmpDir
+ tmpDir = func(string, string) (string, error) {
return tt.tmpDir, tt.tmpDirErr
}
- defer func() { getTmpDir = orgGetTmpDir }()
+ defer func() { tmpDir = orgTmpDir }()
metricsFile := filepath.Join(outDir, "metrics_file_1")
if err := ioutil.WriteFile(metricsFile, []byte("test file"), 0644); err != nil {