1package android
2
3import (
4	"android/soong/android/team_proto"
5	"path/filepath"
6
7	"google.golang.org/protobuf/proto"
8)
9
10const ownershipDirectory = "ownership"
11const allTeamsFile = "all_teams.pb"
12
13func AllTeamsFactory() Singleton {
14	return &allTeamsSingleton{}
15}
16
17func init() {
18	registerAllTeamBuildComponents(InitRegistrationContext)
19}
20
21func registerAllTeamBuildComponents(ctx RegistrationContext) {
22	ctx.RegisterParallelSingletonType("all_teams", AllTeamsFactory)
23}
24
25// For each module, list the team or the bpFile the module is defined in.
26type moduleTeamAndTestInfo struct {
27	// Name field from bp file for the team
28	teamName string
29	// Blueprint file the module is located in.
30	bpFile string
31	// Is this module only used by tests.
32	testOnly bool
33	// Is this a directly testable target by running the module directly
34	// or via tradefed.
35	topLevelTestTarget bool
36	// String name indicating the module, like `java_library` for reporting.
37	kind string
38}
39
40type allTeamsSingleton struct {
41	// Path where the collected metadata is stored after successful validation.
42	outputPath OutputPath
43
44	// Map of all package modules we visit during GenerateBuildActions
45	packages map[string]packageProperties
46	// Map of all team modules we visit during GenerateBuildActions
47	teams map[string]teamProperties
48	// Keeps track of team information or bp file for each module we visit.
49	teams_for_mods map[string]moduleTeamAndTestInfo
50}
51
52// See if there is a package module for the given bpFilePath with a team defined, if so return the team.
53// If not ascend up to the parent directory and do the same.
54func (t *allTeamsSingleton) lookupDefaultTeam(bpFilePath string) (teamProperties, bool) {
55	// return the Default_team listed in the package if is there.
56	if p, ok := t.packages[bpFilePath]; ok {
57		if defaultTeam := p.Default_team; defaultTeam != nil {
58			return t.teams[*defaultTeam], true
59		}
60	}
61	// Strip a directory and go up.
62	// Does android/paths.go basePath,SourcePath help?
63	current, base := filepath.Split(bpFilePath)
64	current = filepath.Clean(current) // removes trailing slash, convert "" -> "."
65	parent, _ := filepath.Split(current)
66	if current == "." {
67		return teamProperties{}, false
68	}
69	return t.lookupDefaultTeam(filepath.Join(parent, base))
70}
71
72// Visit all modules and collect all teams and use WriteFileRuleVerbatim
73// to write it out.
74func (t *allTeamsSingleton) GenerateBuildActions(ctx SingletonContext) {
75	t.packages = make(map[string]packageProperties)
76	t.teams = make(map[string]teamProperties)
77	t.teams_for_mods = make(map[string]moduleTeamAndTestInfo)
78
79	ctx.VisitAllModules(func(module Module) {
80		bpFile := ctx.BlueprintFile(module)
81
82		// Package Modules and Team Modules are stored in a map so we can look them up by name for
83		// modules without a team.
84		if pack, ok := module.(*packageModule); ok {
85			// Packages don't have names, use the blueprint file as the key. we can't get qualifiedModuleId in t context.
86			pkgKey := bpFile
87			t.packages[pkgKey] = pack.properties
88			return
89		}
90		if team, ok := module.(*teamModule); ok {
91			t.teams[team.Name()] = team.properties
92			return
93		}
94
95		testModInfo := TestModuleInformation{}
96		if tmi, ok := SingletonModuleProvider(ctx, module, TestOnlyProviderKey); ok {
97			testModInfo = tmi
98		}
99
100		// Some modules, like java_test_host don't set the provider when the module isn't enabled:
101		//                                                test_only, top_level
102		//     AVFHostTestCases{os:linux_glibc,arch:common} {true true}
103		//     AVFHostTestCases{os:windows,arch:common} {false false}
104		// Generally variant information of true override false or unset.
105		if testModInfo.TestOnly == false {
106			if prevValue, exists := t.teams_for_mods[module.Name()]; exists {
107				if prevValue.testOnly == true {
108					return
109				}
110			}
111		}
112		entry := moduleTeamAndTestInfo{
113			bpFile:             bpFile,
114			testOnly:           testModInfo.TestOnly,
115			topLevelTestTarget: testModInfo.TopLevelTarget,
116			kind:               ctx.ModuleType(module),
117			teamName:           module.base().Team(),
118		}
119		t.teams_for_mods[module.Name()] = entry
120
121	})
122
123	// Visit all modules again and lookup the team name in the package or parent package if the team
124	// isn't assignged at the module level.
125	allTeams := t.lookupTeamForAllModules()
126
127	t.outputPath = PathForOutput(ctx, ownershipDirectory, allTeamsFile)
128	data, err := proto.Marshal(allTeams)
129	if err != nil {
130		ctx.Errorf("Unable to marshal team data. %s", err)
131	}
132
133	WriteFileRuleVerbatim(ctx, t.outputPath, string(data))
134	ctx.Phony("all_teams", t.outputPath)
135}
136
137func (t *allTeamsSingleton) MakeVars(ctx MakeVarsContext) {
138	ctx.DistForGoal("all_teams", t.outputPath)
139}
140
141// Visit every (non-package, non-team) module and write out a proto containing
142// either the declared team data for that module or the package default team data for that module.
143func (t *allTeamsSingleton) lookupTeamForAllModules() *team_proto.AllTeams {
144	teamsProto := make([]*team_proto.Team, len(t.teams_for_mods))
145	for i, moduleName := range SortedKeys(t.teams_for_mods) {
146		m, _ := t.teams_for_mods[moduleName]
147		teamName := m.teamName
148		var teamProperties teamProperties
149		found := false
150		if teamName != "" {
151			teamProperties, found = t.teams[teamName]
152		} else {
153			teamProperties, found = t.lookupDefaultTeam(m.bpFile)
154		}
155
156		trendy_team_id := ""
157		if found {
158			trendy_team_id = *teamProperties.Trendy_team_id
159		}
160
161		teamData := new(team_proto.Team)
162		*teamData = team_proto.Team{
163			TargetName:     proto.String(moduleName),
164			Path:           proto.String(m.bpFile),
165			TestOnly:       proto.Bool(m.testOnly),
166			TopLevelTarget: proto.Bool(m.topLevelTestTarget),
167			Kind:           proto.String(m.kind),
168		}
169		if trendy_team_id != "" {
170			teamData.TrendyTeamId = proto.String(trendy_team_id)
171		} else {
172			// Clients rely on the TrendyTeamId optional field not being set.
173		}
174		teamsProto[i] = teamData
175	}
176	return &team_proto.AllTeams{Teams: teamsProto}
177}
178