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
This commit is contained in:
Ronald Braunstein 2023-12-19 10:24:47 -08:00
parent ee18a66643
commit 73b08ffd0d
13 changed files with 878 additions and 0 deletions

View file

@ -11,6 +11,7 @@ bootstrap_go_package {
"blueprint-metrics",
"sbox_proto",
"soong",
"soong-android_team_proto",
"soong-android-soongconfig",
"soong-remoteexec",
"soong-response",
@ -28,6 +29,7 @@ bootstrap_go_package {
],
srcs: [
"aconfig_providers.go",
"all_teams.go",
"androidmk.go",
"apex.go",
"apex_contributions.go",
@ -91,6 +93,7 @@ bootstrap_go_package {
"singleton.go",
"singleton_module.go",
"soong_config_modules.go",
"team.go",
"test_asserts.go",
"test_suites.go",
"testing.go",

158
android/all_teams.go Normal file
View file

@ -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}
}

208
android/all_teams_test.go Normal file
View file

@ -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)
}

View file

@ -519,6 +519,9 @@ type commonProperties struct {
// 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 @@ type distProperties struct {
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) ComponentDepsMutator(BottomUpMutatorContext) {}
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 @@ func (m *ModuleBase) Owner() string {
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
}

View file

@ -600,6 +600,7 @@ func componentDepsMutator(ctx BottomUpMutatorContext) {
func depsMutator(ctx BottomUpMutatorContext) {
if m := ctx.Module(); m.Enabled() {
m.base().baseDepsMutator(ctx)
m.DepsMutator(ctx)
}
}

View file

@ -35,6 +35,7 @@ type packageProperties struct {
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 @@ func (p *packageModule) GenerateAndroidBuildActions(ModuleContext) {
// 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.
}

58
android/team.go Normal file
View file

@ -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
}

View file

@ -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",
],
}

View file

@ -0,0 +1,5 @@
dariofreni@google.com
joeo@google.com
ronish@google.com
caditya@google.com
rbraunstein@google.com

3
android/team_proto/regen.sh Executable file
View file

@ -0,0 +1,3 @@
#!/bin/bash
aprotoc --go_out=paths=source_relative:. team.proto

View file

@ -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
}

View file

@ -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;
}

99
android/team_test.go Normal file
View file

@ -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",
}
`)
}