Merge "Add binary properties to python_defaults and add modern_python_path_defaults"
diff --git a/android/bazel_paths.go b/android/bazel_paths.go
index 0d38bda..9c50098 100644
--- a/android/bazel_paths.go
+++ b/android/bazel_paths.go
@@ -206,9 +206,9 @@
 //
 // A package boundary is determined by a BUILD file in the directory. This can happen in 2 cases:
 //
-// 1. An Android.bp exists, which bp2build will always convert to a sibling BUILD file.
-// 2. An Android.bp doesn't exist, but a checked-in BUILD/BUILD.bazel file exists, and that file
-//    is allowlisted by the bp2build configuration to be merged into the symlink forest workspace.
+//  1. An Android.bp exists, which bp2build will always convert to a sibling BUILD file.
+//  2. An Android.bp doesn't exist, but a checked-in BUILD/BUILD.bazel file exists, and that file
+//     is allowlisted by the bp2build configuration to be merged into the symlink forest workspace.
 func isPackageBoundary(config Config, prefix string, components []string, componentIndex int) bool {
 	prefix = filepath.Join(prefix, filepath.Join(components[:componentIndex+1]...))
 	if exists, _, _ := config.fs.Exists(filepath.Join(prefix, "Android.bp")); exists {
@@ -248,9 +248,29 @@
 		newPath.Label = path.Label
 		return newPath
 	}
-
-	newLabel := ""
+	if strings.HasPrefix(path.Label, "./") {
+		// Drop "./" for consistent handling of paths.
+		// Specifically, to not let "." be considered a package boundary.
+		// Say `inputPath` is `x/Android.bp` and that file has some module
+		// with `srcs=["y/a.c", "z/b.c"]`.
+		// And say the directory tree is:
+		//     x
+		//     ├── Android.bp
+		//     ├── y
+		//     │   ├── a.c
+		//     │   └── Android.bp
+		//     └── z
+		//         └── b.c
+		// Then bazel equivalent labels in srcs should be:
+		//   //x/y:a.c, x/z/b.c
+		// The above should still be the case if `x/Android.bp` had
+		//   srcs=["./y/a.c", "./z/b.c"]
+		// However, if we didn't strip "./", we'd get
+		//   //x/./y:a.c, //x/.:z/b.c
+		path.Label = strings.TrimPrefix(path.Label, "./")
+	}
 	pathComponents := strings.Split(path.Label, "/")
+	newLabel := ""
 	foundPackageBoundary := false
 	// Check the deepest subdirectory first and work upwards
 	for i := len(pathComponents) - 1; i >= 0; i-- {
diff --git a/android/bazel_paths_test.go b/android/bazel_paths_test.go
index b047511..450bf76 100644
--- a/android/bazel_paths_test.go
+++ b/android/bazel_paths_test.go
@@ -17,6 +17,10 @@
 import (
 	"path/filepath"
 	"testing"
+
+	"android/soong/bazel"
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/pathtools"
 )
 
 type TestBazelPathContext struct{}
@@ -29,7 +33,7 @@
 	return cfg
 }
 
-func (*TestBazelPathContext) AddNinjaFileDeps(deps ...string) {
+func (*TestBazelPathContext) AddNinjaFileDeps(...string) {
 	panic("Unimplemented")
 }
 
@@ -106,3 +110,74 @@
 		t.Errorf("incorrect OutputPath.Rel(): expected %q, got %q", expectedRelPath, out.Rel())
 	}
 }
