diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-03-15 18:57:52 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-03-15 18:57:52 +0000 |
commit | f27da9d76c5091aeef76cecef11eb585ea40eab9 (patch) | |
tree | a685f22f635368151794a610ad7dcdf1ecf5cb92 | |
parent | 25af3923bd87276c96de3aa9237ed683e15af95d (diff) | |
parent | 93457734287e785474d21ba07ba6cade3464cb04 (diff) | |
download | soong-android12-mainline-sdkext-release.tar.gz |
Snap for 8303596 from 93457734287e785474d21ba07ba6cade3464cb04 to mainline-sdkext-releaseandroid-mainline-12.0.0_r109aml_sdk_311710000android12-mainline-sdkext-release
Change-Id: I2bae19c774c7702145b6c667aac3fecb00e7ae07
-rw-r--r-- | android/module.go | 11 | ||||
-rw-r--r-- | apex/apex.go | 13 | ||||
-rw-r--r-- | apex/apex_test.go | 9 | ||||
-rw-r--r-- | bpf/bpf.go | 14 | ||||
-rw-r--r-- | java/bootclasspath_fragment.go | 72 | ||||
-rw-r--r-- | java/dex.go | 39 | ||||
-rw-r--r-- | java/sdk_library.go | 8 | ||||
-rw-r--r-- | java/sdk_library_test.go | 9 | ||||
-rw-r--r-- | java/systemserver_classpath_fragment.go | 26 | ||||
-rw-r--r-- | java/systemserver_classpath_fragment_test.go | 2 | ||||
-rw-r--r-- | rust/rust.go | 10 | ||||
-rw-r--r-- | sdk/Android.bp | 2 | ||||
-rw-r--r-- | sdk/build_release.go | 388 | ||||
-rw-r--r-- | sdk/build_release_test.go | 227 | ||||
-rw-r--r-- | sdk/java_sdk_test.go | 52 | ||||
-rw-r--r-- | sdk/sdk.go | 8 | ||||
-rw-r--r-- | sdk/sdk_test.go | 86 | ||||
-rw-r--r-- | sdk/testing.go | 11 | ||||
-rw-r--r-- | sdk/update.go | 206 | ||||
-rw-r--r-- | ui/build/dumpvars.go | 1 |
20 files changed, 1136 insertions, 58 deletions
diff --git a/android/module.go b/android/module.go index f745a4ab4..786f79d57 100644 --- a/android/module.go +++ b/android/module.go @@ -15,7 +15,6 @@ package android import ( - "android/soong/bazel" "fmt" "os" "path" @@ -24,6 +23,8 @@ import ( "strings" "text/scanner" + "android/soong/bazel" + "github.com/google/blueprint" "github.com/google/blueprint/proptools" ) @@ -452,6 +453,14 @@ type Module interface { Enabled() bool Target() Target MultiTargets() []Target + + // ImageVariation returns the image variation of this module. + // + // The returned structure has its Mutator field set to "image" and its Variation field set to the + // image variation, e.g. recovery, ramdisk, etc.. The Variation field is "" for host modules and + // device modules that have no image variation. + ImageVariation() blueprint.Variation + Owner() string InstallInData() bool InstallInTestcases() bool diff --git a/apex/apex.go b/apex/apex.go index f92e75d3f..e1bc030c5 100644 --- a/apex/apex.go +++ b/apex/apex.go @@ -1572,8 +1572,8 @@ func apexFileForRuntimeResourceOverlay(ctx android.BaseModuleContext, rro java.R return af } -func apexFileForBpfProgram(ctx android.BaseModuleContext, builtFile android.Path, bpfProgram bpf.BpfModule) apexFile { - dirInApex := filepath.Join("etc", "bpf") +func apexFileForBpfProgram(ctx android.BaseModuleContext, builtFile android.Path, apex_sub_dir string, bpfProgram bpf.BpfModule) apexFile { + dirInApex := filepath.Join("etc", "bpf", apex_sub_dir) return newApexFile(ctx, builtFile, builtFile.Base(), dirInApex, etc, bpfProgram) } @@ -1772,8 +1772,9 @@ func (a *apexBundle) GenerateAndroidBuildActions(ctx android.ModuleContext) { case bpfTag: if bpfProgram, ok := child.(bpf.BpfModule); ok { filesToCopy, _ := bpfProgram.OutputFiles("") + apex_sub_dir := bpfProgram.SubDir() for _, bpfFile := range filesToCopy { - filesInfo = append(filesInfo, apexFileForBpfProgram(ctx, bpfFile, bpfProgram)) + filesInfo = append(filesInfo, apexFileForBpfProgram(ctx, bpfFile, apex_sub_dir, bpfProgram)) } } else { ctx.PropertyErrorf("bpfs", "%q is not a bpf module", depName) @@ -2347,6 +2348,12 @@ func (a *apexBundle) checkUpdatable(ctx android.ModuleContext) { func (a *apexBundle) checkClasspathFragments(ctx android.ModuleContext) { ctx.VisitDirectDeps(func(module android.Module) { if tag := ctx.OtherModuleDependencyTag(module); tag == bcpfTag || tag == sscpfTag { + if tag == sscpfTag { + sscpf := module.(*java.SystemServerClasspathModule) + if sscpf.ShouldIgnore() { + return + } + } info := ctx.OtherModuleProvider(module, java.ClasspathFragmentProtoContentInfoProvider).(java.ClasspathFragmentProtoContentInfo) if !info.ClasspathFragmentProtoGenerated { ctx.OtherModuleErrorf(module, "is included in updatable apex %v, it must not set generate_classpaths_proto to false", ctx.ModuleName()) diff --git a/apex/apex_test.go b/apex/apex_test.go index 663a9a0b4..f3c3b44c5 100644 --- a/apex/apex_test.go +++ b/apex/apex_test.go @@ -622,7 +622,7 @@ func TestDefaults(t *testing.T) { java_libs: ["myjar"], apps: ["AppFoo"], rros: ["rro"], - bpfs: ["bpf"], + bpfs: ["bpf", "netd_test"], updatable: false, } @@ -675,6 +675,12 @@ func TestDefaults(t *testing.T) { srcs: ["bpf.c", "bpf2.c"], } + bpf { + name: "netd_test", + srcs: ["netd_test.c"], + sub_dir: "netd", + } + `) ensureExactContents(t, ctx, "myapex", "android_common_myapex_image", []string{ "etc/myetc", @@ -684,6 +690,7 @@ func TestDefaults(t *testing.T) { "overlay/blue/rro.apk", "etc/bpf/bpf.o", "etc/bpf/bpf2.o", + "etc/bpf/netd/netd_test.o", }) } diff --git a/bpf/bpf.go b/bpf/bpf.go index 9f0c86ce2..187b4db61 100644 --- a/bpf/bpf.go +++ b/bpf/bpf.go @@ -54,12 +54,16 @@ type BpfModule interface { android.Module OutputFiles(tag string) (android.Paths, error) + + // Returns the sub install directory if the bpf module is included by apex. + SubDir() string } type BpfProperties struct { Srcs []string `android:"path"` Cflags []string Include_dirs []string + Sub_dir string } type bpf struct { @@ -121,6 +125,10 @@ func (bpf *bpf) AndroidMk() android.AndroidMkData { fmt.Fprintln(w) fmt.Fprintln(w, "LOCAL_PATH :=", moduleDir) fmt.Fprintln(w) + localModulePath := "LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/bpf" + if len(bpf.properties.Sub_dir) > 0 { + localModulePath += "/" + bpf.properties.Sub_dir + } for _, obj := range bpf.objs { objName := name + "_" + obj.Base() names = append(names, objName) @@ -130,7 +138,7 @@ func (bpf *bpf) AndroidMk() android.AndroidMkData { fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", obj.String()) fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", obj.Base()) fmt.Fprintln(w, "LOCAL_MODULE_CLASS := ETC") - fmt.Fprintln(w, "LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/bpf") + fmt.Fprintln(w, localModulePath) fmt.Fprintln(w, "include $(BUILD_PREBUILT)") fmt.Fprintln(w) } @@ -154,6 +162,10 @@ func (bpf *bpf) OutputFiles(tag string) (android.Paths, error) { } } +func (bpf *bpf) SubDir() string { + return bpf.properties.Sub_dir +} + var _ android.OutputFileProducer = (*bpf)(nil) func BpfFactory() android.Module { diff --git a/java/bootclasspath_fragment.go b/java/bootclasspath_fragment.go index 750ed852f..7b4ddc573 100644 --- a/java/bootclasspath_fragment.go +++ b/java/bootclasspath_fragment.go @@ -139,6 +139,74 @@ type bootclasspathFragmentProperties struct { BootclasspathFragmentsDepsProperties } +type SourceOnlyBootclasspathProperties struct { + Hidden_api struct { + // Contains prefixes of a package hierarchy that is provided solely by this + // bootclasspath_fragment. + // + // This affects the signature patterns file that is used to select the subset of monolithic + // hidden API flags. See split_packages property for more details. + Package_prefixes []string + + // The list of split packages provided by this bootclasspath_fragment. + // + // A split package is one that contains classes which are provided by multiple + // bootclasspath_fragment modules. + // + // This defaults to "*" - which treats all packages as being split. A module that has no split + // packages must specify an empty list. + // + // This affects the signature patterns file that is generated by a bootclasspath_fragment and + // used to select the subset of monolithic hidden API flags against which the flags generated + // by the bootclasspath_fragment are compared. + // + // The signature patterns file selects the subset of monolithic hidden API flags using a number + // of patterns, i.e.: + // * The qualified name (including package) of an outermost class, e.g. java/lang/Character. + // This selects all the flags for all the members of this class and any nested classes. + // * A package wildcard, e.g. java/lang/*. This selects all the flags for all the members of all + // the classes in this package (but not in sub-packages). + // * A recursive package wildcard, e.g. java/**. This selects all the flags for all the members + // of all the classes in this package and sub-packages. + // + // The signature patterns file is constructed as follows: + // * All the signatures are retrieved from the all-flags.csv file. + // * The member and inner class names are removed. + // * If a class is in a split package then that is kept, otherwise the class part is removed + // and replaced with a wildcard, i.e. *. + // * If a package matches a package prefix then the package is removed. + // * All the package prefixes are added with a recursive wildcard appended to each, i.e. **. + // * The resulting patterns are sorted. + // + // So, by default (i.e. without specifying any package_prefixes or split_packages) the signature + // patterns is a list of class names, because there are no package packages and all packages are + // assumed to be split. + // + // If any split packages are specified then only those packages are treated as split and all + // other packages are treated as belonging solely to the bootclasspath_fragment and so they use + // wildcard package patterns. + // + // So, if an empty list of split packages is specified then the signature patterns file just + // includes a wildcard package pattern for every package provided by the bootclasspath_fragment. + // + // If split_packages are specified and a package that is split is not listed then it could lead + // to build failures as it will select monolithic flags that are generated by another + // bootclasspath_fragment to compare against the flags provided by this fragment. The latter + // will obviously not contain those flags and that can cause the comparison and build to fail. + // + // If any package prefixes are specified then any matching packages are removed from the + // signature patterns and replaced with a single recursive package pattern. + // + // It is not strictly necessary to specify either package_prefixes or split_packages as the + // defaults will produce a valid set of signature patterns. However, those patterns may include + // implementation details, e.g. names of implementation classes or packages, which will be + // exported to the sdk snapshot in the signature patterns file. That is something that should be + // avoided where possible. Specifying package_prefixes and split_packages allows those + // implementation details to be excluded from the snapshot. + Split_packages []string + } +} + type BootclasspathFragmentModule struct { android.ModuleBase android.ApexModuleBase @@ -146,6 +214,8 @@ type BootclasspathFragmentModule struct { ClasspathFragmentBase properties bootclasspathFragmentProperties + + sourceOnlyProperties SourceOnlyBootclasspathProperties } // commonBootclasspathFragment defines the methods that are implemented by both source and prebuilt @@ -177,7 +247,7 @@ type bootImageFilesByArch map[android.ArchType]android.Paths func bootclasspathFragmentFactory() android.Module { m := &BootclasspathFragmentModule{} - m.AddProperties(&m.properties) + m.AddProperties(&m.properties, &m.sourceOnlyProperties) android.InitApexModule(m) android.InitSdkAwareModule(m) initClasspathFragment(m, BOOTCLASSPATH) diff --git a/java/dex.go b/java/dex.go index 7898e9dff..1acfebd51 100644 --- a/java/dex.go +++ b/java/dex.go @@ -69,6 +69,9 @@ type DexProperties struct { // This defaults to reasonable value based on module and should not be set. // It exists only to support ART tests. Uncompress_dex *bool + + // Exclude kotlinc generate files: *.kotlin_module, *.kotlin_builtins. Defaults to false. + Exclude_kotlinc_generated_files *bool } type dexer struct { @@ -89,7 +92,7 @@ var d8, d8RE = pctx.MultiCommandRemoteStaticRules("d8", Command: `rm -rf "$outDir" && mkdir -p "$outDir" && ` + `$d8Template${config.D8Cmd} ${config.DexFlags} --output $outDir $d8Flags $in && ` + `$zipTemplate${config.SoongZipCmd} $zipFlags -o $outDir/classes.dex.jar -C $outDir -f "$outDir/classes*.dex" && ` + - `${config.MergeZipsCmd} -D -stripFile "**/*.class" $out $outDir/classes.dex.jar $in`, + `${config.MergeZipsCmd} -D -stripFile "**/*.class" $mergeZipsFlags $out $outDir/classes.dex.jar $in`, CommandDeps: []string{ "${config.D8Cmd}", "${config.SoongZipCmd}", @@ -110,7 +113,7 @@ var d8, d8RE = pctx.MultiCommandRemoteStaticRules("d8", ExecStrategy: "${config.RED8ExecStrategy}", Platform: map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"}, }, - }, []string{"outDir", "d8Flags", "zipFlags"}, nil) + }, []string{"outDir", "d8Flags", "zipFlags", "mergeZipsFlags"}, nil) var r8, r8RE = pctx.MultiCommandRemoteStaticRules("r8", blueprint.RuleParams{ @@ -126,7 +129,7 @@ var r8, r8RE = pctx.MultiCommandRemoteStaticRules("r8", `${config.SoongZipCmd} -o ${outUsageZip} -C ${outUsageDir} -f ${outUsage} && ` + `rm -rf ${outUsageDir} && ` + `$zipTemplate${config.SoongZipCmd} $zipFlags -o $outDir/classes.dex.jar -C $outDir -f "$outDir/classes*.dex" && ` + - `${config.MergeZipsCmd} -D -stripFile "**/*.class" $out $outDir/classes.dex.jar $in`, + `${config.MergeZipsCmd} -D -stripFile "**/*.class" $mergeZipsFlags $out $outDir/classes.dex.jar $in`, CommandDeps: []string{ "${config.R8Cmd}", "${config.SoongZipCmd}", @@ -156,7 +159,7 @@ var r8, r8RE = pctx.MultiCommandRemoteStaticRules("r8", Platform: map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"}, }, }, []string{"outDir", "outDict", "outUsage", "outUsageZip", "outUsageDir", - "r8Flags", "zipFlags"}, []string{"implicits"}) + "r8Flags", "zipFlags", "mergeZipsFlags"}, []string{"implicits"}) func (d *dexer) dexCommonFlags(ctx android.ModuleContext, minSdkVersion android.SdkSpec) []string { flags := d.dexProperties.Dxflags @@ -281,6 +284,12 @@ func (d *dexer) compileDex(ctx android.ModuleContext, flags javaBuilderFlags, mi commonFlags := d.dexCommonFlags(ctx, minSdkVersion) + // Exclude kotlinc generated files when "exclude_kotlinc_generated_files" is set to true. + mergeZipsFlags := "" + if proptools.BoolDefault(d.dexProperties.Exclude_kotlinc_generated_files, false) { + mergeZipsFlags = "-stripFile META-INF/*.kotlin_module -stripFile **/*.kotlin_builtins" + } + useR8 := d.effectiveOptimizeEnabled() if useR8 { proguardDictionary := android.PathForModuleOut(ctx, "proguard_dictionary") @@ -293,13 +302,14 @@ func (d *dexer) compileDex(ctx android.ModuleContext, flags javaBuilderFlags, mi r8Flags, r8Deps := d.r8Flags(ctx, flags) rule := r8 args := map[string]string{ - "r8Flags": strings.Join(append(commonFlags, r8Flags...), " "), - "zipFlags": zipFlags, - "outDict": proguardDictionary.String(), - "outUsageDir": proguardUsageDir.String(), - "outUsage": proguardUsage.String(), - "outUsageZip": proguardUsageZip.String(), - "outDir": outDir.String(), + "r8Flags": strings.Join(append(commonFlags, r8Flags...), " "), + "zipFlags": zipFlags, + "outDict": proguardDictionary.String(), + "outUsageDir": proguardUsageDir.String(), + "outUsage": proguardUsage.String(), + "outUsageZip": proguardUsageZip.String(), + "outDir": outDir.String(), + "mergeZipsFlags": mergeZipsFlags, } if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_R8") { rule = r8RE @@ -327,9 +337,10 @@ func (d *dexer) compileDex(ctx android.ModuleContext, flags javaBuilderFlags, mi Input: classesJar, Implicits: d8Deps, Args: map[string]string{ - "d8Flags": strings.Join(append(commonFlags, d8Flags...), " "), - "zipFlags": zipFlags, - "outDir": outDir.String(), + "d8Flags": strings.Join(append(commonFlags, d8Flags...), " "), + "zipFlags": zipFlags, + "outDir": outDir.String(), + "mergeZipsFlags": mergeZipsFlags, }, }) } diff --git a/java/sdk_library.go b/java/sdk_library.go index c6747c730..3ecba9ae9 100644 --- a/java/sdk_library.go +++ b/java/sdk_library.go @@ -2536,7 +2536,7 @@ type sdkLibrarySdkMemberProperties struct { android.SdkMemberPropertiesBase // Scope to per scope properties. - Scopes map[*apiScope]scopeProperties + Scopes map[*apiScope]*scopeProperties // The Java stubs source files. Stub_srcs []string @@ -2562,14 +2562,14 @@ type scopeProperties struct { StubsSrcJar android.Path CurrentApiFile android.Path RemovedApiFile android.Path - AnnotationsZip android.Path + AnnotationsZip android.Path `supported_build_releases:"Tiramisu+"` SdkVersion string } func (s *sdkLibrarySdkMemberProperties) PopulateFromVariant(ctx android.SdkMemberContext, variant android.Module) { sdk := variant.(*SdkLibrary) - s.Scopes = make(map[*apiScope]scopeProperties) + s.Scopes = make(map[*apiScope]*scopeProperties) for _, apiScope := range allApiScopes { paths := sdk.findScopePaths(apiScope) if paths == nil { @@ -2592,7 +2592,7 @@ func (s *sdkLibrarySdkMemberProperties) PopulateFromVariant(ctx android.SdkMembe if paths.annotationsZip.Valid() { properties.AnnotationsZip = paths.annotationsZip.Path() } - s.Scopes[apiScope] = properties + s.Scopes[apiScope] = &properties } } diff --git a/java/sdk_library_test.go b/java/sdk_library_test.go index 0c7b79853..f8717c066 100644 --- a/java/sdk_library_test.go +++ b/java/sdk_library_test.go @@ -47,6 +47,7 @@ func TestJavaSdkLibrary(t *testing.T) { name: "bar", srcs: ["a.java", "b.java"], api_packages: ["bar"], + exclude_kotlinc_generated_files: true, } java_library { name: "baz", @@ -159,6 +160,14 @@ func TestJavaSdkLibrary(t *testing.T) { sdkLibs := quxLib.ClassLoaderContexts().UsesLibs() android.AssertDeepEquals(t, "qux exports", []string{"foo", "bar", "fred", "quuz"}, sdkLibs) } + + fooDexJar := result.ModuleForTests("foo", "android_common").Rule("d8") + // tests if kotlinc generated files are NOT excluded from output of foo. + android.AssertStringDoesNotContain(t, "foo dex", fooDexJar.BuildParams.Args["mergeZipsFlags"], "-stripFile META-INF/*.kotlin_module") + + barDexJar := result.ModuleForTests("bar", "android_common").Rule("d8") + // tests if kotlinc generated files are excluded from output of bar. + android.AssertStringDoesContain(t, "bar dex", barDexJar.BuildParams.Args["mergeZipsFlags"], "-stripFile META-INF/*.kotlin_module") } func TestJavaSdkLibrary_StubOrImplOnlyLibs(t *testing.T) { diff --git a/java/systemserver_classpath_fragment.go b/java/systemserver_classpath_fragment.go index 10dbd01e2..be28fe9c2 100644 --- a/java/systemserver_classpath_fragment.go +++ b/java/systemserver_classpath_fragment.go @@ -72,10 +72,15 @@ func (s *SystemServerClasspathModule) ShouldSupportSdkVersion(ctx android.BaseMo } type systemServerClasspathFragmentProperties struct { - // The contents of this systemserverclasspath_fragment, could be either java_library, or java_sdk_library. + // List of system_server classpath jars, could be either java_library, or java_sdk_library. // // The order of this list matters as it is the order that is used in the SYSTEMSERVERCLASSPATH. Contents []string + + // List of jars that system_server loads dynamically using separate classloaders. + // + // The order does not matter. + Standalone_contents []string } func systemServerClasspathFactory() android.Module { @@ -88,8 +93,12 @@ func systemServerClasspathFactory() android.Module { } func (s *SystemServerClasspathModule) GenerateAndroidBuildActions(ctx android.ModuleContext) { - if len(s.properties.Contents) == 0 { - ctx.PropertyErrorf("contents", "empty contents are not allowed") + if len(s.properties.Contents) == 0 && len(s.properties.Standalone_contents) == 0 { + ctx.PropertyErrorf("contents", "Either contents or standalone_contents needs to be non-empty") + } + + if s.ShouldIgnore() { + return } configuredJars := s.configuredJars(ctx) @@ -128,8 +137,17 @@ func IsSystemServerClasspathFragmentContentDepTag(tag blueprint.DependencyTag) b func (s *SystemServerClasspathModule) ComponentDepsMutator(ctx android.BottomUpMutatorContext) { module := ctx.Module() + var deps []string + deps = append(deps, s.properties.Contents...) + deps = append(deps, s.properties.Standalone_contents...) - for _, name := range s.properties.Contents { + for _, name := range deps { ctx.AddDependency(module, systemServerClasspathFragmentContentDepTag, name) } } + +func (s *SystemServerClasspathModule) ShouldIgnore() bool { + // Ignore this `systemserverclasspath_fragment` if it only contains `standalone_contents` because + // it is for T and above. + return len(s.properties.Contents) == 0 +} diff --git a/java/systemserver_classpath_fragment_test.go b/java/systemserver_classpath_fragment_test.go index 9ad50dd4a..ba328e7b1 100644 --- a/java/systemserver_classpath_fragment_test.go +++ b/java/systemserver_classpath_fragment_test.go @@ -99,7 +99,7 @@ func TestPlatformSystemServerClasspathModule_AndroidMkEntries(t *testing.T) { func TestSystemServerClasspathFragmentWithoutContents(t *testing.T) { prepareForTestWithSystemServerClasspath. ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern( - `\Qempty contents are not allowed\E`)). + `\QEither contents or standalone_contents needs to be non-empty\E`)). RunTestWithBp(t, ` systemserverclasspath_fragment { name: "systemserverclasspath-fragment", diff --git a/rust/rust.go b/rust/rust.go index f068b3d7b..cdeecc191 100644 --- a/rust/rust.go +++ b/rust/rust.go @@ -328,10 +328,6 @@ func (mod *Module) SdkVersion() string { return "" } -func (mod *Module) MinSdkVersion() string { - return "" -} - func (mod *Module) AlwaysSdk() bool { return false } @@ -1276,15 +1272,13 @@ func (mod *Module) HostToolPath() android.OptionalPath { var _ android.ApexModule = (*Module)(nil) -func (mod *Module) minSdkVersion() string { +func (mod *Module) MinSdkVersion() string { return String(mod.Properties.Min_sdk_version) } -var _ android.ApexModule = (*Module)(nil) - // Implements android.ApexModule func (mod *Module) ShouldSupportSdkVersion(ctx android.BaseModuleContext, sdkVersion android.ApiLevel) error { - minSdkVersion := mod.minSdkVersion() + minSdkVersion := mod.MinSdkVersion() if minSdkVersion == "apex_inherit" { return nil } diff --git a/sdk/Android.bp b/sdk/Android.bp index 368c03aec..5ba2d9d59 100644 --- a/sdk/Android.bp +++ b/sdk/Android.bp @@ -15,6 +15,7 @@ bootstrap_go_package { ], srcs: [ "bp.go", + "build_release.go", "exports.go", "sdk.go", "update.go", @@ -22,6 +23,7 @@ bootstrap_go_package { testSrcs: [ "bootclasspath_fragment_sdk_test.go", "bp_test.go", + "build_release_test.go", "cc_sdk_test.go", "compat_config_sdk_test.go", "exports_test.go", diff --git a/sdk/build_release.go b/sdk/build_release.go new file mode 100644 index 000000000..4c2277e85 --- /dev/null +++ b/sdk/build_release.go @@ -0,0 +1,388 @@ +// 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 sdk + +import ( + "fmt" + "reflect" + "strings" +) + +// Supports customizing sdk snapshot output based on target build release. + +// buildRelease represents the version of a build system used to create a specific release. +// +// The name of the release, is the same as the code for the dessert release, e.g. S, T, etc. +type buildRelease struct { + // The name of the release, e.g. S, T, etc. + name string + + // The index of this structure within the buildReleases list. + ordinal int +} + +// String returns the name of the build release. +func (s *buildRelease) String() string { + return s.name +} + +// buildReleaseSet represents a set of buildRelease objects. +type buildReleaseSet struct { + // Set of *buildRelease represented as a map from *buildRelease to struct{}. + contents map[*buildRelease]struct{} +} + +// addItem adds a build release to the set. +func (s *buildReleaseSet) addItem(release *buildRelease) { + s.contents[release] = struct{}{} +} + +// addRange adds all the build releases from start (inclusive) to end (inclusive). +func (s *buildReleaseSet) addRange(start *buildRelease, end *buildRelease) { + for i := start.ordinal; i <= end.ordinal; i += 1 { + s.addItem(buildReleases[i]) + } +} + +// contains returns true if the set contains the specified build release. +func (s *buildReleaseSet) contains(release *buildRelease) bool { + _, ok := s.contents[release] + return ok +} + +// String returns a string representation of the set, sorted from earliest to latest release. +func (s *buildReleaseSet) String() string { + list := []string{} + for _, release := range buildReleases { + if _, ok := s.contents[release]; ok { + list = append(list, release.name) + } + } + return fmt.Sprintf("[%s]", strings.Join(list, ",")) +} + +var ( + // nameToBuildRelease contains a map from name to build release. + nameToBuildRelease = map[string]*buildRelease{} + + // buildReleases lists all the available build releases. + buildReleases = []*buildRelease{} + + // allBuildReleaseSet is the set of all build releases. + allBuildReleaseSet = &buildReleaseSet{contents: map[*buildRelease]struct{}{}} + + // Add the build releases from oldest to newest. + buildReleaseS = initBuildRelease("S") + buildReleaseT = initBuildRelease("Tiramisu") +) + +// initBuildRelease creates a new build release with the specified name. +func initBuildRelease(name string) *buildRelease { + ordinal := len(nameToBuildRelease) + release := &buildRelease{name: name, ordinal: ordinal} + nameToBuildRelease[name] = release + buildReleases = append(buildReleases, release) + allBuildReleaseSet.addItem(release) + return release +} + +// latestBuildRelease returns the latest build release, i.e. the last one added. +func latestBuildRelease() *buildRelease { + return buildReleases[len(buildReleases)-1] +} + +// nameToRelease maps from build release name to the corresponding build release (if it exists) or +// the error if it does not. +func nameToRelease(name string) (*buildRelease, error) { + if r, ok := nameToBuildRelease[name]; ok { + return r, nil + } + + return nil, fmt.Errorf("unknown release %q, expected one of %s", name, allBuildReleaseSet) +} + +// parseBuildReleaseSet parses a build release set string specification into a build release set. +// +// The specification consists of one of the following: +// * a single build release name, e.g. S, T, etc. +// * a closed range (inclusive to inclusive), e.g. S-T +// * an open range, e.g. T+. +// +// This returns the set if the specification was valid or an error. +func parseBuildReleaseSet(specification string) (*buildReleaseSet, error) { + set := &buildReleaseSet{contents: map[*buildRelease]struct{}{}} + + if strings.HasSuffix(specification, "+") { + rangeStart := strings.TrimSuffix(specification, "+") + start, err := nameToRelease(rangeStart) + if err != nil { + return nil, err + } + end := latestBuildRelease() + set.addRange(start, end) + } else if strings.Contains(specification, "-") { + limits := strings.SplitN(specification, "-", 2) + start, err := nameToRelease(limits[0]) + if err != nil { + return nil, err + } + + end, err := nameToRelease(limits[1]) + if err != nil { + return nil, err + } + + if start.ordinal > end.ordinal { + return nil, fmt.Errorf("invalid closed range, start release %q is later than end release %q", start.name, end.name) + } + + set.addRange(start, end) + } else { + release, err := nameToRelease(specification) + if err != nil { + return nil, err + } + set.addItem(release) + } + + return set, nil +} + +// Given a set of properties (struct value), set the value of a field within that struct (or one of +// its embedded structs) to its zero value. +type fieldPrunerFunc func(structValue reflect.Value) + +// A property that can be cleared by a propertyPruner. +type prunerProperty struct { + // The name of the field for this property. It is a "."-separated path for fields in non-anonymous + // sub-structs. + name string + + // Sets the associated field to its zero value. + prunerFunc fieldPrunerFunc +} + +// propertyPruner provides support for pruning (i.e. setting to their zero value) properties from +// a properties structure. +type propertyPruner struct { + // The properties that the pruner will clear. + properties []prunerProperty +} + +// gatherFields recursively processes the supplied structure and a nested structures, selecting the +// fields that require pruning and populates the propertyPruner.properties with the information +// needed to prune those fields. +// +// containingStructAccessor is a func that if given an object will return a field whose value is +// of the supplied structType. It is nil on initial entry to this method but when this method is +// called recursively on a field that is a nested structure containingStructAccessor is set to a +// func that provides access to the field's value. +// +// namePrefix is the prefix to the fields that are being visited. It is "" on initial entry to this +// method but when this method is called recursively on a field that is a nested structure +// namePrefix is the result of appending the field name (plus a ".") to the previous name prefix. +// Unless the field is anonymous in which case it is passed through unchanged. +// +// selector is a func that will select whether the supplied field requires pruning or not. If it +// returns true then the field will be added to those to be pruned, otherwise it will not. +func (p *propertyPruner) gatherFields(structType reflect.Type, containingStructAccessor fieldAccessorFunc, namePrefix string, selector fieldSelectorFunc) { + for f := 0; f < structType.NumField(); f++ { + field := structType.Field(f) + if field.PkgPath != "" { + // Ignore unexported fields. + continue + } + + // Save a copy of the field index for use in the function. + fieldIndex := f + + name := namePrefix + field.Name + + fieldGetter := func(container reflect.Value) reflect.Value { + if containingStructAccessor != nil { + // This is an embedded structure so first access the field for the embedded + // structure. + container = containingStructAccessor(container) + } + + // Skip through interface and pointer values to find the structure. + container = getStructValue(container) + + defer func() { + if r := recover(); r != nil { + panic(fmt.Errorf("%s for fieldIndex %d of field %s of container %#v", r, fieldIndex, name, container.Interface())) + } + }() + + // Return the field. + return container.Field(fieldIndex) + } + + fieldType := field.Type + if selector(name, field) { + zeroValue := reflect.Zero(fieldType) + fieldPruner := func(container reflect.Value) { + if containingStructAccessor != nil { + // This is an embedded structure so first access the field for the embedded + // structure. + container = containingStructAccessor(container) + } + + // Skip through interface and pointer values to find the structure. + container = getStructValue(container) + + defer func() { + if r := recover(); r != nil { + panic(fmt.Errorf("%s\n\tfor field (index %d, name %s)", r, fieldIndex, name)) + } + }() + + // Set the field. + container.Field(fieldIndex).Set(zeroValue) + } + + property := prunerProperty{ + name, + fieldPruner, + } + p.properties = append(p.properties, property) + } else { + switch fieldType.Kind() { + case reflect.Struct: + // Gather fields from the nested or embedded structure. + var subNamePrefix string + if field.Anonymous { + subNamePrefix = namePrefix + } else { + subNamePrefix = name + "." + } + p.gatherFields(fieldType, fieldGetter, subNamePrefix, selector) + + case reflect.Map: + // Get the type of the values stored in the map. + valueType := fieldType.Elem() + // Skip over * types. + if valueType.Kind() == reflect.Ptr { + valueType = valueType.Elem() + } + if valueType.Kind() == reflect.Struct { + // If this is not referenced by a pointer then it is an error as it is impossible to + // modify a struct that is stored directly as a value in a map. + if fieldType.Elem().Kind() != reflect.Ptr { + panic(fmt.Errorf("Cannot prune struct %s stored by value in map %s, map values must"+ + " be pointers to structs", + fieldType.Elem(), name)) + } + + // Create a new pruner for the values of the map. + valuePruner := newPropertyPrunerForStructType(valueType, selector) + + // Create a new fieldPruner that will iterate over all the items in the map and call the + // pruner on them. + fieldPruner := func(container reflect.Value) { + mapValue := fieldGetter(container) + + for _, keyValue := range mapValue.MapKeys() { + itemValue := mapValue.MapIndex(keyValue) + + defer func() { + if r := recover(); r != nil { + panic(fmt.Errorf("%s\n\tfor key %q", r, keyValue)) + } + }() + + valuePruner.pruneProperties(itemValue.Interface()) + } + } + + // Add the map field pruner to the list of property pruners. + property := prunerProperty{ + name + "[*]", + fieldPruner, + } + p.properties = append(p.properties, property) + } + } + } + } +} + +// pruneProperties will prune (set to zero value) any properties in the struct referenced by the +// supplied struct pointer. +// +// The struct must be of the same type as was originally passed to newPropertyPruner to create this +// propertyPruner. +func (p *propertyPruner) pruneProperties(propertiesStruct interface{}) { + + defer func() { + if r := recover(); r != nil { + panic(fmt.Errorf("%s\n\tof container %#v", r, propertiesStruct)) + } + }() + + structValue := reflect.ValueOf(propertiesStruct) + for _, property := range p.properties { + property.prunerFunc(structValue) + } +} + +// fieldSelectorFunc is called to select whether a specific field should be pruned or not. +// name is the name of the field, including any prefixes from containing str +type fieldSelectorFunc func(name string, field reflect.StructField) bool + +// newPropertyPruner creates a new property pruner for the structure type for the supplied +// properties struct. +// +// The returned pruner can be used on any properties structure of the same type as the supplied set +// of properties. +func newPropertyPruner(propertiesStruct interface{}, selector fieldSelectorFunc) *propertyPruner { + structType := getStructValue(reflect.ValueOf(propertiesStruct)).Type() + return newPropertyPrunerForStructType(structType, selector) +} + +// newPropertyPruner creates a new property pruner for the supplied properties struct type. +// +// The returned pruner can be used on any properties structure of the supplied type. +func newPropertyPrunerForStructType(structType reflect.Type, selector fieldSelectorFunc) *propertyPruner { + pruner := &propertyPruner{} + pruner.gatherFields(structType, nil, "", selector) + return pruner +} + +// newPropertyPrunerByBuildRelease creates a property pruner that will clear any properties in the +// structure which are not supported by the specified target build release. +// +// A property is pruned if its field has a tag of the form: +// `supported_build_releases:"<build-release-set>"` +// and the resulting build release set does not contain the target build release. Properties that +// have no such tag are assumed to be supported by all releases. +func newPropertyPrunerByBuildRelease(propertiesStruct interface{}, targetBuildRelease *buildRelease) *propertyPruner { + return newPropertyPruner(propertiesStruct, func(name string, field reflect.StructField) bool { + if supportedBuildReleases, ok := field.Tag.Lookup("supported_build_releases"); ok { + set, err := parseBuildReleaseSet(supportedBuildReleases) + if err != nil { + panic(fmt.Errorf("invalid `supported_build_releases` tag on %s of %T: %s", name, propertiesStruct, err)) + } + + // If the field does not support tha target release then prune it. + return !set.contains(targetBuildRelease) + + } else { + // Any untagged fields are assumed to be supported by all build releases so should never be + // pruned. + return false + } + }) +} diff --git a/sdk/build_release_test.go b/sdk/build_release_test.go new file mode 100644 index 000000000..6f1ef9e30 --- /dev/null +++ b/sdk/build_release_test.go @@ -0,0 +1,227 @@ +// 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 sdk + +import ( + "encoding/json" + "fmt" + "testing" + + "android/soong/android" +) + +// Tests for build_release.go + +var ( + // Some additional test specific releases that are added after the currently supported ones and + // so are treated as being for future releases. + buildReleaseFuture1 = initBuildRelease("F1") + buildReleaseFuture2 = initBuildRelease("F2") +) + +func TestNameToRelease(t *testing.T) { + t.Run("single release", func(t *testing.T) { + release, err := nameToRelease("S") + android.AssertDeepEquals(t, "errors", nil, err) + android.AssertDeepEquals(t, "release", buildReleaseS, release) + }) + t.Run("invalid release", func(t *testing.T) { + release, err := nameToRelease("A") + android.AssertDeepEquals(t, "release", (*buildRelease)(nil), release) + // Uses a wildcard in the error message to allow for additional build releases to be added to + // the supported set without breaking this test. + android.FailIfNoMatchingErrors(t, `unknown release "A", expected one of \[S,T.*,F1,F2\]`, []error{err}) + }) +} + +func TestParseBuildReleaseSet(t *testing.T) { + t.Run("single release", func(t *testing.T) { + set, err := parseBuildReleaseSet("S") + android.AssertDeepEquals(t, "errors", nil, err) + android.AssertStringEquals(t, "set", "[S]", set.String()) + }) + t.Run("open range", func(t *testing.T) { + set, err := parseBuildReleaseSet("F1+") + android.AssertDeepEquals(t, "errors", nil, err) + android.AssertStringEquals(t, "set", "[F1,F2]", set.String()) + }) + t.Run("closed range", func(t *testing.T) { + set, err := parseBuildReleaseSet("S-F1") + android.AssertDeepEquals(t, "errors", nil, err) + android.AssertStringEquals(t, "set", "[S,Tiramisu,F1]", set.String()) + }) + invalidAReleaseMessage := `unknown release "A", expected one of ` + allBuildReleaseSet.String() + t.Run("invalid release", func(t *testing.T) { + set, err := parseBuildReleaseSet("A") + android.AssertDeepEquals(t, "set", (*buildReleaseSet)(nil), set) + android.AssertStringDoesContain(t, "errors", fmt.Sprint(err), invalidAReleaseMessage) + }) + t.Run("invalid release in open range", func(t *testing.T) { + set, err := parseBuildReleaseSet("A+") + android.AssertDeepEquals(t, "set", (*buildReleaseSet)(nil), set) + android.AssertStringDoesContain(t, "errors", fmt.Sprint(err), invalidAReleaseMessage) + }) + t.Run("invalid release in closed range start", func(t *testing.T) { + set, err := parseBuildReleaseSet("A-S") + android.AssertDeepEquals(t, "set", (*buildReleaseSet)(nil), set) + android.AssertStringDoesContain(t, "errors", fmt.Sprint(err), invalidAReleaseMessage) + }) + t.Run("invalid release in closed range end", func(t *testing.T) { + set, err := parseBuildReleaseSet("Tiramisu-A") + android.AssertDeepEquals(t, "set", (*buildReleaseSet)(nil), set) + android.AssertStringDoesContain(t, "errors", fmt.Sprint(err), invalidAReleaseMessage) + }) + t.Run("invalid closed range reversed", func(t *testing.T) { + set, err := parseBuildReleaseSet("F1-S") + android.AssertDeepEquals(t, "set", (*buildReleaseSet)(nil), set) + android.AssertStringDoesContain(t, "errors", fmt.Sprint(err), `invalid closed range, start release "F1" is later than end release "S"`) + }) +} + +func TestBuildReleaseSetContains(t *testing.T) { + t.Run("contains", func(t *testing.T) { + set, _ := parseBuildReleaseSet("F1-F2") + android.AssertBoolEquals(t, "set contains F1", true, set.contains(buildReleaseFuture1)) + android.AssertBoolEquals(t, "set does not contain S", false, set.contains(buildReleaseS)) + android.AssertBoolEquals(t, "set contains F2", true, set.contains(buildReleaseFuture2)) + android.AssertBoolEquals(t, "set does not contain T", false, set.contains(buildReleaseT)) + }) +} + +func TestPropertyPrunerInvalidTag(t *testing.T) { + type brokenStruct struct { + Broken string `supported_build_releases:"A"` + } + type containingStruct struct { + Nested brokenStruct + } + + t.Run("broken struct", func(t *testing.T) { + android.AssertPanicMessageContains(t, "error", "invalid `supported_build_releases` tag on Broken of *sdk.brokenStruct: unknown release \"A\"", func() { + newPropertyPrunerByBuildRelease(&brokenStruct{}, buildReleaseS) + }) + }) + + t.Run("nested broken struct", func(t *testing.T) { + android.AssertPanicMessageContains(t, "error", "invalid `supported_build_releases` tag on Nested.Broken of *sdk.containingStruct: unknown release \"A\"", func() { + newPropertyPrunerByBuildRelease(&containingStruct{}, buildReleaseS) + }) + }) +} + +func TestPropertyPrunerByBuildRelease(t *testing.T) { + type nested struct { + F1_only string `supported_build_releases:"F1"` + } + + type mapped struct { + Default string + T_only string `supported_build_releases:"Tiramisu"` + } + + type testBuildReleasePruner struct { + Default string + S_and_T_only string `supported_build_releases:"S-Tiramisu"` + T_later string `supported_build_releases:"Tiramisu+"` + Nested nested + Mapped map[string]*mapped + } + + inputFactory := func() testBuildReleasePruner { + return testBuildReleasePruner{ + Default: "Default", + S_and_T_only: "S_and_T_only", + T_later: "T_later", + Nested: nested{ + F1_only: "F1_only", + }, + Mapped: map[string]*mapped{ + "one": { + Default: "one-default", + T_only: "one-t-only", + }, + "two": { + Default: "two-default", + T_only: "two-t-only", + }, + }, + } + } + + marshal := func(t interface{}) string { + bytes, err := json.MarshalIndent(t, "", " ") + if err != nil { + panic(err) + } + return string(bytes) + } + + assertJsonEquals := func(t *testing.T, expected, actual interface{}) { + t.Helper() + expectedJson := marshal(expected) + actualJson := marshal(actual) + if actualJson != expectedJson { + t.Errorf("test struct: expected:\n%s\n got:\n%s", expectedJson, actualJson) + } + } + + t.Run("target S", func(t *testing.T) { + testStruct := inputFactory() + pruner := newPropertyPrunerByBuildRelease(&testStruct, buildReleaseS) + pruner.pruneProperties(&testStruct) + + expected := inputFactory() + expected.T_later = "" + expected.Nested.F1_only = "" + expected.Mapped["one"].T_only = "" + expected.Mapped["two"].T_only = "" + assertJsonEquals(t, expected, testStruct) + }) + + t.Run("target T", func(t *testing.T) { + testStruct := inputFactory() + pruner := newPropertyPrunerByBuildRelease(&testStruct, buildReleaseT) + pruner.pruneProperties(&testStruct) + + expected := inputFactory() + expected.Nested.F1_only = "" + assertJsonEquals(t, expected, testStruct) + }) + + t.Run("target F1", func(t *testing.T) { + testStruct := inputFactory() + pruner := newPropertyPrunerByBuildRelease(&testStruct, buildReleaseFuture1) + pruner.pruneProperties(&testStruct) + + expected := inputFactory() + expected.S_and_T_only = "" + expected.Mapped["one"].T_only = "" + expected.Mapped["two"].T_only = "" + assertJsonEquals(t, expected, testStruct) + }) + + t.Run("target F2", func(t *testing.T) { + testStruct := inputFactory() + pruner := newPropertyPrunerByBuildRelease(&testStruct, buildReleaseFuture2) + pruner.pruneProperties(&testStruct) + + expected := inputFactory() + expected.S_and_T_only = "" + expected.Nested.F1_only = "" + expected.Mapped["one"].T_only = "" + expected.Mapped["two"].T_only = "" + assertJsonEquals(t, expected, testStruct) + }) +} diff --git a/sdk/java_sdk_test.go b/sdk/java_sdk_test.go index 42826b79e..db13e77ac 100644 --- a/sdk/java_sdk_test.go +++ b/sdk/java_sdk_test.go @@ -1253,6 +1253,58 @@ java_sdk_library_import { ) } +func TestSnapshotWithJavaSdkLibrary_AnnotationsZip_PreT(t *testing.T) { + result := android.GroupFixturePreparers( + prepareForSdkTestWithJavaSdkLibrary, + android.FixtureMergeEnv(map[string]string{ + "SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE": "S", + }), + ).RunTestWithBp(t, ` + sdk { + name: "mysdk", + java_sdk_libs: ["myjavalib"], + } + + java_sdk_library { + name: "myjavalib", + srcs: ["Test.java"], + sdk_version: "current", + shared_library: false, + annotations_enabled: true, + public: { + enabled: true, + }, + } + `) + + CheckSnapshot(t, result, "mysdk", "", + checkUnversionedAndroidBpContents(` +// This is auto-generated. DO NOT EDIT. + +java_sdk_library_import { + name: "myjavalib", + prefer: false, + visibility: ["//visibility:public"], + apex_available: ["//apex_available:platform"], + shared_library: false, + public: { + jars: ["sdk_library/public/myjavalib-stubs.jar"], + stub_srcs: ["sdk_library/public/myjavalib_stub_sources"], + current_api: "sdk_library/public/myjavalib.txt", + removed_api: "sdk_library/public/myjavalib-removed.txt", + sdk_version: "current", + }, +} + `), + checkAllCopyRules(` +.intermediates/myjavalib.stubs/android_common/javac/myjavalib.stubs.jar -> sdk_library/public/myjavalib-stubs.jar +.intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_api.txt -> sdk_library/public/myjavalib.txt +.intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_removed.txt -> sdk_library/public/myjavalib-removed.txt + `), + checkMergeZips(".intermediates/mysdk/common_os/tmp/sdk_library/public/myjavalib_stub_sources.zip"), + ) +} + func TestSnapshotWithJavaSdkLibrary_CompileDex(t *testing.T) { result := android.GroupFixturePreparers(prepareForSdkTestWithJavaSdkLibrary).RunTestWithBp(t, ` sdk { diff --git a/sdk/sdk.go b/sdk/sdk.go index b1c8aebf9..d8196fffc 100644 --- a/sdk/sdk.go +++ b/sdk/sdk.go @@ -257,7 +257,13 @@ func newSdkModule(moduleExports bool) *sdk { // Create an instance of the dynamically created struct that contains all the // properties for the member type specific list properties. s.dynamicMemberTypeListProperties = s.dynamicSdkMemberTypes.createMemberListProperties() - s.AddProperties(&s.properties, s.dynamicMemberTypeListProperties) + + ignoredProperties := struct { + // `systemserverclasspath_fragments` is for T and above. + Systemserverclasspath_fragments []string + }{} + + s.AddProperties(&s.properties, s.dynamicMemberTypeListProperties, &ignoredProperties) // Make sure that the prebuilt visibility property is verified for errors. android.AddVisibilityProperty(s, "prebuilt_visibility", &s.properties.Prebuilt_visibility) diff --git a/sdk/sdk_test.go b/sdk/sdk_test.go index a13b0d7d0..969462b04 100644 --- a/sdk/sdk_test.go +++ b/sdk/sdk_test.go @@ -15,11 +15,13 @@ package sdk import ( - "android/soong/android" "log" "os" "testing" + "android/soong/android" + "android/soong/java" + "github.com/google/blueprint/proptools" ) @@ -661,4 +663,86 @@ sdk_snapshot { snapshotTestPreparer(checkSnapshotWithoutSource, android.FixtureWithRootAndroidBp(bp)), ) }) + + t.Run("SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE=S", func(t *testing.T) { + result := android.GroupFixturePreparers( + prepareForSdkTestWithJava, + java.PrepareForTestWithJavaDefaultModules, + java.PrepareForTestWithJavaSdkLibraryFiles, + java.FixtureWithLastReleaseApis("mysdklibrary"), + android.FixtureWithRootAndroidBp(` + sdk { + name: "mysdk", + bootclasspath_fragments: ["mybootclasspathfragment"], + } + + bootclasspath_fragment { + name: "mybootclasspathfragment", + apex_available: ["myapex"], + contents: ["mysdklibrary"], + } + + java_sdk_library { + name: "mysdklibrary", + srcs: ["Test.java"], + compile_dex: true, + public: {enabled: true}, + permitted_packages: ["mysdklibrary"], + } + `), + android.FixtureMergeEnv(map[string]string{ + "SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE": "S", + }), + ).RunTest(t) + + CheckSnapshot(t, result, "mysdk", "", + checkUnversionedAndroidBpContents(` +// This is auto-generated. DO NOT EDIT. + +prebuilt_bootclasspath_fragment { + name: "mybootclasspathfragment", + prefer: false, + visibility: ["//visibility:public"], + apex_available: ["myapex"], + contents: ["mysdklibrary"], + hidden_api: { + stub_flags: "hiddenapi/stub-flags.csv", + annotation_flags: "hiddenapi/annotation-flags.csv", + metadata: "hiddenapi/metadata.csv", + index: "hiddenapi/index.csv", + all_flags: "hiddenapi/all-flags.csv", + }, +} + +java_sdk_library_import { + name: "mysdklibrary", + prefer: false, + visibility: ["//visibility:public"], + apex_available: ["//apex_available:platform"], + shared_library: true, + compile_dex: true, + permitted_packages: ["mysdklibrary"], + public: { + jars: ["sdk_library/public/mysdklibrary-stubs.jar"], + stub_srcs: ["sdk_library/public/mysdklibrary_stub_sources"], + current_api: "sdk_library/public/mysdklibrary.txt", + removed_api: "sdk_library/public/mysdklibrary-removed.txt", + sdk_version: "current", + }, +} +`), + + checkAllCopyRules(` +.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/stub-flags.csv -> hiddenapi/stub-flags.csv +.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/annotation-flags.csv -> hiddenapi/annotation-flags.csv +.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/metadata.csv -> hiddenapi/metadata.csv +.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/index.csv -> hiddenapi/index.csv +.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/all-flags.csv -> hiddenapi/all-flags.csv +.intermediates/mysdklibrary.stubs/android_common/javac/mysdklibrary.stubs.jar -> sdk_library/public/mysdklibrary-stubs.jar +.intermediates/mysdklibrary.stubs.source/android_common/metalava/mysdklibrary.stubs.source_api.txt -> sdk_library/public/mysdklibrary.txt +.intermediates/mysdklibrary.stubs.source/android_common/metalava/mysdklibrary.stubs.source_removed.txt -> sdk_library/public/mysdklibrary-removed.txt +`), + ) + }) + } diff --git a/sdk/testing.go b/sdk/testing.go index 3254cf9f3..294f1a57d 100644 --- a/sdk/testing.go +++ b/sdk/testing.go @@ -136,6 +136,7 @@ func getSdkSnapshotBuildInfo(t *testing.T, result *android.TestResult, sdk *sdk) androidUnversionedBpContents: sdk.GetUnversionedAndroidBpContentsForTests(), androidVersionedBpContents: sdk.GetVersionedAndroidBpContentsForTests(), snapshotTestCustomizations: map[snapshotTest]*snapshotTestCustomization{}, + targetBuildRelease: sdk.builderForTests.targetBuildRelease, } buildParams := sdk.BuildParamsForTests() @@ -253,6 +254,13 @@ func CheckSnapshot(t *testing.T, result *android.TestResult, name string, dir st } fs[filepath.Join(snapshotSubDir, "Android.bp")] = []byte(snapshotBuildInfo.androidBpContents) + // If the generated snapshot builders not for the current release then it cannot be loaded by + // the current release. + currentBuildRelease := latestBuildRelease() + if snapshotBuildInfo.targetBuildRelease != currentBuildRelease { + return + } + // The preparers from the original source fixture. sourcePreparers := result.Preparer() @@ -476,6 +484,9 @@ type snapshotBuildInfo struct { // The final output zip. outputZip string + // The target build release. + targetBuildRelease *buildRelease + // The test specific customizations for each snapshot test. snapshotTestCustomizations map[snapshotTest]*snapshotTestCustomization } diff --git a/sdk/update.go b/sdk/update.go index 84f0e4e2f..d9c57c375 100644 --- a/sdk/update.go +++ b/sdk/update.go @@ -22,6 +22,7 @@ import ( "android/soong/apex" "android/soong/cc" + "github.com/google/blueprint" "github.com/google/blueprint/proptools" @@ -49,6 +50,19 @@ import ( // snapshot module only. The zip file containing the generated snapshot will be // <sdk-name>-<number>.zip. // +// SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE +// This allows the target build release (i.e. the release version of the build within which +// the snapshot will be used) of the snapshot to be specified. If unspecified then it defaults +// to the current build release version. Otherwise, it must be the name of one of the build +// releases defined in nameToBuildRelease, e.g. S, T, etc.. +// +// The generated snapshot must only be used in the specified target release. If the target +// build release is not the current build release then the generated Android.bp file not be +// checked for compatibility. +// +// e.g. if setting SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE=S will cause the generated snapshot +// to be compatible with S. +// var pctx = android.NewPackageContext("android/soong/sdk") @@ -318,6 +332,14 @@ func (s *sdk) buildSnapshot(ctx android.ModuleContext, sdkVariants []*sdk) andro snapshotZipFileSuffix = "-" + version } + currentBuildRelease := latestBuildRelease() + targetBuildReleaseEnv := config.GetenvWithDefault("SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE", currentBuildRelease.name) + targetBuildRelease, err := nameToRelease(targetBuildReleaseEnv) + if err != nil { + ctx.ModuleErrorf("invalid SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE: %s", err) + targetBuildRelease = currentBuildRelease + } + builder := &snapshotBuilder{ ctx: ctx, sdk: s, @@ -329,6 +351,7 @@ func (s *sdk) buildSnapshot(ctx android.ModuleContext, sdkVariants []*sdk) andro prebuiltModules: make(map[string]*bpModule), allMembersByName: allMembersByName, exportedMembersByName: exportedMembersByName, + targetBuildRelease: targetBuildRelease, } s.builderForTests = builder @@ -401,7 +424,11 @@ be unnecessary as every module in the sdk already has its own licenses property. generateBpContents(&bp.generatedContents, bpFile) contents := bp.content.String() - syntaxCheckSnapshotBpFile(ctx, contents) + // If the snapshot is being generated for the current build release then check the syntax to make + // sure that it is compatible. + if targetBuildRelease == currentBuildRelease { + syntaxCheckSnapshotBpFile(ctx, contents) + } bp.build(pctx, ctx, nil) @@ -938,6 +965,9 @@ type snapshotBuilder struct { // The set of exported members by name. exportedMembersByName map[string]struct{} + + // The target build release for which the snapshot is to be generated. + targetBuildRelease *buildRelease } func (s *snapshotBuilder) CopyToSnapshot(src android.Path, dest string) { @@ -1302,6 +1332,16 @@ func newOsTypeSpecificInfo(ctx android.SdkMemberContext, osType android.OsType, return osInfo } +func (osInfo *osTypeSpecificInfo) pruneUnsupportedProperties(pruner *propertyPruner) { + if len(osInfo.archInfos) == 0 { + pruner.pruneProperties(osInfo.Properties) + } else { + for _, archInfo := range osInfo.archInfos { + archInfo.pruneUnsupportedProperties(pruner) + } + } +} + // Optimize the properties by extracting common properties from arch type specific // properties into os type specific properties. func (osInfo *osTypeSpecificInfo) optimizeProperties(ctx *memberContext, commonValueExtractor *commonValueExtractor) { @@ -1411,7 +1451,7 @@ type archTypeSpecificInfo struct { archType android.ArchType osType android.OsType - linkInfos []*linkTypeSpecificInfo + imageVariantInfos []*imageVariantSpecificInfo } var _ propertiesContainer = (*archTypeSpecificInfo)(nil) @@ -1430,17 +1470,17 @@ func newArchSpecificInfo(ctx android.SdkMemberContext, archType android.ArchType if len(archVariants) == 1 { archInfo.Properties.PopulateFromVariant(ctx, archVariants[0]) } else { - // There is more than one variant for this arch type which must be differentiated - // by link type. - for _, linkVariant := range archVariants { - linkType := getLinkType(linkVariant) - if linkType == "" { - panic(fmt.Errorf("expected one arch specific variant as it is not identified by link type but found %d", len(archVariants))) - } else { - linkInfo := newLinkSpecificInfo(ctx, linkType, variantPropertiesFactory, linkVariant) + // Group the variants by image type. + variantsByImage := make(map[string][]android.Module) + for _, variant := range archVariants { + image := variant.ImageVariation().Variation + variantsByImage[image] = append(variantsByImage[image], variant) + } - archInfo.linkInfos = append(archInfo.linkInfos, linkInfo) - } + // Create the image variant info in a fixed order. + for _, imageVariantName := range android.SortedStringKeys(variantsByImage) { + variants := variantsByImage[imageVariantName] + archInfo.imageVariantInfos = append(archInfo.imageVariantInfos, newImageVariantSpecificInfo(ctx, imageVariantName, variantPropertiesFactory, variants)) } } @@ -1471,14 +1511,29 @@ func getLinkType(variant android.Module) string { return linkType } +func (archInfo *archTypeSpecificInfo) pruneUnsupportedProperties(pruner *propertyPruner) { + if len(archInfo.imageVariantInfos) == 0 { + pruner.pruneProperties(archInfo.Properties) + } else { + for _, imageVariantInfo := range archInfo.imageVariantInfos { + imageVariantInfo.pruneUnsupportedProperties(pruner) + } + } +} + // Optimize the properties by extracting common properties from link type specific // properties into arch type specific properties. func (archInfo *archTypeSpecificInfo) optimizeProperties(ctx *memberContext, commonValueExtractor *commonValueExtractor) { - if len(archInfo.linkInfos) == 0 { + if len(archInfo.imageVariantInfos) == 0 { return } - extractCommonProperties(ctx.sdkMemberContext, commonValueExtractor, archInfo.Properties, archInfo.linkInfos) + // Optimize the image variant properties first. + for _, imageVariantInfo := range archInfo.imageVariantInfos { + imageVariantInfo.optimizeProperties(ctx, commonValueExtractor) + } + + extractCommonProperties(ctx.sdkMemberContext, commonValueExtractor, archInfo.Properties, archInfo.imageVariantInfos) } // Add the properties for an arch type to a property set. @@ -1491,16 +1546,114 @@ func (archInfo *archTypeSpecificInfo) addToPropertySet(ctx *memberContext, archP } addSdkMemberPropertiesToSet(ctx, archInfo.Properties, archTypePropertySet) - for _, linkInfo := range archInfo.linkInfos { - linkPropertySet := archTypePropertySet.AddPropertySet(linkInfo.linkType) - addSdkMemberPropertiesToSet(ctx, linkInfo.Properties, linkPropertySet) + for _, imageVariantInfo := range archInfo.imageVariantInfos { + imageVariantInfo.addToPropertySet(ctx, archTypePropertySet) + } +} + +// getPropertySetContents returns the string representation of the contents of a property set, after +// recursively pruning any empty nested property sets. +func getPropertySetContents(propertySet android.BpPropertySet) string { + set := propertySet.(*bpPropertySet) + set.transformContents(pruneEmptySetTransformer{}) + if len(set.properties) != 0 { + contents := &generatedContents{} + contents.Indent() + outputPropertySet(contents, set) + setAsString := contents.content.String() + return setAsString } + return "" } func (archInfo *archTypeSpecificInfo) String() string { return fmt.Sprintf("ArchType{%s}", archInfo.archType) } +type imageVariantSpecificInfo struct { + baseInfo + + imageVariant string + + linkInfos []*linkTypeSpecificInfo +} + +func newImageVariantSpecificInfo(ctx android.SdkMemberContext, imageVariant string, variantPropertiesFactory variantPropertiesFactoryFunc, imageVariants []android.Module) *imageVariantSpecificInfo { + + // Create an image variant specific info into which the variant properties can be copied. + imageInfo := &imageVariantSpecificInfo{imageVariant: imageVariant} + + // Create the properties into which the image variant specific properties will be added. + imageInfo.Properties = variantPropertiesFactory() + + if len(imageVariants) == 1 { + imageInfo.Properties.PopulateFromVariant(ctx, imageVariants[0]) + } else { + // There is more than one variant for this image variant which must be differentiated by link + // type. + for _, linkVariant := range imageVariants { + linkType := getLinkType(linkVariant) + if linkType == "" { + panic(fmt.Errorf("expected one arch specific variant as it is not identified by link type but found %d", len(imageVariants))) + } else { + linkInfo := newLinkSpecificInfo(ctx, linkType, variantPropertiesFactory, linkVariant) + + imageInfo.linkInfos = append(imageInfo.linkInfos, linkInfo) + } + } + } + + return imageInfo +} + +func (imageInfo *imageVariantSpecificInfo) pruneUnsupportedProperties(pruner *propertyPruner) { + if len(imageInfo.linkInfos) == 0 { + pruner.pruneProperties(imageInfo.Properties) + } else { + for _, linkInfo := range imageInfo.linkInfos { + linkInfo.pruneUnsupportedProperties(pruner) + } + } +} + +// Optimize the properties by extracting common properties from link type specific +// properties into arch type specific properties. +func (imageInfo *imageVariantSpecificInfo) optimizeProperties(ctx *memberContext, commonValueExtractor *commonValueExtractor) { + if len(imageInfo.linkInfos) == 0 { + return + } + + extractCommonProperties(ctx.sdkMemberContext, commonValueExtractor, imageInfo.Properties, imageInfo.linkInfos) +} + +// Add the properties for an arch type to a property set. +func (imageInfo *imageVariantSpecificInfo) addToPropertySet(ctx *memberContext, propertySet android.BpPropertySet) { + if imageInfo.imageVariant != android.CoreVariation { + propertySet = propertySet.AddPropertySet(imageInfo.imageVariant) + } + + addSdkMemberPropertiesToSet(ctx, imageInfo.Properties, propertySet) + + for _, linkInfo := range imageInfo.linkInfos { + linkInfo.addToPropertySet(ctx, propertySet) + } + + // If this is for a non-core image variant then make sure that the property set does not contain + // any properties as providing non-core image variant specific properties for prebuilts is not + // currently supported. + if imageInfo.imageVariant != android.CoreVariation { + propertySetContents := getPropertySetContents(propertySet) + if propertySetContents != "" { + ctx.SdkModuleContext().ModuleErrorf("Image variant %q of sdk member %q has properties distinct from other variants; this is not yet supported. The properties are:\n%s", + imageInfo.imageVariant, ctx.name, propertySetContents) + } + } +} + +func (imageInfo *imageVariantSpecificInfo) String() string { + return imageInfo.imageVariant +} + type linkTypeSpecificInfo struct { baseInfo @@ -1524,6 +1677,15 @@ func newLinkSpecificInfo(ctx android.SdkMemberContext, linkType string, variantP return linkInfo } +func (l *linkTypeSpecificInfo) addToPropertySet(ctx *memberContext, propertySet android.BpPropertySet) { + linkPropertySet := propertySet.AddPropertySet(l.linkType) + addSdkMemberPropertiesToSet(ctx, l.Properties, linkPropertySet) +} + +func (l *linkTypeSpecificInfo) pruneUnsupportedProperties(pruner *propertyPruner) { + pruner.pruneProperties(l.Properties) +} + func (l *linkTypeSpecificInfo) String() string { return fmt.Sprintf("LinkType{%s}", l.linkType) } @@ -1556,12 +1718,13 @@ func (s *sdk) createMemberSnapshot(ctx *memberContext, member *sdkMember, bpModu memberType := member.memberType // Do not add the prefer property if the member snapshot module is a source module type. + config := ctx.sdkMemberContext.Config() if !memberType.UsesSourceModuleTypeInSnapshot() { // Set the prefer based on the environment variable. This is a temporary work around to allow a // snapshot to be created that sets prefer: true. // TODO(b/174997203): Remove once the ability to select the modules to prefer can be done // dynamically at build time not at snapshot generation time. - prefer := ctx.sdkMemberContext.Config().IsEnvTrue("SOONG_SDK_SNAPSHOT_PREFER") + prefer := config.IsEnvTrue("SOONG_SDK_SNAPSHOT_PREFER") // Set prefer. Setting this to false is not strictly required as that is the default but it does // provide a convenient hook to post-process the generated Android.bp file, e.g. in tests to @@ -1592,6 +1755,11 @@ func (s *sdk) createMemberSnapshot(ctx *memberContext, member *sdkMember, bpModu commonProperties := variantPropertiesFactory() commonProperties.Base().Os = android.CommonOS + // Create a property pruner that will prune any properties unsupported by the target build + // release. + targetBuildRelease := ctx.builder.targetBuildRelease + unsupportedPropertyPruner := newPropertyPrunerByBuildRelease(commonProperties, targetBuildRelease) + // Create common value extractor that can be used to optimize the properties. commonValueExtractor := newCommonValueExtractor(commonProperties) @@ -1606,6 +1774,8 @@ func (s *sdk) createMemberSnapshot(ctx *memberContext, member *sdkMember, bpModu // independent properties structs. osSpecificPropertiesContainers = append(osSpecificPropertiesContainers, osInfo) + osInfo.pruneUnsupportedProperties(unsupportedPropertyPruner) + // Optimize the properties across all the variants for a specific os type. osInfo.optimizeProperties(ctx, commonValueExtractor) } diff --git a/ui/build/dumpvars.go b/ui/build/dumpvars.go index 54aeda082..248ad50d7 100644 --- a/ui/build/dumpvars.go +++ b/ui/build/dumpvars.go @@ -163,6 +163,7 @@ var BannerVars = []string{ "AUX_OS_VARIANT_LIST", "PRODUCT_SOONG_NAMESPACES", "SOONG_SDK_SNAPSHOT_PREFER", + "SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE", "SOONG_SDK_SNAPSHOT_VERSION", } |