Merge "Improve aquery-related validation and error handling"
diff --git a/android/Android.bp b/android/Android.bp
index efa70a9..eabb137 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -21,6 +21,7 @@
"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 47d14cc..31c62e9 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -64,6 +64,14 @@
// 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 @@
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 @@
// 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 512b50f..1f786e6 100644
--- a/android/apex_test.go
+++ b/android/apex_test.go
@@ -20,6 +20,10 @@
)
func Test_mergeApexVariations(t *testing.T) {
+ const (
+ ForPrebuiltApex = true
+ NotForPrebuiltApex = false
+ )
tests := []struct {
name string
in []ApexInfo
@@ -29,10 +33,10 @@
{
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 @@
{
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 @@
{
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 @@
{
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 @@
{
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 0000000..63508d7
--- /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 <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{}
diff --git a/android/sdk.go b/android/sdk.go
index f2cdc88..bab0ed8 100644
--- a/android/sdk.go
+++ b/android/sdk.go
@@ -177,6 +177,12 @@
// to the zip
CopyToSnapshot(src Path, dest string)
+ // Return the path to an empty file.
+ //
+ // This can be used by sdk member types that need to create an empty file in the snapshot, simply
+ // pass the value returned from this to the CopyToSnapshot() method.
+ EmptyFile() Path
+
// Unzip the supplied zip into the snapshot relative directory destDir.
UnzipToSnapshot(zipPath Path, destDir string)
diff --git a/android/testing.go b/android/testing.go
index a66b1e1..470cfd6 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -469,7 +469,14 @@
//
// 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/") {
+ return p
+ }
if w, ok := path.(WritablePath); ok {
rel, err := filepath.Rel(w.buildDir(), p)
if err != nil {
diff --git a/apex/Android.bp b/apex/Android.bp
index e3a547c..77dde72 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -18,6 +18,7 @@
"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 5cd18ed..376811a 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -1501,7 +1501,7 @@
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 fc74672..58739b0 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -190,6 +190,7 @@
"testdata/baz": nil,
"AppSet.apks": nil,
"foo.rs": nil,
+ "libfoo.jar": nil,
}
cc.GatherRequiredFilesForTest(fs)
@@ -4219,6 +4220,121 @@
}
}
+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 @@
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 @@
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 @@
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 @@
}
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 0000000..651cadf
--- /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 50e892e..c72a9eb 100644
--- a/apex/prebuilt.go
+++ b/apex/prebuilt.go
@@ -158,6 +158,7 @@
type PrebuiltProperties struct {
ApexFileProperties
+ DeapexerProperties
Installable *bool
// Optional name for the installed apex. If unspecified, name of the
@@ -198,19 +199,152 @@
}
// 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/cc/config/tidy.go b/cc/config/tidy.go
index 4ac9e58..b3a86f3 100644
--- a/cc/config/tidy.go
+++ b/cc/config/tidy.go
@@ -20,24 +20,48 @@
)
func init() {
- // Most Android source files are not clang-tidy clean yet.
- // Global tidy checks include only google*, performance*,
- // and misc-macro-parentheses, but not google-readability*
- // or google-runtime-references.
+ // Many clang-tidy checks like altera-*, llvm-*, modernize-*
+ // are not designed for Android source code or creating too
+ // many (false-positive) warnings. The global default tidy checks
+ // should include only tested groups and exclude known noisy checks.
+ // See https://clang.llvm.org/extra/clang-tidy/checks/list.html
pctx.VariableFunc("TidyDefaultGlobalChecks", func(ctx android.PackageVarContext) string {
if override := ctx.Config().Getenv("DEFAULT_GLOBAL_TIDY_CHECKS"); override != "" {
return override
}
return strings.Join([]string{
"-*",
- "bugprone*",
+ "abseil-*",
+ "android-*",
+ "bugprone-*",
+ "cert-*",
+ // clang-analyzer-* check is slow and enables clang-diagnostic-padded,
+ // which cannot be suppressed yet.
+ // "clang-analyzer-*",
"clang-diagnostic-unused-command-line-argument",
- "google*",
- "misc-macro-parentheses",
- "performance*",
+ "google-*",
+ "misc-*",
+ "performance-*",
+ "portability-*",
"-bugprone-narrowing-conversions",
"-google-readability*",
"-google-runtime-references",
+ "-misc-no-recursion",
+ "-misc-non-private-member-variables-in-classes",
+ "-misc-unused-parameters",
+ // the following groups are excluded by -*
+ // -altera-*
+ // -cppcoreguidelines-*
+ // -darwin-*
+ // -fuchsia-*
+ // -hicpp-*
+ // -llvm-*
+ // -llvmlibc-*
+ // -modernize-*
+ // -mpi-*
+ // -objc-*
+ // -readability-*
+ // -zircon-*
}, ",")
})
diff --git a/cc/tidy.go b/cc/tidy.go
index 17471e6..972ad7b 100644
--- a/cc/tidy.go
+++ b/cc/tidy.go
@@ -15,6 +15,7 @@
package cc
import (
+ "regexp"
"strings"
"github.com/google/blueprint/proptools"
@@ -130,7 +131,31 @@
tidyChecks = tidyChecks + ",-bugprone-branch-clone"
flags.TidyFlags = append(flags.TidyFlags, tidyChecks)
- if len(tidy.Properties.Tidy_checks_as_errors) > 0 {
+ if ctx.Config().IsEnvTrue("WITH_TIDY") {
+ // WITH_TIDY=1 enables clang-tidy globally. There could be many unexpected
+ // warnings from new checks and many local tidy_checks_as_errors and
+ // -warnings-as-errors can break a global build.
+ // So allow all clang-tidy warnings.
+ inserted := false
+ for i, s := range flags.TidyFlags {
+ if strings.Contains(s, "-warnings-as-errors=") {
+ // clang-tidy accepts only one -warnings-as-errors
+ // replace the old one
+ re := regexp.MustCompile(`'?-?-warnings-as-errors=[^ ]* *`)
+ newFlag := re.ReplaceAllString(s, "")
+ if newFlag == "" {
+ flags.TidyFlags[i] = "-warnings-as-errors=-*"
+ } else {
+ flags.TidyFlags[i] = newFlag + " -warnings-as-errors=-*"
+ }
+ inserted = true
+ break
+ }
+ }
+ if !inserted {
+ flags.TidyFlags = append(flags.TidyFlags, "-warnings-as-errors=-*")
+ }
+ } else if len(tidy.Properties.Tidy_checks_as_errors) > 0 {
tidyChecksAsErrors := "-warnings-as-errors=" + strings.Join(esc(tidy.Properties.Tidy_checks_as_errors), ",")
flags.TidyFlags = append(flags.TidyFlags, tidyChecksAsErrors)
}
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index 907bed3..1b7ac55 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -57,7 +57,7 @@
return configuration.IsEnvTrue("CONVERT_TO_BAZEL")
}
-func newContext(srcDir string, configuration android.Config) *android.Context {
+func newContext(configuration android.Config) *android.Context {
ctx := android.NewContext(configuration)
if bazelConversionRequested(configuration) {
// Register an alternate set of singletons and mutators for bazel
@@ -106,7 +106,7 @@
// needed to correctly evaluate the tree in the second pass.
// TODO(cparsons): Don't output any ninja file, as the second pass will overwrite
// the incorrect results from the first pass, and file I/O is expensive.
- firstCtx := newContext(srcDir, configuration)
+ firstCtx := newContext(configuration)
configuration.SetStopBefore(bootstrap.StopBeforeWriteNinja)
bootstrap.Main(firstCtx.Context, configuration, extraNinjaDeps...)
// Invoke bazel commands and save results for second pass.
@@ -120,10 +120,10 @@
fmt.Fprintf(os.Stderr, "%s", err)
os.Exit(1)
}
- ctx = newContext(srcDir, secondPassConfig)
+ ctx = newContext(secondPassConfig)
bootstrap.Main(ctx.Context, secondPassConfig, extraNinjaDeps...)
} else {
- ctx = newContext(srcDir, configuration)
+ ctx = newContext(configuration)
bootstrap.Main(ctx.Context, configuration, extraNinjaDeps...)
}
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index 062005b..80211fd 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -422,16 +422,19 @@
// 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 f684a00..3c6146b 100644
--- a/java/java.go
+++ b/java/java.go
@@ -40,20 +40,55 @@
// Register sdk member types.
android.RegisterSdkMemberType(javaHeaderLibsSdkMemberType)
+ // Register java implementation libraries for use only in module_exports (not sdk).
android.RegisterSdkMemberType(&librarySdkMemberType{
android.SdkMemberTypeBase{
PropertyName: "java_libs",
},
- func(j *Library) android.Path {
+ func(_ android.SdkMemberContext, j *Library) android.Path {
implementationJars := j.ImplementationAndResourcesJars()
if len(implementationJars) != 1 {
panic(fmt.Errorf("there must be only one implementation jar from %q", j.Name()))
}
-
return implementationJars[0]
},
+ sdkSnapshotFilePathForJar,
+ copyEverythingToSnapshot,
})
+ // Register java boot libraries for use in sdk.
+ //
+ // The build has some implicit dependencies (via the boot jars configuration) on a number of
+ // modules, e.g. core-oj, apache-xml, that are part of the java boot class path and which are
+ // provided by mainline modules (e.g. art, conscrypt, runtime-i18n) but which are not otherwise
+ // used outside those mainline modules.
+ //
+ // As they are not needed outside the mainline modules adding them to the sdk/module-exports as
+ // either java_libs, or java_header_libs would end up exporting more information than was strictly
+ // necessary. The java_boot_libs property to allow those modules to be exported as part of the
+ // sdk/module_exports without exposing any unnecessary information.
+ android.RegisterSdkMemberType(&librarySdkMemberType{
+ android.SdkMemberTypeBase{
+ PropertyName: "java_boot_libs",
+ SupportsSdk: true,
+ },
+ func(ctx android.SdkMemberContext, j *Library) android.Path {
+ // Java boot libs are only provided in the SDK to provide access to their dex implementation
+ // jar for use by dexpreopting and boot jars package check. They do not need to provide an
+ // actual implementation jar but the java_import will need a file that exists so just copy an
+ // empty file. Any attempt to use that file as a jar will cause a build error.
+ return ctx.SnapshotBuilder().EmptyFile()
+ },
+ func(osPrefix, name string) string {
+ // Create a special name for the implementation jar to try and provide some useful information
+ // to a developer that attempts to compile against this.
+ // TODO(b/175714559): Provide a proper error message in Soong not ninja.
+ return filepath.Join(osPrefix, "java_boot_libs", "snapshot", "jars", "are", "invalid", name+jarFileSuffix)
+ },
+ onlyCopyJarToSnapshot,
+ })
+
+ // Register java test libraries for use only in module_exports (not sdk).
android.RegisterSdkMemberType(&testSdkMemberType{
SdkMemberTypeBase: android.SdkMemberTypeBase{
PropertyName: "java_tests",
@@ -2165,9 +2200,22 @@
// Function to retrieve the appropriate output jar (implementation or header) from
// the library.
- jarToExportGetter func(j *Library) android.Path
+ jarToExportGetter func(ctx android.SdkMemberContext, j *Library) android.Path
+
+ // Function to compute the snapshot relative path to which the named library's
+ // jar should be copied.
+ snapshotPathGetter func(osPrefix, name string) string
+
+ // True if only the jar should be copied to the snapshot, false if the jar plus any additional
+ // files like aidl files should also be copied.
+ onlyCopyJarToSnapshot bool
}
+const (
+ onlyCopyJarToSnapshot = true
+ copyEverythingToSnapshot = false
+)
+
func (mt *librarySdkMemberType) AddDependencies(mctx android.BottomUpMutatorContext, dependencyTag blueprint.DependencyTag, names []string) {
mctx.AddVariationDependencies(nil, dependencyTag, names...)
}
@@ -2195,21 +2243,32 @@
func (p *librarySdkMemberProperties) PopulateFromVariant(ctx android.SdkMemberContext, variant android.Module) {
j := variant.(*Library)
- p.JarToExport = ctx.MemberType().(*librarySdkMemberType).jarToExportGetter(j)
+ p.JarToExport = ctx.MemberType().(*librarySdkMemberType).jarToExportGetter(ctx, j)
+
p.AidlIncludeDirs = j.AidlIncludeDirs()
}
func (p *librarySdkMemberProperties) AddToPropertySet(ctx android.SdkMemberContext, propertySet android.BpPropertySet) {
builder := ctx.SnapshotBuilder()
+ memberType := ctx.MemberType().(*librarySdkMemberType)
+
exportedJar := p.JarToExport
if exportedJar != nil {
- snapshotRelativeJavaLibPath := sdkSnapshotFilePathForJar(p.OsPrefix(), ctx.Name())
+ // Delegate the creation of the snapshot relative path to the member type.
+ snapshotRelativeJavaLibPath := memberType.snapshotPathGetter(p.OsPrefix(), ctx.Name())
+
+ // Copy the exported jar to the snapshot.
builder.CopyToSnapshot(exportedJar, snapshotRelativeJavaLibPath)
propertySet.AddProperty("jars", []string{snapshotRelativeJavaLibPath})
}
+ // Do not copy anything else to the snapshot.
+ if memberType.onlyCopyJarToSnapshot {
+ return
+ }
+
aidlIncludeDirs := p.AidlIncludeDirs
if len(aidlIncludeDirs) != 0 {
sdkModuleContext := ctx.SdkModuleContext()
@@ -2230,7 +2289,7 @@
PropertyName: "java_header_libs",
SupportsSdk: true,
},
- func(j *Library) android.Path {
+ func(_ android.SdkMemberContext, j *Library) android.Path {
headerJars := j.HeaderJars()
if len(headerJars) != 1 {
panic(fmt.Errorf("there must be only one header jar from %q", j.Name()))
@@ -2238,6 +2297,8 @@
return headerJars[0]
},
+ sdkSnapshotFilePathForJar,
+ copyEverythingToSnapshot,
}
// java_library builds and links sources into a `.jar` file for the device, and possibly for the host as well.
@@ -2798,6 +2859,7 @@
j.classLoaderContexts = make(dexpreopt.ClassLoaderContextMap)
var flags javaBuilderFlags
+ var deapexerModule android.Module
ctx.VisitDirectDeps(func(module android.Module) {
tag := ctx.OtherModuleDependencyTag(module)
@@ -2818,6 +2880,11 @@
}
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) {
@@ -2827,39 +2894,60 @@
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 0000000..1acdeb5
--- /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 <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
diff --git a/sdk/java_sdk_test.go b/sdk/java_sdk_test.go
index d989c5b..488afd8 100644
--- a/sdk/java_sdk_test.go
+++ b/sdk/java_sdk_test.go
@@ -472,6 +472,61 @@
)
}
+func TestSnapshotWithJavaBootLibrary(t *testing.T) {
+ result := testSdkWithJava(t, `
+ module_exports {
+ name: "myexports",
+ java_boot_libs: ["myjavalib"],
+ }
+
+ java_library {
+ name: "myjavalib",
+ srcs: ["Test.java"],
+ java_resources: ["resource.txt"],
+ // The aidl files should not be copied to the snapshot because a java_boot_libs member is not
+ // intended to be used for compiling Java, only for accessing the dex implementation jar.
+ aidl: {
+ export_include_dirs: ["aidl"],
+ },
+ system_modules: "none",
+ sdk_version: "none",
+ compile_dex: true,
+ }
+ `)
+
+ result.CheckSnapshot("myexports", "",
+ checkAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+java_import {
+ name: "myexports_myjavalib@current",
+ sdk_member_name: "myjavalib",
+ visibility: ["//visibility:public"],
+ apex_available: ["//apex_available:platform"],
+ jars: ["java_boot_libs/snapshot/jars/are/invalid/myjavalib.jar"],
+}
+
+java_import {
+ name: "myjavalib",
+ prefer: false,
+ visibility: ["//visibility:public"],
+ apex_available: ["//apex_available:platform"],
+ jars: ["java_boot_libs/snapshot/jars/are/invalid/myjavalib.jar"],
+}
+
+module_exports_snapshot {
+ name: "myexports@current",
+ visibility: ["//visibility:public"],
+ java_boot_libs: ["myexports_myjavalib@current"],
+}
+
+`),
+ checkAllCopyRules(`
+.intermediates/myexports/common_os/empty -> java_boot_libs/snapshot/jars/are/invalid/myjavalib.jar
+`),
+ )
+}
+
func TestHostSnapshotWithJavaImplLibrary(t *testing.T) {
result := testSdkWithJava(t, `
module_exports {
diff --git a/sdk/update.go b/sdk/update.go
index 377aaae..b5bc9f4 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -653,6 +653,9 @@
filesToZip android.Paths
zipsToMerge android.Paths
+ // The path to an empty file.
+ emptyFile android.WritablePath
+
prebuiltModules map[string]*bpModule
prebuiltOrder []*bpModule
@@ -703,6 +706,19 @@
s.zipsToMerge = append(s.zipsToMerge, tmpZipPath)
}
+func (s *snapshotBuilder) EmptyFile() android.Path {
+ if s.emptyFile == nil {
+ ctx := s.ctx
+ s.emptyFile = android.PathForModuleOut(ctx, "empty")
+ s.ctx.Build(pctx, android.BuildParams{
+ Rule: android.Touch,
+ Output: s.emptyFile,
+ })
+ }
+
+ return s.emptyFile
+}
+
func (s *snapshotBuilder) AddPrebuiltModule(member android.SdkMember, moduleType string) android.BpModule {
name := member.Name()
if s.prebuiltModules[name] != nil {
diff --git a/sysprop/sysprop_library.go b/sysprop/sysprop_library.go
index 38306ad..2ccce15 100644
--- a/sysprop/sysprop_library.go
+++ b/sysprop/sysprop_library.go
@@ -19,6 +19,7 @@
import (
"fmt"
"io"
+ "os"
"path"
"sync"
@@ -125,8 +126,8 @@
properties syspropLibraryProperties
checkApiFileTimeStamp android.WritablePath
- latestApiFile android.Path
- currentApiFile android.Path
+ latestApiFile android.OptionalPath
+ currentApiFile android.OptionalPath
dumpedApiFile android.WritablePath
}
@@ -224,7 +225,7 @@
return proptools.Bool(m.properties.Public_stub)
}
-func (m *syspropLibrary) CurrentSyspropApiFile() android.Path {
+func (m *syspropLibrary) CurrentSyspropApiFile() android.OptionalPath {
return m.currentApiFile
}
@@ -243,8 +244,11 @@
return
}
- m.currentApiFile = android.PathForSource(ctx, ctx.ModuleDir(), "api", baseModuleName+"-current.txt")
- m.latestApiFile = android.PathForSource(ctx, ctx.ModuleDir(), "api", baseModuleName+"-latest.txt")
+ apiDirectoryPath := path.Join(ctx.ModuleDir(), "api")
+ currentApiFilePath := path.Join(apiDirectoryPath, baseModuleName+"-current.txt")
+ latestApiFilePath := path.Join(apiDirectoryPath, baseModuleName+"-latest.txt")
+ m.currentApiFile = android.ExistentPathForSource(ctx, currentApiFilePath)
+ m.latestApiFile = android.ExistentPathForSource(ctx, latestApiFilePath)
// dump API rule
rule := android.NewRuleBuilder(pctx, ctx)
@@ -258,19 +262,37 @@
// check API rule
rule = android.NewRuleBuilder(pctx, ctx)
+ // We allow that the API txt files don't exist, when the sysprop_library only contains internal
+ // properties. But we have to feed current api file and latest api file to the rule builder.
+ // Currently we can't get android.Path representing the null device, so we add any existing API
+ // txt files to implicits, and then directly feed string paths, rather than calling Input(Path)
+ // method.
+ var apiFileList android.Paths
+ currentApiArgument := os.DevNull
+ if m.currentApiFile.Valid() {
+ apiFileList = append(apiFileList, m.currentApiFile.Path())
+ currentApiArgument = m.currentApiFile.String()
+ }
+
+ latestApiArgument := os.DevNull
+ if m.latestApiFile.Valid() {
+ apiFileList = append(apiFileList, m.latestApiFile.Path())
+ latestApiArgument = m.latestApiFile.String()
+ }
+
// 1. compares current.txt to api-dump.txt
// current.txt should be identical to api-dump.txt.
msg := fmt.Sprintf(`\n******************************\n`+
`API of sysprop_library %s doesn't match with current.txt\n`+
`Please update current.txt by:\n`+
- `m %s-dump-api && rm -rf %q && cp -f %q %q\n`+
+ `m %s-dump-api && mkdir -p %q && rm -rf %q && cp -f %q %q\n`+
`******************************\n`, baseModuleName, baseModuleName,
- m.currentApiFile.String(), m.dumpedApiFile.String(), m.currentApiFile.String())
+ apiDirectoryPath, currentApiFilePath, m.dumpedApiFile.String(), currentApiFilePath)
rule.Command().
Text("( cmp").Flag("-s").
Input(m.dumpedApiFile).
- Input(m.currentApiFile).
+ Text(currentApiArgument).
Text("|| ( echo").Flag("-e").
Flag(`"` + msg + `"`).
Text("; exit 38) )")
@@ -285,11 +307,12 @@
rule.Command().
Text("( ").
BuiltTool("sysprop_api_checker").
- Input(m.latestApiFile).
- Input(m.currentApiFile).
+ Text(latestApiArgument).
+ Text(currentApiArgument).
Text(" || ( echo").Flag("-e").
Flag(`"` + msg + `"`).
- Text("; exit 38) )")
+ Text("; exit 38) )").
+ Implicits(apiFileList)
m.checkApiFileTimeStamp = android.PathForModuleOut(ctx, "check_api.timestamp")
@@ -393,31 +416,6 @@
ctx.PropertyErrorf("srcs", "sysprop_library must specify srcs")
}
- missingApi := false
-
- for _, txt := range []string{"-current.txt", "-latest.txt"} {
- path := path.Join(ctx.ModuleDir(), "api", m.BaseModuleName()+txt)
- file := android.ExistentPathForSource(ctx, path)
- if !file.Valid() {
- ctx.ModuleErrorf("API file %#v doesn't exist", path)
- missingApi = true
- }
- }
-
- if missingApi {
- script := "build/soong/scripts/gen-sysprop-api-files.sh"
- p := android.ExistentPathForSource(ctx, script)
-
- if !p.Valid() {
- panic(fmt.Sprintf("script file %s doesn't exist", script))
- }
-
- ctx.ModuleErrorf("One or more api files are missing. "+
- "You can create them by:\n"+
- "%s %q %q", script, ctx.ModuleDir(), m.BaseModuleName())
- return
- }
-
// ctx's Platform or Specific functions represent where this sysprop_library installed.
installedInSystem := ctx.Platform() || ctx.SystemExtSpecific()
installedInVendorOrOdm := ctx.SocSpecific() || ctx.DeviceSpecific()