+
+type TestBazelConversionPathContext struct {
+	TestBazelConversionContext
+	moduleDir string
+	cfg       Config
+}
+
+func (ctx *TestBazelConversionPathContext) AddNinjaFileDeps(...string) {
+	panic("Unimplemented")
+}
+
+func (ctx *TestBazelConversionPathContext) GlobWithDeps(string, []string) ([]string, error) {
+	panic("Unimplemented")
+}
+
+func (ctx *TestBazelConversionPathContext) PropertyErrorf(string, string, ...interface{}) {
+	panic("Unimplemented")
+}
+
+func (ctx *TestBazelConversionPathContext) GetDirectDep(string) (blueprint.Module, blueprint.DependencyTag) {
+	panic("Unimplemented")
+}
+
+func (ctx *TestBazelConversionPathContext) ModuleFromName(string) (blueprint.Module, bool) {
+	panic("Unimplemented")
+}
+
+func (ctx *TestBazelConversionPathContext) AddUnconvertedBp2buildDep(string) {
+	panic("Unimplemented")
+}
+
+func (ctx *TestBazelConversionPathContext) AddMissingBp2buildDep(string) {
+	panic("Unimplemented")
+}
+
+func (ctx *TestBazelConversionPathContext) Module() Module {
+	panic("Unimplemented")
+}
+
+func (ctx *TestBazelConversionPathContext) Config() Config {
+	return ctx.cfg
+}
+
+func (ctx *TestBazelConversionPathContext) ModuleDir() string {
+	return ctx.moduleDir
+}
+
+func TestTransformSubpackagePath(t *testing.T) {
+	cfg := NullConfig("out", "out/soong")
+	cfg.fs = pathtools.MockFs(map[string][]byte{
+		"x/Android.bp":   nil,
+		"x/y/Android.bp": nil,
+	})
+
+	var ctx BazelConversionPathContext = &TestBazelConversionPathContext{
+		moduleDir: "x",
+		cfg:       cfg,
+	}
+	pairs := map[string]string{
+		"y/a.c":   "//x/y:a.c",
+		"./y/a.c": "//x/y:a.c",
+		"z/b.c":   "z/b.c",
+		"./z/b.c": "z/b.c",
+	}
+	for in, out := range pairs {
+		actual := transformSubpackagePath(ctx, bazel.Label{Label: in}).Label
+		if actual != out {
+			t.Errorf("expected:\n%v\nactual:\n%v", out, actual)
+		}
+	}
+}
diff --git a/android/sdk.go b/android/sdk.go
index a9cc547..bd2f5d1 100644
--- a/android/sdk.go
+++ b/android/sdk.go
@@ -735,8 +735,13 @@
 	//   have common values. Those fields are cleared and the common value added to the common
 	//   properties.
 	//
-	//   A field annotated with a tag of `sdk:"keep"` will be treated as if it
-	//   was not capitalized, i.e. not optimized for common values.
+	//   A field annotated with a tag of `sdk:"ignore"` will be treated as if it
+	//   was not capitalized, i.e. ignored and not optimized for common values.
+	//
+	//   A field annotated with a tag of `sdk:"keep"` will not be cleared even if the value is common
+	//   across multiple structs. Common values will still be copied into the common property struct.
+	//   So, if the same value is placed in all structs populated from variants that value would be
+	//   copied into all common property structs and so be available in every instance.
 	//
 	//   A field annotated with a tag of `android:"arch_variant"` will be allowed to have
 	//   values that differ by arch, fields not tagged as such must have common values across
@@ -923,18 +928,18 @@
 	// the locations of any of their prebuilt files in the snapshot by os type to prevent them
 	// from colliding. See OsPrefix().
 	//
-	// This property is the same for all variants of a member and so would be optimized away
-	// if it was not explicitly kept.
-	Os_count int `sdk:"keep"`
+	// Ignore this property during optimization. This is needed because this property is the same for
+	// all variants of a member and so would be optimized away if it was not ignored.
+	Os_count int `sdk:"ignore"`
 
 	// The os type for which these properties refer.
 	//
 	// Provided to allow a member to differentiate between os types in the locations of their
 	// prebuilt files when it supports more than one os type.
 	//
-	// This property is the same for all os type specific variants of a member and so would be
-	// optimized away if it was not explicitly kept.
-	Os OsType `sdk:"keep"`
+	// Ignore this property during optimization. This is needed because this property is the same for
+	// all variants of a member and so would be optimized away if it was not ignored.
+	Os OsType `sdk:"ignore"`
 
 	// The setting to use for the compile_multilib property.
 	Compile_multilib string `android:"arch_variant"`
