From 73b08ffd0dc12eb4107700220979fb52fb642b92 Mon Sep 17 00:00:00 2001 From: Ronald Braunstein Date: Tue, 19 Dec 2023 10:24:47 -0800 Subject: [PATCH] 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 --- android/Android.bp | 3 + android/all_teams.go | 158 +++++++++++++++++++++ android/all_teams_test.go | 208 ++++++++++++++++++++++++++++ android/module.go | 19 +++ android/mutator.go | 1 + android/package.go | 8 ++ android/team.go | 58 ++++++++ android/team_proto/Android.bp | 29 ++++ android/team_proto/OWNERS | 5 + android/team_proto/regen.sh | 3 + android/team_proto/team.pb.go | 253 ++++++++++++++++++++++++++++++++++ android/team_proto/team.proto | 34 +++++ android/team_test.go | 99 +++++++++++++ 13 files changed, 878 insertions(+) create mode 100644 android/all_teams.go create mode 100644 android/all_teams_test.go create mode 100644 android/team.go create mode 100644 android/team_proto/Android.bp create mode 100644 android/team_proto/OWNERS create mode 100755 android/team_proto/regen.sh create mode 100644 android/team_proto/team.pb.go create mode 100644 android/team_proto/team.proto create mode 100644 android/team_test.go diff --git a/android/Android.bp b/android/Android.bp index 8de0c7650..ad07fdaa0 100644 --- a/android/Android.bp +++ b/android/Android.bp @@ -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", diff --git a/android/all_teams.go b/android/all_teams.go new file mode 100644 index 000000000..6c3a219ab --- /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 000000000..a02b86e06 --- /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 d8f004c61..71b1d2c06 100644 --- a/android/module.go +++ b/android/module.go @@ -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 } diff --git a/android/mutator.go b/android/mutator.go index 93c519d27..5b76d209c 100644 --- a/android/mutator.go +++ b/android/mutator.go @@ -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) } } diff --git a/android/package.go b/android/package.go index 878e4c4ed..eb76751f5 100644 --- a/android/package.go +++ b/android/package.go @@ -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. } diff --git a/android/team.go b/android/team.go new file mode 100644 index 000000000..df61f4021 --- /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 000000000..061e77e03 --- /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 000000000..2beb4f47d --- /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 000000000..63b2016e1 --- /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 000000000..61260cf78 --- /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 000000000..401eccc6e --- /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 000000000..75b3e9fbb --- /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", + } + `) +}