Merge "Minor cleanup of soong.go."
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/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/config.go b/android/config.go
index 04c9129..57ab70b 100644
--- a/android/config.go
+++ b/android/config.go
@@ -1321,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)
}
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/module.go b/android/module.go
index bd60829..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)
}
}
@@ -1579,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() {
@@ -1631,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...)
@@ -1659,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/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/cc.go b/cc/cc.go
index 6deb1b4..1a7ccf2 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -584,7 +584,7 @@
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"}
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/testing.go b/cc/testing.go
index 198764b..95a93a0 100644
--- a/cc/testing.go
+++ b/cc/testing.go
@@ -166,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/sbox.go b/cmd/sbox/sbox.go
index db483f1..a4f57ea 100644
--- a/cmd/sbox/sbox.go
+++ b/cmd/sbox/sbox.go
@@ -16,6 +16,8 @@
import (
"bytes"
+ "crypto/sha1"
+ "encoding/hex"
"errors"
"flag"
"fmt"
@@ -121,7 +123,22 @@
return fmt.Errorf("failed to create %q: %w", sandboxesRoot, err)
}
- tempDir, err := ioutil.TempDir(sandboxesRoot, "sbox")
+ // 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)))
+
+ err = os.RemoveAll(tempDir)
+ if err != nil {
+ return err
+ }
+ err = os.MkdirAll(tempDir, 0777)
if err != nil {
return fmt.Errorf("failed to create temporary dir in %q: %w", sandboxesRoot, err)
}
@@ -207,7 +224,7 @@
}
// Copy in any files specified by the manifest.
- err = linkOrCopyFiles(command.CopyBefore, "", tempDir)
+ err = copyFiles(command.CopyBefore, "", tempDir)
if err != nil {
return "", err
}
@@ -315,12 +332,12 @@
return missingOutputErrors
}
-// linkOrCopyFiles hardlinks or copies files in or out of the sandbox.
-func linkOrCopyFiles(copies []*sbox_proto.Copy, fromDir, toDir string) error {
+// 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 := linkOrCopyOneFile(fromPath, toPath)
+ err := copyOneFile(fromPath, toPath)
if err != nil {
return fmt.Errorf("error copying %q to %q: %w", fromPath, toPath, err)
}
@@ -328,30 +345,13 @@
return nil
}
-// linkOrCopyOneFile first attempts to hardlink a file to a destination, and falls back to making
-// a copy if the hardlink fails.
-func linkOrCopyOneFile(from string, to string) error {
+// copyOneFile copies a file.
+func copyOneFile(from string, to string) error {
err := os.MkdirAll(filepath.Dir(to), 0777)
if err != nil {
return err
}
- // First try hardlinking
- err = os.Link(from, to)
- if err != nil {
- // Retry with copying in case the source an destination are on different filesystems.
- // TODO: check for specific hardlink error?
- err = copyOneFile(from, to)
- if err != nil {
- return err
- }
- }
-
- return nil
-}
-
-// copyOneFile copies a file.
-func copyOneFile(from string, to string) error {
stat, err := os.Stat(from)
if err != nil {
return err
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/genrule.go b/genrule/genrule.go
index cd7c52f..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 (
@@ -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 {
@@ -322,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")
@@ -434,6 +448,7 @@
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)
}
@@ -584,8 +599,8 @@
for i, shard := range shards {
var commands []string
var outFiles android.WritablePaths
+ var commandDepFiles []string
var copyTo android.WritablePaths
- 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
@@ -598,7 +613,7 @@
genDir := android.PathForModuleGen(ctx, genSubDir)
- for j, in := range shard {
+ 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
@@ -610,10 +625,6 @@
outFile = shardFile
}
- if j == 0 {
- depFile = outFile.ReplaceExtension(ctx, "d")
- }
-
outFiles = append(outFiles, outFile)
// pre-expand the command line to replace $in and $out with references to
@@ -624,6 +635,12 @@
return in.String(), nil
case "out":
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
}
@@ -638,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: genDir,
- 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,
})
}
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..da8489a 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
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 5012279..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":
@@ -772,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 {
@@ -1051,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)
@@ -2105,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) {
@@ -2793,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())
}
}
})
@@ -2809,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 83db443..f7cf03f 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -805,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/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 c27a096..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 {
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 187f0b6..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
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 001f322..a8496d9 100644
--- a/rust/testing.go
+++ b/rust/testing.go
@@ -77,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",
@@ -93,6 +94,7 @@
host_supported: true,
native_coverage: false,
sysroot: true,
+ apex_available: ["//apex_available:platform", "//apex_available:anyapex"],
}
rust_library {
name: "libtest",
@@ -102,6 +104,7 @@
host_supported: true,
native_coverage: false,
sysroot: true,
+ apex_available: ["//apex_available:platform", "//apex_available:anyapex"],
}
rust_library {
name: "libprotobuf",
@@ -122,15 +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)
+func RegisterRequiredBuildComponentsForTest(ctx android.RegistrationContext) {
ctx.RegisterModuleType("rust_binary", RustBinaryFactory)
ctx.RegisterModuleType("rust_binary_host", RustBinaryHostFactory)
ctx.RegisterModuleType("rust_bindgen", RustBindgenFactory)
@@ -164,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/ui/build/build.go b/ui/build/build.go
index cfd0b83..da088e4 100644
--- a/ui/build/build.go
+++ b/ui/build/build.go
@@ -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)
}
@@ -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)
+ help(ctx, config)
return
}
@@ -191,16 +189,31 @@
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.SkipMake() {
+ ctx.Verboseln("Skipping Make/Kati as requested")
+ // If Make/Kati is disabled, then explicitly build using Soong and Ninja.
+ what = what & (BuildSoong | BuildNinja)
+ }
+
if config.StartGoma() {
// Ensure start Goma compiler_proxy
startGoma(ctx, config)
@@ -216,12 +229,16 @@
runMakeProductConfig(ctx, config)
}
+ // Everything below here depends on product config.
+
if inList("installclean", config.Arguments()) ||
inList("install-clean", config.Arguments()) {
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)
ctx.Println("Deleted data files.")
@@ -240,7 +257,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 {
@@ -267,6 +284,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 +300,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 +319,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/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 {