diff --git a/bp2build/build_conversion_test.go b/bp2build/build_conversion_test.go
index 9f4f7c1..d29eb9c 100644
--- a/bp2build/build_conversion_test.go
+++ b/bp2build/build_conversion_test.go
@@ -971,6 +971,24 @@
 			},
 		},
 		{
+			Description:                "filegroup with dot-slash-prefixed srcs",
+			ModuleTypeUnderTest:        "filegroup",
+			ModuleTypeUnderTestFactory: android.FileGroupFactory,
+			Blueprint: `filegroup {
+    name: "fg_foo",
+    srcs: ["./a", "./b"],
+    bazel_module: { bp2build_available: true },
+}`,
+			ExpectedBazelTargets: []string{
+				MakeBazelTargetNoRestrictions("filegroup", "fg_foo", map[string]string{
+					"srcs": `[
+        "a",
+        "b",
+    ]`,
+				}),
+			},
+		},
+		{
 			Description:                "filegroup with excludes srcs",
 			ModuleTypeUnderTest:        "filegroup",
 			ModuleTypeUnderTestFactory: android.FileGroupFactory,
@@ -1035,6 +1053,29 @@
 }`,
 			},
 		},
+		{
+			Description:                "depends_on_other_missing_module_error",
+			ModuleTypeUnderTest:        "filegroup",
+			ModuleTypeUnderTestFactory: android.FileGroupFactory,
+			UnconvertedDepsMode:        errorModulesUnconvertedDeps,
+			Blueprint: `filegroup {
+    name: "foobar",
+    srcs: [
+        "c",
+        "//other:foo",
+        "//other:goo",
+    ],
+    bazel_module: { bp2build_available: true },
+}`,
+			ExpectedErr: fmt.Errorf(`filegroup .:foobar depends on missing modules: //other:goo`),
+			Filesystem: map[string]string{"other/Android.bp": `filegroup {
+    name: "foo",
+    srcs: ["a"],
+    bazel_module: { bp2build_available: true },
+}
+`,
+			},
+		},
 	}
 
 	for _, testCase := range testCases {
@@ -1044,8 +1085,6 @@
 	}
 }
 
-type bp2buildMutator = func(android.TopDownMutatorContext)
-
 func TestAllowlistingBp2buildTargetsExplicitly(t *testing.T) {
 	testCases := []struct {
 		moduleTypeUnderTest        string
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 8f499b1..d188133 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -539,7 +539,7 @@
 	}
 
 	// TODO: determines whether to create HTML doc or not
-	//Html_doc *bool
+	// Html_doc *bool
 }
 
 // Paths to outputs from java_sdk_library and java_sdk_library_import.
@@ -1354,7 +1354,7 @@
 	// Provide additional information for inclusion in an sdk's generated .info file.
 	additionalSdkInfo := map[string]interface{}{}
 	additionalSdkInfo["dist_stem"] = module.distStem()
