Merge "Validate aconfig libs are built with the correct modes." into main
diff --git a/android/Android.bp b/android/Android.bp
index e73f355..03619f4 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -16,7 +16,6 @@
         "soong-remoteexec",
         "soong-response",
         "soong-shared",
-        "soong-starlark",
         "soong-starlark-format",
         "soong-ui-metrics_proto",
         "soong-android-allowlists",
@@ -135,6 +134,7 @@
         "rule_builder_test.go",
         "sdk_version_test.go",
         "sdk_test.go",
+        "selects_test.go",
         "singleton_module_test.go",
         "soong_config_modules_test.go",
         "util_test.go",
diff --git a/android/apex_contributions.go b/android/apex_contributions.go
index c388aff..c76d9c2 100644
--- a/android/apex_contributions.go
+++ b/android/apex_contributions.go
@@ -27,11 +27,13 @@
 
 func RegisterApexContributionsBuildComponents(ctx RegistrationContext) {
 	ctx.RegisterModuleType("apex_contributions", apexContributionsFactory)
+	ctx.RegisterModuleType("apex_contributions_defaults", apexContributionsDefaultsFactory)
 	ctx.RegisterSingletonModuleType("all_apex_contributions", allApexContributionsFactory)
 }
 
 type apexContributions struct {
 	ModuleBase
+	DefaultableModuleBase
 	properties contributionProps
 }
 
@@ -61,6 +63,7 @@
 	module := &apexContributions{}
 	module.AddProperties(&module.properties)
 	InitAndroidModule(module)
+	InitDefaultableModule(module)
 	return module
 }
 
@@ -70,6 +73,18 @@
 func (m *apexContributions) GenerateAndroidBuildActions(ctx ModuleContext) {
 }
 
+type apexContributionsDefaults struct {
+	ModuleBase
+	DefaultsModuleBase
+}
+
+func apexContributionsDefaultsFactory() Module {
+	module := &apexContributionsDefaults{}
+	module.AddProperties(&contributionProps{})
+	InitDefaultsModule(module)
+	return module
+}
+
 // A container for apex_contributions.
 // Based on product_config, it will create a dependency on the selected
 // apex_contributions per mainline module
diff --git a/android/api_levels.go b/android/api_levels.go
index 6fa4a0e..1130c3e 100644
--- a/android/api_levels.go
+++ b/android/api_levels.go
@@ -15,7 +15,6 @@
 package android
 
 import (
-	"android/soong/starlark_import"
 	"encoding/json"
 	"fmt"
 	"strconv"
@@ -440,7 +439,28 @@
 }
 
 func getApiLevelsMapReleasedVersions() (map[string]int, error) {
-	return starlark_import.GetStarlarkValue[map[string]int]("api_levels_released_versions")
+	return map[string]int{
+		"G":              9,
+		"I":              14,
+		"J":              16,
+		"J-MR1":          17,
+		"J-MR2":          18,
+		"K":              19,
+		"L":              21,
+		"L-MR1":          22,
+		"M":              23,
+		"N":              24,
+		"N-MR1":          25,
+		"O":              26,
+		"O-MR1":          27,
+		"P":              28,
+		"Q":              29,
+		"R":              30,
+		"S":              31,
+		"S-V2":           32,
+		"Tiramisu":       33,
+		"UpsideDownCake": 34,
+	}, nil
 }
 
 var finalCodenamesMapKey = NewOnceKey("FinalCodenamesMap")
diff --git a/android/config.go b/android/config.go
index 951aed1..567ebd8 100644
--- a/android/config.go
+++ b/android/config.go
@@ -639,9 +639,12 @@
 		"framework-adservices":              {},
 		"framework-appsearch":               {},
 		"framework-bluetooth":               {},
+		"framework-configinfrastructure":    {},
 		"framework-connectivity":            {},
 		"framework-connectivity-t":          {},
+		"framework-devicelock":              {},
 		"framework-graphics":                {},
+		"framework-healthfitness":           {},
 		"framework-location":                {},
 		"framework-media":                   {},
 		"framework-mediaprovider":           {},
@@ -1343,13 +1346,16 @@
 		panic(fmt.Errorf("Cannot parse vendor API level %s to an integer: %s",
 			c.VendorApiLevel(), err))
 	}
-	if vendorApiLevel < 202404 || vendorApiLevel%100 != 4 {
-		panic("Unknown vendor API level " + c.VendorApiLevel())
-	}
 	// The version before trunk stable is 34.
 	if vendorApiLevel == 202404 {
 		return "34"
 	}
+	if vendorApiLevel >= 1 && vendorApiLevel <= 34 {
+		return strconv.Itoa(vendorApiLevel - 1)
+	}
+	if vendorApiLevel < 202404 || vendorApiLevel%100 != 4 {
+		panic("Unknown vendor API level " + c.VendorApiLevel())
+	}
 	return strconv.Itoa(vendorApiLevel - 100)
 }
 
diff --git a/android/module_context.go b/android/module_context.go
index 1cab630..3fc5d01 100644
--- a/android/module_context.go
+++ b/android/module_context.go
@@ -21,6 +21,7 @@
 	"strings"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/parser"
 	"github.com/google/blueprint/proptools"
 )
 
@@ -212,6 +213,10 @@
 	// GenerateAndroidBuildActions.  If it is called then the struct will be written out and included in
 	// the module-info.json generated by Make, and Make will not generate its own data for this module.
 	ModuleInfoJSON() *ModuleInfoJSON
+
+	// EvaluateConfiguration makes ModuleContext a valid proptools.ConfigurableEvaluator, so this context
+	// can be used to evaluate the final value of Configurable properties.
+	EvaluateConfiguration(parser.SelectType, string) (string, bool)
 }
 
 type moduleContext struct {
@@ -714,3 +719,32 @@
 func (m *moduleContext) TargetRequiredModuleNames() []string {
 	return m.module.TargetRequiredModuleNames()
 }
+
+func (m *moduleContext) EvaluateConfiguration(ty parser.SelectType, condition string) (string, bool) {
+	switch ty {
+	case parser.SelectTypeReleaseVariable:
+		if v, ok := m.Config().productVariables.BuildFlags[condition]; ok {
+			return v, true
+		}
+		return "", false
+	case parser.SelectTypeProductVariable:
+		// TODO: Might add these on a case-by-case basis
+		m.ModuleErrorf("TODO(b/323382414): Product variables are not yet supported in selects")
+		return "", false
+	case parser.SelectTypeSoongConfigVariable:
+		parts := strings.Split(condition, ":")
+		namespace := parts[0]
+		variable := parts[1]
+		if n, ok := m.Config().productVariables.VendorVars[namespace]; ok {
+			if v, ok := n[variable]; ok {
+				return v, true
+			}
+		}
+		return "", false
+	case parser.SelectTypeVariant:
+		m.ModuleErrorf("TODO(b/323382414): Variants are not yet supported in selects")
+		return "", false
+	default:
+		panic("Should be unreachable")
+	}
+}
diff --git a/android/ninja_deps.go b/android/ninja_deps.go
index 1d50a47..bdf465e 100644
--- a/android/ninja_deps.go
+++ b/android/ninja_deps.go
@@ -15,7 +15,6 @@
 package android
 
 import (
-	"android/soong/starlark_import"
 	"sort"
 )
 
@@ -43,11 +42,4 @@
 
 func (ninjaDepsSingleton) GenerateBuildActions(ctx SingletonContext) {
 	ctx.AddNinjaFileDeps(ctx.Config().ninjaFileDeps()...)
-
-	deps, err := starlark_import.GetNinjaDeps()
-	if err != nil {
-		ctx.Errorf("Error running starlark code: %s", err)
-	} else {
-		ctx.AddNinjaFileDeps(deps...)
-	}
 }
diff --git a/android/selects_test.go b/android/selects_test.go
new file mode 100644
index 0000000..dca3789
--- /dev/null
+++ b/android/selects_test.go
@@ -0,0 +1,282 @@
+// Copyright 2024 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import (
+	"fmt"
+	"reflect"
+	"testing"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+)
+
+func TestSelects(t *testing.T) {
+	testCases := []struct {
+		name          string
+		bp            string
+		provider      selectsTestProvider
+		vendorVars    map[string]map[string]string
+		expectedError string
+	}{
+		{
+			name: "basic string list",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_string_list: select(soong_config_variable("my_namespace", "my_variable"), {
+					"a": ["a.cpp"],
+					"b": ["b.cpp"],
+					_: ["c.cpp"],
+				}),
+			}
+			`,
+			provider: selectsTestProvider{
+				my_string_list: &[]string{"c.cpp"},
+			},
+		},
+		{
+			name: "basic string",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
+					"a": "a.cpp",
+					"b": "b.cpp",
+					_: "c.cpp",
+				}),
+			}
+			`,
+			provider: selectsTestProvider{
+				my_string: proptools.StringPtr("c.cpp"),
+			},
+		},
+		{
+			name: "basic bool",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_bool: select(soong_config_variable("my_namespace", "my_variable"), {
+					"a": true,
+					"b": false,
+					_: true,
+				}),
+			}
+			`,
+			provider: selectsTestProvider{
+				my_bool: proptools.BoolPtr(true),
+			},
+		},
+		{
+			name: "Differing types",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
+					"a": "a.cpp",
+					"b": true,
+					_: "c.cpp",
+				}),
+			}
+			`,
+			expectedError: `can't assign bool value to string property "my_string\[1\]"`,
+		},
+		{
+			name: "String list non-default",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_string_list: select(soong_config_variable("my_namespace", "my_variable"), {
+					"a": ["a.cpp"],
+					"b": ["b.cpp"],
+					_: ["c.cpp"],
+				}),
+			}
+			`,
+			provider: selectsTestProvider{
+				my_string_list: &[]string{"a.cpp"},
+			},
+			vendorVars: map[string]map[string]string{
+				"my_namespace": {
+					"my_variable": "a",
+				},
+			},
+		},
+		{
+			name: "String list append",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_string_list: select(soong_config_variable("my_namespace", "my_variable"), {
+					"a": ["a.cpp"],
+					"b": ["b.cpp"],
+					_: ["c.cpp"],
+				}) + select(soong_config_variable("my_namespace", "my_variable_2"), {
+					"a2": ["a2.cpp"],
+					"b2": ["b2.cpp"],
+					_: ["c2.cpp"],
+				}),
+			}
+			`,
+			provider: selectsTestProvider{
+				my_string_list: &[]string{"a.cpp", "c2.cpp"},
+			},
+			vendorVars: map[string]map[string]string{
+				"my_namespace": {
+					"my_variable": "a",
+				},
+			},
+		},
+		{
+			name: "String list prepend literal",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_string_list: ["literal.cpp"] + select(soong_config_variable("my_namespace", "my_variable"), {
+					"a2": ["a2.cpp"],
+					"b2": ["b2.cpp"],
+					_: ["c2.cpp"],
+				}),
+			}
+			`,
+			provider: selectsTestProvider{
+				my_string_list: &[]string{"literal.cpp", "c2.cpp"},
+			},
+		},
+		{
+			name: "String list append literal",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_string_list: select(soong_config_variable("my_namespace", "my_variable"), {
+					"a2": ["a2.cpp"],
+					"b2": ["b2.cpp"],
+					_: ["c2.cpp"],
+				}) + ["literal.cpp"],
+			}
+			`,
+			provider: selectsTestProvider{
+				my_string_list: &[]string{"c2.cpp", "literal.cpp"},
+			},
+		},
+		{
+			name: "Can't append bools",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_bool: select(soong_config_variable("my_namespace", "my_variable"), {
+					"a": true,
+					"b": false,
+					_: true,
+				}) + false,
+			}
+			`,
+			expectedError: "my_bool: Cannot append bools",
+		},
+		{
+			name: "Append string",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
+					"a": "a",
+					"b": "b",
+					_: "c",
+				}) + ".cpp",
+			}
+			`,
+			provider: selectsTestProvider{
+				my_string: proptools.StringPtr("c.cpp"),
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			fixtures := GroupFixturePreparers(
+				FixtureRegisterWithContext(func(ctx RegistrationContext) {
+					ctx.RegisterModuleType("my_module_type", newSelectsMockModule)
+				}),
+				FixtureModifyProductVariables(func(variables FixtureProductVariables) {
+					variables.VendorVars = tc.vendorVars
+				}),
+			)
+			if tc.expectedError != "" {
+				fixtures = fixtures.ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(tc.expectedError))
+			}
+			result := fixtures.RunTestWithBp(t, tc.bp)
+
+			if tc.expectedError == "" {
+				m := result.ModuleForTests("foo", "")
+				p, _ := OtherModuleProvider[selectsTestProvider](result.testContext.OtherModuleProviderAdaptor(), m.Module(), selectsTestProviderKey)
+				if !reflect.DeepEqual(p, tc.provider) {
+					t.Errorf("Expected:\n  %q\ngot:\n  %q", tc.provider.String(), p.String())
+				}
+			}
+		})
+	}
+}
+
+type selectsTestProvider struct {
+	my_bool        *bool
+	my_string      *string
+	my_string_list *[]string
+}
+
+func (p *selectsTestProvider) String() string {
+	myBoolStr := "nil"
+	if p.my_bool != nil {
+		myBoolStr = fmt.Sprintf("%t", *p.my_bool)
+	}
+	myStringStr := "nil"
+	if p.my_string != nil {
+		myStringStr = *p.my_string
+	}
+	return fmt.Sprintf(`selectsTestProvider {
+	my_bool: %v,
+	my_string: %s,
+    my_string_list: %s,
+}`, myBoolStr, myStringStr, p.my_string_list)
+}
+
+var selectsTestProviderKey = blueprint.NewProvider[selectsTestProvider]()
+
+type selectsMockModuleProperties struct {
+	My_bool        proptools.Configurable[bool]
+	My_string      proptools.Configurable[string]
+	My_string_list proptools.Configurable[[]string]
+}
+
+type selectsMockModule struct {
+	ModuleBase
+	DefaultableModuleBase
+	properties selectsMockModuleProperties
+}
+
+func (p *selectsMockModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	SetProvider[selectsTestProvider](ctx, selectsTestProviderKey, selectsTestProvider{
+		my_bool:        p.properties.My_bool.Evaluate(ctx),
+		my_string:      p.properties.My_string.Evaluate(ctx),
+		my_string_list: p.properties.My_string_list.Evaluate(ctx),
+	})
+}
+
+func newSelectsMockModule() Module {
+	m := &selectsMockModule{}
+	m.AddProperties(&m.properties)
+	InitAndroidArchModule(m, HostAndDeviceSupported, MultilibCommon)
+	InitDefaultableModule(m)
+	return m
+}
diff --git a/android/updatable_modules.go b/android/updatable_modules.go
index 6d0eeb7..1548170 100644
--- a/android/updatable_modules.go
+++ b/android/updatable_modules.go
@@ -33,4 +33,4 @@
 // * AOSP            - xx9990000
 // * x-mainline-prod - xx9990000
 // * master          - 990090000
