Add team property to all modules.
This allows vendors (like google) to specify which team owns the test
module and code.
Team is a commonProperty on modules and points to the designate "team"
module. The DepsMutator adds the dependency on the "team" module and
"GenerateBuildActions" write the team data to intermediate files.
A new singleton rule, all_teams visits all modules and writes out
the proto containing the team for each module.
If a module doesn't have a team, then it finds the package in the
blueprint file and parent directory blueprint files that have a
default_team and uses that team.
Test: m all_teams
Test: go test ./python ./java ./cc ./rust ./android
Test: added team to HelloWorldHostTest and built the new asciiproto target
Test: added package default_team and checkout output proto.
Change-Id: I5c07bf489de460a04fc540f5fff0394f39f574a7
diff --git a/android/Android.bp b/android/Android.bp
index 8de0c76..ad07fda 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -11,6 +11,7 @@
"blueprint-metrics",
"sbox_proto",
"soong",
+ "soong-android_team_proto",
"soong-android-soongconfig",
"soong-remoteexec",
"soong-response",
@@ -28,6 +29,7 @@
],
srcs: [
"aconfig_providers.go",
+ "all_teams.go",
"androidmk.go",
"apex.go",
"apex_contributions.go",
@@ -91,6 +93,7 @@
"singleton.go",
"singleton_module.go",
"soong_config_modules.go",
+ "team.go",
"test_asserts.go",
"test_suites.go",
"testing.go",
diff --git a/android/all_teams.go b/android/all_teams.go
new file mode 100644
index 0000000..6c3a219
--- /dev/null
+++ b/android/all_teams.go
@@ -0,0 +1,158 @@
+package android
+
+import (
+ "android/soong/android/team_proto"
+ "path/filepath"
+
+ "google.golang.org/protobuf/proto"
+)
+
+const ownershipDirectory = "ownership"
+const allTeamsFile = "all_teams.pb"
+
+func AllTeamsFactory() Singleton {
+ return &allTeamsSingleton{}
+}
+
+func init() {
+ registerAllTeamBuildComponents(InitRegistrationContext)
+}
+
+func registerAllTeamBuildComponents(ctx RegistrationContext) {
+ ctx.RegisterParallelSingletonType("all_teams", AllTeamsFactory)
+}
+
+// For each module, list the team or the bpFile the module is defined in.
+type moduleTeamInfo struct {
+ teamName string
+ bpFile string
+}
+
+type allTeamsSingleton struct {
+ // Path where the collected metadata is stored after successful validation.
+ outputPath OutputPath
+
+ // Map of all package modules we visit during GenerateBuildActions
+ packages map[string]packageProperties
+ // 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
+}
+
+// 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) {
+ // 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
+ }
+ }
+ // Strip a directory and go up.
+ // Does android/paths.go basePath,SourcePath help?
+ current, base := filepath.Split(bpFilePath)
+ current = filepath.Clean(current) // removes trailing slash, convert "" -> "."
+ parent, _ := filepath.Split(current)
+ if current == "." {
+ return teamProperties{}, false
+ }
+ return this.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)
+
+ ctx.VisitAllModules(func(module Module) {
+ if !module.Enabled() {
+ return
+ }
+
+ bpFile := ctx.BlueprintFile(module)
+
+ // 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.
+ pkgKey := bpFile
+ this.packages[pkgKey] = pack.properties
+ return
+ }
+ if team, ok := module.(*teamModule); ok {
+ this.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}
+ }
+ })
+
+ // 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()
+
+ this.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)
+}
+
+func (this *allTeamsSingleton) MakeVars(ctx MakeVarsContext) {
+ ctx.DistForGoal("all_teams", this.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))
+ i := 0
+ for moduleName, m := range this.teams_for_mods {
+ teamName := m.teamName
+ var teamProperties teamProperties
+ found := false
+ if teamName != "" {
+ teamProperties, found = this.teams[teamName]
+ } else {
+ teamProperties, found = this.lookupDefaultTeam(m.bpFile)
+ }
+
+ trendy_team_id := ""
+ if found {
+ trendy_team_id = *teamProperties.Trendy_team_id
+ }
+
+ var files []string
+ teamData := new(team_proto.Team)
+ 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,
+ }
+ } 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
+ i++
+ }
+ return &team_proto.AllTeams{Teams: teamsProto}
+}
diff --git a/android/all_teams_test.go b/android/all_teams_test.go
new file mode 100644
index 0000000..a02b86e
--- /dev/null
+++ b/android/all_teams_test.go
@@ -0,0 +1,208 @@
+// 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 (
+ "android/soong/android/team_proto"
+ "log"
+ "testing"
+
+ "google.golang.org/protobuf/proto"
+)
+
+func TestAllTeams(t *testing.T) {
+ t.Parallel()
+ ctx := GroupFixturePreparers(
+ PrepareForTestWithTeamBuildComponents,
+ FixtureRegisterWithContext(func(ctx RegistrationContext) {
+ ctx.RegisterModuleType("fake", fakeModuleFactory)
+ ctx.RegisterParallelSingletonType("all_teams", AllTeamsFactory)
+ }),
+ ).RunTestWithBp(t, `
+ fake {
+ name: "main_test",
+ team: "someteam",
+ }
+ team {
+ name: "someteam",
+ trendy_team_id: "cool_team",
+ }
+
+ team {
+ name: "team2",
+ trendy_team_id: "22222",
+ }
+
+ fake {
+ name: "tool",
+ team: "team2",
+ }
+
+ fake {
+ name: "noteam",
+ }
+ `)
+
+ var teams *team_proto.AllTeams
+ teams = getTeamProtoOutput(t, ctx)
+
+ // map of module name -> trendy team name.
+ actualTeams := make(map[string]*string)
+ for _, teamProto := range teams.Teams {
+ actualTeams[teamProto.GetTargetName()] = teamProto.TrendyTeamId
+ }
+ expectedTeams := map[string]*string{
+ "main_test": proto.String("cool_team"),
+ "tool": proto.String("22222"),
+ "noteam": nil,
+ }
+
+ AssertDeepEquals(t, "compare maps", expectedTeams, actualTeams)
+}
+
+func getTeamProtoOutput(t *testing.T, ctx *TestResult) *team_proto.AllTeams {
+ teams := new(team_proto.AllTeams)
+ config := ctx.SingletonForTests("all_teams")
+ allOutputs := config.AllOutputs()
+
+ protoPath := allOutputs[0]
+
+ out := config.MaybeOutput(protoPath)
+ outProto := []byte(ContentFromFileRuleForTests(t, ctx.TestContext, out))
+ if err := proto.Unmarshal(outProto, teams); err != nil {
+ log.Fatalln("Failed to parse teams proto:", err)
+ }
+ return teams
+}
+
+// Android.bp
+//
+// team: team_top
+//
+// # dir1 has no modules with teams,
+// # but has a dir with no Android.bp
+// dir1/Android.bp
+//
+// module_dir1
+//
+// # dirs without and Android.bp should be fine.
+// dir1/dir2/dir3/Android.bp
+//
+// package {}
+// module_dir123
+//
+// teams_dir/Android.bp
+//
+// module_with_team1: team1
+// team1: 111
+//
+// # team comes from upper package default
+// teams_dir/deeper/Android.bp
+//
+// module2_with_team1: team1
+//
+// package_defaults/Android.bp
+// package_defaults/pd2/Android.bp
+//
+// package{ default_team: team_top}
+// module_pd2 ## should get team_top
+//
+// package_defaults/pd2/pd3/Android.bp
+//
+// module_pd3 ## should get team_top
+func TestPackageLookup(t *testing.T) {
+ t.Parallel()
+ rootBp := `
+ team {
+ name: "team_top",
+ trendy_team_id: "trendy://team_top",
+ } `
+
+ dir1Bp := `
+ fake {
+ name: "module_dir1",
+ } `
+ dir3Bp := `
+ package {}
+ fake {
+ name: "module_dir123",
+ } `
+ teamsDirBp := `
+ fake {
+ name: "module_with_team1",
+ team: "team1"
+
+ }
+ team {
+ name: "team1",
+ trendy_team_id: "111",
+ } `
+ teamsDirDeeper := `
+ fake {
+ name: "module2_with_team1",
+ team: "team1"
+ } `
+ // create an empty one.
+ packageDefaultsBp := ""
+ packageDefaultspd2 := `
+ package { default_team: "team_top"}
+ fake {
+ name: "modulepd2",
+ } `
+
+ packageDefaultspd3 := `
+ fake {
+ name: "modulepd3",
+ }
+ fake {
+ name: "modulepd3b",
+ team: "team1"
+ } `
+
+ ctx := GroupFixturePreparers(
+ PrepareForTestWithTeamBuildComponents,
+ PrepareForTestWithPackageModule,
+ FixtureRegisterWithContext(func(ctx RegistrationContext) {
+ ctx.RegisterModuleType("fake", fakeModuleFactory)
+ ctx.RegisterParallelSingletonType("all_teams", AllTeamsFactory)
+ }),
+ FixtureAddTextFile("Android.bp", rootBp),
+ FixtureAddTextFile("dir1/Android.bp", dir1Bp),
+ FixtureAddTextFile("dir1/dir2/dir3/Android.bp", dir3Bp),
+ FixtureAddTextFile("teams_dir/Android.bp", teamsDirBp),
+ FixtureAddTextFile("teams_dir/deeper/Android.bp", teamsDirDeeper),
+ FixtureAddTextFile("package_defaults/Android.bp", packageDefaultsBp),
+ FixtureAddTextFile("package_defaults/pd2/Android.bp", packageDefaultspd2),
+ FixtureAddTextFile("package_defaults/pd2/pd3/Android.bp", packageDefaultspd3),
+ ).RunTest(t)
+
+ var teams *team_proto.AllTeams
+ teams = getTeamProtoOutput(t, ctx)
+
+ // map of module name -> trendy team name.
+ actualTeams := make(map[string]*string)
+ for _, teamProto := range teams.Teams {
+ actualTeams[teamProto.GetTargetName()] = teamProto.TrendyTeamId
+ }
+ expectedTeams := map[string]*string{
+ "module_with_team1": proto.String("111"),
+ "module2_with_team1": proto.String("111"),
+ "modulepd2": proto.String("trendy://team_top"),
+ "modulepd3": proto.String("trendy://team_top"),
+ "modulepd3b": proto.String("111"),
+ "module_dir1": nil,
+ "module_dir123": nil,
+ }
+ AssertDeepEquals(t, "compare maps", expectedTeams, actualTeams)
+}
diff --git a/android/module.go b/android/module.go
index d8f004c..71b1d2c 100644
--- a/android/module.go
+++ b/android/module.go
@@ -519,6 +519,9 @@
// trace, but influence modules among products.
SoongConfigTrace soongConfigTrace `blueprint:"mutated"`
SoongConfigTraceHash string `blueprint:"mutated"`
+
+ // The team (defined by the owner/vendor) who owns the property.
+ Team *string `android:"path"`
}
type distProperties struct {
@@ -531,6 +534,12 @@
Dists []Dist `android:"arch_variant"`
}
+type TeamDepTagType struct {
+ blueprint.BaseDependencyTag
+}
+
+var teamDepTag = TeamDepTagType{}
+
// CommonTestOptions represents the common `test_options` properties in
// Android.bp.
type CommonTestOptions struct {
@@ -992,6 +1001,12 @@
func (m *ModuleBase) DepsMutator(BottomUpMutatorContext) {}
+func (m *ModuleBase) baseDepsMutator(ctx BottomUpMutatorContext) {
+ if m.Team() != "" {
+ ctx.AddDependency(ctx.Module(), teamDepTag, m.Team())
+ }
+}
+
// AddProperties "registers" the provided props
// each value in props MUST be a pointer to a struct
func (m *ModuleBase) AddProperties(props ...interface{}) {
@@ -1437,6 +1452,10 @@
return String(m.commonProperties.Owner)
}
+func (m *ModuleBase) Team() string {
+ return String(m.commonProperties.Team)
+}
+
func (m *ModuleBase) setImageVariation(variant string) {
m.commonProperties.ImageVariation = variant
}
diff --git a/android/mutator.go b/android/mutator.go
index 93c519d..5b76d20 100644
--- a/android/mutator.go
+++ b/android/mutator.go
@@ -600,6 +600,7 @@
func depsMutator(ctx BottomUpMutatorContext) {
if m := ctx.Module(); m.Enabled() {
+ m.base().baseDepsMutator(ctx)
m.DepsMutator(ctx)
}
}
diff --git a/android/package.go b/android/package.go
index 878e4c4..eb76751 100644
--- a/android/package.go
+++ b/android/package.go
@@ -35,6 +35,7 @@
Default_visibility []string
// Specifies the default license terms for all modules defined in this package.
Default_applicable_licenses []string
+ Default_team *string `android:"path"`
}
type packageModule struct {
@@ -47,6 +48,13 @@
// Nothing to do.
}
+func (p *packageModule) DepsMutator(ctx BottomUpMutatorContext) {
+ // Add the dependency to do a validity check
+ if p.properties.Default_team != nil {
+ ctx.AddDependency(ctx.Module(), nil, *p.properties.Default_team)
+ }
+}
+
func (p *packageModule) GenerateBuildActions(ctx blueprint.ModuleContext) {
// Nothing to do.
}
diff --git a/android/team.go b/android/team.go
new file mode 100644
index 0000000..df61f40
--- /dev/null
+++ b/android/team.go
@@ -0,0 +1,58 @@
+// Copyright 2020 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
+
+func init() {
+ RegisterTeamBuildComponents(InitRegistrationContext)
+}
+
+func RegisterTeamBuildComponents(ctx RegistrationContext) {
+ ctx.RegisterModuleType("team", TeamFactory)
+}
+
+var PrepareForTestWithTeamBuildComponents = GroupFixturePreparers(
+ FixtureRegisterWithContext(RegisterTeamBuildComponents),
+)
+
+type teamProperties struct {
+ Trendy_team_id *string `json:"trendy_team_id"`
+}
+
+type teamModule struct {
+ ModuleBase
+ DefaultableModuleBase
+
+ properties teamProperties
+}
+
+// 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) {}
+
+func (t *teamModule) TrendyTeamId(ctx ModuleContext) string {
+ return *t.properties.Trendy_team_id
+}
+
+func TeamFactory() Module {
+ module := &teamModule{}
+
+ base := module.base()
+ module.AddProperties(&base.nameProperties, &module.properties)
+
+ InitAndroidModule(module)
+ InitDefaultableModule(module)
+
+ return module
+}
diff --git a/android/team_proto/Android.bp b/android/team_proto/Android.bp
new file mode 100644
index 0000000..061e77e
--- /dev/null
+++ b/android/team_proto/Android.bp
@@ -0,0 +1,29 @@
+// Copyright 2022 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-android_team_proto",
+ pkgPath: "android/soong/android/team_proto",
+ deps: [
+ "golang-protobuf-reflect-protoreflect",
+ "golang-protobuf-runtime-protoimpl",
+ ],
+ srcs: [
+ "team.pb.go",
+ ],
+}
diff --git a/android/team_proto/OWNERS b/android/team_proto/OWNERS
new file mode 100644
index 0000000..2beb4f4
--- /dev/null
+++ b/android/team_proto/OWNERS
@@ -0,0 +1,5 @@
+dariofreni@google.com
+joeo@google.com
+ronish@google.com
+caditya@google.com
+rbraunstein@google.com
diff --git a/android/team_proto/regen.sh b/android/team_proto/regen.sh
new file mode 100755
index 0000000..63b2016
--- /dev/null
+++ b/android/team_proto/regen.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+aprotoc --go_out=paths=source_relative:. team.proto
diff --git a/android/team_proto/team.pb.go b/android/team_proto/team.pb.go
new file mode 100644
index 0000000..61260cf
--- /dev/null
+++ b/android/team_proto/team.pb.go
@@ -0,0 +1,253 @@
+// 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.
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.30.0
+// protoc v3.21.12
+// source: team.proto
+
+package team_proto
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type Team struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ // REQUIRED: Name of the build target
+ TargetName *string `protobuf:"bytes,1,opt,name=target_name,json=targetName" json:"target_name,omitempty"`
+ // REQUIRED: Code location of the target.
+ // To be used to support legacy/backup systems that use OWNERS file and is
+ // also required for our dashboard to support per code location basis UI
+ Path *string `protobuf:"bytes,2,opt,name=path" json:"path,omitempty"`
+ // REQUIRED: Team ID of the team that owns this target.
+ 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"`
+}
+
+func (x *Team) Reset() {
+ *x = Team{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_team_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *Team) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Team) ProtoMessage() {}
+
+func (x *Team) ProtoReflect() protoreflect.Message {
+ mi := &file_team_proto_msgTypes[0]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use Team.ProtoReflect.Descriptor instead.
+func (*Team) Descriptor() ([]byte, []int) {
+ return file_team_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *Team) GetTargetName() string {
+ if x != nil && x.TargetName != nil {
+ return *x.TargetName
+ }
+ return ""
+}
+
+func (x *Team) GetPath() string {
+ if x != nil && x.Path != nil {
+ return *x.Path
+ }
+ return ""
+}
+
+func (x *Team) GetTrendyTeamId() string {
+ if x != nil && x.TrendyTeamId != nil {
+ return *x.TrendyTeamId
+ }
+ return ""
+}
+
+func (x *Team) GetFile() []string {
+ if x != nil {
+ return x.File
+ }
+ return nil
+}
+
+type AllTeams struct {
+ state protoimpl.MessageState
+ sizeCache protoimpl.SizeCache
+ unknownFields protoimpl.UnknownFields
+
+ Teams []*Team `protobuf:"bytes,1,rep,name=teams" json:"teams,omitempty"`
+}
+
+func (x *AllTeams) Reset() {
+ *x = AllTeams{}
+ if protoimpl.UnsafeEnabled {
+ mi := &file_team_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+ }
+}
+
+func (x *AllTeams) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*AllTeams) ProtoMessage() {}
+
+func (x *AllTeams) ProtoReflect() protoreflect.Message {
+ mi := &file_team_proto_msgTypes[1]
+ if protoimpl.UnsafeEnabled && x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use AllTeams.ProtoReflect.Descriptor instead.
+func (*AllTeams) Descriptor() ([]byte, []int) {
+ return file_team_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *AllTeams) GetTeams() []*Team {
+ if x != nil {
+ return x.Teams
+ }
+ return nil
+}
+
+var File_team_proto protoreflect.FileDescriptor
+
+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,
+}
+
+var (
+ file_team_proto_rawDescOnce sync.Once
+ file_team_proto_rawDescData = file_team_proto_rawDesc
+)
+
+func file_team_proto_rawDescGZIP() []byte {
+ file_team_proto_rawDescOnce.Do(func() {
+ file_team_proto_rawDescData = protoimpl.X.CompressGZIP(file_team_proto_rawDescData)
+ })
+ return file_team_proto_rawDescData
+}
+
+var file_team_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_team_proto_goTypes = []interface{}{
+ (*Team)(nil), // 0: team_proto.Team
+ (*AllTeams)(nil), // 1: team_proto.AllTeams
+}
+var file_team_proto_depIdxs = []int32{
+ 0, // 0: team_proto.AllTeams.teams:type_name -> team_proto.Team
+ 1, // [1:1] is the sub-list for method output_type
+ 1, // [1:1] is the sub-list for method input_type
+ 1, // [1:1] is the sub-list for extension type_name
+ 1, // [1:1] is the sub-list for extension extendee
+ 0, // [0:1] is the sub-list for field type_name
+}
+
+func init() { file_team_proto_init() }
+func file_team_proto_init() {
+ if File_team_proto != nil {
+ return
+ }
+ if !protoimpl.UnsafeEnabled {
+ file_team_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*Team); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ file_team_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+ switch v := v.(*AllTeams); i {
+ case 0:
+ return &v.state
+ case 1:
+ return &v.sizeCache
+ case 2:
+ return &v.unknownFields
+ default:
+ return nil
+ }
+ }
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor: file_team_proto_rawDesc,
+ NumEnums: 0,
+ NumMessages: 2,
+ NumExtensions: 0,
+ NumServices: 0,
+ },
+ GoTypes: file_team_proto_goTypes,
+ DependencyIndexes: file_team_proto_depIdxs,
+ MessageInfos: file_team_proto_msgTypes,
+ }.Build()
+ File_team_proto = out.File
+ file_team_proto_rawDesc = nil
+ file_team_proto_goTypes = nil
+ file_team_proto_depIdxs = nil
+}
diff --git a/android/team_proto/team.proto b/android/team_proto/team.proto
new file mode 100644
index 0000000..401eccc
--- /dev/null
+++ b/android/team_proto/team.proto
@@ -0,0 +1,34 @@
+// 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.
+
+syntax = "proto2";
+package team_proto;
+option go_package = "android/soong/android/team_proto";
+
+message Team {
+ // REQUIRED: Name of the build target
+ optional string target_name = 1;
+
+ // REQUIRED: Code location of the target.
+ // To be used to support legacy/backup systems that use OWNERS file and is
+ // also required for our dashboard to support per code location basis UI
+ optional string path = 2;
+
+ // REQUIRED: Team ID of the team that owns this target.
+ optional string trendy_team_id = 3;
+
+ // OPTIONAL: Files directly owned by this module.
+ repeated string file = 4;
+}
+
+message AllTeams {
+ repeated Team teams = 1;
+}
diff --git a/android/team_test.go b/android/team_test.go
new file mode 100644
index 0000000..75b3e9f
--- /dev/null
+++ b/android/team_test.go
@@ -0,0 +1,99 @@
+// 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 (
+ "testing"
+)
+
+type fakeModuleForTests struct {
+ ModuleBase
+}
+
+func fakeModuleFactory() Module {
+ module := &fakeModuleForTests{}
+ InitAndroidModule(module)
+ return module
+}
+
+func (*fakeModuleForTests) GenerateAndroidBuildActions(ModuleContext) {}
+
+func TestTeam(t *testing.T) {
+ t.Parallel()
+ ctx := GroupFixturePreparers(
+ PrepareForTestWithTeamBuildComponents,
+ FixtureRegisterWithContext(func(ctx RegistrationContext) {
+ ctx.RegisterModuleType("fake", fakeModuleFactory)
+ }),
+ ).RunTestWithBp(t, `
+ fake {
+ name: "main_test",
+ team: "someteam",
+ }
+ team {
+ name: "someteam",
+ trendy_team_id: "cool_team",
+ }
+
+ team {
+ name: "team2",
+ trendy_team_id: "22222",
+ }
+
+ fake {
+ name: "tool",
+ team: "team2",
+ }
+ `)
+
+ // Assert the rule from GenerateAndroidBuildActions exists.
+ m := ctx.ModuleForTests("main_test", "")
+ AssertStringEquals(t, "msg", m.Module().base().Team(), "someteam")
+ m = ctx.ModuleForTests("tool", "")
+ AssertStringEquals(t, "msg", m.Module().base().Team(), "team2")
+}
+
+func TestMissingTeamFails(t *testing.T) {
+ t.Parallel()
+ GroupFixturePreparers(
+ PrepareForTestWithTeamBuildComponents,
+ FixtureRegisterWithContext(func(ctx RegistrationContext) {
+ ctx.RegisterModuleType("fake", fakeModuleFactory)
+ }),
+ ).
+ ExtendWithErrorHandler(FixtureExpectsAtLeastOneErrorMatchingPattern("depends on undefined module \"ring-bearer")).
+ RunTestWithBp(t, `
+ fake {
+ name: "you_cannot_pass",
+ team: "ring-bearer",
+ }
+ `)
+}
+
+func TestPackageBadTeamNameFails(t *testing.T) {
+ t.Parallel()
+ GroupFixturePreparers(
+ PrepareForTestWithTeamBuildComponents,
+ PrepareForTestWithPackageModule,
+ FixtureRegisterWithContext(func(ctx RegistrationContext) {
+ ctx.RegisterModuleType("fake", fakeModuleFactory)
+ }),
+ ).
+ ExtendWithErrorHandler(FixtureExpectsAtLeastOneErrorMatchingPattern("depends on undefined module \"ring-bearer")).
+ RunTestWithBp(t, `
+ package {
+ default_team: "ring-bearer",
+ }
+ `)
+}