-	baseModuleName := module.BaseModuleName()
+	baseModuleName := module.distStem()
 	scopes := map[string]interface{}{}
 	additionalSdkInfo["scopes"] = scopes
 	for scope, scopePaths := range module.scopePaths {
@@ -2904,6 +2904,18 @@
 type sdkLibrarySdkMemberProperties struct {
 	android.SdkMemberPropertiesBase
 
+	// Stem name for files in the sdk snapshot.
+	//
+	// This is used to construct the path names of various sdk library files in the sdk snapshot to
+	// make sure that they match the finalized versions of those files in prebuilts/sdk.
+	//
+	// This property is marked as keep so that it will be kept in all instances of this struct, will
+	// not be cleared but will be copied to common structs. That is needed because this field is used
+	// to construct many file names for other parts of this struct and so it needs to be present in
+	// all structs. If it was not marked as keep then it would be cleared in some structs and so would
+	// be unavailable for generating file names if there were other properties that were still set.
+	Stem string `sdk:"keep"`
+
 	// Scope to per scope properties.
 	Scopes map[*apiScope]*scopeProperties
 
@@ -2965,6 +2977,9 @@
 func (s *sdkLibrarySdkMemberProperties) PopulateFromVariant(ctx android.SdkMemberContext, variant android.Module) {
 	sdk := variant.(*SdkLibrary)
 
+	// Copy the stem name for files in the sdk snapshot.
+	s.Stem = sdk.distStem()
+
 	s.Scopes = make(map[*apiScope]*scopeProperties)
 	for _, apiScope := range allApiScopes {
 		paths := sdk.findScopePaths(apiScope)
@@ -3017,6 +3032,8 @@
 		propertySet.AddProperty("permitted_packages", s.Permitted_packages)
 	}
 
+	stem := s.Stem
+
 	for _, apiScope := range allApiScopes {
 		if properties, ok := s.Scopes[apiScope]; ok {
 			scopeSet := propertySet.AddPropertySet(apiScope.propertyName)
@@ -3025,7 +3042,7 @@
 
 			var jars []string
 			for _, p := range properties.Jars {
-				dest := filepath.Join(scopeDir, ctx.Name()+"-stubs.jar")
+				dest := filepath.Join(scopeDir, stem+"-stubs.jar")
 				ctx.SnapshotBuilder().CopyToSnapshot(p, dest)
 				jars = append(jars, dest)
 			}
@@ -3033,31 +3050,31 @@
 
 			if ctx.SdkModuleContext().Config().IsEnvTrue("SOONG_SDK_SNAPSHOT_USE_SRCJAR") {
 				// Copy the stubs source jar into the snapshot zip as is.
-				srcJarSnapshotPath := filepath.Join(scopeDir, ctx.Name()+".srcjar")
+				srcJarSnapshotPath := filepath.Join(scopeDir, stem+".srcjar")
 				ctx.SnapshotBuilder().CopyToSnapshot(properties.StubsSrcJar, srcJarSnapshotPath)
 				scopeSet.AddProperty("stub_srcs", []string{srcJarSnapshotPath})
 			} else {
 				// Merge the stubs source jar into the snapshot zip so that when it is unpacked
 				// the source files are also unpacked.
-				snapshotRelativeDir := filepath.Join(scopeDir, ctx.Name()+"_stub_sources")
+				snapshotRelativeDir := filepath.Join(scopeDir, stem+"_stub_sources")
 				ctx.SnapshotBuilder().UnzipToSnapshot(properties.StubsSrcJar, snapshotRelativeDir)
 				scopeSet.AddProperty("stub_srcs", []string{snapshotRelativeDir})
 			}
 
 			if properties.CurrentApiFile != nil {
-				currentApiSnapshotPath := apiScope.snapshotRelativeCurrentApiTxtPath(ctx.Name())
+				currentApiSnapshotPath := apiScope.snapshotRelativeCurrentApiTxtPath(stem)
 				ctx.SnapshotBuilder().CopyToSnapshot(properties.CurrentApiFile, currentApiSnapshotPath)
 				scopeSet.AddProperty("current_api", currentApiSnapshotPath)
 			}
 
 			if properties.RemovedApiFile != nil {
-				removedApiSnapshotPath := apiScope.snapshotRelativeRemovedApiTxtPath(ctx.Name())
+				removedApiSnapshotPath := apiScope.snapshotRelativeRemovedApiTxtPath(stem)
 				ctx.SnapshotBuilder().CopyToSnapshot(properties.RemovedApiFile, removedApiSnapshotPath)
 				scopeSet.AddProperty("removed_api", removedApiSnapshotPath)
 			}
 
 			if properties.AnnotationsZip != nil {
-				annotationsSnapshotPath := filepath.Join(scopeDir, ctx.Name()+"_annotations.zip")
+				annotationsSnapshotPath := filepath.Join(scopeDir, stem+"_annotations.zip")
 				ctx.SnapshotBuilder().CopyToSnapshot(properties.AnnotationsZip, annotationsSnapshotPath)
 				scopeSet.AddProperty("annotations", annotationsSnapshotPath)
 			}
diff --git a/rust/config/global.go b/rust/config/global.go
index e676837..81aec7e 100644
--- a/rust/config/global.go
+++ b/rust/config/global.go
@@ -24,7 +24,7 @@
 var pctx = android.NewPackageContext("android/soong/rust/config")
 
 var (
-	RustDefaultVersion = "1.63.0"
+	RustDefaultVersion = "1.64.0"
 	RustDefaultBase    = "prebuilts/rust/"
 	DefaultEdition     = "2021"
 	Stdlibs            = []string{
diff --git a/sdk/java_sdk_test.go b/sdk/java_sdk_test.go
index d598834..51903ce3 100644
--- a/sdk/java_sdk_test.go
+++ b/sdk/java_sdk_test.go
@@ -889,6 +889,56 @@
 	)
 }
 
+func TestSnapshotWithJavaSdkLibrary_DistStem(t *testing.T) {
+	result := android.GroupFixturePreparers(prepareForSdkTestWithJavaSdkLibrary).RunTestWithBp(t, `
+		sdk {
+			name: "mysdk",
+			java_sdk_libs: ["myjavalib-foo"],
+		}
+
+		java_sdk_library {
+			name: "myjavalib-foo",
+			apex_available: ["//apex_available:anyapex"],
+			srcs: ["Test.java"],
+			sdk_version: "current",
+			shared_library: false,
+			public: {
+				enabled: true,
+			},
+			dist_stem: "myjavalib",
+		}
+	`)
+
+	CheckSnapshot(t, result, "mysdk", "",
+		checkAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+java_sdk_library_import {
+    name: "myjavalib-foo",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:anyapex"],
+    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-foo.stubs/android_common/javac/myjavalib-foo.stubs.jar -> sdk_library/public/myjavalib-stubs.jar
+.intermediates/myjavalib-foo.stubs.source/android_common/metalava/myjavalib-foo.stubs.source_api.txt -> sdk_library/public/myjavalib.txt
+.intermediates/myjavalib-foo.stubs.source/android_common/metalava/myjavalib-foo.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_UseSrcJar(t *testing.T) {
 	result := android.GroupFixturePreparers(
 		prepareForSdkTestWithJavaSdkLibrary,
diff --git a/sdk/sdk_test.go b/sdk/sdk_test.go
index 8b8e1d7..2f9aee9 100644
--- a/sdk/sdk_test.go
+++ b/sdk/sdk_test.go
@@ -218,15 +218,16 @@
 }
 
 type testPropertiesStruct struct {
-	name        string
-	private     string
-	Public_Kept string `sdk:"keep"`
-	S_Common    string
-	S_Different string `android:"arch_variant"`
-	A_Common    []string
-	A_Different []string `android:"arch_variant"`
-	F_Common    *bool
-	F_Different *bool `android:"arch_variant"`
+	name          string
+	private       string
+	Public_Ignore string `sdk:"ignore"`
+	Public_Keep   string `sdk:"keep"`
+	S_Common      string
+	S_Different   string `android:"arch_variant"`
+	A_Common      []string
+	A_Different   []string `android:"arch_variant"`
+	F_Common      *bool
+	F_Different   *bool `android:"arch_variant"`
 	EmbeddedPropertiesStruct
 }
 
@@ -244,30 +245,32 @@
 	common := &testPropertiesStruct{name: "common"}
 	structs := []propertiesContainer{
 		&testPropertiesStruct{
-			name:        "struct-0",
-			private:     "common",
-			Public_Kept: "common",
-			S_Common:    "common",
-			S_Different: "upper",
-			A_Common:    []string{"first", "second"},
-			A_Different: []string{"alpha", "beta"},
-			F_Common:    proptools.BoolPtr(false),
-			F_Different: proptools.BoolPtr(false),
+			name:          "struct-0",
+			private:       "common",
+			Public_Ignore: "common",
+			Public_Keep:   "keep",
+			S_Common:      "common",
+			S_Different:   "upper",
+			A_Common:      []string{"first", "second"},
+			A_Different:   []string{"alpha", "beta"},
+			F_Common:      proptools.BoolPtr(false),
+			F_Different:   proptools.BoolPtr(false),
 			EmbeddedPropertiesStruct: EmbeddedPropertiesStruct{
 				S_Embedded_Common:    "embedded_common",
 				S_Embedded_Different: "embedded_upper",
 			},
 		},
 		&testPropertiesStruct{
-			name:        "struct-1",
-			private:     "common",
-			Public_Kept: "common",
-			S_Common:    "common",
-			S_Different: "lower",
-			A_Common:    []string{"first", "second"},
-			A_Different: []string{"alpha", "delta"},
-			F_Common:    proptools.BoolPtr(false),
-			F_Different: proptools.BoolPtr(true),
+			name:          "struct-1",
+			private:       "common",
+			Public_Ignore: "common",
+			Public_Keep:   "keep",
+			S_Common:      "common",
+			S_Different:   "lower",
+			A_Common:      []string{"first", "second"},
+			A_Different:   []string{"alpha", "delta"},
+			F_Common:      proptools.BoolPtr(false),
+			F_Different:   proptools.BoolPtr(true),
 			EmbeddedPropertiesStruct: EmbeddedPropertiesStruct{
 				S_Embedded_Common:    "embedded_common",
 				S_Embedded_Different: "embedded_lower",
@@ -282,15 +285,16 @@
 
 	android.AssertDeepEquals(t, "common properties not correct",
 		&testPropertiesStruct{
-			name:        "common",
-			private:     "",
-			Public_Kept: "",
-			S_Common:    "common",
-			S_Different: "",
-			A_Common:    []string{"first", "second"},
-			A_Different: []string(nil),
-			F_Common:    proptools.BoolPtr(false),
-			F_Different: nil,
+			name:          "common",
+			private:       "",
+			Public_Ignore: "",
+			Public_Keep:   "keep",
+			S_Common:      "common",
+			S_Different:   "",
+			A_Common:      []string{"first", "second"},
+			A_Different:   []string(nil),
+			F_Common:      proptools.BoolPtr(false),
+			F_Different:   nil,
 			EmbeddedPropertiesStruct: EmbeddedPropertiesStruct{
 				S_Embedded_Common:    "embedded_common",
 				S_Embedded_Different: "",
@@ -300,15 +304,16 @@
 
 	android.AssertDeepEquals(t, "updated properties[0] not correct",
 		&testPropertiesStruct{
-			name:        "struct-0",
-			private:     "common",
-			Public_Kept: "common",
-			S_Common:    "",
-			S_Different: "upper",
-			A_Common:    nil,
-			A_Different: []string{"alpha", "beta"},
-			F_Common:    nil,
-			F_Different: proptools.BoolPtr(false),
+			name:          "struct-0",
+			private:       "common",
+			Public_Ignore: "common",
+			Public_Keep:   "keep",
+			S_Common:      "",
+			S_Different:   "upper",
+			A_Common:      nil,
+			A_Different:   []string{"alpha", "beta"},
+			F_Common:      nil,
+			F_Different:   proptools.BoolPtr(false),
 			EmbeddedPropertiesStruct: EmbeddedPropertiesStruct{
 				S_Embedded_Common:    "",
 				S_Embedded_Different: "embedded_upper",
@@ -318,15 +323,16 @@
 
 	android.AssertDeepEquals(t, "updated properties[1] not correct",
 		&testPropertiesStruct{
-			name:        "struct-1",
-			private:     "common",
-			Public_Kept: "common",
-			S_Common:    "",
-			S_Different: "lower",
-			A_Common:    nil,
-			A_Different: []string{"alpha", "delta"},
-			F_Common:    nil,
-			F_Different: proptools.BoolPtr(true),
+			name:          "struct-1",
+			private:       "common",
+			Public_Ignore: "common",
+			Public_Keep:   "keep",
+			S_Common:      "",
+			S_Different:   "lower",
+			A_Common:      nil,
+			A_Different:   []string{"alpha", "delta"},
+			F_Common:      nil,
+			F_Different:   proptools.BoolPtr(true),
 			EmbeddedPropertiesStruct: EmbeddedPropertiesStruct{
 				S_Embedded_Common:    "",
 				S_Embedded_Different: "embedded_lower",
diff --git a/sdk/update.go b/sdk/update.go
index 81f3672..92a13fa 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -2172,6 +2172,11 @@
 	// Retrieves the value on which common value optimization will be performed.
 	getter fieldAccessorFunc
 
+	// True if the field should never be cleared.
+	//
+	// This is set to true if and only if the field is annotated with `sdk:"keep"`.
+	keep bool
+
 	// The empty value for the field.
 	emptyValue reflect.Value
 
@@ -2217,8 +2222,8 @@
 			continue
 		}
 
-		// Ignore fields whose value should be kept.
-		if proptools.HasTag(field, "sdk", "keep") {
+		// Ignore fields tagged with sdk:"ignore".
+		if proptools.HasTag(field, "sdk", "ignore") {
 			continue
 		}
 
@@ -2236,6 +2241,8 @@
 			}
 		}
 
+		keep := proptools.HasTag(field, "sdk", "keep")
+
 		// Save a copy of the field index for use in the function.
 		fieldIndex := f
 
@@ -2275,6 +2282,7 @@
 				name,
 				filter,
 				fieldGetter,
+				keep,
 				reflect.Zero(field.Type),
 				proptools.HasTag(field, "android", "arch_variant"),
 			}
@@ -2394,11 +2402,13 @@
 		if commonValue != nil {
 			emptyValue := property.emptyValue
 			fieldGetter(commonStructValue).Set(*commonValue)
-			for i := 0; i < sliceValue.Len(); i++ {
-				container := sliceValue.Index(i).Interface().(propertiesContainer)
-				itemValue := reflect.ValueOf(container.optimizableProperties())
-				fieldValue := fieldGetter(itemValue)
-				fieldValue.Set(emptyValue)
+			if !property.keep {
+				for i := 0; i < sliceValue.Len(); i++ {
+					container := sliceValue.Index(i).Interface().(propertiesContainer)
+					itemValue := reflect.ValueOf(container.optimizableProperties())
+					fieldValue := fieldGetter(itemValue)
+					fieldValue.Set(emptyValue)
+				}
 			}
 		}
 
diff --git a/ui/build/build.go b/ui/build/build.go
index ff2d5f2..ab8cd56 100644
--- a/ui/build/build.go
+++ b/ui/build/build.go
@@ -249,41 +249,7 @@
 
 	SetupPath(ctx, config)
 
-	what := RunAll
-	if config.Checkbuild() {
-		what |= RunBuildTests
-	}
-	if config.SkipConfig() {
-		ctx.Verboseln("Skipping Config as requested")
-		what = what &^ RunProductConfig
-	}
-	if config.SkipKati() {
-		ctx.Verboseln("Skipping Kati as requested")
-		what = what &^ RunKati
-	}
-	if config.SkipKatiNinja() {
-		ctx.Verboseln("Skipping use of Kati ninja as requested")
-		what = what &^ RunKatiNinja
-	}
-	if config.SkipSoong() {
-		ctx.Verboseln("Skipping use of Soong as requested")
-		what = what &^ RunSoong
-	}
-
-	if config.SkipNinja() {
-		ctx.Verboseln("Skipping Ninja as requested")
-		what = what &^ RunNinja
-	}
-
-	if !config.SoongBuildInvocationNeeded() {
-		// This means that the output of soong_build is not needed and thus it would
-		// run unnecessarily. In addition, if this code wasn't there invocations
-		// with only special-cased target names like "m bp2build" would result in
-		// passing Ninja the empty target list and it would then build the default
-		// targets which is not what the user asked for.
-		what = what &^ RunNinja
-		what = what &^ RunKati
-	}
+	what := evaluateWhatToRun(config, ctx.Verboseln)
 
 	if config.StartGoma() {
 		startGoma(ctx, config)
@@ -359,6 +325,46 @@
 	}
 }
 
+func evaluateWhatToRun(config Config, verboseln func(v ...interface{})) int {
+	//evaluate what to run
+	what := RunAll
+	if config.Checkbuild() {
+		what |= RunBuildTests
+	}
+	if config.SkipConfig() {
+		verboseln("Skipping Config as requested")
+		what = what &^ RunProductConfig
+	}
+	if config.SkipKati() {
+		verboseln("Skipping Kati as requested")
+		what = what &^ RunKati
+	}
+	if config.SkipKatiNinja() {
+		verboseln("Skipping use of Kati ninja as requested")
+		what = what &^ RunKatiNinja
+	}
+	if config.SkipSoong() {
+		verboseln("Skipping use of Soong as requested")
+		what = what &^ RunSoong
+	}
+
+	if config.SkipNinja() {
+		verboseln("Skipping Ninja as requested")
+		what = what &^ RunNinja
+	}
+
+	if !config.SoongBuildInvocationNeeded() {
+		// This means that the output of soong_build is not needed and thus it would
+		// run unnecessarily. In addition, if this code wasn't there invocations
+		// with only special-cased target names like "m bp2build" would result in
+		// passing Ninja the empty target list and it would then build the default
+		// targets which is not what the user asked for.
+		what = what &^ RunNinja
+		what = what &^ RunKati
+	}
+	return what
+}
+
 var distWaitGroup sync.WaitGroup
 
 // waitForDist waits for all backgrounded distGzipFile and distFile writes to finish