Support multiple dists per Android.bp module, and dist output selection.
This CL adds "dists" to the base property struct to support multiple
dist file configurations, and generic tag support to dist tagged outputs
of modules.
Fixes: b/152834186
Test: soong tests and `m sdk dist`
Change-Id: I80c86bc9b7b09e671f640a4480c45d438bdd9a2a
Signed-off-by: Jingwen Chen <jingwen@google.com>
diff --git a/android/androidmk.go b/android/androidmk.go
index d579e30..3487b28 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -46,7 +46,7 @@
type AndroidMkData struct {
Class string
SubName string
- DistFile OptionalPath
+ DistFiles TaggedDistFiles
OutputFile OptionalPath
Disabled bool
Include string
@@ -72,7 +72,7 @@
type AndroidMkEntries struct {
Class string
SubName string
- DistFile OptionalPath
+ DistFiles TaggedDistFiles
OutputFile OptionalPath
Disabled bool
Include string
@@ -137,6 +137,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 {
+ amod := mod.(Module).base()
+ name := amod.BaseModuleName()
+
+ var ret []string
+
+ availableTaggedDists := TaggedDistFiles{}
+ if a.DistFiles != nil && len(a.DistFiles[""]) > 0 {
+ availableTaggedDists = a.DistFiles
+ } else if a.OutputFile.Valid() {
+ availableTaggedDists = MakeDefaultDistFiles(a.OutputFile.Path())
+ }
+
+ // 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
+ goals := strings.Join(dist.Targets, " ")
+
+ // Get the tag representing the output files to be dist'd. e.g. ".jar", ".proguard_map"
+ var tag string
+ if dist.Tag == nil {
+ // If the dist struct does not specify a tag, use the default output files tag.
+ tag = ""
+ } else {
+ tag = *dist.Tag
+ }
+
+ // Get the paths of the output files to be dist'd, represented by the tag.
+ // Can be an empty list.
+ tagPaths := availableTaggedDists[tag]
+ if len(tagPaths) == 0 {
+ // Nothing to dist for this tag, continue to the next dist.
+ continue
+ }
+
+ 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, " +
+ "which should have a single element, is:\n%s"
+ panic(fmt.Errorf(errorMessage, goals, name, tagPaths))
+ }
+
+ ret = append(ret, fmt.Sprintf(".PHONY: %s\n", goals))
+
+ // Create dist-for-goals calls for each path in the dist'd files.
+ for _, path := range tagPaths {
+ // It's possible that the Path is nil from errant modules. Be defensive here.
+ if path == nil {
+ tagName := "default" // for error message readability
+ if dist.Tag != nil {
+ tagName = *dist.Tag
+ }
+ panic(fmt.Errorf("Dist file should not be nil for the %s tag in %s", tagName, name))
+ }
+
+ dest := filepath.Base(path.String())
+
+ if dist.Dest != nil {
+ var err error
+ if dest, err = validateSafePath(*dist.Dest); err != nil {
+ // This was checked in ModuleBase.GenerateBuildActions
+ panic(err)
+ }
+ }
+
+ if dist.Suffix != nil {
+ ext := filepath.Ext(dest)
+ suffix := *dist.Suffix
+ dest = strings.TrimSuffix(dest, ext) + suffix + ext
+ }
+
+ if dist.Dir != nil {
+ var err error
+ if dest, err = validateSafePath(*dist.Dir, dest); err != nil {
+ // This was checked in ModuleBase.GenerateBuildActions
+ panic(err)
+ }
+ }
+
+ ret = append(
+ ret,
+ fmt.Sprintf("$(call dist-for-goals,%s,%s:%s)\n", goals, path.String(), dest))
+ }
+ }
+
+ return ret
+}
+
func (a *AndroidMkEntries) fillInEntries(config Config, bpPath string, mod blueprint.Module) {
a.EntryMap = make(map[string][]string)
amod := mod.(Module).base()
@@ -149,42 +239,8 @@
a.Host_required = append(a.Host_required, amod.commonProperties.Host_required...)
a.Target_required = append(a.Target_required, amod.commonProperties.Target_required...)
- // Fill in the header part.
- if len(amod.commonProperties.Dist.Targets) > 0 {
- distFile := a.DistFile
- if !distFile.Valid() {
- distFile = a.OutputFile
- }
- if distFile.Valid() {
- dest := filepath.Base(distFile.String())
-
- if amod.commonProperties.Dist.Dest != nil {
- var err error
- if dest, err = validateSafePath(*amod.commonProperties.Dist.Dest); err != nil {
- // This was checked in ModuleBase.GenerateBuildActions
- panic(err)
- }
- }
-
- if amod.commonProperties.Dist.Suffix != nil {
- ext := filepath.Ext(dest)
- suffix := *amod.commonProperties.Dist.Suffix
- dest = strings.TrimSuffix(dest, ext) + suffix + ext
- }
-
- if amod.commonProperties.Dist.Dir != nil {
- var err error
- if dest, err = validateSafePath(*amod.commonProperties.Dist.Dir, dest); err != nil {
- // This was checked in ModuleBase.GenerateBuildActions
- panic(err)
- }
- }
-
- goals := strings.Join(amod.commonProperties.Dist.Targets, " ")
- fmt.Fprintln(&a.header, ".PHONY:", goals)
- fmt.Fprintf(&a.header, "$(call dist-for-goals,%s,%s:%s)\n",
- goals, distFile.String(), dest)
- }
+ for _, distString := range a.GetDistForGoals(mod) {
+ fmt.Fprintf(&a.header, distString)
}
fmt.Fprintln(&a.header, "\ninclude $(CLEAR_VARS)")
@@ -430,7 +486,7 @@
entries := AndroidMkEntries{
Class: data.Class,
SubName: data.SubName,
- DistFile: data.DistFile,
+ DistFiles: data.DistFiles,
OutputFile: data.OutputFile,
Disabled: data.Disabled,
Include: data.Include,
diff --git a/android/androidmk_test.go b/android/androidmk_test.go
index 71f8020..250f086 100644
--- a/android/androidmk_test.go
+++ b/android/androidmk_test.go
@@ -15,6 +15,7 @@
package android
import (
+ "fmt"
"io"
"reflect"
"testing"
@@ -22,10 +23,12 @@
type customModule struct {
ModuleBase
- data AndroidMkData
+ data AndroidMkData
+ distFiles TaggedDistFiles
}
func (m *customModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+ m.distFiles = m.GenerateTaggedDistFiles(ctx)
}
func (m *customModule) AndroidMk() AndroidMkData {
@@ -36,6 +39,26 @@
}
}
+func (m *customModule) OutputFiles(tag string) (Paths, error) {
+ switch tag {
+ case "":
+ return PathsForTesting("one.out"), nil
+ case ".multiple":
+ return PathsForTesting("two.out", "three/four.out"), nil
+ default:
+ return nil, fmt.Errorf("unsupported module reference tag %q", tag)
+ }
+}
+
+func (m *customModule) AndroidMkEntries() []AndroidMkEntries {
+ return []AndroidMkEntries{
+ {
+ Class: "CUSTOM_MODULE",
+ DistFiles: m.distFiles,
+ },
+ }
+}
+
func customModuleFactory() Module {
module := &customModule{}
InitAndroidModule(module)
@@ -76,3 +99,159 @@
assertEqual([]string{"baz"}, m.data.Host_required)
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",
+ 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"],
+ },
+ 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: `
+ 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",
+ },
+ ],
+ }
+ `,
+ 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.inMake = true // Enable androidmk Singleton
+
+ ctx := NewTestContext()
+ ctx.RegisterSingletonType("androidmk", AndroidMkSingleton)
+ ctx.RegisterModuleType("custom", customModuleFactory)
+ ctx.Register(config)
+
+ _, 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) {
+ t.Errorf(
+ "Expected %d AndroidMk lines, got %d:\n%v",
+ len(testCase.expectedAndroidMkLines),
+ len(androidMkLines),
+ androidMkLines,
+ )
+ }
+ for idx, line := range androidMkLines {
+ expectedLine := testCase.expectedAndroidMkLines[idx]
+ if line != expectedLine {
+ t.Errorf(
+ "Expected AndroidMk line to be '%s', got '%s'",
+ line,
+ expectedLine,
+ )
+ }
+ }
+ }
+}
diff --git a/android/module.go b/android/module.go
index ac3394d..06079ca 100644
--- a/android/module.go
+++ b/android/module.go
@@ -315,6 +315,28 @@
return qualifiedModuleName{pkg: pkg, name: ""}
}
+type Dist struct {
+ // Copy the output of this module to the $DIST_DIR when `dist` is specified on the
+ // command line and any of these targets are also on the command line, or otherwise
+ // built
+ Targets []string `android:"arch_variant"`
+
+ // The name of the output artifact. This defaults to the basename of the output of
+ // the module.
+ Dest *string `android:"arch_variant"`
+
+ // The directory within the dist directory to store the artifact. Defaults to the
+ // top level directory ("").
+ Dir *string `android:"arch_variant"`
+
+ // 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.
+ Tag *string `android:"arch_variant"`
+}
+
type nameProperties struct {
// The name of the module. Must be unique across all modules.
Name *string
@@ -454,23 +476,13 @@
// relative path to a file to include in the list of notices for the device
Notice *string `android:"path"`
- Dist struct {
- // copy the output of this module to the $DIST_DIR when `dist` is specified on the
- // command line and any of these targets are also on the command line, or otherwise
- // built
- Targets []string `android:"arch_variant"`
+ // configuration to distribute output files from this module to the distribution
+ // directory (default: $OUT/dist, configurable with $DIST_DIR)
+ Dist Dist `android:"arch_variant"`
- // The name of the output artifact. This defaults to the basename of the output of
- // the module.
- Dest *string `android:"arch_variant"`
-
- // The directory within the dist directory to store the artifact. Defaults to the
- // top level directory ("").
- Dir *string `android:"arch_variant"`
-
- // A suffix to add to the artifact file name (before any extension).
- Suffix *string `android:"arch_variant"`
- } `android:"arch_variant"`
+ // a list of configurations to distribute output files from this module to the
+ // distribution directory (default: $OUT/dist, configurable with $DIST_DIR)
+ Dists []Dist `android:"arch_variant"`
// The OsType of artifacts that this module variant is responsible for creating.
//
@@ -537,6 +549,14 @@
ImageVariation string `blueprint:"mutated"`
}
+// A map of OutputFile tag keys to Paths, for disting purposes.
+type TaggedDistFiles map[string]Paths
+
+func MakeDefaultDistFiles(paths ...Path) TaggedDistFiles {
+ // The default OutputFile tag is the empty "" string.
+ return TaggedDistFiles{"": paths}
+}
+
type hostAndDeviceProperties struct {
// If set to true, build a variant of the module for the host. Defaults to false.
Host_supported *bool
@@ -815,6 +835,41 @@
return m.visibilityPropertyInfo
}
+func (m *ModuleBase) Dists() []Dist {
+ if len(m.commonProperties.Dist.Targets) > 0 {
+ // Make a copy of the underlying Dists slice to protect against
+ // backing array modifications with repeated calls to this method.
+ distsCopy := append([]Dist(nil), m.commonProperties.Dists...)
+ return append(distsCopy, m.commonProperties.Dist)
+ } else {
+ return m.commonProperties.Dists
+ }
+}
+
+func (m *ModuleBase) GenerateTaggedDistFiles(ctx BaseModuleContext) TaggedDistFiles {
+ distFiles := make(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)
+ }
+ }
+ }
+
+ return distFiles
+}
+
func (m *ModuleBase) Target() Target {
return m.commonProperties.CompileTarget
}
diff --git a/android/paths.go b/android/paths.go
index bed6f3f..d8d51a7 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -220,6 +220,15 @@
// Paths is a slice of Path objects, with helpers to operate on the collection.
type Paths []Path
+func (paths Paths) containsPath(path Path) bool {
+ for _, p := range paths {
+ if p == path {
+ return true
+ }
+ }
+ return false
+}
+
// PathsForSource returns Paths rooted from SrcDir
func PathsForSource(ctx PathContext, paths []string) Paths {
ret := make(Paths, len(paths))