Merge "release_config: Initial implementation" into main
diff --git a/android/Android.bp b/android/Android.bp
index bf7804a..4c59592 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -89,6 +89,7 @@
         "sandbox.go",
         "sdk.go",
         "sdk_version.go",
+        "shared_properties.go",
         "singleton.go",
         "singleton_module.go",
         "soong_config_modules.go",
@@ -102,6 +103,7 @@
         "visibility.go",
     ],
     testSrcs: [
+        "all_teams_test.go",
         "android_test.go",
         "androidmk_test.go",
         "apex_test.go",
diff --git a/android/all_teams.go b/android/all_teams.go
index b177e20..0c433a6 100644
--- a/android/all_teams.go
+++ b/android/all_teams.go
@@ -23,9 +23,18 @@
 }
 
 // For each module, list the team or the bpFile the module is defined in.
-type moduleTeamInfo struct {
+type moduleTeamAndTestInfo struct {
+	// Name field from bp file for the team
 	teamName string
-	bpFile   string
+	// Blueprint file the module is located in.
+	bpFile string
+	// Is this module only used by tests.
+	testOnly bool
+	// Is this a directly testable target by running the module directly
+	// or via tradefed.
+	topLevelTestTarget bool
+	// String name indicating the module, like `java_library` for reporting.
+	kind string
 }
 
 type allTeamsSingleton struct {
@@ -37,16 +46,16 @@
 	// Map of all team modules we visit during GenerateBuildActions
 	teams map[string]teamProperties
 	// Keeps track of team information or bp file for each module we visit.
-	teams_for_mods map[string]moduleTeamInfo
+	teams_for_mods map[string]moduleTeamAndTestInfo
 }
 
 // See if there is a package module for the given bpFilePath with a team defined, if so return the team.
 // If not ascend up to the parent directory and do the same.
-func (this *allTeamsSingleton) lookupDefaultTeam(bpFilePath string) (teamProperties, bool) {
+func (t *allTeamsSingleton) lookupDefaultTeam(bpFilePath string) (teamProperties, bool) {
 	// return the Default_team listed in the package if is there.
-	if p, ok := this.packages[bpFilePath]; ok {
-		if t := p.Default_team; t != nil {
-			return this.teams[*p.Default_team], true
+	if p, ok := t.packages[bpFilePath]; ok {
+		if defaultTeam := p.Default_team; defaultTeam != nil {
+			return t.teams[*defaultTeam], true
 		}
 	}
 	// Strip a directory and go up.
@@ -57,72 +66,79 @@
 	if current == "." {
 		return teamProperties{}, false
 	}
-	return this.lookupDefaultTeam(filepath.Join(parent, base))
+	return t.lookupDefaultTeam(filepath.Join(parent, base))
 }
 
-// Create a rule to run a tool to collect all the intermediate files
-// which list the team per module into one proto file.
-func (this *allTeamsSingleton) GenerateBuildActions(ctx SingletonContext) {
-	this.packages = make(map[string]packageProperties)
-	this.teams = make(map[string]teamProperties)
-	this.teams_for_mods = make(map[string]moduleTeamInfo)
+// Visit all modules and collect all teams and use WriteFileRuleVerbatim
+// to write it out.
+func (t *allTeamsSingleton) GenerateBuildActions(ctx SingletonContext) {
+	t.packages = make(map[string]packageProperties)
+	t.teams = make(map[string]teamProperties)
+	t.teams_for_mods = make(map[string]moduleTeamAndTestInfo)
 
 	ctx.VisitAllModules(func(module Module) {
 		bpFile := ctx.BlueprintFile(module)
 
+		testModInfo := TestModuleInformation{}
+		if tmi, ok := SingletonModuleProvider(ctx, module, TestOnlyProviderKey); ok {
+			testModInfo = tmi
+		}
+
 		// Package Modules and Team Modules are stored in a map so we can look them up by name for
 		// modules without a team.
 		if pack, ok := module.(*packageModule); ok {
-			// Packages don't have names, use the blueprint file as the key. we can't get qualifiedModuleId in this context.
+			// Packages don't have names, use the blueprint file as the key. we can't get qualifiedModuleId in t context.
 			pkgKey := bpFile
-			this.packages[pkgKey] = pack.properties
+			t.packages[pkgKey] = pack.properties
 			return
 		}
 		if team, ok := module.(*teamModule); ok {
-			this.teams[team.Name()] = team.properties
+			t.teams[team.Name()] = team.properties
 			return
 		}
 
-		// If a team name is given for a module, store it.
-		// Otherwise store the bpFile so we can do a package walk later.
-		if module.base().Team() != "" {
-			this.teams_for_mods[module.Name()] = moduleTeamInfo{teamName: module.base().Team(), bpFile: bpFile}
-		} else {
-			this.teams_for_mods[module.Name()] = moduleTeamInfo{bpFile: bpFile}
+		entry := moduleTeamAndTestInfo{
+			bpFile:             bpFile,
+			testOnly:           testModInfo.TestOnly,
+			topLevelTestTarget: testModInfo.TopLevelTarget,
+			kind:               ctx.ModuleType(module),
+			teamName:           module.base().Team(),
 		}
+		t.teams_for_mods[module.Name()] = entry
+
 	})
 
 	// Visit all modules again and lookup the team name in the package or parent package if the team
 	// isn't assignged at the module level.
-	allTeams := this.lookupTeamForAllModules()
+	allTeams := t.lookupTeamForAllModules()
 
-	this.outputPath = PathForOutput(ctx, ownershipDirectory, allTeamsFile)
+	t.outputPath = PathForOutput(ctx, ownershipDirectory, allTeamsFile)
 	data, err := proto.Marshal(allTeams)
 	if err != nil {
 		ctx.Errorf("Unable to marshal team data. %s", err)
 	}
 
-	WriteFileRuleVerbatim(ctx, this.outputPath, string(data))
-	ctx.Phony("all_teams", this.outputPath)
+	WriteFileRuleVerbatim(ctx, t.outputPath, string(data))
+	ctx.Phony("all_teams", t.outputPath)
 }
 
-func (this *allTeamsSingleton) MakeVars(ctx MakeVarsContext) {
-	ctx.DistForGoal("all_teams", this.outputPath)
+func (t *allTeamsSingleton) MakeVars(ctx MakeVarsContext) {
+	ctx.DistForGoal("all_teams", t.outputPath)
 }
 
 // Visit every (non-package, non-team) module and write out a proto containing
 // either the declared team data for that module or the package default team data for that module.
-func (this *allTeamsSingleton) lookupTeamForAllModules() *team_proto.AllTeams {
-	teamsProto := make([]*team_proto.Team, len(this.teams_for_mods))
-	for i, moduleName := range SortedKeys(this.teams_for_mods) {
-		m, _ := this.teams_for_mods[moduleName]
+func (t *allTeamsSingleton) lookupTeamForAllModules() *team_proto.AllTeams {
+	teamsProto := make([]*team_proto.Team, len(t.teams_for_mods))
+	for i, moduleName := range SortedKeys(t.teams_for_mods) {
+		m, _ := t.teams_for_mods[moduleName]
 		teamName := m.teamName
 		var teamProperties teamProperties
 		found := false
 		if teamName != "" {
-			teamProperties, found = this.teams[teamName]
+			teamProperties, found = t.teams[teamName]
 		} else {
-			teamProperties, found = this.lookupDefaultTeam(m.bpFile)
+			teamProperties, found = t.lookupDefaultTeam(m.bpFile)
 		}
 
 		trendy_team_id := ""
@@ -130,22 +146,18 @@
 			trendy_team_id = *teamProperties.Trendy_team_id
 		}
 
-		var files []string
 		teamData := new(team_proto.Team)
+		*teamData = team_proto.Team{
+			TargetName:     proto.String(moduleName),
+			Path:           proto.String(m.bpFile),
+			TestOnly:       proto.Bool(m.testOnly),
+			TopLevelTarget: proto.Bool(m.topLevelTestTarget),
+			Kind:           proto.String(m.kind),
+		}
 		if trendy_team_id != "" {
-			*teamData = team_proto.Team{
-				TrendyTeamId: proto.String(trendy_team_id),
-				TargetName:   proto.String(moduleName),
-				Path:         proto.String(m.bpFile),
-				File:         files,
-			}
+			teamData.TrendyTeamId = proto.String(trendy_team_id)
 		} else {
 			// Clients rely on the TrendyTeamId optional field not being set.
-			*teamData = team_proto.Team{
-				TargetName: proto.String(moduleName),
-				Path:       proto.String(m.bpFile),
-				File:       files,
-			}
 		}
 		teamsProto[i] = teamData
 	}
diff --git a/android/all_teams_test.go b/android/all_teams_test.go
index a02b86e..9c2b38e 100644
--- a/android/all_teams_test.go
+++ b/android/all_teams_test.go
@@ -24,9 +24,8 @@
 func TestAllTeams(t *testing.T) {
 	t.Parallel()
 	ctx := GroupFixturePreparers(
-		PrepareForTestWithTeamBuildComponents,
+		prepareForTestWithTeamAndFakes,
 		FixtureRegisterWithContext(func(ctx RegistrationContext) {
-			ctx.RegisterModuleType("fake", fakeModuleFactory)
 			ctx.RegisterParallelSingletonType("all_teams", AllTeamsFactory)
 		}),
 	).RunTestWithBp(t, `
@@ -51,6 +50,12 @@
 
 		fake {
 			name: "noteam",
+                        test_only: true,
+		}
+		fake {
+			name: "test-and-team-and-top",
+                        test_only: true,
+                        team: "team2",
 		}
 	`)
 
@@ -59,16 +64,31 @@
 
 	// map of module name -> trendy team name.
 	actualTeams := make(map[string]*string)
+	actualTests := []string{}
+	actualTopLevelTests := []string{}
+
 	for _, teamProto := range teams.Teams {
 		actualTeams[teamProto.GetTargetName()] = teamProto.TrendyTeamId
+		if teamProto.GetTestOnly() {
+			actualTests = append(actualTests, teamProto.GetTargetName())
+		}
+		if teamProto.GetTopLevelTarget() {
+			actualTopLevelTests = append(actualTopLevelTests, teamProto.GetTargetName())
+		}
 	}
 	expectedTeams := map[string]*string{
-		"main_test": proto.String("cool_team"),
-		"tool":      proto.String("22222"),
-		"noteam":    nil,
+		"main_test":             proto.String("cool_team"),
+		"tool":                  proto.String("22222"),
+		"test-and-team-and-top": proto.String("22222"),
+		"noteam":                nil,
 	}
 
+	expectedTests := []string{
+		"noteam",
+		"test-and-team-and-top",
+	}
 	AssertDeepEquals(t, "compare maps", expectedTeams, actualTeams)
+	AssertDeepEquals(t, "test matchup", expectedTests, actualTests)
 }
 
 func getTeamProtoOutput(t *testing.T, ctx *TestResult) *team_proto.AllTeams {
@@ -171,10 +191,9 @@
 		} `
 
 	ctx := GroupFixturePreparers(
-		PrepareForTestWithTeamBuildComponents,
+		prepareForTestWithTeamAndFakes,
 		PrepareForTestWithPackageModule,
 		FixtureRegisterWithContext(func(ctx RegistrationContext) {
-			ctx.RegisterModuleType("fake", fakeModuleFactory)
 			ctx.RegisterParallelSingletonType("all_teams", AllTeamsFactory)
 		}),
 		FixtureAddTextFile("Android.bp", rootBp),
@@ -206,3 +225,33 @@
 	}
 	AssertDeepEquals(t, "compare maps", expectedTeams, actualTeams)
 }
+
+type fakeForTests struct {
+	ModuleBase
+
+	sourceProperties SourceProperties
+}
+
+func fakeFactory() Module {
+	module := &fakeForTests{}
+	module.AddProperties(&module.sourceProperties)
+	InitAndroidModule(module)
+
+	return module
+}
+
+var prepareForTestWithTeamAndFakes = GroupFixturePreparers(
+	FixtureRegisterWithContext(RegisterTeamBuildComponents),
+	FixtureRegisterWithContext(func(ctx RegistrationContext) {
+		ctx.RegisterModuleType("fake", fakeFactory)
+	}),
+)
+
+func (f *fakeForTests) GenerateAndroidBuildActions(ctx ModuleContext) {
+	if Bool(f.sourceProperties.Test_only) {
+		SetProvider(ctx, TestOnlyProviderKey, TestModuleInformation{
+			TestOnly:       Bool(f.sourceProperties.Test_only),
+			TopLevelTarget: false,
+		})
+	}
+}
diff --git a/android/config.go b/android/config.go
index 714ce5d..11bd81b 100644
--- a/android/config.go
+++ b/android/config.go
@@ -1409,6 +1409,11 @@
 	return strconv.Itoa(vendorApiLevel - 100)
 }
 
+func IsTrunkStableVendorApiLevel(level string) bool {
+	levelInt, err := strconv.Atoi(level)
+	return err == nil && levelInt >= 202404
+}
+
 func (c *config) VendorApiLevelFrozen() bool {
 	return c.productVariables.GetBuildFlagBool("RELEASE_BOARD_API_LEVEL_FROZEN")
 }
diff --git a/android/shared_properties.go b/android/shared_properties.go
new file mode 100644
index 0000000..84d68fa
--- /dev/null
+++ b/android/shared_properties.go
@@ -0,0 +1,26 @@
+// 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
+
+// For storing user-supplied properties about source code on a module to be queried later.
+type SourceProperties struct {
+	// Indicates that the module and its source code are only used in tests, not
+	// production code. Used by coverage reports and potentially other tools.
+	Test_only *bool
+	// Used internally to write if this is a top level test target.
+	// i.e. something that can be run directly or through tradefed as a test.
+	// `java_library` would be false, `java_test` would be true.
+	Top_level_test_target bool `blueprint:"mutated"`
+}
diff --git a/android/team.go b/android/team.go
index df61f40..c273dc6 100644
--- a/android/team.go
+++ b/android/team.go
@@ -14,6 +14,8 @@
 
 package android
 
+import "github.com/google/blueprint"
+
 func init() {
 	RegisterTeamBuildComponents(InitRegistrationContext)
 }
@@ -37,6 +39,13 @@
 	properties teamProperties
 }
 
+type TestModuleInformation struct {
+	TestOnly       bool
+	TopLevelTarget bool
+}
+
+var TestOnlyProviderKey = blueprint.NewProvider[TestModuleInformation]()
+
 // Real work is done for the module that depends on us.
 // If needed, the team can serialize the config to json/proto file as well.
 func (t *teamModule) GenerateAndroidBuildActions(ctx ModuleContext) {}
diff --git a/android/team_proto/team.pb.go b/android/team_proto/team.pb.go
index 61260cf..8414d17 100644
--- a/android/team_proto/team.pb.go
+++ b/android/team_proto/team.pb.go
@@ -46,6 +46,13 @@
 	TrendyTeamId *string `protobuf:"bytes,3,opt,name=trendy_team_id,json=trendyTeamId" json:"trendy_team_id,omitempty"`
 	// OPTIONAL: Files directly owned by this module.
 	File []string `protobuf:"bytes,4,rep,name=file" json:"file,omitempty"`
+	// OPTIONAL: Is this a test-only module.
+	TestOnly *bool `protobuf:"varint,5,opt,name=test_only,json=testOnly" json:"test_only,omitempty"`
+	// OPTIONAL: Is this intended to be run as a test target.
+	// This target can be run directly as a test or passed to tradefed.
+	TopLevelTarget *bool `protobuf:"varint,6,opt,name=top_level_target,json=topLevelTarget" json:"top_level_target,omitempty"`
+	// OPTIONAL: Name of module kind, i.e. java_library
+	Kind *string `protobuf:"bytes,7,opt,name=kind" json:"kind,omitempty"`
 }
 
 func (x *Team) Reset() {
@@ -108,6 +115,27 @@
 	return nil
 }
 
+func (x *Team) GetTestOnly() bool {
+	if x != nil && x.TestOnly != nil {
+		return *x.TestOnly
+	}
+	return false
+}
+
+func (x *Team) GetTopLevelTarget() bool {
+	if x != nil && x.TopLevelTarget != nil {
+		return *x.TopLevelTarget
+	}
+	return false
+}
+
+func (x *Team) GetKind() string {
+	if x != nil && x.Kind != nil {
+		return *x.Kind
+	}
+	return ""
+}
+
 type AllTeams struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -159,20 +187,26 @@
 
 var file_team_proto_rawDesc = []byte{
 	0x0a, 0x0a, 0x74, 0x65, 0x61, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x74, 0x65,
-	0x61, 0x6d, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x75, 0x0a, 0x04, 0x54, 0x65, 0x61, 0x6d,
-	0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4e, 0x61, 0x6d,
-	0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x72, 0x65, 0x6e, 0x64, 0x79, 0x5f,
-	0x74, 0x65, 0x61, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74,
-	0x72, 0x65, 0x6e, 0x64, 0x79, 0x54, 0x65, 0x61, 0x6d, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66,
-	0x69, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x22,
-	0x32, 0x0a, 0x08, 0x41, 0x6c, 0x6c, 0x54, 0x65, 0x61, 0x6d, 0x73, 0x12, 0x26, 0x0a, 0x05, 0x74,
-	0x65, 0x61, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x65, 0x61,
-	0x6d, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x65, 0x61, 0x6d, 0x52, 0x05, 0x74, 0x65,
-	0x61, 0x6d, 0x73, 0x42, 0x22, 0x5a, 0x20, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73,
-	0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x74, 0x65, 0x61,
-	0x6d, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x61, 0x6d, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd0, 0x01, 0x0a, 0x04, 0x54, 0x65, 0x61,
+	0x6d, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4e, 0x61,
+	0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x72, 0x65, 0x6e, 0x64, 0x79,
+	0x5f, 0x74, 0x65, 0x61, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c,
+	0x74, 0x72, 0x65, 0x6e, 0x64, 0x79, 0x54, 0x65, 0x61, 0x6d, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04,
+	0x66, 0x69, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65,
+	0x12, 0x1b, 0x0a, 0x09, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x05, 0x20,
+	0x01, 0x28, 0x08, 0x52, 0x08, 0x74, 0x65, 0x73, 0x74, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x28, 0x0a,
+	0x10, 0x74, 0x6f, 0x70, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x5f, 0x74, 0x61, 0x72, 0x67, 0x65,
+	0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x74, 0x6f, 0x70, 0x4c, 0x65, 0x76, 0x65,
+	0x6c, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18,
+	0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x22, 0x32, 0x0a, 0x08, 0x41,
+	0x6c, 0x6c, 0x54, 0x65, 0x61, 0x6d, 0x73, 0x12, 0x26, 0x0a, 0x05, 0x74, 0x65, 0x61, 0x6d, 0x73,
+	0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x65, 0x61, 0x6d, 0x5f, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x2e, 0x54, 0x65, 0x61, 0x6d, 0x52, 0x05, 0x74, 0x65, 0x61, 0x6d, 0x73, 0x42,
+	0x22, 0x5a, 0x20, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67,
+	0x2f, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x74, 0x65, 0x61, 0x6d, 0x5f, 0x70, 0x72,
+	0x6f, 0x74, 0x6f,
 }
 
 var (
diff --git a/android/team_proto/team.proto b/android/team_proto/team.proto
index 401eccc..69ebf43 100644
--- a/android/team_proto/team.proto
+++ b/android/team_proto/team.proto
@@ -27,6 +27,16 @@
 
   // OPTIONAL: Files directly owned by this module.
   repeated string file = 4;
+
+  // OPTIONAL: Is this a test-only module.
+  optional bool test_only = 5;
+
+  // OPTIONAL: Is this intended to be run as a test target.
+  // This target can be run directly as a test or passed to tradefed.
+  optional bool top_level_target = 6;
+
+  // OPTIONAL: Name of module kind, i.e. java_library
+  optional string kind = 7;
 }
 
 message AllTeams {
diff --git a/android/team_test.go b/android/team_test.go
index 75b3e9f..ccfcaaa 100644
--- a/android/team_test.go
+++ b/android/team_test.go
@@ -27,16 +27,19 @@
 	return module
 }
 
+var prepareForTestWithTeamAndFakeBuildComponents = GroupFixturePreparers(
+	FixtureRegisterWithContext(RegisterTeamBuildComponents),
+	FixtureRegisterWithContext(func(ctx RegistrationContext) {
+		ctx.RegisterModuleType("fake", fakeModuleFactory)
+	}),
+)
+
 func (*fakeModuleForTests) GenerateAndroidBuildActions(ModuleContext) {}
 
 func TestTeam(t *testing.T) {
 	t.Parallel()
-	ctx := GroupFixturePreparers(
-		PrepareForTestWithTeamBuildComponents,
-		FixtureRegisterWithContext(func(ctx RegistrationContext) {
-			ctx.RegisterModuleType("fake", fakeModuleFactory)
-		}),
-	).RunTestWithBp(t, `
+	ctx := prepareForTestWithTeamAndFakeBuildComponents.
+		RunTestWithBp(t, `
 		fake {
 			name: "main_test",
 			team: "someteam",
@@ -66,12 +69,7 @@
 
 func TestMissingTeamFails(t *testing.T) {
 	t.Parallel()
-	GroupFixturePreparers(
-		PrepareForTestWithTeamBuildComponents,
-		FixtureRegisterWithContext(func(ctx RegistrationContext) {
-			ctx.RegisterModuleType("fake", fakeModuleFactory)
-		}),
-	).
+	prepareForTestWithTeamAndFakeBuildComponents.
 		ExtendWithErrorHandler(FixtureExpectsAtLeastOneErrorMatchingPattern("depends on undefined module \"ring-bearer")).
 		RunTestWithBp(t, `
 		fake {
@@ -86,9 +84,6 @@
 	GroupFixturePreparers(
 		PrepareForTestWithTeamBuildComponents,
 		PrepareForTestWithPackageModule,
-		FixtureRegisterWithContext(func(ctx RegistrationContext) {
-			ctx.RegisterModuleType("fake", fakeModuleFactory)
-		}),
 	).
 		ExtendWithErrorHandler(FixtureExpectsAtLeastOneErrorMatchingPattern("depends on undefined module \"ring-bearer")).
 		RunTestWithBp(t, `
diff --git a/cc/cc_test.go b/cc/cc_test.go
index 74fae04..d4955c6 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -998,12 +998,30 @@
 			expectedDirs, f.IncludeDirs)
 	}
 
-	checkExportedIncludeDirs("libllndk", "android_arm64_armv8-a_shared", "include")
-	checkExportedIncludeDirs("libllndk", "android_vendor_arm64_armv8-a_shared", "include")
-	checkExportedIncludeDirs("libllndk_with_external_headers", "android_arm64_armv8-a_shared", "include")
-	checkExportedIncludeDirs("libllndk_with_external_headers", "android_vendor_arm64_armv8-a_shared", "include_llndk")
-	checkExportedIncludeDirs("libllndk_with_override_headers", "android_arm64_armv8-a_shared", "include")
-	checkExportedIncludeDirs("libllndk_with_override_headers", "android_vendor_arm64_armv8-a_shared", "include_llndk")
+	checkExportedIncludeDirs("libllndk", coreVariant, "include")
+	checkExportedIncludeDirs("libllndk", vendorVariant, "include")
+	checkExportedIncludeDirs("libllndk_with_external_headers", coreVariant, "include")
+	checkExportedIncludeDirs("libllndk_with_external_headers", vendorVariant, "include_llndk")
+	checkExportedIncludeDirs("libllndk_with_override_headers", coreVariant, "include")
+	checkExportedIncludeDirs("libllndk_with_override_headers", vendorVariant, "include_llndk")
+
+	checkAbiLinkerIncludeDirs := func(module string) {
+		t.Helper()
+		coreModule := result.ModuleForTests(module, coreVariant)
+		abiCheckFlags := ""
+		for _, output := range coreModule.AllOutputs() {
+			if strings.HasSuffix(output, ".so.llndk.lsdump") {
+				abiCheckFlags = coreModule.Output(output).Args["exportedHeaderFlags"]
+			}
+		}
+		vendorModule := result.ModuleForTests(module, vendorVariant).Module()
+		vendorInfo, _ := android.SingletonModuleProvider(result, vendorModule, FlagExporterInfoProvider)
+		android.AssertStringEquals(t, module+" has different exported include dirs for vendor variant and ABI check",
+			android.JoinPathsWithPrefix(vendorInfo.IncludeDirs, "-I"), abiCheckFlags)
+	}
+	checkAbiLinkerIncludeDirs("libllndk")
+	checkAbiLinkerIncludeDirs("libllndk_with_override_headers")
+	checkAbiLinkerIncludeDirs("libllndk_with_external_headers")
 }
 
 func TestLlndkHeaders(t *testing.T) {
diff --git a/cc/library.go b/cc/library.go
index f3db2ef..d9754df 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -1138,7 +1138,7 @@
 	objs.sAbiDumpFiles = append(objs.sAbiDumpFiles, deps.WholeStaticLibObjs.sAbiDumpFiles...)
 
 	library.coverageOutputFile = transformCoverageFilesToZip(ctx, objs, library.getLibName(ctx))
-	library.linkSAbiDumpFiles(ctx, objs, fileName, unstrippedOutputFile)
+	library.linkSAbiDumpFiles(ctx, deps, objs, fileName, unstrippedOutputFile)
 
 	var transitiveStaticLibrariesForOrdering *android.DepSet[android.Path]
 	if static := ctx.GetDirectDepsWithTag(staticVariantTag); len(static) > 0 {
@@ -1207,6 +1207,45 @@
 	return exportIncludeDirs
 }
 
+func (library *libraryDecorator) llndkIncludeDirsForAbiCheck(ctx ModuleContext, deps PathDeps) []string {
+	// The ABI checker does not need the preprocess which adds macro guards to function declarations.
+	includeDirs := android.PathsForModuleSrc(ctx, library.Properties.Llndk.Export_preprocessed_headers).Strings()
+
+	if library.Properties.Llndk.Override_export_include_dirs != nil {
+		includeDirs = append(includeDirs, android.PathsForModuleSrc(
+			ctx, library.Properties.Llndk.Override_export_include_dirs).Strings()...)
+	} else {
+		includeDirs = append(includeDirs, library.flagExporter.exportedIncludes(ctx).Strings()...)
+		// Ignore library.sabi.Properties.ReexportedIncludes because
+		// LLNDK does not reexport the implementation's dependencies, such as export_header_libs.
+	}
+
+	systemIncludeDirs := []string{}
+	if Bool(library.Properties.Llndk.Export_headers_as_system) {
+		systemIncludeDirs = append(systemIncludeDirs, includeDirs...)
+		includeDirs = nil
+	}
+	// Header libs.
+	includeDirs = append(includeDirs, deps.LlndkIncludeDirs.Strings()...)
+	systemIncludeDirs = append(systemIncludeDirs, deps.LlndkSystemIncludeDirs.Strings()...)
+	// The ABI checker does not distinguish normal and system headers.
+	return append(includeDirs, systemIncludeDirs...)
+}
+
+func (library *libraryDecorator) linkLlndkSAbiDumpFiles(ctx ModuleContext,
+	deps PathDeps, sAbiDumpFiles android.Paths, soFile android.Path, libFileName string,
+	excludeSymbolVersions, excludeSymbolTags []string) android.Path {
+	// NDK symbols in version 34 are LLNDK symbols. Those in version 35 are not.
+	// TODO(b/314010764): Add parameters to read LLNDK symbols from the symbol file.
+	return transformDumpToLinkedDump(ctx,
+		sAbiDumpFiles, soFile, libFileName+".llndk",
+		library.llndkIncludeDirsForAbiCheck(ctx, deps),
+		android.OptionalPathForModuleSrc(ctx, library.Properties.Llndk.Symbol_file),
+		append([]string{"*_PLATFORM", "*_PRIVATE"}, excludeSymbolVersions...),
+		append([]string{"platform-only"}, excludeSymbolTags...),
+		"34")
+}
+
 func getRefAbiDumpFile(ctx android.ModuleInstallPathContext,
 	versionedDumpDir, fileName string) android.OptionalPath {
 
@@ -1317,13 +1356,15 @@
 		false /* isLlndkOrNdk */, false /* allowExtensions */, "current", errorMessage)
 }
 
-func (library *libraryDecorator) linkSAbiDumpFiles(ctx ModuleContext, objs Objects, fileName string, soFile android.Path) {
+func (library *libraryDecorator) linkSAbiDumpFiles(ctx ModuleContext, deps PathDeps, objs Objects, fileName string, soFile android.Path) {
 	if library.sabi.shouldCreateSourceAbiDump() {
 		exportedIncludeDirs := library.exportedIncludeDirsForAbiCheck(ctx)
 		headerAbiChecker := library.getHeaderAbiCheckerProperties(ctx)
 		currSdkVersion := currRefAbiDumpSdkVersion(ctx)
 		currVendorVersion := ctx.Config().VendorApiLevel()
-		sourceDump := transformDumpToLinkedDump(ctx,
+
+		// Generate source dumps.
+		implDump := transformDumpToLinkedDump(ctx,
 			objs.sAbiDumpFiles, soFile, fileName,
 			exportedIncludeDirs,
 			android.OptionalPathForModuleSrc(ctx, library.symbolFileForAbiCheck(ctx)),
@@ -1331,8 +1372,25 @@
 			headerAbiChecker.Exclude_symbol_tags,
 			currSdkVersion)
 
-		for _, tag := range classifySourceAbiDump(ctx) {
-			addLsdumpPath(string(tag) + ":" + sourceDump.String())
+		var llndkDump android.Path
+		tags := classifySourceAbiDump(ctx)
+		for _, tag := range tags {
+			if tag == llndkLsdumpTag {
+				if llndkDump == nil {
+					// TODO(b/323447559): Evaluate if replacing sAbiDumpFiles with implDump is faster
+					llndkDump = library.linkLlndkSAbiDumpFiles(ctx,
+						deps, objs.sAbiDumpFiles, soFile, fileName,
+						headerAbiChecker.Exclude_symbol_versions,
+						headerAbiChecker.Exclude_symbol_tags)
+				}
+				addLsdumpPath(string(tag) + ":" + llndkDump.String())
+			} else {
+				addLsdumpPath(string(tag) + ":" + implDump.String())
+			}
+		}
+
+		// Diff source dumps and reference dumps.
+		for _, tag := range tags {
 			dumpDirName := tag.dirName()
 			if dumpDirName == "" {
 				continue
@@ -1347,10 +1405,15 @@
 			}
 			// Check against the previous version.
 			var prevVersion, currVersion string
+			sourceDump := implDump
 			// If this release config does not define VendorApiLevel, fall back to the old policy.
 			if isLlndk && currVendorVersion != "" {
 				prevVersion = ctx.Config().PrevVendorApiLevel()
 				currVersion = currVendorVersion
+				// LLNDK dumps are generated by different rules after trunk stable.
+				if android.IsTrunkStableVendorApiLevel(prevVersion) {
+					sourceDump = llndkDump
+				}
 			} else {
 				prevVersion, currVersion = crossVersionAbiDiffSdkVersions(ctx, dumpDir)
 			}
@@ -1361,8 +1424,12 @@
 					fileName, isLlndk || isNdk, currVersion, nameExt+prevVersion)
 			}
 			// Check against the current version.
+			sourceDump = implDump
 			if isLlndk && currVendorVersion != "" {
 				currVersion = currVendorVersion
+				if android.IsTrunkStableVendorApiLevel(currVersion) {
+					sourceDump = llndkDump
+				}
 			} else {
 				currVersion = currSdkVersion
 			}
@@ -1383,7 +1450,7 @@
 				continue
 			}
 			library.optInAbiDiff(ctx,
-				sourceDump, optInDumpFile.Path(),
+				implDump, optInDumpFile.Path(),
 				fileName, "opt"+strconv.Itoa(i), optInDumpDirPath.String())
 		}
 	}
@@ -1750,9 +1817,6 @@
 	if props := library.getHeaderAbiCheckerProperties(ctx); props.Symbol_file != nil {
 		return props.Symbol_file
 	}
-	if ctx.Module().(*Module).IsLlndk() {
-		return library.Properties.Llndk.Symbol_file
-	}
 	if library.hasStubsVariants() && library.Properties.Stubs.Symbol_file != nil {
 		return library.Properties.Stubs.Symbol_file
 	}
diff --git a/cc/sanitize.go b/cc/sanitize.go
index 37b3e85..db046ec 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -858,6 +858,7 @@
 
 		flags.Local.CFlags = append(flags.Local.CFlags, cfiCflags...)
 		flags.Local.AsFlags = append(flags.Local.AsFlags, cfiAsflags...)
+		flags.CFlagsDeps = append(flags.CFlagsDeps, android.PathForSource(ctx, cfiBlocklistPath + "/" + cfiBlocklistFilename))
 		if Bool(s.Properties.Sanitize.Config.Cfi_assembly_support) {
 			flags.Local.CFlags = append(flags.Local.CFlags, cfiAssemblySupportFlag)
 		}
diff --git a/cc/testing.go b/cc/testing.go
index 2aecd5f..20c435a 100644
--- a/cc/testing.go
+++ b/cc/testing.go
@@ -580,6 +580,7 @@
 		"defaults/cc/common/crtend_so.c":       nil,
 		"defaults/cc/common/crtend.c":          nil,
 		"defaults/cc/common/crtbrand.c":        nil,
+		"external/compiler-rt/lib/cfi/cfi_blocklist.txt":   nil,
 
 		"defaults/cc/common/libclang_rt.ubsan_minimal.android_arm64.a": nil,
 		"defaults/cc/common/libclang_rt.ubsan_minimal.android_arm.a":   nil,
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index 61127bf..cadf9c24 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -36,8 +36,8 @@
 
 func registerBuildComponents(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("android_filesystem", filesystemFactory)
+	ctx.RegisterModuleType("android_filesystem_defaults", filesystemDefaultsFactory)
 	ctx.RegisterModuleType("android_system_image", systemImageFactory)
-	ctx.RegisterModuleType("android_system_image_defaults", systemImageDefaultsFactory)
 	ctx.RegisterModuleType("avb_add_hash_footer", avbAddHashFooterFactory)
 	ctx.RegisterModuleType("avb_add_hash_footer_defaults", avbAddHashFooterDefaultsFactory)
 	ctx.RegisterModuleType("avb_gen_vbmeta_image", avbGenVbmetaImageFactory)
@@ -47,6 +47,7 @@
 type filesystem struct {
 	android.ModuleBase
 	android.PackagingBase
+	android.DefaultableModuleBase
 
 	properties filesystemProperties
 
@@ -144,6 +145,7 @@
 	module.AddProperties(&module.properties)
 	android.InitPackageModule(module)
 	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
 }
 
 var dependencyTag = struct {
@@ -190,9 +192,7 @@
 var pctx = android.NewPackageContext("android/soong/filesystem")
 
 func (f *filesystem) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	if !android.InList(f.PartitionType(), validPartitions) {
-		ctx.PropertyErrorf("partition_type", "partition_type must be one of %s, found: %s", validPartitions, f.PartitionType())
-	}
+	validatePartitionType(ctx, f)
 	switch f.fsType(ctx) {
 	case ext4Type:
 		f.output = f.buildImageUsingBuildImage(ctx)
@@ -208,6 +208,22 @@
 	ctx.InstallFile(f.installDir, f.installFileName(), f.output)
 }
 
+func validatePartitionType(ctx android.ModuleContext, p partition) {
+	if !android.InList(p.PartitionType(), validPartitions) {
+		ctx.PropertyErrorf("partition_type", "partition_type must be one of %s, found: %s", validPartitions, p.PartitionType())
+	}
+
+	ctx.VisitDirectDepsWithTag(android.DefaultsDepTag, func(m android.Module) {
+		if fdm, ok := m.(*filesystemDefaults); ok {
+			if p.PartitionType() != fdm.PartitionType() {
+				ctx.PropertyErrorf("partition_type",
+					"%s doesn't match with the partition type %s of the filesystem default module %s",
+					p.PartitionType(), fdm.PartitionType(), m.Name())
+			}
+		}
+	})
+}
+
 // Copy extra files/dirs that are not from the `deps` property to `rootDir`, checking for conflicts with files
 // already in `rootDir`.
 func (f *filesystem) buildNonDepsFiles(ctx android.ModuleContext, builder *android.RuleBuilder, rootDir android.OutputPath) {
@@ -469,10 +485,16 @@
 		Text(android.PathForArbitraryOutput(ctx, stagingDir).String())
 }
 
+type partition interface {
+	PartitionType() string
+}
+
 func (f *filesystem) PartitionType() string {
 	return proptools.StringDefault(f.properties.Partition_type, "system")
 }
 
+var _ partition = (*filesystem)(nil)
+
 var _ android.AndroidMkEntriesProvider = (*filesystem)(nil)
 
 // Implements android.AndroidMkEntriesProvider
@@ -546,3 +568,37 @@
 func (*filesystem) IsNativeCoverageNeeded(ctx android.IncomingTransitionContext) bool {
 	return ctx.Device() && ctx.DeviceConfig().NativeCoverageEnabled()
 }
+
+// android_filesystem_defaults
+
+type filesystemDefaults struct {
+	android.ModuleBase
+	android.DefaultsModuleBase
+
+	properties filesystemDefaultsProperties
+}
+
+type filesystemDefaultsProperties struct {
+	// Identifies which partition this is for //visibility:any_system_image (and others) visibility
+	// checks, and will be used in the future for API surface checks.
+	Partition_type *string
+}
+
+// android_filesystem_defaults is a default module for android_filesystem and android_system_image
+func filesystemDefaultsFactory() android.Module {
+	module := &filesystemDefaults{}
+	module.AddProperties(&module.properties)
+	module.AddProperties(&android.PackagingProperties{})
+	android.InitDefaultsModule(module)
+	return module
+}
+
+func (f *filesystemDefaults) PartitionType() string {
+	return proptools.StringDefault(f.properties.Partition_type, "system")
+}
+
+var _ partition = (*filesystemDefaults)(nil)
+
+func (f *filesystemDefaults) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	validatePartitionType(ctx, f)
+}
diff --git a/filesystem/filesystem_test.go b/filesystem/filesystem_test.go
index 3ce5d4e..f4ecad4 100644
--- a/filesystem/filesystem_test.go
+++ b/filesystem/filesystem_test.go
@@ -381,7 +381,7 @@
 
 func TestSystemImageDefaults(t *testing.T) {
 	result := fixture.RunTestWithBp(t, `
-		android_system_image_defaults {
+		android_filesystem_defaults {
 			name: "defaults",
 			multilib: {
 				common: {
@@ -447,3 +447,25 @@
 		android.AssertStringListContains(t, "missing entry", fs.entries, e)
 	}
 }
+
+func TestInconsistentPartitionTypesInDefaults(t *testing.T) {
+	fixture.ExtendWithErrorHandler(android.FixtureExpectsOneErrorPattern(
+		"doesn't match with the partition type")).
+		RunTestWithBp(t, `
+		android_filesystem_defaults {
+			name: "system_ext_def",
+			partition_type: "system_ext",
+		}
+
+		android_filesystem_defaults {
+			name: "system_def",
+			partition_type: "system",
+			defaults: ["system_ext_def"],
+		}
+
+		android_system_image {
+			name: "system",
+			defaults: ["system_def"],
+		}
+	`)
+}
diff --git a/filesystem/system_image.go b/filesystem/system_image.go
index 92bb206..5028a49 100644
--- a/filesystem/system_image.go
+++ b/filesystem/system_image.go
@@ -21,7 +21,6 @@
 
 type systemImage struct {
 	filesystem
-	android.DefaultableModuleBase
 
 	properties systemImageProperties
 }
@@ -40,7 +39,6 @@
 	module.filesystem.buildExtraFiles = module.buildExtraFiles
 	module.filesystem.filterPackagingSpec = module.filterPackagingSpec
 	initFilesystemModule(&module.filesystem)
-	android.InitDefaultableModule(module)
 	return module
 }
 
@@ -102,17 +100,3 @@
 func (s *systemImage) filterPackagingSpec(ps android.PackagingSpec) bool {
 	return ps.Partition() == "system"
 }
-
-type systemImageDefaults struct {
-	android.ModuleBase
-	android.DefaultsModuleBase
-}
-
-// android_system_image_defaults is a default module for android_system_image module.
-func systemImageDefaultsFactory() android.Module {
-	module := &systemImageDefaults{}
-	module.AddProperties(&android.PackagingProperties{})
-	module.AddProperties(&systemImageProperties{})
-	android.InitDefaultsModule(module)
-	return module
-}