-const DefaultUpdatableModuleVersion = "340090000"
+const DefaultUpdatableModuleVersion = "990090000"
diff --git a/apex/apex.go b/apex/apex.go
index d0b3986..7c6fb89 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -2430,6 +2430,33 @@
 
 	// Set a provider for dexpreopt of bootjars
 	a.provideApexExportsInfo(ctx)
+
+	a.providePrebuiltInfo(ctx)
+}
+
+var prebuiltInfoProvider = blueprint.NewProvider[prebuiltInfo]()
+
+// contents of prebuilt_info.json
+type prebuiltInfo struct {
+	// Name of the apex, without the prebuilt_ prefix
+	Name string
+
+	Is_prebuilt bool
+
+	// This is relative to root of the workspace.
+	// In case of mainline modules, this file contains the build_id that was used
+	// to generate the mainline module prebuilt.
+	Prebuilt_info_file_path string `json:",omitempty"`
+}
+
+// Set prebuiltInfoProvider. This will be used by `apex_prebuiltinfo_singleton` to print out a metadata file
+// with information about whether source or prebuilt of an apex was used during the build.
+func (a *apexBundle) providePrebuiltInfo(ctx android.ModuleContext) {
+	info := prebuiltInfo{
+		Name:        a.Name(),
+		Is_prebuilt: false,
+	}
+	android.SetProvider(ctx, prebuiltInfoProvider, info)
 }
 
 // Set a provider containing information about the jars and .prof provided by the apex
diff --git a/apex/apex_singleton.go b/apex/apex_singleton.go
index 25c0cc4..8aaddbe 100644
--- a/apex/apex_singleton.go
+++ b/apex/apex_singleton.go
@@ -17,6 +17,8 @@
 package apex
 
 import (
+	"encoding/json"
+
 	"github.com/google/blueprint"
 
 	"android/soong/android"
@@ -129,3 +131,43 @@
 	// Export check result to Make. The path is added to droidcore.
 	ctx.Strict("APEX_ALLOWED_DEPS_CHECK", s.allowedApexDepsInfoCheckResult.String())
 }
+
+func init() {
+	registerApexPrebuiltInfoComponents(android.InitRegistrationContext)
+}
+
+func registerApexPrebuiltInfoComponents(ctx android.RegistrationContext) {
+	ctx.RegisterParallelSingletonType("apex_prebuiltinfo_singleton", apexPrebuiltInfoFactory)
+}
+
+func apexPrebuiltInfoFactory() android.Singleton {
+	return &apexPrebuiltInfo{}
+}
+
+type apexPrebuiltInfo struct {
+	out android.WritablePath
+}
+
+func (a *apexPrebuiltInfo) GenerateBuildActions(ctx android.SingletonContext) {
+	prebuiltInfos := []prebuiltInfo{}
+
+	ctx.VisitAllModules(func(m android.Module) {
+		prebuiltInfo, exists := android.SingletonModuleProvider(ctx, m, prebuiltInfoProvider)
+		// Use prebuiltInfoProvider to filter out non apex soong modules.
+		// Use HideFromMake to filter out the unselected variants of a specific apex.
+		if exists && !m.IsHideFromMake() {
+			prebuiltInfos = append(prebuiltInfos, prebuiltInfo)
+		}
+	})
+
+	j, err := json.Marshal(prebuiltInfos)
+	if err != nil {
+		ctx.Errorf("Could not convert prebuilt info of apexes to json due to error: %v", err)
+	}
+	a.out = android.PathForOutput(ctx, "prebuilt_info.json")
+	android.WriteFileRule(ctx, a.out, string(j))
+}
+
+func (a *apexPrebuiltInfo) MakeVars(ctx android.MakeVarsContext) {
+	ctx.DistForGoal("droidcore", a.out)
+}
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 54d2d08..19b9d16 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -2068,7 +2068,7 @@
 
 	// Ensure that mylib links with "current" LLNDK
 	libFlags := names(mylib.Rule("ld").Args["libFlags"])
-	ensureListContains(t, libFlags, "out/soong/.intermediates/libbar/"+vendorVariant+"_shared_current/libbar.so")
+	ensureListContains(t, libFlags, "out/soong/.intermediates/libbar/"+vendorVariant+"_shared/libbar.so")
 
 	// Ensure that mylib is targeting 29
 	ccRule := ctx.ModuleForTests("mylib", vendorVariant+"_static_apex29").Output("obj/mylib.o")
diff --git a/apex/prebuilt.go b/apex/prebuilt.go
index cebbae9..34dfc46 100644
--- a/apex/prebuilt.go
+++ b/apex/prebuilt.go
@@ -121,6 +121,11 @@
 	// List of systemserverclasspath fragments inside this prebuilt APEX bundle and for which this
 	// APEX bundle will create an APEX variant.
 	Exported_systemserverclasspath_fragments []string
+
+	// Path to the .prebuilt_info file of the prebuilt apex.
+	// In case of mainline modules, the .prebuilt_info file contains the build_id that was used to
+	// generate the prebuilt.
+	Prebuilt_info *string `android:"path"`
 }
 
 // initPrebuiltCommon initializes the prebuiltCommon structure and performs initialization of the
@@ -819,6 +824,20 @@
 	}
 }
 
+// Set prebuiltInfoProvider. This will be used by `apex_prebuiltinfo_singleton` to print out a metadata file
+// with information about whether source or prebuilt of an apex was used during the build.
+func (p *prebuiltCommon) providePrebuiltInfo(ctx android.ModuleContext) {
+	info := prebuiltInfo{
+		Name:        p.BaseModuleName(), // BaseModuleName ensures that this will not contain the prebuilt_ prefix.
+		Is_prebuilt: true,
+	}
+	// If Prebuilt_info information is available in the soong module definition, add it to prebuilt_info.json.
+	if p.prebuiltCommonProperties.Prebuilt_info != nil {
+		info.Prebuilt_info_file_path = android.PathForModuleSrc(ctx, *p.prebuiltCommonProperties.Prebuilt_info).String()
+	}
+	android.SetProvider(ctx, prebuiltInfoProvider, info)
+}
+
 func (p *Prebuilt) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	p.apexKeysPath = writeApexKeys(ctx, p)
 	// TODO(jungjw): Check the key validity.
@@ -846,6 +865,8 @@
 	// provide info used for generating the boot image
 	p.provideApexExportsInfo(ctx)
 
+	p.providePrebuiltInfo(ctx)
+
 	// Save the files that need to be made available to Make.
 	p.initApexFilesForAndroidMk(ctx)
 
@@ -1068,6 +1089,8 @@
 	// provide info used for generating the boot image
 	a.provideApexExportsInfo(ctx)
 
+	a.providePrebuiltInfo(ctx)
+
 	// Save the files that need to be made available to Make.
 	a.initApexFilesForAndroidMk(ctx)
 
diff --git a/cc/cc_test.go b/cc/cc_test.go
index 6cc500b..d1b728e 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -2680,15 +2680,13 @@
 		}
 	}
 	expected := []string{
-		"android_vendor.29_arm64_armv8-a_shared_current",
 		"android_vendor.29_arm64_armv8-a_shared",
-		"android_vendor.29_arm_armv7-a-neon_shared_current",
 		"android_vendor.29_arm_armv7-a-neon_shared",
 	}
 	android.AssertArrayString(t, "variants for llndk stubs", expected, actual)
 
 	params := result.ModuleForTests("libllndk", "android_vendor.29_arm_armv7-a-neon_shared").Description("generate stub")
-	android.AssertSame(t, "use VNDK version for default stubs", "current", params.Args["apiLevel"])
+	android.AssertSame(t, "use Vendor API level for default stubs", "202404", params.Args["apiLevel"])
 
 	checkExportedIncludeDirs := func(module, variant string, expectedDirs ...string) {
 		t.Helper()
diff --git a/cc/config/arm64_device.go b/cc/config/arm64_device.go
index 82beb29..0de9e05 100644
--- a/cc/config/arm64_device.go
+++ b/cc/config/arm64_device.go
@@ -51,6 +51,7 @@
 	arm64Ldflags = []string{
 		"-Wl,--hash-style=gnu",
 		"-Wl,-z,separate-code",
+		"-Wl,-z,separate-loadable-segments",
 	}
 
 	arm64Lldflags = arm64Ldflags
diff --git a/cc/config/x86_64_device.go b/cc/config/x86_64_device.go
index b97d511..12119a7 100644
--- a/cc/config/x86_64_device.go
+++ b/cc/config/x86_64_device.go
@@ -31,6 +31,7 @@
 
 	x86_64Ldflags = []string{
 		"-Wl,--hash-style=gnu",
+		"-Wl,-z,separate-loadable-segments",
 	}
 
 	X86_64Lldflags = x86_64Ldflags
diff --git a/cc/library.go b/cc/library.go
index e2b4d4f..5607632 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -677,18 +677,16 @@
 
 func (library *libraryDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) Objects {
 	if ctx.IsLlndk() {
+		vendorApiLevel := ctx.Config().VendorApiLevel()
+		if vendorApiLevel == "" {
+			// TODO(b/321892570): Some tests relying on old fixtures which
+			// doesn't set vendorApiLevel. Needs to fix them.
+			vendorApiLevel = ctx.Config().PlatformSdkVersion().String()
+		}
 		// This is the vendor variant of an LLNDK library, build the LLNDK stubs.
-		vndkVer := ctx.Module().(*Module).VndkVersion()
-		if !inList(vndkVer, ctx.Config().PlatformVersionActiveCodenames()) || vndkVer == "" {
-			// For non-enforcing devices, vndkVer is empty. Use "current" in that case, too.
-			vndkVer = "current"
-		}
-		if library.stubsVersion() != "" {
-			vndkVer = library.stubsVersion()
-		}
 		nativeAbiResult := parseNativeAbiDefinition(ctx,
 			String(library.Properties.Llndk.Symbol_file),
-			android.ApiLevelOrPanic(ctx, vndkVer), "--llndk")
+			android.ApiLevelOrPanic(ctx, vendorApiLevel), "--llndk")
 		objs := compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc)
 		if !Bool(library.Properties.Llndk.Unversioned) {
 			library.versionScriptPath = android.OptionalPathForPath(
@@ -1893,6 +1891,10 @@
 	if library.hasStubsVariants() && library.Properties.Stubs.Symbol_file != nil {
 		return library.Properties.Stubs.Symbol_file
 	}
+	// TODO(b/309880485): Distinguish platform, NDK, LLNDK, and APEX version scripts.
+	if library.baseLinker.Properties.Version_script != nil {
+		return library.baseLinker.Properties.Version_script
+	}
 	return nil
 }
 
@@ -1913,12 +1915,15 @@
 	}
 
 	if library.hasLLNDKStubs() && ctx.Module().(*Module).InVendorOrProduct() {
-		// LLNDK libraries only need a single stubs variant.
-		return []string{android.FutureApiLevel.String()}
+		// LLNDK libraries only need a single stubs variant (""), which is
+		// added automatically in createVersionVariations().
+		return nil
 	}
 
 	// Future API level is implicitly added if there isn't
-	return addCurrentVersionIfNotPresent(library.Properties.Stubs.Versions)
+	versions := addCurrentVersionIfNotPresent(library.Properties.Stubs.Versions)
+	normalizeVersions(ctx, versions)
+	return versions
 }
 
 func addCurrentVersionIfNotPresent(vers []string) []string {
@@ -2290,10 +2295,6 @@
 		return
 	}
 	versions := library.stubsVersions(mctx)
-	if len(versions) <= 0 {
-		return
-	}
-	normalizeVersions(mctx, versions)
 	if mctx.Failed() {
 		return
 	}
