Export dex implementation jars from prebuilt_apex

Dexpreopt and boot jars package check all require access to dex
implementation jars created for java_library and java_sdk_library. They
were available when building from source but not when building from
prebuilts, even though they are embedded within the .apex files that
are referenced from prebuilt_apex.

This changes adds support to prebuilt_apex to export the dex
implementation jars and updates java_import to use those exported dex
implementation jars.

In a source build dexpreopt/boot jars package check access the apex (or
platform) specific variant of a java_library, e.g. core-oj, from which
it retrieves the dex implementation jar path.

After this change in a prebuilt build dexpreopt/boot jars package check
behave in the same way except in this case they retrieve the dex
implementation jar path from the apex (or platform) specific variant of
the java_import, e.g. core-oj.

The work to export files from a `.apex` file for use by other modules
is performed by a new `deapexer` module type. It is not used directly
in an `Android.bp` file but instead is created implicitly by
`prebuilt_apex`,

In order to do that this contains the following changes:
* Adds a new `dexapexer` module type to handle the exporting of files
  from the `.apex` file.
* Adds an exported_java_libs property to prebuilt_apex to specify the
  set of libraries whose dex implementation jars need exporting.
* Creates apex specific variants of the libraries listed in the
  exported_java_libs property.
* Adds the set of exported files to the ApexInfo to make them available
  to the apex specific variants.
* Prevents the prebuilt_apex variants from being merged together as
  they will not be compatible.
* Modifies java_import to use the exported file for variants of a
  prebuilt_apex.
* Adds a ninja rule to unpack (using deapexer) the contents of the
  prebuilt_apex's apex file, verify that the required files are present
  and make them available as outputs for other rules to use.
* Some minor refactorings to support these changes.
* Adds tests to cover prebuilt only, prebuilt with source preferred,
  and prebuilt preferred with source.

Test: m nothing
Bug: 171061220
Change-Id: Ic9bed81fb65b92f0d59f64c0bce168a9ed44cfac
This commit is contained in:
Paul Duffin 2020-11-02 17:32:38 +00:00
parent db170e4a92
commit 064b70c918
13 changed files with 719 additions and 56 deletions

View file

@ -21,6 +21,7 @@ bootstrap_go_package {
"bazel_handler.go",
"config.go",
"csuite_config.go",
"deapexer.go",
"defaults.go",
"defs.go",
"depset_generic.go",

View file

@ -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,

View file

@ -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)

83
android/deapexer.go Normal file
View file

@ -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 <module>{<tag>} where <tag> 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{}

View file

@ -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 "<nil path>"
}
p := path.String()
// Allow absolute paths to /dev/
if strings.HasPrefix(p, "/dev/") {

View file

@ -18,6 +18,7 @@ bootstrap_go_package {
"apex.go",
"apex_singleton.go",
"builder.go",
"deapexer.go",
"key.go",
"prebuilt.go",
"vndk.go",

View file

@ -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.

View file

@ -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) {

139
apex/deapexer.go Normal file
View file

@ -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())
}
}

View file

@ -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) {

View file

@ -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)

View file

@ -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
}
}

59
scripts/unpack-prebuilt-apex.sh Executable file
View file

@ -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 <deapaxer_path> <debugfs_path> <apex file> <output_dir> <required_files>+" >&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