diff --git a/android/Android.bp b/android/Android.bp index efa70a980..eabb137a5 100644 --- a/android/Android.bp +++ b/android/Android.bp @@ -21,6 +21,7 @@ bootstrap_go_package { "bazel_handler.go", "config.go", "csuite_config.go", + "deapexer.go", "defaults.go", "defs.go", "depset_generic.go", diff --git a/android/apex.go b/android/apex.go index 47d14cce1..31c62e994 100644 --- a/android/apex.go +++ b/android/apex.go @@ -64,6 +64,14 @@ type ApexInfo struct { // module is part of. The ApexContents gives information about which modules the apexBundle // has and whether a module became part of the apexBundle via a direct dependency or not. ApexContents []*ApexContents + + // True if this is for a prebuilt_apex. + // + // If true then this will customize the apex processing to make it suitable for handling + // prebuilt_apex, e.g. it will prevent ApexInfos from being merged together. + // + // See Prebuilt.ApexInfoMutator for more information. + ForPrebuiltApex bool } var ApexInfoProvider = blueprint.NewMutatorProvider(ApexInfo{}, "apex") @@ -412,6 +420,16 @@ func mergeApexVariations(ctx PathContext, apexInfos []ApexInfo) (merged []ApexIn sort.Sort(byApexName(apexInfos)) seen := make(map[string]int) for _, apexInfo := range apexInfos { + // If this is for a prebuilt apex then use the actual name of the apex variation to prevent this + // from being merged with other ApexInfo. See Prebuilt.ApexInfoMutator for more information. + if apexInfo.ForPrebuiltApex { + merged = append(merged, apexInfo) + continue + } + + // Merge the ApexInfo together. If a compatible ApexInfo exists then merge the information from + // this one into it, otherwise create a new merged ApexInfo from this one and save it away so + // other ApexInfo instances can be merged into it. apexName := apexInfo.ApexVariationName mergedName := apexInfo.mergedName(ctx) if index, exists := seen[mergedName]; exists { @@ -582,10 +600,14 @@ const ( // apexContents, and modules in that apex have a provider containing the apexContents of each // apexBundle they are part of. type ApexContents struct { - // map from a module name to its membership to this apexBUndle + // map from a module name to its membership in this apexBundle contents map[string]ApexMembership } +// NewApexContents creates and initializes an ApexContents that is suitable +// for use with an apex module. +// * contents is a map from a module name to information about its membership within +// the apex. func NewApexContents(contents map[string]ApexMembership) *ApexContents { return &ApexContents{ contents: contents, diff --git a/android/apex_test.go b/android/apex_test.go index 512b50f96..1f786e65f 100644 --- a/android/apex_test.go +++ b/android/apex_test.go @@ -20,6 +20,10 @@ import ( ) func Test_mergeApexVariations(t *testing.T) { + const ( + ForPrebuiltApex = true + NotForPrebuiltApex = false + ) tests := []struct { name string in []ApexInfo @@ -29,10 +33,10 @@ func Test_mergeApexVariations(t *testing.T) { { name: "single", in: []ApexInfo{ - {"foo", "current", false, nil, []string{"foo"}, nil}, + {"foo", "current", false, nil, []string{"foo"}, nil, NotForPrebuiltApex}, }, wantMerged: []ApexInfo{ - {"apex10000", "current", false, nil, []string{"foo"}, nil}, + {"apex10000", "current", false, nil, []string{"foo"}, nil, NotForPrebuiltApex}, }, wantAliases: [][2]string{ {"foo", "apex10000"}, @@ -41,11 +45,11 @@ func Test_mergeApexVariations(t *testing.T) { { name: "merge", in: []ApexInfo{ - {"foo", "current", false, SdkRefs{{"baz", "1"}}, []string{"foo"}, nil}, - {"bar", "current", false, SdkRefs{{"baz", "1"}}, []string{"bar"}, nil}, + {"foo", "current", false, SdkRefs{{"baz", "1"}}, []string{"foo"}, nil, NotForPrebuiltApex}, + {"bar", "current", false, SdkRefs{{"baz", "1"}}, []string{"bar"}, nil, NotForPrebuiltApex}, }, wantMerged: []ApexInfo{ - {"apex10000_baz_1", "current", false, SdkRefs{{"baz", "1"}}, []string{"bar", "foo"}, nil}}, + {"apex10000_baz_1", "current", false, SdkRefs{{"baz", "1"}}, []string{"bar", "foo"}, nil, false}}, wantAliases: [][2]string{ {"bar", "apex10000_baz_1"}, {"foo", "apex10000_baz_1"}, @@ -54,12 +58,12 @@ func Test_mergeApexVariations(t *testing.T) { { name: "don't merge version", in: []ApexInfo{ - {"foo", "current", false, nil, []string{"foo"}, nil}, - {"bar", "30", false, nil, []string{"bar"}, nil}, + {"foo", "current", false, nil, []string{"foo"}, nil, NotForPrebuiltApex}, + {"bar", "30", false, nil, []string{"bar"}, nil, NotForPrebuiltApex}, }, wantMerged: []ApexInfo{ - {"apex30", "30", false, nil, []string{"bar"}, nil}, - {"apex10000", "current", false, nil, []string{"foo"}, nil}, + {"apex30", "30", false, nil, []string{"bar"}, nil, NotForPrebuiltApex}, + {"apex10000", "current", false, nil, []string{"foo"}, nil, NotForPrebuiltApex}, }, wantAliases: [][2]string{ {"bar", "apex30"}, @@ -69,11 +73,11 @@ func Test_mergeApexVariations(t *testing.T) { { name: "merge updatable", in: []ApexInfo{ - {"foo", "current", false, nil, []string{"foo"}, nil}, - {"bar", "current", true, nil, []string{"bar"}, nil}, + {"foo", "current", false, nil, []string{"foo"}, nil, NotForPrebuiltApex}, + {"bar", "current", true, nil, []string{"bar"}, nil, NotForPrebuiltApex}, }, wantMerged: []ApexInfo{ - {"apex10000", "current", true, nil, []string{"bar", "foo"}, nil}, + {"apex10000", "current", true, nil, []string{"bar", "foo"}, nil, NotForPrebuiltApex}, }, wantAliases: [][2]string{ {"bar", "apex10000"}, @@ -83,19 +87,38 @@ func Test_mergeApexVariations(t *testing.T) { { name: "don't merge sdks", in: []ApexInfo{ - {"foo", "current", false, SdkRefs{{"baz", "1"}}, []string{"foo"}, nil}, - {"bar", "current", false, SdkRefs{{"baz", "2"}}, []string{"bar"}, nil}, + {"foo", "current", false, SdkRefs{{"baz", "1"}}, []string{"foo"}, nil, NotForPrebuiltApex}, + {"bar", "current", false, SdkRefs{{"baz", "2"}}, []string{"bar"}, nil, NotForPrebuiltApex}, }, wantMerged: []ApexInfo{ - {"apex10000_baz_2", "current", false, SdkRefs{{"baz", "2"}}, []string{"bar"}, nil}, - {"apex10000_baz_1", "current", false, SdkRefs{{"baz", "1"}}, []string{"foo"}, nil}, + {"apex10000_baz_2", "current", false, SdkRefs{{"baz", "2"}}, []string{"bar"}, nil, NotForPrebuiltApex}, + {"apex10000_baz_1", "current", false, SdkRefs{{"baz", "1"}}, []string{"foo"}, nil, NotForPrebuiltApex}, }, wantAliases: [][2]string{ {"bar", "apex10000_baz_2"}, {"foo", "apex10000_baz_1"}, }, }, + { + name: "don't merge when for prebuilt_apex", + in: []ApexInfo{ + {"foo", "current", false, nil, []string{"foo"}, nil, NotForPrebuiltApex}, + {"bar", "current", true, nil, []string{"bar"}, nil, NotForPrebuiltApex}, + // This one should not be merged in with the others because it is for + // a prebuilt_apex. + {"baz", "current", true, nil, []string{"baz"}, nil, ForPrebuiltApex}, + }, + wantMerged: []ApexInfo{ + {"apex10000", "current", true, nil, []string{"bar", "foo"}, nil, NotForPrebuiltApex}, + {"baz", "current", true, nil, []string{"baz"}, nil, ForPrebuiltApex}, + }, + wantAliases: [][2]string{ + {"bar", "apex10000"}, + {"foo", "apex10000"}, + }, + }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { config := TestConfig(buildDir, nil, "", nil) diff --git a/android/deapexer.go b/android/deapexer.go new file mode 100644 index 000000000..63508d78b --- /dev/null +++ b/android/deapexer.go @@ -0,0 +1,83 @@ +// Copyright (C) 2021 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package android + +import ( + "fmt" + "strings" + + "github.com/google/blueprint" +) + +// Provides support for interacting with the `deapexer` module to which a `prebuilt_apex` module +// will delegate the work to export files from a prebuilt '.apex` file. + +// The information exported by the `deapexer` module, access it using `DeapxerInfoProvider`. +type DeapexerInfo struct { + // map from the name of an exported file from a prebuilt_apex to the path to that file. The + // exported file name is of the form {} where is currently only allowed to be + // ".dexjar". + // + // See Prebuilt.ApexInfoMutator for more information. + exports map[string]Path +} + +// The set of supported prebuilt export tags. Used to verify the tag parameter for +// `PrebuiltExportPath`. +var supportedPrebuiltExportTags = map[string]struct{}{ + ".dexjar": {}, +} + +// PrebuiltExportPath provides the path, or nil if not available, of a file exported from the +// prebuilt_apex that created this ApexInfo. +// +// The exported file is identified by the module name and the tag: +// * The module name is the name of the module that contributed the file when the .apex file +// referenced by the prebuilt_apex was built. It must be specified in one of the exported_... +// properties on the prebuilt_apex module. +// * The tag identifies the type of file and is dependent on the module type. +// +// See apex/deapexer.go for more information. +func (i DeapexerInfo) PrebuiltExportPath(name, tag string) Path { + + if _, ok := supportedPrebuiltExportTags[tag]; !ok { + panic(fmt.Errorf("unsupported prebuilt export tag %q, expected one of %s", + tag, strings.Join(SortedStringKeys(supportedPrebuiltExportTags), ", "))) + } + + path := i.exports[name+"{"+tag+"}"] + return path +} + +// Provider that can be used from within the `GenerateAndroidBuildActions` of a module that depends +// on a `deapexer` module to retrieve its `DeapexerInfo`. +var DeapexerProvider = blueprint.NewProvider(DeapexerInfo{}) + +// NewDeapexerInfo creates and initializes a DeapexerInfo that is suitable +// for use with a prebuilt_apex module. +// +// See apex/deapexer.go for more information. +func NewDeapexerInfo(exports map[string]Path) DeapexerInfo { + return DeapexerInfo{ + exports: exports, + } +} + +type deapexerTagStruct struct { + blueprint.BaseDependencyTag +} + +// A tag that is used for dependencies on the `deapexer` module. +var DeapexerTag = deapexerTagStruct{} diff --git a/android/testing.go b/android/testing.go index 92ce014a9..470cfd63f 100644 --- a/android/testing.go +++ b/android/testing.go @@ -469,6 +469,9 @@ func AndroidMkDataForTest(t *testing.T, config Config, bpPath string, mod bluepr // // The build and source paths should be distinguishable based on their contents. func NormalizePathForTesting(path Path) string { + if path == nil { + return "" + } p := path.String() // Allow absolute paths to /dev/ if strings.HasPrefix(p, "/dev/") { diff --git a/apex/Android.bp b/apex/Android.bp index e3a547cbf..77dde72ac 100644 --- a/apex/Android.bp +++ b/apex/Android.bp @@ -18,6 +18,7 @@ bootstrap_go_package { "apex.go", "apex_singleton.go", "builder.go", + "deapexer.go", "key.go", "prebuilt.go", "vndk.go", diff --git a/apex/apex.go b/apex/apex.go index 5cd18ed28..376811af2 100644 --- a/apex/apex.go +++ b/apex/apex.go @@ -1501,7 +1501,7 @@ func apexFileForFilesystem(ctx android.BaseModuleContext, buildFile android.Path return newApexFile(ctx, buildFile, buildFile.Base(), dirInApex, etc, fs) } -// WalyPayloadDeps visits dependencies that contributes to the payload of this APEX. For each of the +// WalkPayloadDeps visits dependencies that contributes to the payload of this APEX. For each of the // visited module, the `do` callback is executed. Returning true in the callback continues the visit // to the child modules. Returning false makes the visit to continue in the sibling or the parent // modules. This is used in check* functions below. diff --git a/apex/apex_test.go b/apex/apex_test.go index fc746724a..58739b0d6 100644 --- a/apex/apex_test.go +++ b/apex/apex_test.go @@ -190,6 +190,7 @@ func testApexContext(_ *testing.T, bp string, handlers ...testCustomizer) (*andr "testdata/baz": nil, "AppSet.apks": nil, "foo.rs": nil, + "libfoo.jar": nil, } cc.GatherRequiredFilesForTest(fs) @@ -4219,6 +4220,121 @@ func TestPrebuiltOverrides(t *testing.T) { } } +func TestPrebuiltExportDexImplementationJars(t *testing.T) { + transform := func(config *dexpreopt.GlobalConfig) { + config.BootJars = android.CreateTestConfiguredJarList([]string{"myapex:libfoo"}) + } + + checkDexJarBuildPath := func(ctx *android.TestContext, name string) { + // Make sure the import has been given the correct path to the dex jar. + p := ctx.ModuleForTests(name, "android_common_myapex").Module().(java.Dependency) + dexJarBuildPath := p.DexJarBuildPath() + if expected, actual := ".intermediates/myapex.deapexer/android_common/deapexer/javalib/libfoo.jar", android.NormalizePathForTesting(dexJarBuildPath); actual != expected { + t.Errorf("Incorrect DexJarBuildPath value '%s', expected '%s'", actual, expected) + } + } + + ensureNoSourceVariant := func(ctx *android.TestContext) { + // Make sure that an apex variant is not created for the source module. + if expected, actual := []string{"android_common"}, ctx.ModuleVariantsForTests("libfoo"); !reflect.DeepEqual(expected, actual) { + t.Errorf("invalid set of variants for %q: expected %q, found %q", "libfoo", expected, actual) + } + } + + t.Run("prebuilt only", func(t *testing.T) { + bp := ` + prebuilt_apex { + name: "myapex", + arch: { + arm64: { + src: "myapex-arm64.apex", + }, + arm: { + src: "myapex-arm.apex", + }, + }, + exported_java_libs: ["libfoo"], + } + + java_import { + name: "libfoo", + jars: ["libfoo.jar"], + } + ` + + // Make sure that dexpreopt can access dex implementation files from the prebuilt. + ctx := testDexpreoptWithApexes(t, bp, "", transform) + + checkDexJarBuildPath(ctx, "libfoo") + }) + + t.Run("prebuilt with source preferred", func(t *testing.T) { + + bp := ` + prebuilt_apex { + name: "myapex", + arch: { + arm64: { + src: "myapex-arm64.apex", + }, + arm: { + src: "myapex-arm.apex", + }, + }, + exported_java_libs: ["libfoo"], + } + + java_import { + name: "libfoo", + jars: ["libfoo.jar"], + } + + java_library { + name: "libfoo", + } + ` + + // Make sure that dexpreopt can access dex implementation files from the prebuilt. + ctx := testDexpreoptWithApexes(t, bp, "", transform) + + checkDexJarBuildPath(ctx, "prebuilt_libfoo") + ensureNoSourceVariant(ctx) + }) + + t.Run("prebuilt preferred with source", func(t *testing.T) { + bp := ` + prebuilt_apex { + name: "myapex", + prefer: true, + arch: { + arm64: { + src: "myapex-arm64.apex", + }, + arm: { + src: "myapex-arm.apex", + }, + }, + exported_java_libs: ["libfoo"], + } + + java_import { + name: "libfoo", + jars: ["libfoo.jar"], + } + + java_library { + name: "libfoo", + } + ` + + // Make sure that dexpreopt can access dex implementation files from the prebuilt. + ctx := testDexpreoptWithApexes(t, bp, "", transform) + + checkDexJarBuildPath(ctx, "prebuilt_libfoo") + ensureNoSourceVariant(ctx) + }) +} + func TestApexWithTests(t *testing.T) { ctx, config := testApex(t, ` apex_test { @@ -5783,7 +5899,7 @@ func testNoUpdatableJarsInBootImage(t *testing.T, errmsg string, transformDexpre testDexpreoptWithApexes(t, bp, errmsg, transformDexpreoptConfig) } -func testDexpreoptWithApexes(t *testing.T, bp, errmsg string, transformDexpreoptConfig func(*dexpreopt.GlobalConfig)) { +func testDexpreoptWithApexes(t *testing.T, bp, errmsg string, transformDexpreoptConfig func(*dexpreopt.GlobalConfig)) *android.TestContext { t.Helper() bp += cc.GatherRequiredDepsForTest(android.Android) @@ -5808,6 +5924,7 @@ func testDexpreoptWithApexes(t *testing.T, bp, errmsg string, transformDexpreopt ctx := android.NewTestArchContext(config) ctx.RegisterModuleType("apex", BundleFactory) ctx.RegisterModuleType("apex_key", ApexKeyFactory) + ctx.RegisterModuleType("prebuilt_apex", PrebuiltFactory) ctx.RegisterModuleType("filegroup", android.FileGroupFactory) ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators) android.RegisterPrebuiltMutators(ctx) @@ -5837,10 +5954,11 @@ func testDexpreoptWithApexes(t *testing.T, bp, errmsg string, transformDexpreopt android.FailIfErrored(t, errs) } else if len(errs) > 0 { android.FailIfNoMatchingErrors(t, errmsg, errs) - return } else { t.Fatalf("missing expected error %q (0 errors are returned)", errmsg) } + + return ctx } func TestUpdatable_should_set_min_sdk_version(t *testing.T) { @@ -5939,6 +6057,56 @@ func TestNoUpdatableJarsInBootImage(t *testing.T) { } testNoUpdatableJarsInBootImage(t, "", transform) }) + +} + +func TestDexpreoptAccessDexFilesFromPrebuiltApex(t *testing.T) { + transform := func(config *dexpreopt.GlobalConfig) { + config.BootJars = android.CreateTestConfiguredJarList([]string{"myapex:libfoo"}) + } + t.Run("prebuilt no source", func(t *testing.T) { + testDexpreoptWithApexes(t, ` + prebuilt_apex { + name: "myapex" , + arch: { + arm64: { + src: "myapex-arm64.apex", + }, + arm: { + src: "myapex-arm.apex", + }, + }, + exported_java_libs: ["libfoo"], + } + + java_import { + name: "libfoo", + jars: ["libfoo.jar"], + } +`, "", transform) + }) + + t.Run("prebuilt no source", func(t *testing.T) { + testDexpreoptWithApexes(t, ` + prebuilt_apex { + name: "myapex" , + arch: { + arm64: { + src: "myapex-arm64.apex", + }, + arm: { + src: "myapex-arm.apex", + }, + }, + exported_java_libs: ["libfoo"], + } + + java_import { + name: "libfoo", + jars: ["libfoo.jar"], + } +`, "", transform) + }) } func testApexPermittedPackagesRules(t *testing.T, errmsg, bp string, apexBootJars []string, rules []android.Rule) { diff --git a/apex/deapexer.go b/apex/deapexer.go new file mode 100644 index 000000000..651cadfe7 --- /dev/null +++ b/apex/deapexer.go @@ -0,0 +1,139 @@ +// Copyright (C) 2021 The Android Open Source Project +// +// 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 apex + +import ( + "android/soong/android" +) + +// Contains 'deapexer' a private module type used by 'prebuilt_apex' to make dex files contained +// within a .apex file referenced by `prebuilt_apex` available for use by their associated +// `java_import` modules. +// +// An 'apex' module references `java_library` modules from which .dex files are obtained that are +// stored in the resulting `.apex` file. The resulting `.apex` file is then made available as a +// prebuilt by referencing it from a `prebuilt_apex`. For each such `java_library` that is used by +// modules outside the `.apex` file a `java_import` prebuilt is made available referencing a jar +// that contains the Java classes. +// +// When building a Java module type, e.g. `java_module` or `android_app` against such prebuilts the +// `java_import` provides the classes jar (jar containing `.class` files) against which the +// module's `.java` files are compiled. That classes jar usually contains only stub classes. The +// resulting classes jar is converted into a dex jar (jar containing `.dex` files). Then if +// necessary the dex jar is further processed by `dexpreopt` to produce an optimized form of the +// library specific to the current Android version. This process requires access to implementation +// dex jars for each `java_import`. The `java_import` will obtain the implementation dex jar from +// the `.apex` file in the associated `prebuilt_apex`. +// +// This is intentionally not registered by name as it is not intended to be used from within an +// `Android.bp` file. + +// Properties that are specific to `deapexer` but which need to be provided on the `prebuilt_apex` +// module.` +type DeapexerProperties struct { + // List of java libraries that are embedded inside this prebuilt APEX bundle and for which this + // APEX bundle will provide dex implementation jars for use by dexpreopt and boot jars package + // check. + Exported_java_libs []string +} + +type Deapexer struct { + android.ModuleBase + prebuilt android.Prebuilt + + properties DeapexerProperties + apexFileProperties ApexFileProperties + + inputApex android.Path +} + +func privateDeapexerFactory() android.Module { + module := &Deapexer{} + module.AddProperties( + &module.properties, + &module.apexFileProperties, + ) + android.InitSingleSourcePrebuiltModule(module, &module.apexFileProperties, "Source") + android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon) + return module +} + +func (p *Deapexer) Prebuilt() *android.Prebuilt { + return &p.prebuilt +} + +func (p *Deapexer) Name() string { + return p.prebuilt.Name(p.ModuleBase.Name()) +} + +func (p *Deapexer) DepsMutator(ctx android.BottomUpMutatorContext) { + if err := p.apexFileProperties.selectSource(ctx); err != nil { + ctx.ModuleErrorf("%s", err) + return + } + + // Add dependencies from the java modules to which this exports files from the `.apex` file onto + // this module so that they can access the `DeapexerInfo` object that this provides. + for _, lib := range p.properties.Exported_java_libs { + dep := prebuiltApexExportedModuleName(ctx, lib) + ctx.AddReverseDependency(ctx.Module(), android.DeapexerTag, dep) + } +} + +func (p *Deapexer) GenerateAndroidBuildActions(ctx android.ModuleContext) { + p.inputApex = p.Prebuilt().SingleSourcePath(ctx) + + // Create and remember the directory into which the .apex file's contents will be unpacked. + deapexerOutput := android.PathForModuleOut(ctx, "deapexer") + + exports := make(map[string]android.Path) + + // Create mappings from name+tag to all the required exported paths. + for _, l := range p.properties.Exported_java_libs { + // Populate the exports that this makes available. The path here must match the path of the + // file in the APEX created by apexFileForJavaModule(...). + exports[l+"{.dexjar}"] = deapexerOutput.Join(ctx, "javalib", l+".jar") + } + + // If the prebuilt_apex exports any files then create a build rule that unpacks the apex using + // deapexer and verifies that all the required files were created. Also, make the mapping from + // name+tag to path available for other modules. + if len(exports) > 0 { + // Make the information available for other modules. + ctx.SetProvider(android.DeapexerProvider, android.NewDeapexerInfo(exports)) + + // Create a sorted list of the files that this exports. + exportedPaths := make(android.Paths, 0, len(exports)) + for _, p := range exports { + exportedPaths = append(exportedPaths, p) + } + exportedPaths = android.SortedUniquePaths(exportedPaths) + + // The apex needs to export some files so create a ninja rule to unpack the apex and check that + // the required files are present. + builder := android.NewRuleBuilder(pctx, ctx) + command := builder.Command() + command. + Tool(android.PathForSource(ctx, "build/soong/scripts/unpack-prebuilt-apex.sh")). + BuiltTool("deapexer"). + BuiltTool("debugfs"). + Input(p.inputApex). + Text(deapexerOutput.String()) + for _, p := range exportedPaths { + command.Output(p.(android.WritablePath)) + } + builder.Build("deapexer", "deapex "+ctx.ModuleName()) + } +} diff --git a/apex/prebuilt.go b/apex/prebuilt.go index 50e892e8b..c72a9ebfc 100644 --- a/apex/prebuilt.go +++ b/apex/prebuilt.go @@ -158,6 +158,7 @@ func (p *ApexFileProperties) selectSource(ctx android.BottomUpMutatorContext) er type PrebuiltProperties struct { ApexFileProperties + DeapexerProperties Installable *bool // Optional name for the installed apex. If unspecified, name of the @@ -198,19 +199,152 @@ func (p *Prebuilt) Name() string { } // prebuilt_apex imports an `.apex` file into the build graph as if it was built with apex. +// +// If this needs to make files from within a `.apex` file available for use by other Soong modules, +// e.g. make dex implementation jars available for java_import modules isted in exported_java_libs, +// it does so as follows: +// +// 1. It creates a `deapexer` module that actually extracts the files from the `.apex` file and +// makes them available for use by other modules, at both Soong and ninja levels. +// +// 2. It adds a dependency onto those modules and creates an apex specific variant similar to what +// an `apex` module does. That ensures that code which looks for specific apex variant, e.g. +// dexpreopt, will work the same way from source and prebuilt. +// +// 3. The `deapexer` module adds a dependency from the modules that require the exported files onto +// itself so that they can retrieve the file paths to those files. +// func PrebuiltFactory() android.Module { module := &Prebuilt{} module.AddProperties(&module.properties) android.InitSingleSourcePrebuiltModule(module, &module.properties, "Source") android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon) + + android.AddLoadHook(module, func(ctx android.LoadHookContext) { + props := struct { + Name *string + }{ + Name: proptools.StringPtr(module.BaseModuleName() + ".deapexer"), + } + ctx.CreateModule(privateDeapexerFactory, + &props, + &module.properties.ApexFileProperties, + &module.properties.DeapexerProperties, + ) + }) + return module } +func prebuiltApexExportedModuleName(ctx android.BottomUpMutatorContext, name string) string { + // The prebuilt_apex should be depending on prebuilt modules but as this runs after + // prebuilt_rename the prebuilt module may or may not be using the prebuilt_ prefixed named. So, + // check to see if the prefixed name is in use first, if it is then use that, otherwise assume + // the unprefixed name is the one to use. If the unprefixed one turns out to be a source module + // and not a renamed prebuilt module then that will be detected and reported as an error when + // processing the dependency in ApexInfoMutator(). + prebuiltName := "prebuilt_" + name + if ctx.OtherModuleExists(prebuiltName) { + name = prebuiltName + } + return name +} + func (p *Prebuilt) DepsMutator(ctx android.BottomUpMutatorContext) { if err := p.properties.selectSource(ctx); err != nil { ctx.ModuleErrorf("%s", err) return } + + // Add dependencies onto the java modules that represent the java libraries that are provided by + // and exported from this prebuilt apex. + for _, lib := range p.properties.Exported_java_libs { + dep := prebuiltApexExportedModuleName(ctx, lib) + ctx.AddFarVariationDependencies(ctx.Config().AndroidCommonTarget.Variations(), javaLibTag, dep) + } +} + +var _ ApexInfoMutator = (*Prebuilt)(nil) + +// ApexInfoMutator marks any modules for which this apex exports a file as requiring an apex +// specific variant and checks that they are supported. +// +// The apexMutator will ensure that the ApexInfo objects passed to BuildForApex(ApexInfo) are +// associated with the apex specific variant using the ApexInfoProvider for later retrieval. +// +// Unlike the source apex module type the prebuilt_apex module type cannot share compatible variants +// across prebuilt_apex modules. That is because there is no way to determine whether two +// prebuilt_apex modules that export files for the same module are compatible. e.g. they could have +// been built from different source at different times or they could have been built with different +// build options that affect the libraries. +// +// While it may be possible to provide sufficient information to determine whether two prebuilt_apex +// modules were compatible it would be a lot of work and would not provide much benefit for a couple +// of reasons: +// * The number of prebuilt_apex modules that will be exporting files for the same module will be +// low as the prebuilt_apex only exports files for the direct dependencies that require it and +// very few modules are direct dependencies of multiple prebuilt_apex modules, e.g. there are a +// few com.android.art* apex files that contain the same contents and could export files for the +// same modules but only one of them needs to do so. Contrast that with source apex modules which +// need apex specific variants for every module that contributes code to the apex, whether direct +// or indirect. +// * The build cost of a prebuilt_apex variant is generally low as at worst it will involve some +// extra copying of files. Contrast that with source apex modules that has to build each variant +// from source. +func (p *Prebuilt) ApexInfoMutator(mctx android.TopDownMutatorContext) { + + // Collect direct dependencies into contents. + contents := make(map[string]android.ApexMembership) + + // Collect the list of dependencies. + var dependencies []android.ApexModule + mctx.VisitDirectDeps(func(m android.Module) { + tag := mctx.OtherModuleDependencyTag(m) + if tag == javaLibTag { + depName := mctx.OtherModuleName(m) + + // It is an error if the other module is not a prebuilt. + if _, ok := m.(android.PrebuiltInterface); !ok { + mctx.PropertyErrorf("exported_java_libs", "%q is not a prebuilt module", depName) + return + } + + // It is an error if the other module is not an ApexModule. + if _, ok := m.(android.ApexModule); !ok { + mctx.PropertyErrorf("exported_java_libs", "%q is not usable within an apex", depName) + return + } + + // Strip off the prebuilt_ prefix if present before storing content to ensure consistent + // behavior whether there is a corresponding source module present or not. + depName = android.RemoveOptionalPrebuiltPrefix(depName) + + // Remember that this module was added as a direct dependency. + contents[depName] = contents[depName].Add(true) + + // Add the module to the list of dependencies that need to have an APEX variant. + dependencies = append(dependencies, m.(android.ApexModule)) + } + }) + + // Create contents for the prebuilt_apex and store it away for later use. + apexContents := android.NewApexContents(contents) + mctx.SetProvider(ApexBundleInfoProvider, ApexBundleInfo{ + Contents: apexContents, + }) + + // Create an ApexInfo for the prebuilt_apex. + apexInfo := android.ApexInfo{ + ApexVariationName: mctx.ModuleName(), + InApexes: []string{mctx.ModuleName()}, + ApexContents: []*android.ApexContents{apexContents}, + ForPrebuiltApex: true, + } + + // Mark the dependencies of this module as requiring a variant for this module. + for _, am := range dependencies { + am.BuildForApex(apexInfo) + } } func (p *Prebuilt) GenerateAndroidBuildActions(ctx android.ModuleContext) { diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go index 062005b5b..80211fd7a 100644 --- a/java/dexpreopt_bootjars.go +++ b/java/dexpreopt_bootjars.go @@ -422,16 +422,19 @@ func (d *dexpreoptBootJars) GenerateBuildActions(ctx android.SingletonContext) { // Note that the same jar may occur in multiple modules. // This logic is tested in the apex package to avoid import cycle apex <-> java. func getBootImageJar(ctx android.SingletonContext, image *bootImageConfig, module android.Module) (int, android.Path) { - // Ignore any module that is not listed in the boot image configuration. name := ctx.ModuleName(module) + + // Strip a prebuilt_ prefix so that this can access the dex jar from a prebuilt module. + name = android.RemoveOptionalPrebuiltPrefix(name) + + // Ignore any module that is not listed in the boot image configuration. index := image.modules.IndexOfJar(name) if index == -1 { return -1, nil } - // It is an error if a module configured in the boot image does not support - // accessing the dex jar. This is safe because every module that has the same - // name has to have the same module type. + // It is an error if a module configured in the boot image does not support accessing the dex jar. + // This is safe because every module that has the same name has to have the same module type. jar, hasJar := module.(interface{ DexJarBuildPath() android.Path }) if !hasJar { ctx.Errorf("module %q configured in boot image %q does not support accessing dex jar", module, image.name) diff --git a/java/java.go b/java/java.go index 99bc55dd6..3c6146b76 100644 --- a/java/java.go +++ b/java/java.go @@ -2859,6 +2859,7 @@ func (j *Import) GenerateAndroidBuildActions(ctx android.ModuleContext) { j.classLoaderContexts = make(dexpreopt.ClassLoaderContextMap) var flags javaBuilderFlags + var deapexerModule android.Module ctx.VisitDirectDeps(func(module android.Module) { tag := ctx.OtherModuleDependencyTag(module) @@ -2879,6 +2880,11 @@ func (j *Import) GenerateAndroidBuildActions(ctx android.ModuleContext) { } addCLCFromDep(ctx, module, j.classLoaderContexts) + + // Save away the `deapexer` module on which this depends, if any. + if tag == android.DeapexerTag { + deapexerModule = module + } }) if Bool(j.properties.Installable) { @@ -2888,39 +2894,60 @@ func (j *Import) GenerateAndroidBuildActions(ctx android.ModuleContext) { j.exportAidlIncludeDirs = android.PathsForModuleSrc(ctx, j.properties.Aidl.Export_include_dirs) - if ctx.Device() && Bool(j.dexProperties.Compile_dex) { - sdkDep := decodeSdkDep(ctx, sdkContext(j)) - if sdkDep.invalidVersion { - ctx.AddMissingDependencies(sdkDep.bootclasspath) - ctx.AddMissingDependencies(sdkDep.java9Classpath) - } else if sdkDep.useFiles { - // sdkDep.jar is actually equivalent to turbine header.jar. - flags.classpath = append(flags.classpath, sdkDep.jars...) + if ctx.Device() { + // If this is a variant created for a prebuilt_apex then use the dex implementation jar + // obtained from the associated deapexer module. + ai := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo) + if ai.ForPrebuiltApex { + if deapexerModule == nil { + // This should never happen as a variant for a prebuilt_apex is only created if the + // deapxer module has been configured to export the dex implementation jar for this module. + ctx.ModuleErrorf("internal error: module %q does not depend on a `deapexer` module for prebuilt_apex %q", + j.Name(), ai.ApexVariationName) + } + + // Get the path of the dex implementation jar from the `deapexer` module. + di := ctx.OtherModuleProvider(deapexerModule, android.DeapexerProvider).(android.DeapexerInfo) + j.dexJarFile = di.PrebuiltExportPath(j.BaseModuleName(), ".dexjar") + if j.dexJarFile == nil { + // This should never happen as a variant for a prebuilt_apex is only created if the + // prebuilt_apex has been configured to export the java library dex file. + ctx.ModuleErrorf("internal error: no dex implementation jar available from prebuilt_apex %q", deapexerModule.Name()) + } + } else if Bool(j.dexProperties.Compile_dex) { + sdkDep := decodeSdkDep(ctx, sdkContext(j)) + if sdkDep.invalidVersion { + ctx.AddMissingDependencies(sdkDep.bootclasspath) + ctx.AddMissingDependencies(sdkDep.java9Classpath) + } else if sdkDep.useFiles { + // sdkDep.jar is actually equivalent to turbine header.jar. + flags.classpath = append(flags.classpath, sdkDep.jars...) + } + + // Dex compilation + + j.dexpreopter.installPath = android.PathForModuleInstall(ctx, "framework", jarName) + if j.dexProperties.Uncompress_dex == nil { + // If the value was not force-set by the user, use reasonable default based on the module. + j.dexProperties.Uncompress_dex = proptools.BoolPtr(shouldUncompressDex(ctx, &j.dexpreopter)) + } + j.dexpreopter.uncompressedDex = *j.dexProperties.Uncompress_dex + + var dexOutputFile android.ModuleOutPath + dexOutputFile = j.dexer.compileDex(ctx, flags, j.minSdkVersion(), outputFile, jarName) + if ctx.Failed() { + return + } + + configurationName := j.BaseModuleName() + primary := j.Prebuilt().UsePrebuilt() + + // Hidden API CSV generation and dex encoding + dexOutputFile = j.hiddenAPI.hiddenAPI(ctx, configurationName, primary, dexOutputFile, outputFile, + proptools.Bool(j.dexProperties.Uncompress_dex)) + + j.dexJarFile = dexOutputFile } - - // Dex compilation - - j.dexpreopter.installPath = android.PathForModuleInstall(ctx, "framework", jarName) - if j.dexProperties.Uncompress_dex == nil { - // If the value was not force-set by the user, use reasonable default based on the module. - j.dexProperties.Uncompress_dex = proptools.BoolPtr(shouldUncompressDex(ctx, &j.dexpreopter)) - } - j.dexpreopter.uncompressedDex = *j.dexProperties.Uncompress_dex - - var dexOutputFile android.ModuleOutPath - dexOutputFile = j.dexer.compileDex(ctx, flags, j.minSdkVersion(), outputFile, jarName) - if ctx.Failed() { - return - } - - configurationName := j.BaseModuleName() - primary := j.Prebuilt().UsePrebuilt() - - // Hidden API CSV generation and dex encoding - dexOutputFile = j.hiddenAPI.hiddenAPI(ctx, configurationName, primary, dexOutputFile, outputFile, - proptools.Bool(j.dexProperties.Uncompress_dex)) - - j.dexJarFile = dexOutputFile } } diff --git a/scripts/unpack-prebuilt-apex.sh b/scripts/unpack-prebuilt-apex.sh new file mode 100755 index 000000000..1acdeb56a --- /dev/null +++ b/scripts/unpack-prebuilt-apex.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +set -eu + +# 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. + +# Tool to unpack an apex file and verify that the required files were extracted. +if [ $# -lt 5 ]; then + echo "usage: $0 +" >&2 + exit 1 +fi + +DEAPEXER_PATH=$1 +DEBUGFS_PATH=$2 +APEX_FILE=$3 +OUTPUT_DIR=$4 +shift 4 +REQUIRED_PATHS=$@ + +set -x 1 + +rm -fr $OUTPUT_DIR +mkdir -p $OUTPUT_DIR + +# Unpack the apex file contents. +$DEAPEXER_PATH --debugfs_path $DEBUGFS_PATH extract $APEX_FILE $OUTPUT_DIR + +# Verify that the files that the build expects to be in the .apex file actually +# exist, and make sure they have a fresh mtime to not confuse ninja. +typeset -i FAILED=0 +for r in $REQUIRED_PATHS; do + if [ ! -f $r ]; then + echo "Required file $r not present in apex $APEX_FILE" >&2 + FAILED=$FAILED+1 + else + # TODO(http:/b/177646343) - deapexer extracts the files with a timestamp of 1 Jan 1970. + # touch the file so that ninja knows it has changed. + touch $r + fi +done + +if [ $FAILED -gt 0 ]; then + echo "$FAILED required files were missing from $APEX_FILE" >&2 + echo "Available files are:" >&2 + find $OUTPUT_DIR -type f | sed "s|^| |" >&2 + exit 1 +fi