diff --git a/cc/ndkstubgen/test_ndkstubgen.py b/cc/ndkstubgen/test_ndkstubgen.py
index 1e0bdf3..22f31d9 100755
--- a/cc/ndkstubgen/test_ndkstubgen.py
+++ b/cc/ndkstubgen/test_ndkstubgen.py
@@ -463,6 +463,98 @@
         """)
         self.assertEqual(expected_version, version_file.getvalue())
 
+    def test_integration_with_llndk(self) -> None:
+        input_file = io.StringIO(textwrap.dedent("""\
+            VERSION_34 { # introduced=34
+                global:
+                    foo;
+                    bar; # llndk
+            };
+            VERSION_35 { # introduced=35
+                global:
+                    wiggle;
+                    waggle;
+                    waggle; # llndk=202404
+                    bubble; # llndk=202404
+                    duddle;
+                    duddle; # llndk=202504
+            } VERSION_34;
+        """))
+        f = copy(self.filter)
+        f.llndk = True
+        f.api = 202404
+        parser = symbolfile.SymbolFileParser(input_file, {}, f)
+        versions = parser.parse()
+
+        src_file = io.StringIO()
+        version_file = io.StringIO()
+        symbol_list_file = io.StringIO()
+
+        generator = ndkstubgen.Generator(src_file,
+                                         version_file, symbol_list_file, f)
+        generator.write(versions)
+
+        expected_src = textwrap.dedent("""\
+            void foo() {}
+            void bar() {}
+            void waggle() {}
+            void bubble() {}
+        """)
+        self.assertEqual(expected_src, src_file.getvalue())
+
+        expected_version = textwrap.dedent("""\
+            VERSION_34 {
+                global:
+                    foo;
+                    bar;
+            };
+            VERSION_35 {
+                global:
+                    waggle;
+                    bubble;
+            } VERSION_34;
+        """)
+        self.assertEqual(expected_version, version_file.getvalue())
+
+    def test_integration_with_llndk_with_single_version_block(self) -> None:
+        input_file = io.StringIO(textwrap.dedent("""\
+            LIBANDROID {
+                global:
+                    foo; # introduced=34
+                    bar; # introduced=35
+                    bar; # llndk=202404
+                    baz; # introduced=35
+            };
+        """))
+        f = copy(self.filter)
+        f.llndk = True
+        f.api = 202404
+        parser = symbolfile.SymbolFileParser(input_file, {}, f)
+        versions = parser.parse()
+
+        src_file = io.StringIO()
+        version_file = io.StringIO()
+        symbol_list_file = io.StringIO()
+
+        generator = ndkstubgen.Generator(src_file,
+                                         version_file, symbol_list_file, f)
+        generator.write(versions)
+
+        expected_src = textwrap.dedent("""\
+            void foo() {}
+            void bar() {}
+        """)
+        self.assertEqual(expected_src, src_file.getvalue())
+
+        expected_version = textwrap.dedent("""\
+            LIBANDROID {
+                global:
+                    foo;
+                    bar;
+            };
+        """)
+        self.assertEqual(expected_version, version_file.getvalue())
+
     def test_empty_stub(self) -> None:
         """Tests that empty stubs can be generated.
 
diff --git a/cc/symbolfile/__init__.py b/cc/symbolfile/__init__.py
index 345e9f9..4553616 100644
--- a/cc/symbolfile/__init__.py
+++ b/cc/symbolfile/__init__.py
@@ -103,13 +103,24 @@
     @property
     def has_llndk_tags(self) -> bool:
         """Returns True if any LL-NDK tags are set."""
-        return 'llndk' in self.tags
+        for tag in self.tags:
+            if tag == 'llndk' or tag.startswith('llndk='):
+                return True
+        return False
 
     @property
     def has_platform_only_tags(self) -> bool:
         """Returns True if any platform-only tags are set."""
         return 'platform-only' in self.tags
 
+    def copy_introduced_from(self, tags: Tags) -> None:
+        """Copies introduced= or introduced-*= tags."""
+        for tag in tags:
+            if tag.startswith('introduced=') or tag.startswith('introduced-'):
+                name, _ = split_tag(tag)
+                if not any(self_tag.startswith(name + '=') for self_tag in self.tags):
+                    self.tags += (tag,)
+
 
 @dataclass
 class Symbol:
@@ -147,6 +158,8 @@
     """Returns true if this tag has an API level that may need decoding."""
     if tag.startswith('llndk-deprecated='):
         return True
+    if tag.startswith('llndk='):
+        return True
     if tag.startswith('introduced='):
         return True
     if tag.startswith('introduced-'):
