aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-03-15 18:57:52 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-03-15 18:57:52 +0000
commitf27da9d76c5091aeef76cecef11eb585ea40eab9 (patch)
treea685f22f635368151794a610ad7dcdf1ecf5cb92
parent25af3923bd87276c96de3aa9237ed683e15af95d (diff)
parent93457734287e785474d21ba07ba6cade3464cb04 (diff)
downloadsoong-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.go11
-rw-r--r--apex/apex.go13
-rw-r--r--apex/apex_test.go9
-rw-r--r--bpf/bpf.go14
-rw-r--r--java/bootclasspath_fragment.go72
-rw-r--r--java/dex.go39
-rw-r--r--java/sdk_library.go8
-rw-r--r--java/sdk_library_test.go9
-rw-r--r--java/systemserver_classpath_fragment.go26
-rw-r--r--java/systemserver_classpath_fragment_test.go2
-rw-r--r--rust/rust.go10
-rw-r--r--sdk/Android.bp2
-rw-r--r--sdk/build_release.go388
-rw-r--r--sdk/build_release_test.go227
-rw-r--r--sdk/java_sdk_test.go52
-rw-r--r--sdk/sdk.go8
-rw-r--r--sdk/sdk_test.go86
-rw-r--r--sdk/testing.go11
-rw-r--r--sdk/update.go206
-rw-r--r--ui/build/dumpvars.go1
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",
}