@@ -237,15 +250,22 @@
 
         This defines the rules shared between version tagging and symbol tagging.
         """
-        # The apex and llndk tags will only exclude APIs from other modes. If in
+        # LLNDK mode/tags follow the similar filtering except that API level checking
+        # is based llndk= instead of introduced=.
+        if self.llndk:
+            if tags.has_mode_tags and not tags.has_llndk_tags:
+                return True
+            if not symbol_in_arch(tags, self.arch):
+                return True
+            if not symbol_in_llndk_api(tags, self.arch, self.api):
+                return True
+            return False
         # APEX or LLNDK mode and neither tag is provided, we fall back to the
         # default behavior because all NDK symbols are implicitly available to
         # APEX and LLNDK.
         if tags.has_mode_tags:
             if self.apex and tags.has_apex_tags:
                 return False
-            if self.llndk and tags.has_llndk_tags:
-                return False
             if self.systemapi and tags.has_systemapi_tags:
                 return False
             return True
@@ -266,6 +286,10 @@
             return True
         if version.tags.has_platform_only_tags:
             return True
+        # Include all versions when targeting LLNDK because LLNDK symbols are self-versioned.
+        # Empty version block will be handled separately.
+        if self.llndk:
+            return False
         return self._should_omit_tags(version.tags)
 
     def should_omit_symbol(self, symbol: Symbol) -> bool:
@@ -292,6 +316,14 @@
     # for the tagged architectures.
     return not has_arch_tags
 
+def symbol_in_llndk_api(tags: Iterable[Tag], arch: Arch, api: int) -> bool:
+    """Returns true if the symbol is present for the given LLNDK API level."""
+    # Check llndk= first.
+    for tag in tags:
+        if tag.startswith('llndk='):
+            return api >= int(get_tag_value(tag))
+    # If not, we keep old behavior: NDK symbols in <= 34 are LLNDK symbols.
+    return symbol_in_api(tags, arch, 34)
 
 def symbol_in_api(tags: Iterable[Tag], arch: Arch, api: int) -> bool:
     """Returns true if the symbol is present for the given API level."""
@@ -368,6 +400,7 @@
                     f'Unexpected contents at top level: {self.current_line}')
 
         self.check_no_duplicate_symbols(versions)
+        self.check_llndk_introduced(versions)
         return versions
 
     def check_no_duplicate_symbols(self, versions: Iterable[Version]) -> None:
@@ -396,6 +429,31 @@
             raise MultiplyDefinedSymbolError(
                 sorted(list(multiply_defined_symbols)))
 
+    def check_llndk_introduced(self, versions: Iterable[Version]) -> None:
+        """Raises errors when llndk= is missing for new llndk symbols."""
+        if not self.filter.llndk:
+            return
+
+        def assert_llndk_with_version(tags: Tags,  name: str) -> None:
+            has_llndk_introduced = False
+            for tag in tags:
+                if tag.startswith('llndk='):
+                    has_llndk_introduced = True
+                    break
+            if not has_llndk_introduced:
+                raise ParseError(f'{name}: missing version. `llndk=yyyymm`')
+
+        arch = self.filter.arch
+        for version in versions:
+            # llndk symbols >= introduced=35 should be tagged
+            # explicitly with llndk=yyyymm.
+            for symbol in version.symbols:
+                if not symbol.tags.has_llndk_tags:
+                    continue
+                if symbol_in_api(symbol.tags, arch, 34):
+                    continue
+                assert_llndk_with_version(symbol.tags, symbol.name)
+
     def parse_version(self) -> Version:
         """Parses a single version section and returns a Version object."""
         assert self.current_line is not None
@@ -429,7 +487,9 @@
                 else:
                     raise ParseError('Unknown visiblity label: ' + visibility)
             elif global_scope and not cpp_symbols:
-                symbols.append(self.parse_symbol())
+                symbol = self.parse_symbol()
+                symbol.tags.copy_introduced_from(tags)
+                symbols.append(symbol)
             else:
                 # We're in a hidden scope or in 'extern "C++"' block. Ignore
                 # everything.
diff --git a/cc/symbolfile/test_symbolfile.py b/cc/symbolfile/test_symbolfile.py
index 83becc2..8b412b9 100644
--- a/cc/symbolfile/test_symbolfile.py
+++ b/cc/symbolfile/test_symbolfile.py
@@ -344,6 +344,45 @@
         self.assertInclude(f_llndk, s_none)
         self.assertInclude(f_llndk, s_llndk)
 
+    def test_omit_llndk_versioned(self) -> None:
+        f_ndk = self.filter
+        f_ndk.api = 35
+
+        f_llndk = copy(f_ndk)
+        f_llndk.llndk = True
+        f_llndk.api = 202404
+
+        s = Symbol('foo', Tags())
+        s_llndk = Symbol('foo', Tags.from_strs(['llndk']))
+        s_llndk_202404 = Symbol('foo', Tags.from_strs(['llndk=202404']))
+        s_34 = Symbol('foo', Tags.from_strs(['introduced=34']))
+        s_34_llndk = Symbol('foo', Tags.from_strs(['introduced=34', 'llndk']))
+        s_35 = Symbol('foo', Tags.from_strs(['introduced=35']))
+        s_35_llndk_202404 = Symbol('foo', Tags.from_strs(['introduced=35', 'llndk=202404']))
+        s_35_llndk_202504 = Symbol('foo', Tags.from_strs(['introduced=35', 'llndk=202504']))
+
+        # When targeting NDK, omit LLNDK tags
+        self.assertInclude(f_ndk, s)
+        self.assertOmit(f_ndk, s_llndk)
+        self.assertOmit(f_ndk, s_llndk_202404)
+        self.assertInclude(f_ndk, s_34)
+        self.assertOmit(f_ndk, s_34_llndk)
+        self.assertInclude(f_ndk, s_35)
+        self.assertOmit(f_ndk, s_35_llndk_202404)
+        self.assertOmit(f_ndk, s_35_llndk_202504)
+
+        # When targeting LLNDK, old symbols without any mode tags are included as LLNDK
+        self.assertInclude(f_llndk, s)
+        # When targeting LLNDK, old symbols with #llndk are included as LLNDK
+        self.assertInclude(f_llndk, s_llndk)
+        self.assertInclude(f_llndk, s_llndk_202404)
+        self.assertInclude(f_llndk, s_34)
+        self.assertInclude(f_llndk, s_34_llndk)
+        # When targeting LLNDK, new symbols(>=35) should be tagged with llndk-introduced=.
+        self.assertOmit(f_llndk, s_35)
+        self.assertInclude(f_llndk, s_35_llndk_202404)
+        self.assertOmit(f_llndk, s_35_llndk_202504)
+
     def test_omit_apex(self) -> None:
         f_none = self.filter
         f_apex = copy(f_none)
@@ -451,9 +490,12 @@
         self.assertIsNone(version.base)
         self.assertEqual(Tags.from_strs(['weak', 'introduced=35']), version.tags)
 
+        # Inherit introduced= tags from version block so that
+        # should_omit_tags() can differently based on introduced API level when treating
+        # LLNDK-available symbols.
         expected_symbols = [
-            Symbol('baz', Tags()),
-            Symbol('qux', Tags.from_strs(['apex', 'llndk'])),
+            Symbol('baz', Tags.from_strs(['introduced=35'])),
+            Symbol('qux', Tags.from_strs(['apex', 'llndk', 'introduced=35'])),
         ]
         self.assertEqual(expected_symbols, version.symbols)
 
@@ -601,6 +643,19 @@
         ]
         self.assertEqual(expected_symbols, version.symbols)
 
+    def test_parse_llndk_version_is_missing(self) -> None:
+        input_file = io.StringIO(textwrap.dedent("""\
+            VERSION_1 { # introduced=35
+                foo;
+                bar; # llndk
+            };
+        """))
+        f = copy(self.filter)
+        f.llndk = True
+        parser = symbolfile.SymbolFileParser(input_file, {}, f)
+        with self.assertRaises(symbolfile.ParseError):
+            parser.parse()
+
 
 def main() -> None:
     suite = unittest.TestLoader().loadTestsFromName(__name__)
diff --git a/cmd/soong_build/queryview.go b/cmd/soong_build/queryview.go
index 5c2316a..eafd67a 100644
--- a/cmd/soong_build/queryview.go
+++ b/cmd/soong_build/queryview.go
@@ -22,7 +22,6 @@
 
 	"android/soong/android"
 	"android/soong/bp2build"
-	"android/soong/starlark_import"
 )
 
 // A helper function to generate a Read-only Bazel workspace in outDir
@@ -47,14 +46,6 @@
 		}
 	}
 
-	// Add starlark deps here, so that they apply to both queryview and apibp2build which
-	// both run this function.
-	starlarkDeps, err2 := starlark_import.GetNinjaDeps()
-	if err2 != nil {
-		return err2
-	}
-	ctx.AddNinjaFileDeps(starlarkDeps...)
-
 	return nil
 }
 
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index 6612a6f..795a0aa 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -311,18 +311,16 @@
 }
 
 func (f *filesystem) buildPropFile(ctx android.ModuleContext) (propFile android.OutputPath, toolDeps android.Paths) {
-	type prop struct {
-		name  string
-		value string
-	}
-
-	var props []prop
 	var deps android.Paths
+	var propFileString strings.Builder
 	addStr := func(name string, value string) {
-		props = append(props, prop{name, value})
+		propFileString.WriteString(name)
+		propFileString.WriteRune('=')
+		propFileString.WriteString(value)
+		propFileString.WriteRune('\n')
 	}
 	addPath := func(name string, path android.Path) {
-		props = append(props, prop{name, path.String()})
+		addStr(name, path.String())
 		deps = append(deps, path)
 	}
 
@@ -376,15 +374,7 @@
 		addStr("hash_seed", uuid)
 	}
 	propFile = android.PathForModuleOut(ctx, "prop").OutputPath
-	builder := android.NewRuleBuilder(pctx, ctx)
-	builder.Command().Text("rm").Flag("-rf").Output(propFile)
-	for _, p := range props {
-		builder.Command().
-			Text("echo").
-			Flag(`"` + p.name + "=" + p.value + `"`).
-			Text(">>").Output(propFile)
-	}
-	builder.Build("build_filesystem_prop", fmt.Sprintf("Creating filesystem props for %s", f.BaseModuleName()))
+	android.WriteFileRuleVerbatim(ctx, propFile, propFileString.String())
 	return propFile, deps
 }
 
diff --git a/java/aapt2.go b/java/aapt2.go
index 445e912..f704fc6 100644
--- a/java/aapt2.go
+++ b/java/aapt2.go
@@ -309,7 +309,8 @@
 
 var aapt2ConvertRule = pctx.AndroidStaticRule("aapt2Convert",
 	blueprint.RuleParams{
-		Command:     `${config.Aapt2Cmd} convert --output-format $format $in -o $out`,
+		Command: `${config.Aapt2Cmd} convert --enable-compact-entries ` +
+			`--output-format $format $in -o $out`,
 		CommandDeps: []string{"${config.Aapt2Cmd}"},
 	}, "format",
 )
diff --git a/java/aar.go b/java/aar.go
index 7cb362a..27dd38b 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -204,6 +204,8 @@
 	// Flags specified in Android.bp
 	linkFlags = append(linkFlags, a.aaptProperties.Aaptflags...)
 
+	linkFlags = append(linkFlags, "--enable-compact-entries")
+
 	// Find implicit or explicit asset and resource dirs
 	assets := android.PathsRelativeToModuleSourceDir(android.SourceInput{
 		Context:     ctx,
@@ -453,6 +455,11 @@
 		// as imports.  The resources from dependencies will not be merged into this module's package-res.apk, and
 		// instead modules depending on this module will reference package-res.apk from all transitive static
 		// dependencies.
+		for _, sharedDep := range sharedDeps {
+			if sharedDep.usedResourceProcessor {
+				transitiveRJars = append(transitiveRJars, sharedDep.rJar)
+			}
+		}
 		for _, staticDep := range staticDeps {
 			linkDeps = append(linkDeps, staticDep.resPackage)
 			linkFlags = append(linkFlags, "-I "+staticDep.resPackage.String())
@@ -460,11 +467,6 @@
 				transitiveRJars = append(transitiveRJars, staticDep.rJar)
 			}
 		}
-		for _, sharedDep := range sharedDeps {
-			if sharedDep.usedResourceProcessor {
-				transitiveRJars = append(transitiveRJars, sharedDep.rJar)
-			}
-		}
 	} else {
 		// When building an app or building a library without ResourceProcessorBusyBox enabled all static
 		// dependencies are compiled into this module's package-res.apk as overlays.
@@ -554,6 +556,10 @@
 	transitiveAaptResourcePackagesFile := android.PathForModuleOut(ctx, "transitive-res-packages")
 	android.WriteFileRule(ctx, transitiveAaptResourcePackagesFile, strings.Join(transitiveAaptResourcePackages, "\n"))
 
+	// Reverse the list of R.jar files so that the current module comes first, and direct dependencies come before
+	// transitive dependencies.
+	transitiveRJars = android.ReversePaths(transitiveRJars)
+
 	a.aaptSrcJar = srcJar
 	a.transitiveAaptRJars = transitiveRJars
 	a.transitiveAaptResourcePackagesFile = transitiveAaptResourcePackagesFile
diff --git a/java/app_test.go b/java/app_test.go
index 28bea0a..5d7b048 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -948,10 +948,10 @@
 			directSrcJars: nil,
 			directClasspath: []string{
 				"out/soong/.intermediates/default/java/android_stubs_current/android_common/turbine-combined/android_stubs_current.jar",
-				"out/soong/.intermediates/transitive_import/android_common/busybox/R.jar",
-				"out/soong/.intermediates/transitive_import_dep/android_common/busybox/R.jar",
-				"out/soong/.intermediates/transitive/android_common/busybox/R.jar",
 				"out/soong/.intermediates/direct/android_common/busybox/R.jar",
+				"out/soong/.intermediates/transitive/android_common/busybox/R.jar",
+				"out/soong/.intermediates/transitive_import_dep/android_common/busybox/R.jar",
+				"out/soong/.intermediates/transitive_import/android_common/busybox/R.jar",
 				"out/soong/.intermediates/transitive/android_common/turbine-combined/transitive.jar",
 				"out/soong/.intermediates/transitive_import/android_common/aar/classes-combined.jar",
 			},
@@ -981,9 +981,9 @@
 			sharedSrcJars: nil,
 			sharedClasspath: []string{
 				"out/soong/.intermediates/default/java/android_stubs_current/android_common/turbine-combined/android_stubs_current.jar",
+				"out/soong/.intermediates/shared/android_common/busybox/R.jar",
 				"out/soong/.intermediates/shared_transitive_static/android_common/busybox/R.jar",
 				"out/soong/.intermediates/shared_transitive_shared/android_common/busybox/R.jar",
-				"out/soong/.intermediates/shared/android_common/busybox/R.jar",
 				"out/soong/.intermediates/shared_transitive_shared/android_common/turbine-combined/shared_transitive_shared.jar",
 				"out/soong/.intermediates/shared_transitive_static/android_common/turbine-combined/shared_transitive_static.jar",
 			},
@@ -1094,9 +1094,9 @@
 			directSrcJars: nil,
 			directClasspath: []string{
 				"out/soong/.intermediates/default/java/android_stubs_current/android_common/turbine-combined/android_stubs_current.jar",
-				"out/soong/.intermediates/transitive_import/android_common/busybox/R.jar",
-				"out/soong/.intermediates/transitive_import_dep/android_common/busybox/R.jar",
 				"out/soong/.intermediates/direct/android_common/busybox/R.jar",
+				"out/soong/.intermediates/transitive_import_dep/android_common/busybox/R.jar",
+				"out/soong/.intermediates/transitive_import/android_common/busybox/R.jar",
 				"out/soong/.intermediates/transitive/android_common/turbine-combined/transitive.jar",
 				"out/soong/.intermediates/transitive_import/android_common/aar/classes-combined.jar",
 			},
diff --git a/java/base.go b/java/base.go
index c5f22d2..69f88be 100644
--- a/java/base.go
+++ b/java/base.go
@@ -94,6 +94,9 @@
 	// if not blank, used as prefix to generate repackage rule
 	Jarjar_prefix *string
 
+	// if set to true, skip the jarjar repackaging
+	Skip_jarjar_repackage *bool
+
 	// If not blank, set the java version passed to javac as -source and -target
 	Java_version *string
 
@@ -1100,11 +1103,13 @@
 	jarjarProviderData := j.collectJarJarRules(ctx)
 	if jarjarProviderData != nil {
 		android.SetProvider(ctx, JarJarProvider, *jarjarProviderData)
-		text := getJarJarRuleText(jarjarProviderData)
-		if text != "" {
-			ruleTextFile := android.PathForModuleOut(ctx, "repackaged-jarjar", "repackaging.txt")
-			android.WriteFileRule(ctx, ruleTextFile, text)
-			j.repackageJarjarRules = ruleTextFile
+		if !proptools.Bool(j.properties.Skip_jarjar_repackage) {
+			text := getJarJarRuleText(jarjarProviderData)
+			if text != "" {
+				ruleTextFile := android.PathForModuleOut(ctx, "repackaged-jarjar", "repackaging.txt")
+				android.WriteFileRule(ctx, ruleTextFile, text)
+				j.repackageJarjarRules = ruleTextFile
+			}
 		}
 	}
 
diff --git a/java/config/config.go b/java/config/config.go
index 6a945ac..d720046 100644
--- a/java/config/config.go
+++ b/java/config/config.go
@@ -131,12 +131,7 @@
 		if override := ctx.Config().Getenv("OVERRIDE_JLINK_VERSION_NUMBER"); override != "" {
 			return override
 		}
-		switch ctx.Config().Getenv("EXPERIMENTAL_USE_OPENJDK21_TOOLCHAIN") {
-		case "true":
-			return "21"
-		default:
-			return "17"
-		}
+		return "21"
 	})
 
 	pctx.SourcePathVariable("JavaToolchain", "${JavaHome}/bin")
diff --git a/java/config/droidstubs.go b/java/config/droidstubs.go
index f46c893..39eec44 100644
--- a/java/config/droidstubs.go
+++ b/java/config/droidstubs.go
@@ -23,7 +23,6 @@
 		"--format=v2",
 		"--repeat-errors-max 10",
 		"--hide UnresolvedImport",
-		"--hide InvalidNullabilityOverride",
 
 		// Force metalava to ignore classes on the classpath when an API file contains missing classes.
 		// See b/285140653 for more information.
@@ -49,9 +48,6 @@
 		// TODO(tnorbye): find owners to fix these warnings when annotation was enabled.
 		"--hide HiddenTypedefConstant",
 		"--hide SuperfluousPrefix",
-		"--hide AnnotationExtraction",
-		// b/222738070
-		"--hide BannedThrow",
 	}
 
 	MetalavaAnnotationsWarningsFlags = strings.Join(metalavaAnnotationsWarningsFlags, " ")
diff --git a/java/java.go b/java/java.go
index 4b99672..e606993 100644
--- a/java/java.go
+++ b/java/java.go
@@ -687,10 +687,11 @@
 		return true
 	}
 
-	// Store uncompressed dex files that are preopted on /system.
-	if !dexpreopter.dexpreoptDisabled(ctx, libName) && (ctx.Host() || !dexpreopter.odexOnSystemOther(ctx, libName, dexpreopter.installPath)) {
+	// Store uncompressed dex files that are preopted on /system or /system_other.
+	if !dexpreopter.dexpreoptDisabled(ctx, libName) {
 		return true
 	}
+
 	if ctx.Config().UncompressPrivAppDex() &&
 		inList(ctx.ModuleName(), ctx.Config().ModulesLoadedByPrivilegedModules()) {
 		return true
diff --git a/mk2rbc/test/version_defaults.mk.test b/mk2rbc/test/version_defaults.mk.test
index 1666392..3ce60bc 100644
--- a/mk2rbc/test/version_defaults.mk.test
+++ b/mk2rbc/test/version_defaults.mk.test
@@ -3,8 +3,8 @@
   include $(INTERNAL_BUILD_ID_MAKEFILE)
 endif
 
-DEFAULT_PLATFORM_VERSION := TP1A
-.KATI_READONLY := DEFAULT_PLATFORM_VERSION
+RELEASE_PLATFORM_VERSION := TP1A
+.KATI_READONLY := RELEASE_PLATFORM_VERSION
 MIN_PLATFORM_VERSION := TP1A
 MAX_PLATFORM_VERSION := TP1A
 PLATFORM_VERSION_LAST_STABLE := 12
diff --git a/sdk/update.go b/sdk/update.go
index 095e0c2..a731414 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -89,19 +89,6 @@
 	indentLevel int
 }
 
-// generatedFile abstracts operations for writing contents into a file and emit a build rule
-// for the file.
-type generatedFile struct {
-	generatedContents
-	path android.OutputPath
-}
-
-func newGeneratedFile(ctx android.ModuleContext, path ...string) *generatedFile {
-	return &generatedFile{
-		path: android.PathForModuleOut(ctx, path...).OutputPath,
-	}
-}
-
 func (gc *generatedContents) Indent() {
 	gc.indentLevel++
 }
@@ -122,26 +109,6 @@
 	_, _ = fmt.Fprintf(&(gc.content), format, args...)
 }
 
-func (gf *generatedFile) build(pctx android.PackageContext, ctx android.BuilderContext, implicits android.Paths) {
-	rb := android.NewRuleBuilder(pctx, ctx)
-
-	content := gf.content.String()
-
-	// ninja consumes newline characters in rspfile_content. Prevent it by
-	// escaping the backslash in the newline character. The extra backslash
-	// is removed when the rspfile is written to the actual script file
-	content = strings.ReplaceAll(content, "\n", "\\n")
-
-	rb.Command().
-		Implicits(implicits).
-		Text("echo -n").Text(proptools.ShellEscape(content)).
-		// convert \\n to \n
-		Text("| sed 's/\\\\n/\\n/g' >").Output(gf.path)
-	rb.Command().
-		Text("chmod a+x").Output(gf.path)
-	rb.Build(gf.path.Base(), "Build "+gf.path.Base())
-}
-
 // Collect all the members.
 //
 // Updates the sdk module with a list of sdkMemberVariantDep instances and details as to which
@@ -170,7 +137,7 @@
 
 			var container android.Module
 			if parent != ctx.Module() {
-				container = parent.(android.Module)
+				container = parent
 			}
 
 			minApiLevel := android.MinApiLevelForSdkSnapshot(ctx, child)
@@ -179,7 +146,7 @@
 			s.memberVariantDeps = append(s.memberVariantDeps, sdkMemberVariantDep{
 				sdkVariant:             s,
 				memberType:             memberType,
-				variant:                child.(android.Module),
+				variant:                child,
 				minApiLevel:            minApiLevel,
 				container:              container,
 				export:                 export,
@@ -375,7 +342,7 @@
 
 	snapshotDir := android.PathForModuleOut(ctx, "snapshot")
 
-	bp := newGeneratedFile(ctx, "snapshot", "Android.bp")
+	bp := android.PathForModuleOut(ctx, "snapshot", "Android.bp")
 
 	bpFile := &bpFile{
 		modules: make(map[string]*bpModule),
@@ -389,7 +356,7 @@
 		sdk:                   s,
 		snapshotDir:           snapshotDir.OutputPath,
 		copies:                make(map[string]string),
-		filesToZip:            []android.Path{bp.path},
+		filesToZip:            []android.Path{bp},
 		bpFile:                bpFile,
 		prebuiltModules:       make(map[string]*bpModule),
 		allMembersByName:      allMembersByName,
@@ -463,17 +430,14 @@
 	}
 
 	// generate Android.bp
-	bp = newGeneratedFile(ctx, "snapshot", "Android.bp")
-	generateBpContents(&bp.generatedContents, bpFile)
-
-	contents := bp.content.String()
+	contents := generateBpContents(bpFile)
 	// If the snapshot is being generated for the current build release then check the syntax to make
 	// sure that it is compatible.
 	if targetBuildRelease == buildReleaseCurrent {
 		syntaxCheckSnapshotBpFile(ctx, contents)
 	}
 
-	bp.build(pctx, ctx, nil)
+	android.WriteFileRuleVerbatim(ctx, bp, contents)
 
 	// Copy the build number file into the snapshot.
 	builder.CopyToSnapshot(ctx.Config().BuildNumberFile(ctx), BUILD_NUMBER_FILE)
@@ -522,16 +486,14 @@
 	modules := s.generateInfoData(ctx, memberVariantDeps)
 
 	// Output the modules information as pretty printed JSON.
-	info := newGeneratedFile(ctx, fmt.Sprintf("%s%s.info", ctx.ModuleName(), snapshotFileSuffix))
+	info := android.PathForModuleOut(ctx, fmt.Sprintf("%s%s.info", ctx.ModuleName(), snapshotFileSuffix))
 	output, err := json.MarshalIndent(modules, "", "  ")
 	if err != nil {
 		ctx.ModuleErrorf("error generating %q: %s", info, err)
 	}
 	builder.infoContents = string(output)
-	info.generatedContents.UnindentedPrintf("%s", output)
-	info.build(pctx, ctx, nil)
-	infoPath := info.path
-	installedInfo := ctx.InstallFile(android.PathForMainlineSdksInstall(ctx), infoPath.Base(), infoPath)
+	android.WriteFileRuleVerbatim(ctx, info, builder.infoContents)
+	installedInfo := ctx.InstallFile(android.PathForMainlineSdksInstall(ctx), info.Base(), info)
 	s.infoFile = android.OptionalPathForPath(installedInfo)
 
 	// Install the zip, making sure that the info file has been installed as well.
@@ -718,105 +680,6 @@
 	}
 }
 
-// snapshotModuleStaticProperties contains snapshot static (i.e. not dynamically generated) properties.
-type snapshotModuleStaticProperties struct {
-	Compile_multilib string `android:"arch_variant"`
-}
-
-// combinedSnapshotModuleProperties are the properties that are associated with the snapshot module.
-type combinedSnapshotModuleProperties struct {
-	// The sdk variant from which this information was collected.
-	sdkVariant *sdk
-
-	// Static snapshot module properties.
-	staticProperties *snapshotModuleStaticProperties
-
-	// The dynamically generated member list properties.
-	dynamicProperties interface{}
-}
-
-// collateSnapshotModuleInfo collates all the snapshot module info from supplied sdk variants.
-func (s *sdk) collateSnapshotModuleInfo(ctx android.BaseModuleContext, sdkVariants []*sdk, memberVariantDeps []sdkMemberVariantDep) []*combinedSnapshotModuleProperties {
-	sdkVariantToCombinedProperties := map[*sdk]*combinedSnapshotModuleProperties{}
-	var list []*combinedSnapshotModuleProperties
-	for _, sdkVariant := range sdkVariants {
-		staticProperties := &snapshotModuleStaticProperties{
-			Compile_multilib: sdkVariant.multilibUsages.String(),
-		}
-		dynamicProperties := s.dynamicSdkMemberTypes.createMemberTypeListProperties()
-
-		combinedProperties := &combinedSnapshotModuleProperties{
-			sdkVariant:        sdkVariant,
-			staticProperties:  staticProperties,
-			dynamicProperties: dynamicProperties,
-		}
-		sdkVariantToCombinedProperties[sdkVariant] = combinedProperties
-
-		list = append(list, combinedProperties)
-	}
-
-	for _, memberVariantDep := range memberVariantDeps {
-		// If the member dependency is internal then do not add the dependency to the snapshot member
-		// list properties.
-		if !memberVariantDep.export {
-			continue
-		}
-
-		combined := sdkVariantToCombinedProperties[memberVariantDep.sdkVariant]
-		memberListProperty := s.memberTypeListProperty(memberVariantDep.memberType)
-		memberName := ctx.OtherModuleName(memberVariantDep.variant)
-
-		if memberListProperty.getter == nil {
-			continue
-		}
-
-		// Append the member to the appropriate list, if it is not already present in the list.
-		memberList := memberListProperty.getter(combined.dynamicProperties)
-		if !android.InList(memberName, memberList) {
-			memberList = append(memberList, memberName)
-		}
-		memberListProperty.setter(combined.dynamicProperties, memberList)
-	}
-
-	return list
-}
-
-func (s *sdk) optimizeSnapshotModuleProperties(ctx android.ModuleContext, list []*combinedSnapshotModuleProperties) *combinedSnapshotModuleProperties {
-
-	// Extract the dynamic properties and add them to a list of propertiesContainer.
-	propertyContainers := []propertiesContainer{}
-	for _, i := range list {
-		propertyContainers = append(propertyContainers, sdkVariantPropertiesContainer{
-			sdkVariant: i.sdkVariant,
-			properties: i.dynamicProperties,
-		})
-	}
-
-	// Extract the common members, removing them from the original properties.
-	commonDynamicProperties := s.dynamicSdkMemberTypes.createMemberTypeListProperties()
-	extractor := newCommonValueExtractor(commonDynamicProperties)
-	extractCommonProperties(ctx, extractor, commonDynamicProperties, propertyContainers)
-
-	// Extract the static properties and add them to a list of propertiesContainer.
-	propertyContainers = []propertiesContainer{}
-	for _, i := range list {
-		propertyContainers = append(propertyContainers, sdkVariantPropertiesContainer{
-			sdkVariant: i.sdkVariant,
-			properties: i.staticProperties,
-		})
-	}
-
-	commonStaticProperties := &snapshotModuleStaticProperties{}
-	extractor = newCommonValueExtractor(commonStaticProperties)
-	extractCommonProperties(ctx, extractor, &commonStaticProperties, propertyContainers)
-
-	return &combinedSnapshotModuleProperties{
-		sdkVariant:        nil,
-		staticProperties:  commonStaticProperties,
-		dynamicProperties: commonDynamicProperties,
-	}
-}
-
 type propertyTag struct {
 	name string
 }
@@ -885,7 +748,8 @@
 	}
 }
 
-func generateBpContents(contents *generatedContents, bpFile *bpFile) {
+func generateBpContents(bpFile *bpFile) string {
+	contents := &generatedContents{}
 	contents.IndentedPrintf("// This is auto-generated. DO NOT EDIT.\n")
 	for _, bpModule := range bpFile.order {
 		contents.IndentedPrintf("\n")
@@ -893,6 +757,7 @@
 		outputPropertySet(contents, bpModule.bpPropertySet)
 		contents.IndentedPrintf("}\n")
 	}
+	return contents.content.String()
 }
 
 func outputPropertySet(contents *generatedContents, set *bpPropertySet) {
@@ -1007,7 +872,7 @@
 		contents.IndentedPrintf("}")
 
 	default:
-		panic(fmt.Errorf("Unknown type: %T of value %#v", value, value))
+		panic(fmt.Errorf("unknown type: %T of value %#v", value, value))
 	}
 }
 
@@ -1018,9 +883,7 @@
 }
 
 func (s *sdk) GetAndroidBpContentsForTests() string {
-	contents := &generatedContents{}
-	generateBpContents(contents, s.builderForTests.bpFile)
-	return contents.content.String()
+	return generateBpContents(s.builderForTests.bpFile)
 }
 
 func (s *sdk) GetInfoContentsForTests() string {
@@ -1334,7 +1197,7 @@
 	case "lib64":
 		return m | multilib64
 	default:
-		panic(fmt.Errorf("Unknown Multilib field in ArchType, expected 'lib32' or 'lib64', found %q", multilib))
+		panic(fmt.Errorf("unknown Multilib field in ArchType, expected 'lib32' or 'lib64', found %q", multilib))
 	}
 }
 
@@ -1349,7 +1212,7 @@
 	case multilibBoth:
 		return "both"
 	default:
-		panic(fmt.Errorf("Unknown multilib value, found %b, expected one of %b, %b, %b or %b",
+		panic(fmt.Errorf("unknown multilib value, found %b, expected one of %b, %b, %b or %b",
 			m, multilibNone, multilib32, multilib64, multilibBoth))
 	}
 }
@@ -2302,20 +2165,6 @@
 	optimizableProperties() interface{}
 }
 
-// A wrapper for sdk variant related properties to allow them to be optimized.
-type sdkVariantPropertiesContainer struct {
-	sdkVariant *sdk
-	properties interface{}
-}
-
-func (c sdkVariantPropertiesContainer) optimizableProperties() interface{} {
-	return c.properties
-}
-
-func (c sdkVariantPropertiesContainer) String() string {
-	return c.sdkVariant.String()
-}
-
 // Extract common properties from a slice of property structures of the same type.
 //
 // All the property structures must be of the same type.
diff --git a/starlark_import/Android.bp b/starlark_import/Android.bp
deleted file mode 100644
index b43217b..0000000
--- a/starlark_import/Android.bp
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2023 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-bootstrap_go_package {
-    name: "soong-starlark",
-    pkgPath: "android/soong/starlark_import",
-    srcs: [
-        "starlark_import.go",
-        "unmarshal.go",
-    ],
-    testSrcs: [
-        "starlark_import_test.go",
-        "unmarshal_test.go",
-    ],
-    deps: [
-        "go-starlark-starlark",
-        "go-starlark-starlarkstruct",
-        "go-starlark-starlarkjson",
-        "go-starlark-starlarktest",
-    ],
-}
diff --git a/starlark_import/README.md b/starlark_import/README.md
deleted file mode 100644
index e444759..0000000
--- a/starlark_import/README.md
+++ /dev/null
@@ -1,14 +0,0 @@
-# starlark_import package
-
-This allows soong to read constant information from starlark files. At package initialization
-time, soong will read `build/bazel/constants_exported_to_soong.bzl`, and then make the
-variables from that file available via `starlark_import.GetStarlarkValue()`. So to import
-a new variable, it must be added to `constants_exported_to_soong.bzl` and then it can
-be accessed by name.
-
-Only constant information can be read, since this is not a full bazel execution but a
-standalone starlark interpreter. This means you can't use bazel contructs like `rule`,
-`provider`, `select`, `glob`, etc.
-
-All starlark files that were loaded must be added as ninja deps that cause soong to rerun.
-The loaded files can be retrieved via `starlark_import.GetNinjaDeps()`.
diff --git a/starlark_import/starlark_import.go b/starlark_import/starlark_import.go
deleted file mode 100644
index ebe4247..0000000
--- a/starlark_import/starlark_import.go
+++ /dev/null
@@ -1,306 +0,0 @@
-// Copyright 2023 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package starlark_import
-
-import (
-	"fmt"
-	"os"
-	"path/filepath"
-	"sort"
-	"strings"
-	"sync"
-	"time"
-
-	"go.starlark.net/starlark"
-	"go.starlark.net/starlarkjson"
-	"go.starlark.net/starlarkstruct"
-)
-
-func init() {
-	go func() {
-		startTime := time.Now()
-		v, d, err := runStarlarkFile("//build/bazel/constants_exported_to_soong.bzl")
-		endTime := time.Now()
-		//fmt.Fprintf(os.Stderr, "starlark run time: %s\n", endTime.Sub(startTime).String())
-		globalResult.Set(starlarkResult{
-			values:    v,
-			ninjaDeps: d,
-			err:       err,
-			startTime: startTime,
-			endTime:   endTime,
-		})
-	}()
-}
-
-type starlarkResult struct {
-	values    starlark.StringDict
-	ninjaDeps []string
-	err       error
-	startTime time.Time
-	endTime   time.Time
-}
-
-// setOnce wraps a value and exposes Set() and Get() accessors for it.
-// The Get() calls will block until a Set() has been called.
-// A second call to Set() will panic.
-// setOnce must be created using newSetOnce()
-type setOnce[T any] struct {
-	value T
-	lock  sync.Mutex
-	wg    sync.WaitGroup
-	isSet bool
-}
-
-func (o *setOnce[T]) Set(value T) {
-	o.lock.Lock()
-	defer o.lock.Unlock()
-	if o.isSet {
-		panic("Value already set")
-	}
-
-	o.value = value
-	o.isSet = true
-	o.wg.Done()
-}
-
-func (o *setOnce[T]) Get() T {
-	if !o.isSet {
-		o.wg.Wait()
-	}
-	return o.value
-}
-
-func newSetOnce[T any]() *setOnce[T] {
-	result := &setOnce[T]{}
-	result.wg.Add(1)
-	return result
-}
-
-var globalResult = newSetOnce[starlarkResult]()
-
-func GetStarlarkValue[T any](key string) (T, error) {
-	result := globalResult.Get()
-	if result.err != nil {
-		var zero T
-		return zero, result.err
-	}
-	if !result.values.Has(key) {
-		var zero T
-		return zero, fmt.Errorf("a starlark variable by that name wasn't found, did you update //build/bazel/constants_exported_to_soong.bzl?")
-	}
-	return Unmarshal[T](result.values[key])
-}
-
-func GetNinjaDeps() ([]string, error) {
-	result := globalResult.Get()
-	if result.err != nil {
-		return nil, result.err
-	}
-	return result.ninjaDeps, nil
-}
-
-func getTopDir() (string, error) {
-	// It's hard to communicate the top dir to this package in any other way than reading the
-	// arguments directly, because we need to know this at package initialization time. Many
-	// soong constants that we'd like to read from starlark are initialized during package
-	// initialization.
-	for i, arg := range os.Args {
-		if arg == "--top" {
-			if i < len(os.Args)-1 && os.Args[i+1] != "" {
-				return os.Args[i+1], nil
-			}
-		}
-	}
-
-	// When running tests, --top is not passed. Instead, search for the top dir manually
-	cwd, err := os.Getwd()
-	if err != nil {
-		return "", err
-	}
-	for cwd != "/" {
-		if _, err := os.Stat(filepath.Join(cwd, "build/soong/soong_ui.bash")); err == nil {
-			return cwd, nil
-		}
-		cwd = filepath.Dir(cwd)
-	}
-	return "", fmt.Errorf("could not find top dir")
-}
-
-const callerDirKey = "callerDir"
-
-type modentry struct {
-	globals starlark.StringDict
-	err     error
-}
-
-func unsupportedMethod(t *starlark.Thread, fn *starlark.Builtin, _ starlark.Tuple, _ []starlark.Tuple) (starlark.Value, error) {
-	return nil, fmt.Errorf("%sthis file is read by soong, and must therefore be pure starlark and include only constant information. %q is not allowed", t.CallStack().String(), fn.Name())
-}
-
-var builtins = starlark.StringDict{
-	"aspect":     starlark.NewBuiltin("aspect", unsupportedMethod),
-	"glob":       starlark.NewBuiltin("glob", unsupportedMethod),
-	"json":       starlarkjson.Module,
-	"provider":   starlark.NewBuiltin("provider", unsupportedMethod),
-	"rule":       starlark.NewBuiltin("rule", unsupportedMethod),
-	"struct":     starlark.NewBuiltin("struct", starlarkstruct.Make),
-	"select":     starlark.NewBuiltin("select", unsupportedMethod),
-	"transition": starlark.NewBuiltin("transition", unsupportedMethod),
-}
-
-// Takes a module name (the first argument to the load() function) and returns the path
-// it's trying to load, stripping out leading //, and handling leading :s.
-func cleanModuleName(moduleName string, callerDir string) (string, error) {
-	if strings.Count(moduleName, ":") > 1 {
-		return "", fmt.Errorf("at most 1 colon must be present in starlark path: %s", moduleName)
-	}
-
-	// We don't have full support for external repositories, but at least support skylib's dicts.
-	if moduleName == "@bazel_skylib//lib:dicts.bzl" {
-		return "external/bazel-skylib/lib/dicts.bzl", nil
-	}
-
-	localLoad := false
-	if strings.HasPrefix(moduleName, "@//") {
-		moduleName = moduleName[3:]
-	} else if strings.HasPrefix(moduleName, "//") {
-		moduleName = moduleName[2:]
-	} else if strings.HasPrefix(moduleName, ":") {
-		moduleName = moduleName[1:]
-		localLoad = true
-	} else {
-		return "", fmt.Errorf("load path must start with // or :")
-	}
-
-	if ix := strings.LastIndex(moduleName, ":"); ix >= 0 {
-		moduleName = moduleName[:ix] + string(os.PathSeparator) + moduleName[ix+1:]
-	}
-
-	if filepath.Clean(moduleName) != moduleName {
-		return "", fmt.Errorf("load path must be clean, found: %s, expected: %s", moduleName, filepath.Clean(moduleName))
-	}
-	if strings.HasPrefix(moduleName, "../") {
-		return "", fmt.Errorf("load path must not start with ../: %s", moduleName)
-	}
-	if strings.HasPrefix(moduleName, "/") {
-		return "", fmt.Errorf("load path starts with /, use // for a absolute path: %s", moduleName)
-	}
-
-	if localLoad {
-		return filepath.Join(callerDir, moduleName), nil
-	}
-
-	return moduleName, nil
-}
-
-// loader implements load statement. The format of the loaded module URI is
-//
-//	[//path]:base
-//
-// The file path is $ROOT/path/base if path is present, <caller_dir>/base otherwise.
-func loader(thread *starlark.Thread, module string, topDir string, moduleCache map[string]*modentry, moduleCacheLock *sync.Mutex, filesystem map[string]string) (starlark.StringDict, error) {
-	modulePath, err := cleanModuleName(module, thread.Local(callerDirKey).(string))
-	if err != nil {
-		return nil, err
-	}
-	moduleCacheLock.Lock()
-	e, ok := moduleCache[modulePath]
-	if e == nil {
-		if ok {
-			moduleCacheLock.Unlock()
-			return nil, fmt.Errorf("cycle in load graph")
-		}
-
-		// Add a placeholder to indicate "load in progress".
-		moduleCache[modulePath] = nil
-		moduleCacheLock.Unlock()
-
-		childThread := &starlark.Thread{Name: "exec " + module, Load: thread.Load}
-
-		// Cheating for the sake of testing:
-		// propagate starlarktest's Reporter key, otherwise testing
-		// the load function may cause panic in starlarktest code.
-		const testReporterKey = "Reporter"
-		if v := thread.Local(testReporterKey); v != nil {
-			childThread.SetLocal(testReporterKey, v)
-		}
-
-		childThread.SetLocal(callerDirKey, filepath.Dir(modulePath))
-
-		if filesystem != nil {
-			globals, err := starlark.ExecFile(childThread, filepath.Join(topDir, modulePath), filesystem[modulePath], builtins)
-			e = &modentry{globals, err}
-		} else {
-			globals, err := starlark.ExecFile(childThread, filepath.Join(topDir, modulePath), nil, builtins)
-			e = &modentry{globals, err}
-		}
-
-		// Update the cache.
-		moduleCacheLock.Lock()
-		moduleCache[modulePath] = e
-	}
-	moduleCacheLock.Unlock()
-	return e.globals, e.err
-}
-
-// Run runs the given starlark file and returns its global variables and a list of all starlark
-// files that were loaded. The top dir for starlark's // is found via getTopDir().
-func runStarlarkFile(filename string) (starlark.StringDict, []string, error) {
-	topDir, err := getTopDir()
-	if err != nil {
-		return nil, nil, err
-	}
-	return runStarlarkFileWithFilesystem(filename, topDir, nil)
-}
-
-func runStarlarkFileWithFilesystem(filename string, topDir string, filesystem map[string]string) (starlark.StringDict, []string, error) {
-	if !strings.HasPrefix(filename, "//") && !strings.HasPrefix(filename, ":") {
-		filename = "//" + filename
-	}
-	filename, err := cleanModuleName(filename, "")
-	if err != nil {
-		return nil, nil, err
-	}
-	moduleCache := make(map[string]*modentry)
-	moduleCache[filename] = nil
-	moduleCacheLock := &sync.Mutex{}
-	mainThread := &starlark.Thread{
-		Name: "main",
-		Print: func(_ *starlark.Thread, msg string) {
-			// Ignore prints
-		},
-		Load: func(thread *starlark.Thread, module string) (starlark.StringDict, error) {
-			return loader(thread, module, topDir, moduleCache, moduleCacheLock, filesystem)
-		},
-	}
-	mainThread.SetLocal(callerDirKey, filepath.Dir(filename))
-
-	var result starlark.StringDict
-	if filesystem != nil {
-		result, err = starlark.ExecFile(mainThread, filepath.Join(topDir, filename), filesystem[filename], builtins)
-	} else {
-		result, err = starlark.ExecFile(mainThread, filepath.Join(topDir, filename), nil, builtins)
-	}
-	return result, sortedStringKeys(moduleCache), err
-}
-
-func sortedStringKeys(m map[string]*modentry) []string {
-	s := make([]string, 0, len(m))
-	for k := range m {
-		s = append(s, k)
-	}
-	sort.Strings(s)
-	return s
-}
diff --git a/starlark_import/starlark_import_test.go b/starlark_import/starlark_import_test.go
deleted file mode 100644
index 8a58e3b..0000000
--- a/starlark_import/starlark_import_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-// Copyright 2023 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package starlark_import
-
-import (
-	"strings"
-	"testing"
-
-	"go.starlark.net/starlark"
-)
-
-func TestBasic(t *testing.T) {
-	globals, _, err := runStarlarkFileWithFilesystem("a.bzl", "", map[string]string{
-		"a.bzl": `
-my_string = "hello, world!"
-`})
-	if err != nil {
-		t.Error(err)
-		return
-	}
-
-	if globals["my_string"].(starlark.String) != "hello, world!" {
-		t.Errorf("Expected %q, got %q", "hello, world!", globals["my_string"].String())
-	}
-}
-
-func TestLoad(t *testing.T) {
-	globals, _, err := runStarlarkFileWithFilesystem("a.bzl", "", map[string]string{
-		"a.bzl": `
-load("//b.bzl", _b_string = "my_string")
-my_string = "hello, " + _b_string
-`,
-		"b.bzl": `
-my_string = "world!"
-`})
-	if err != nil {
-		t.Error(err)
-		return
-	}
-
-	if globals["my_string"].(starlark.String) != "hello, world!" {
-		t.Errorf("Expected %q, got %q", "hello, world!", globals["my_string"].String())
-	}
-}
-
-func TestLoadRelative(t *testing.T) {
-	globals, ninjaDeps, err := runStarlarkFileWithFilesystem("a.bzl", "", map[string]string{
-		"a.bzl": `
-load(":b.bzl", _b_string = "my_string")
-load("//foo/c.bzl", _c_string = "my_string")
-my_string = "hello, " + _b_string
-c_string = _c_string
-`,
-		"b.bzl": `
-my_string = "world!"
-`,
-		"foo/c.bzl": `
-load(":d.bzl", _d_string = "my_string")
-my_string = "hello, " + _d_string
-`,
-		"foo/d.bzl": `
-my_string = "world!"
-`})
-	if err != nil {
-		t.Error(err)
-		return
-	}
-
-	if globals["my_string"].(starlark.String) != "hello, world!" {
-		t.Errorf("Expected %q, got %q", "hello, world!", globals["my_string"].String())
-	}
-
-	expectedNinjaDeps := []string{
-		"a.bzl",
-		"b.bzl",
-		"foo/c.bzl",
-		"foo/d.bzl",
-	}
-	if !slicesEqual(ninjaDeps, expectedNinjaDeps) {
-		t.Errorf("Expected %v ninja deps, got %v", expectedNinjaDeps, ninjaDeps)
-	}
-}
-
-func TestLoadCycle(t *testing.T) {
-	_, _, err := runStarlarkFileWithFilesystem("a.bzl", "", map[string]string{
-		"a.bzl": `
-load(":b.bzl", _b_string = "my_string")
-my_string = "hello, " + _b_string
-`,
-		"b.bzl": `
-load(":a.bzl", _a_string = "my_string")
-my_string = "hello, " + _a_string
-`})
-	if err == nil || !strings.Contains(err.Error(), "cycle in load graph") {
-		t.Errorf("Expected cycle in load graph, got: %v", err)
-		return
-	}
-}
-
-func slicesEqual[T comparable](a []T, b []T) bool {
-	if len(a) != len(b) {
-		return false
-	}
-	for i := range a {
-		if a[i] != b[i] {
-			return false
-		}
-	}
-	return true
-}
diff --git a/starlark_import/unmarshal.go b/starlark_import/unmarshal.go
deleted file mode 100644
index b243471..0000000
--- a/starlark_import/unmarshal.go
+++ /dev/null
@@ -1,304 +0,0 @@
-// Copyright 2023 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package starlark_import
-
-import (
-	"fmt"
-	"math"
-	"reflect"
-	"unsafe"
-
-	"go.starlark.net/starlark"
-	"go.starlark.net/starlarkstruct"
-)
-
-func Unmarshal[T any](value starlark.Value) (T, error) {
-	x, err := UnmarshalReflect(value, reflect.TypeOf((*T)(nil)).Elem())
-	return x.Interface().(T), err
-}
-
-func UnmarshalReflect(value starlark.Value, ty reflect.Type) (reflect.Value, error) {
-	if ty == reflect.TypeOf((*starlark.Value)(nil)).Elem() {
-		return reflect.ValueOf(value), nil
-	}
-	zero := reflect.Zero(ty)
-	if value == nil {
-		panic("nil value")
-	}
-	var result reflect.Value
-	if ty.Kind() == reflect.Interface {
-		var err error
-		ty, err = typeOfStarlarkValue(value)
-		if err != nil {
-			return zero, err
-		}
-	}
-	if ty.Kind() == reflect.Map {
-		result = reflect.MakeMap(ty)
-	} else {
-		result = reflect.Indirect(reflect.New(ty))
-	}
-
-	switch v := value.(type) {
-	case starlark.String:
-		if result.Type().Kind() != reflect.String {
-			return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
-		}
-		result.SetString(v.GoString())
-	case starlark.Int:
-		signedValue, signedOk := v.Int64()
-		unsignedValue, unsignedOk := v.Uint64()
-		switch result.Type().Kind() {
-		case reflect.Int64:
-			if !signedOk {
-				return zero, fmt.Errorf("starlark int didn't fit in go int64")
-			}
-			result.SetInt(signedValue)
-		case reflect.Int32:
-			if !signedOk || signedValue > math.MaxInt32 || signedValue < math.MinInt32 {
-				return zero, fmt.Errorf("starlark int didn't fit in go int32")
-			}
-			result.SetInt(signedValue)
-		case reflect.Int16:
-			if !signedOk || signedValue > math.MaxInt16 || signedValue < math.MinInt16 {
-				return zero, fmt.Errorf("starlark int didn't fit in go int16")
-			}
-			result.SetInt(signedValue)
-		case reflect.Int8:
-			if !signedOk || signedValue > math.MaxInt8 || signedValue < math.MinInt8 {
-				return zero, fmt.Errorf("starlark int didn't fit in go int8")
-			}
-			result.SetInt(signedValue)
-		case reflect.Int:
-			if !signedOk || signedValue > math.MaxInt || signedValue < math.MinInt {
-				return zero, fmt.Errorf("starlark int didn't fit in go int")
-			}
-			result.SetInt(signedValue)
-		case reflect.Uint64:
-			if !unsignedOk {
-				return zero, fmt.Errorf("starlark int didn't fit in go uint64")
-			}
-			result.SetUint(unsignedValue)
-		case reflect.Uint32:
-			if !unsignedOk || unsignedValue > math.MaxUint32 {
-				return zero, fmt.Errorf("starlark int didn't fit in go uint32")
-			}
-			result.SetUint(unsignedValue)
-		case reflect.Uint16:
-			if !unsignedOk || unsignedValue > math.MaxUint16 {
-				return zero, fmt.Errorf("starlark int didn't fit in go uint16")
-			}
-			result.SetUint(unsignedValue)
-		case reflect.Uint8:
-			if !unsignedOk || unsignedValue > math.MaxUint8 {
-				return zero, fmt.Errorf("starlark int didn't fit in go uint8")
-			}
-			result.SetUint(unsignedValue)
-		case reflect.Uint:
-			if !unsignedOk || unsignedValue > math.MaxUint {
-				return zero, fmt.Errorf("starlark int didn't fit in go uint")
-			}
-			result.SetUint(unsignedValue)
-		default:
-			return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
-		}
-	case starlark.Float:
-		f := float64(v)
-		switch result.Type().Kind() {
-		case reflect.Float64:
-			result.SetFloat(f)
-		case reflect.Float32:
-			if f > math.MaxFloat32 || f < -math.MaxFloat32 {
-				return zero, fmt.Errorf("starlark float didn't fit in go float32")
-			}
-			result.SetFloat(f)
-		default:
-			return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
-		}
-	case starlark.Bool:
-		if result.Type().Kind() != reflect.Bool {
-			return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
-		}
-		result.SetBool(bool(v))
-	case starlark.Tuple:
-		if result.Type().Kind() != reflect.Slice {
-			return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
-		}
-		elemType := result.Type().Elem()
-		// TODO: Add this grow call when we're on go 1.20
-		//result.Grow(v.Len())
-		for i := 0; i < v.Len(); i++ {
-			elem, err := UnmarshalReflect(v.Index(i), elemType)
-			if err != nil {
-				return zero, err
-			}
-			result = reflect.Append(result, elem)
-		}
-	case *starlark.List:
-		if result.Type().Kind() != reflect.Slice {
-			return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
-		}
-		elemType := result.Type().Elem()
-		// TODO: Add this grow call when we're on go 1.20
-		//result.Grow(v.Len())
-		for i := 0; i < v.Len(); i++ {
-			elem, err := UnmarshalReflect(v.Index(i), elemType)
-			if err != nil {
-				return zero, err
-			}
-			result = reflect.Append(result, elem)
-		}
-	case *starlark.Dict:
-		if result.Type().Kind() != reflect.Map {
-			return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
-		}
-		keyType := result.Type().Key()
-		valueType := result.Type().Elem()
-		for _, pair := range v.Items() {
-			key := pair.Index(0)
-			value := pair.Index(1)
-
-			unmarshalledKey, err := UnmarshalReflect(key, keyType)
-			if err != nil {
-				return zero, err
-			}
-			unmarshalledValue, err := UnmarshalReflect(value, valueType)
-			if err != nil {
-				return zero, err
-			}
-
-			result.SetMapIndex(unmarshalledKey, unmarshalledValue)
-		}
-	case *starlarkstruct.Struct:
-		if result.Type().Kind() != reflect.Struct {
-			return zero, fmt.Errorf("starlark type was %s, but %s requested", v.Type(), result.Type().Kind().String())
-		}
-		if result.NumField() != len(v.AttrNames()) {
-			return zero, fmt.Errorf("starlark struct and go struct have different number of fields (%d and %d)", len(v.AttrNames()), result.NumField())
-		}
-		for _, attrName := range v.AttrNames() {
-			attr, err := v.Attr(attrName)
-			if err != nil {
-				return zero, err
-			}
-
-			// TODO(b/279787235): this should probably support tags to rename the field
-			resultField := result.FieldByName(attrName)
-			if resultField == (reflect.Value{}) {
-				return zero, fmt.Errorf("starlark struct had field %s, but requested struct type did not", attrName)
-			}
-			// This hack allows us to change unexported fields
-			resultField = reflect.NewAt(resultField.Type(), unsafe.Pointer(resultField.UnsafeAddr())).Elem()
-			x, err := UnmarshalReflect(attr, resultField.Type())
-			if err != nil {
-				return zero, err
-			}
-			resultField.Set(x)
-		}
-	default:
-		return zero, fmt.Errorf("unimplemented starlark type: %s", value.Type())
-	}
-
-	return result, nil
-}
-
-func typeOfStarlarkValue(value starlark.Value) (reflect.Type, error) {
-	var err error
-	switch v := value.(type) {
-	case starlark.String:
-		return reflect.TypeOf(""), nil
-	case *starlark.List:
-		innerType := reflect.TypeOf("")
-		if v.Len() > 0 {
-			innerType, err = typeOfStarlarkValue(v.Index(0))
-			if err != nil {
-				return nil, err
-			}
-		}
-		for i := 1; i < v.Len(); i++ {
-			innerTypeI, err := typeOfStarlarkValue(v.Index(i))
-			if err != nil {
-				return nil, err
-			}
-			if innerType != innerTypeI {
-				return nil, fmt.Errorf("List must contain elements of entirely the same type, found %v and %v", innerType, innerTypeI)
-			}
-		}
-		return reflect.SliceOf(innerType), nil
-	case *starlark.Dict:
-		keyType := reflect.TypeOf("")
-		valueType := reflect.TypeOf("")
-		keys := v.Keys()
-		if v.Len() > 0 {
-			firstKey := keys[0]
-			keyType, err = typeOfStarlarkValue(firstKey)
-			if err != nil {
-				return nil, err
-			}
-			firstValue, found, err := v.Get(firstKey)
-			if !found {
-				err = fmt.Errorf("value not found")
-			}
-			if err != nil {
-				return nil, err
-			}
-			valueType, err = typeOfStarlarkValue(firstValue)
-			if err != nil {
-				return nil, err
-			}
-		}
-		for _, key := range keys {
-			keyTypeI, err := typeOfStarlarkValue(key)
-			if err != nil {
-				return nil, err
-			}
-			if keyType != keyTypeI {
-				return nil, fmt.Errorf("dict must contain elements of entirely the same type, found %v and %v", keyType, keyTypeI)
-			}
-			value, found, err := v.Get(key)
-			if !found {
-				err = fmt.Errorf("value not found")
-			}
-			if err != nil {
-				return nil, err
-			}
-			valueTypeI, err := typeOfStarlarkValue(value)
-			if valueType.Kind() != reflect.Interface && valueTypeI != valueType {
-				// If we see conflicting value types, change the result value type to an empty interface
-				valueType = reflect.TypeOf([]interface{}{}).Elem()
-			}
-		}
-		return reflect.MapOf(keyType, valueType), nil
-	case starlark.Int:
-		return reflect.TypeOf(0), nil
-	case starlark.Float:
-		return reflect.TypeOf(0.0), nil
-	case starlark.Bool:
-		return reflect.TypeOf(true), nil
-	default:
-		return nil, fmt.Errorf("unimplemented starlark type: %s", value.Type())
-	}
-}
-
-// UnmarshalNoneable is like Unmarshal, but it will accept None as the top level (but not nested)
-// starlark value. If the value is None, a nil pointer will be returned, otherwise a pointer
-// to the result of Unmarshal will be returned.
-func UnmarshalNoneable[T any](value starlark.Value) (*T, error) {
-	if _, ok := value.(starlark.NoneType); ok {
-		return nil, nil
-	}
-	ret, err := Unmarshal[T](value)
-	return &ret, err
-}
diff --git a/starlark_import/unmarshal_test.go b/starlark_import/unmarshal_test.go
deleted file mode 100644
index bc0ea4c..0000000
--- a/starlark_import/unmarshal_test.go
+++ /dev/null
@@ -1,148 +0,0 @@
-// Copyright 2023 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package starlark_import
-
-import (
-	"reflect"
-	"testing"
-
-	"go.starlark.net/starlark"
-)
-
-func createStarlarkValue(t *testing.T, code string) starlark.Value {
-	t.Helper()
-	result, err := starlark.ExecFile(&starlark.Thread{}, "main.bzl", "x = "+code, builtins)
-	if err != nil {
-		panic(err)
-	}
-	return result["x"]
-}
-
-func TestUnmarshalConcreteType(t *testing.T) {
-	x, err := Unmarshal[string](createStarlarkValue(t, `"foo"`))
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	if x != "foo" {
-		t.Errorf(`Expected "foo", got %q`, x)
-	}
-}
-
-func TestUnmarshalConcreteTypeWithInterfaces(t *testing.T) {
-	x, err := Unmarshal[map[string]map[string]interface{}](createStarlarkValue(t,
-		`{"foo": {"foo2": "foo3"}, "bar": {"bar2": ["bar3"]}}`))
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	expected := map[string]map[string]interface{}{
-		"foo": {"foo2": "foo3"},
-		"bar": {"bar2": []string{"bar3"}},
-	}
-	if !reflect.DeepEqual(x, expected) {
-		t.Errorf(`Expected %v, got %v`, expected, x)
-	}
-}
-
-func TestUnmarshalToStarlarkValue(t *testing.T) {
-	x, err := Unmarshal[map[string]starlark.Value](createStarlarkValue(t,
-		`{"foo": "Hi", "bar": None}`))
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	if x["foo"].(starlark.String).GoString() != "Hi" {
-		t.Errorf("Expected \"Hi\", got: %q", x["foo"].(starlark.String).GoString())
-	}
-	if x["bar"].Type() != "NoneType" {
-		t.Errorf("Expected \"NoneType\", got: %q", x["bar"].Type())
-	}
-}
-
-func TestUnmarshal(t *testing.T) {
-	testCases := []struct {
-		input    string
-		expected interface{}
-	}{
-		{
-			input:    `"foo"`,
-			expected: "foo",
-		},
-		{
-			input:    `5`,
-			expected: 5,
-		},
-		{
-			input:    `["foo", "bar"]`,
-			expected: []string{"foo", "bar"},
-		},
-		{
-			input:    `("foo", "bar")`,
-			expected: []string{"foo", "bar"},
-		},
-		{
-			input:    `("foo",5)`,
-			expected: []interface{}{"foo", 5},
-		},
-		{
-			input:    `{"foo": 5, "bar": 10}`,
-			expected: map[string]int{"foo": 5, "bar": 10},
-		},
-		{
-			input:    `{"foo": ["qux"], "bar": []}`,
-			expected: map[string][]string{"foo": {"qux"}, "bar": nil},
-		},
-		{
-			input: `struct(Foo="foo", Bar=5)`,
-			expected: struct {
-				Foo string
-				Bar int
-			}{Foo: "foo", Bar: 5},
-		},
-		{
-			// Unexported fields version of the above
-			input: `struct(foo="foo", bar=5)`,
-			expected: struct {
-				foo string
-				bar int
-			}{foo: "foo", bar: 5},
-		},
-		{
-			input: `{"foo": "foo2", "bar": ["bar2"], "baz": 5, "qux": {"qux2": "qux3"}, "quux": {"quux2": "quux3", "quux4": 5}}`,
-			expected: map[string]interface{}{
-				"foo": "foo2",
-				"bar": []string{"bar2"},
-				"baz": 5,
-				"qux": map[string]string{"qux2": "qux3"},
-				"quux": map[string]interface{}{
-					"quux2": "quux3",
-					"quux4": 5,
-				},
-			},
-		},
-	}
-
-	for _, tc := range testCases {
-		x, err := UnmarshalReflect(createStarlarkValue(t, tc.input), reflect.TypeOf(tc.expected))
-		if err != nil {
-			t.Error(err)
-			continue
-		}
-		if !reflect.DeepEqual(x.Interface(), tc.expected) {
-			t.Errorf(`Expected %#v, got %#v`, tc.expected, x.Interface())
-		}
-	}
-}
diff --git a/sysprop/sysprop_library.go b/sysprop/sysprop_library.go
index 2258232..698aaf2 100644
--- a/sysprop/sysprop_library.go
+++ b/sysprop/sysprop_library.go
@@ -154,15 +154,14 @@
 	})
 
 	for _, syspropFile := range android.PathsForModuleSrc(ctx, g.properties.Srcs) {
-		syspropDir := strings.TrimSuffix(syspropFile.String(), syspropFile.Ext())
-		outputDir := android.PathForModuleGen(ctx, syspropDir, "src")
-		libPath := android.PathForModuleGen(ctx, syspropDir, "src", "lib.rs")
-		parsersPath := android.PathForModuleGen(ctx, syspropDir, "src", "gen_parsers_and_formatters.rs")
+		syspropDir := android.GenPathWithExt(ctx, "sysprop", syspropFile, "srcrust")
+		outputDir := syspropDir.Join(ctx, "src")
+		libPath := syspropDir.Join(ctx, "src", "lib.rs")
 
 		ctx.Build(pctx, android.BuildParams{
 			Rule:        syspropRust,
 			Description: "sysprop_rust " + syspropFile.Rel(),
-			Outputs:     android.WritablePaths{libPath, parsersPath},
+			Outputs:     android.WritablePaths{libPath},
 			Input:       syspropFile,
 			Implicit:    checkApiFileTimeStamp,
 			Args: map[string]string{
@@ -171,7 +170,7 @@
 			},
 		})
 
-		g.genSrcs = append(g.genSrcs, libPath, parsersPath)
+		g.genSrcs = append(g.genSrcs, libPath)
 	}
 }
 
diff --git a/ui/build/config.go b/ui/build/config.go
index 1a25397..7426a78 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -386,22 +386,21 @@
 
 	// Configure Java-related variables, including adding it to $PATH
 	java8Home := filepath.Join("prebuilts/jdk/jdk8", ret.HostPrebuiltTag())
-	java17Home := filepath.Join("prebuilts/jdk/jdk17", ret.HostPrebuiltTag())
 	java21Home := filepath.Join("prebuilts/jdk/jdk21", ret.HostPrebuiltTag())
 	javaHome := func() string {
 		if override, ok := ret.environ.Get("OVERRIDE_ANDROID_JAVA_HOME"); ok {
 			return override
 		}
-		if ret.environ.IsEnvTrue("EXPERIMENTAL_USE_OPENJDK21_TOOLCHAIN") {
-			return java21Home
-		}
 		if toolchain11, ok := ret.environ.Get("EXPERIMENTAL_USE_OPENJDK11_TOOLCHAIN"); ok && toolchain11 != "true" {
-			ctx.Fatalln("The environment variable EXPERIMENTAL_USE_OPENJDK11_TOOLCHAIN is no longer supported. An OpenJDK 11 toolchain is now the global default.")
+			ctx.Fatalln("The environment variable EXPERIMENTAL_USE_OPENJDK11_TOOLCHAIN is no longer supported. An OpenJDK 21 toolchain is now the global default.")
 		}
 		if toolchain17, ok := ret.environ.Get("EXPERIMENTAL_USE_OPENJDK17_TOOLCHAIN"); ok && toolchain17 != "true" {
-			ctx.Fatalln("The environment variable EXPERIMENTAL_USE_OPENJDK17_TOOLCHAIN is no longer supported. An OpenJDK 17 toolchain is now the global default.")
+			ctx.Fatalln("The environment variable EXPERIMENTAL_USE_OPENJDK17_TOOLCHAIN is no longer supported. An OpenJDK 21 toolchain is now the global default.")
 		}
-		return java17Home
+		if toolchain21, ok := ret.environ.Get("EXPERIMENTAL_USE_OPENJDK21_TOOLCHAIN"); ok && toolchain21 != "true" {
+			ctx.Fatalln("The environment variable EXPERIMENTAL_USE_OPENJDK21_TOOLCHAIN is no longer supported. An OpenJDK 21 toolchain is now the global default.")
+		}
+		return java21Home
 	}()
 	absJavaHome := absPath(ctx, javaHome)
 
@@ -1387,7 +1386,9 @@
 }
 
 func (c *configImpl) rbeSockAddr(dir string) (string, error) {
-	maxNameLen := len(syscall.RawSockaddrUnix{}.Path)
+	// Absolute path socket addresses have a prefix of //. This should
+	// be included in the length limit.
+	maxNameLen := len(syscall.RawSockaddrUnix{}.Path) - 2
 	base := fmt.Sprintf("reproxy_%v.sock", rbeRandPrefix)
 
 	name := filepath.Join(dir, base)
diff --git a/ui/build/rbe.go b/ui/build/rbe.go
index fa04207..5142a41 100644
--- a/ui/build/rbe.go
+++ b/ui/build/rbe.go
@@ -163,7 +163,7 @@
 		return
 	}
 	fmt.Fprintln(ctx.Writer, "")
-	fmt.Fprintln(ctx.Writer, "\033[33mWARNING: Missing LOAS credentials, please run `gcert`. This will result in failing builds in the future, see go/rbe-android-default-announcement.\033[0m")
+	fmt.Fprintln(ctx.Writer, "\033[33mWARNING: Missing LOAS credentials, please run `gcert`. This is required for a successful build execution. See go/rbe-android-default-announcement for more information.\033[0m")
 	fmt.Fprintln(ctx.Writer, "")
 }
 
diff --git a/ui/status/ninja.go b/ui/status/ninja.go
index f4e3fb8..8c3ff29 100644
--- a/ui/status/ninja.go
+++ b/ui/status/ninja.go
@@ -206,10 +206,11 @@
 		}
 		if msg.EdgeStarted != nil {
 			action := &Action{
-				Description: msg.EdgeStarted.GetDesc(),
-				Outputs:     msg.EdgeStarted.Outputs,
-				Inputs:      msg.EdgeStarted.Inputs,
-				Command:     msg.EdgeStarted.GetCommand(),
+				Description:   msg.EdgeStarted.GetDesc(),
+				Outputs:       msg.EdgeStarted.Outputs,
+				Inputs:        msg.EdgeStarted.Inputs,
+				Command:       msg.EdgeStarted.GetCommand(),
+				ChangedInputs: msg.EdgeStarted.ChangedInputs,
 			}
 			n.status.StartAction(action)
 			running[msg.EdgeStarted.GetId()] = action
diff --git a/ui/status/ninja_frontend/frontend.pb.go b/ui/status/ninja_frontend/frontend.pb.go
index d8344c8..fce7ca2 100644
--- a/ui/status/ninja_frontend/frontend.pb.go
+++ b/ui/status/ninja_frontend/frontend.pb.go
@@ -363,6 +363,8 @@
 	Command *string `protobuf:"bytes,6,opt,name=command" json:"command,omitempty"`
 	// Edge uses console.
 	Console *bool `protobuf:"varint,7,opt,name=console" json:"console,omitempty"`
+	// Changed inputs.
+	ChangedInputs []string `protobuf:"bytes,8,rep,name=changed_inputs,json=changedInputs" json:"changed_inputs,omitempty"`
 }
 
 func (x *Status_EdgeStarted) Reset() {
@@ -446,6 +448,13 @@
 	return false
 }
 
+func (x *Status_EdgeStarted) GetChangedInputs() []string {
+	if x != nil {
+		return x.ChangedInputs
+	}
+	return nil
+}
+
 type Status_EdgeFinished struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -678,7 +687,7 @@
 
 var file_frontend_proto_rawDesc = []byte{
 	0x0a, 0x0e, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x12, 0x05, 0x6e, 0x69, 0x6e, 0x6a, 0x61, 0x22, 0xa9, 0x0b, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74,
+	0x12, 0x05, 0x6e, 0x69, 0x6e, 0x6a, 0x61, 0x22, 0xdb, 0x0b, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74,
 	0x75, 0x73, 0x12, 0x39, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x65, 0x64, 0x67, 0x65,
 	0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6e, 0x69, 0x6e, 0x6a, 0x61, 0x2e,
 	0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x45, 0x64, 0x67, 0x65,
@@ -717,7 +726,7 @@
 	0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x65, 0x73, 0x74, 0x69,
 	0x6d, 0x61, 0x74, 0x65, 0x64, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x1a, 0x0f,
 	0x0a, 0x0d, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x1a,
-	0xb6, 0x01, 0x0a, 0x0b, 0x45, 0x64, 0x67, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x12,
+	0xe8, 0x01, 0x0a, 0x0b, 0x45, 0x64, 0x67, 0x65, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x12,
 	0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12,
 	0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20,
 	0x01, 0x28, 0x0d, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x16,
@@ -728,50 +737,54 @@
 	0x64, 0x65, 0x73, 0x63, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x18,
 	0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x12, 0x18,
 	0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52,
-	0x07, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x1a, 0xf3, 0x03, 0x0a, 0x0c, 0x45, 0x64, 0x67,
-	0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64,
-	0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x65, 0x6e, 0x64,
-	0x54, 0x69, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03,
-	0x20, 0x01, 0x28, 0x11, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16, 0x0a, 0x06,
-	0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x75,
-	0x74, 0x70, 0x75, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d,
-	0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x54, 0x69, 0x6d,
-	0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x74, 0x69, 0x6d, 0x65,
-	0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x54, 0x69,
-	0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x0a, 0x6d, 0x61, 0x78, 0x5f, 0x72, 0x73, 0x73, 0x5f, 0x6b, 0x62,
-	0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x52, 0x73, 0x73, 0x4b, 0x62,
-	0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x66,
-	0x61, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6d, 0x69, 0x6e,
-	0x6f, 0x72, 0x50, 0x61, 0x67, 0x65, 0x46, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11,
-	0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x66, 0x61, 0x75, 0x6c, 0x74,
-	0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x50, 0x61,
-	0x67, 0x65, 0x46, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x69, 0x6f, 0x5f, 0x69,
-	0x6e, 0x70, 0x75, 0x74, 0x5f, 0x6b, 0x62, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x69,
-	0x6f, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x4b, 0x62, 0x12, 0x20, 0x0a, 0x0c, 0x69, 0x6f, 0x5f, 0x6f,
-	0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x6b, 0x62, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a,
-	0x69, 0x6f, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4b, 0x62, 0x12, 0x3c, 0x0a, 0x1a, 0x76, 0x6f,
-	0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f,
-	0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x04, 0x52, 0x18,
-	0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74,
-	0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x12, 0x40, 0x0a, 0x1c, 0x69, 0x6e, 0x76, 0x6f,
-	0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f,
-	0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x04, 0x52, 0x1a,
-	0x69, 0x6e, 0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x74, 0x65,
-	0x78, 0x74, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x61,
-	0x67, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x1a, 0x92,
-	0x01, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x37, 0x0a, 0x05, 0x6c, 0x65,
-	0x76, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x6e, 0x69, 0x6e, 0x6a,
-	0x61, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
-	0x2e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x3a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x52, 0x05, 0x6c, 0x65,
-	0x76, 0x65, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x34, 0x0a,
-	0x05, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x00,
-	0x12, 0x0b, 0x0a, 0x07, 0x57, 0x41, 0x52, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x09, 0x0a,
-	0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45, 0x42, 0x55,
-	0x47, 0x10, 0x03, 0x42, 0x2a, 0x48, 0x03, 0x5a, 0x26, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-	0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x75, 0x69, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
-	0x2f, 0x6e, 0x69, 0x6e, 0x6a, 0x61, 0x5f, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x64,
+	0x07, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x68, 0x61, 0x6e,
+	0x67, 0x65, 0x64, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73,
+	0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x12, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x53,
+	0x6f, 0x75, 0x72, 0x63, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x1a, 0xf3, 0x03, 0x0a, 0x0c, 0x45,
+	0x64, 0x67, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69,
+	0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x65,
+	0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x65,
+	0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73,
+	0x18, 0x03, 0x20, 0x01, 0x28, 0x11, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x16,
+	0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
+	0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x74,
+	0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x54,
+	0x69, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x74, 0x69,
+	0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d,
+	0x54, 0x69, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x0a, 0x6d, 0x61, 0x78, 0x5f, 0x72, 0x73, 0x73, 0x5f,
+	0x6b, 0x62, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x52, 0x73, 0x73,
+	0x4b, 0x62, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x5f, 0x70, 0x61, 0x67, 0x65,
+	0x5f, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6d,
+	0x69, 0x6e, 0x6f, 0x72, 0x50, 0x61, 0x67, 0x65, 0x46, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x2a,
+	0x0a, 0x11, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x66, 0x61, 0x75,
+	0x6c, 0x74, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6d, 0x61, 0x6a, 0x6f, 0x72,
+	0x50, 0x61, 0x67, 0x65, 0x46, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x69, 0x6f,
+	0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x6b, 0x62, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52,
+	0x09, 0x69, 0x6f, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x4b, 0x62, 0x12, 0x20, 0x0a, 0x0c, 0x69, 0x6f,
+	0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x6b, 0x62, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04,
+	0x52, 0x0a, 0x69, 0x6f, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4b, 0x62, 0x12, 0x3c, 0x0a, 0x1a,
+	0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78,
+	0x74, 0x5f, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x04,
+	0x52, 0x18, 0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x74, 0x65,
+	0x78, 0x74, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x12, 0x40, 0x0a, 0x1c, 0x69, 0x6e,
+	0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78,
+	0x74, 0x5f, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x04,
+	0x52, 0x1a, 0x69, 0x6e, 0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x43, 0x6f, 0x6e,
+	0x74, 0x65, 0x78, 0x74, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x12, 0x12, 0x0a, 0x04,
+	0x74, 0x61, 0x67, 0x73, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73,
+	0x1a, 0x92, 0x01, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x37, 0x0a, 0x05,
+	0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x6e, 0x69,
+	0x6e, 0x6a, 0x61, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61,
+	0x67, 0x65, 0x2e, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x3a, 0x04, 0x49, 0x4e, 0x46, 0x4f, 0x52, 0x05,
+	0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22,
+	0x34, 0x0a, 0x05, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x46, 0x4f,
+	0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x57, 0x41, 0x52, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12,
+	0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x44, 0x45,
+	0x42, 0x55, 0x47, 0x10, 0x03, 0x42, 0x2a, 0x48, 0x03, 0x5a, 0x26, 0x61, 0x6e, 0x64, 0x72, 0x6f,
+	0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x75, 0x69, 0x2f, 0x73, 0x74, 0x61, 0x74,
+	0x75, 0x73, 0x2f, 0x6e, 0x69, 0x6e, 0x6a, 0x61, 0x5f, 0x66, 0x72, 0x6f, 0x6e, 0x74, 0x65, 0x6e,
+	0x64,
 }
 
 var (
diff --git a/ui/status/ninja_frontend/frontend.proto b/ui/status/ninja_frontend/frontend.proto
index 42b251b..faa0d34 100644
--- a/ui/status/ninja_frontend/frontend.proto
+++ b/ui/status/ninja_frontend/frontend.proto
@@ -54,6 +54,8 @@
     optional string command = 6;
     // Edge uses console.
     optional bool console = 7;
+    // Changed inputs since last build.
+    repeated string changed_inputs = 8;
   }
 
   message EdgeFinished {
diff --git a/ui/status/status.go b/ui/status/status.go
index da78994..52ed56a 100644
--- a/ui/status/status.go
+++ b/ui/status/status.go
@@ -41,6 +41,10 @@
 	// It's optional, but one of either Description or Command should be
 	// set.
 	Command string
+
+	// ChangedInputs is the (optional) list of inputs that have changed
+	// since last time this action was run.
+	ChangedInputs []string
 }
 
 // ActionResult describes the result of running an Action.
diff --git a/ui/tracer/status.go b/ui/tracer/status.go
index f973613..8acf561 100644
--- a/ui/tracer/status.go
+++ b/ui/tracer/status.go
@@ -15,9 +15,10 @@
 package tracer
 
 import (
-	"android/soong/ui/status"
 	"strings"
 	"time"
+
+	"android/soong/ui/status"
 )
 
 func (t *tracerImpl) StatusTracer() status.StatusOutput {
@@ -110,6 +111,7 @@
 			VoluntaryContextSwitches:   result.Stats.VoluntaryContextSwitches,
 			InvoluntaryContextSwitches: result.Stats.InvoluntaryContextSwitches,
 			Tags:                       s.parseTags(result.Stats.Tags),
+			ChangedInputs:              result.Action.ChangedInputs,
 		},
 	})
 }
@@ -125,6 +127,7 @@
 	VoluntaryContextSwitches   uint64            `json:"voluntary_context_switches"`
 	InvoluntaryContextSwitches uint64            `json:"involuntary_context_switches"`
 	Tags                       map[string]string `json:"tags"`
+	ChangedInputs              []string          `json:"changed_inputs"`
 }
 
 func (s *statusOutput) Flush()                                        {}