blob: 46a5bd8cb3a4bb3a73434150f0dbcf4772333c22 [file] [log] [blame]
Liz Kammer2dd9ca42020-11-25 16:06:39 -08001// Copyright 2020 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package bp2build
16
Alex Márquez Pérez Muñíz Díaz Púras Thaureaux0da7ce62021-08-23 17:04:20 +000017/*
18For shareable/common functionality for conversion from soong-module to build files
19for queryview/bp2build
20*/
21
Liz Kammer2dd9ca42020-11-25 16:06:39 -080022import (
Liz Kammer2dd9ca42020-11-25 16:06:39 -080023 "fmt"
24 "reflect"
Jingwen Chen49109762021-05-25 05:16:48 +000025 "sort"
Liz Kammer2dd9ca42020-11-25 16:06:39 -080026 "strings"
27
Alex Márquez Pérez Muñíz Díaz Púras Thaureaux0da7ce62021-08-23 17:04:20 +000028 "android/soong/android"
29 "android/soong/bazel"
Liz Kammer72beb342022-02-03 08:42:10 -050030 "android/soong/starlark_fmt"
Chris Parsons39a16972023-06-08 14:28:51 +000031 "android/soong/ui/metrics/bp2build_metrics_proto"
Liz Kammer2dd9ca42020-11-25 16:06:39 -080032 "github.com/google/blueprint"
33 "github.com/google/blueprint/proptools"
34)
35
36type BazelAttributes struct {
37 Attrs map[string]string
38}
39
40type BazelTarget struct {
Jingwen Chen40067de2021-01-26 21:58:43 -050041 name string
Jingwen Chenc63677b2021-06-17 05:43:19 +000042 packageName string
Jingwen Chen40067de2021-01-26 21:58:43 -050043 content string
44 ruleClass string
45 bzlLoadLocation string
46}
47
48// IsLoadedFromStarlark determines if the BazelTarget's rule class is loaded from a .bzl file,
49// as opposed to a native rule built into Bazel.
50func (t BazelTarget) IsLoadedFromStarlark() bool {
51 return t.bzlLoadLocation != ""
52}
53
Jingwen Chenc63677b2021-06-17 05:43:19 +000054// Label is the fully qualified Bazel label constructed from the BazelTarget's
55// package name and target name.
56func (t BazelTarget) Label() string {
57 if t.packageName == "." {
58 return "//:" + t.name
59 } else {
60 return "//" + t.packageName + ":" + t.name
61 }
62}
63
Spandan Dasabedff02023-03-07 19:24:34 +000064// PackageName returns the package of the Bazel target.
65// Defaults to root of tree.
66func (t BazelTarget) PackageName() string {
67 if t.packageName == "" {
68 return "."
69 }
70 return t.packageName
71}
72
Jingwen Chen40067de2021-01-26 21:58:43 -050073// BazelTargets is a typedef for a slice of BazelTarget objects.
74type BazelTargets []BazelTarget
75
Sasha Smundak8bea2672022-08-04 13:31:14 -070076func (targets BazelTargets) packageRule() *BazelTarget {
77 for _, target := range targets {
78 if target.ruleClass == "package" {
79 return &target
80 }
81 }
82 return nil
83}
84
85// sort a list of BazelTargets in-place, by name, and by generated/handcrafted types.
Jingwen Chen49109762021-05-25 05:16:48 +000086func (targets BazelTargets) sort() {
87 sort.Slice(targets, func(i, j int) bool {
Jingwen Chen49109762021-05-25 05:16:48 +000088 return targets[i].name < targets[j].name
89 })
90}
91
Jingwen Chen40067de2021-01-26 21:58:43 -050092// String returns the string representation of BazelTargets, without load
93// statements (use LoadStatements for that), since the targets are usually not
94// adjacent to the load statements at the top of the BUILD file.
95func (targets BazelTargets) String() string {
96 var res string
97 for i, target := range targets {
Sasha Smundak8bea2672022-08-04 13:31:14 -070098 if target.ruleClass != "package" {
99 res += target.content
100 }
Jingwen Chen40067de2021-01-26 21:58:43 -0500101 if i != len(targets)-1 {
102 res += "\n\n"
103 }
104 }
105 return res
106}
107
108// LoadStatements return the string representation of the sorted and deduplicated
109// Starlark rule load statements needed by a group of BazelTargets.
110func (targets BazelTargets) LoadStatements() string {
111 bzlToLoadedSymbols := map[string][]string{}
112 for _, target := range targets {
113 if target.IsLoadedFromStarlark() {
114 bzlToLoadedSymbols[target.bzlLoadLocation] =
115 append(bzlToLoadedSymbols[target.bzlLoadLocation], target.ruleClass)
116 }
117 }
118
119 var loadStatements []string
120 for bzl, ruleClasses := range bzlToLoadedSymbols {
121 loadStatement := "load(\""
122 loadStatement += bzl
123 loadStatement += "\", "
124 ruleClasses = android.SortedUniqueStrings(ruleClasses)
125 for i, ruleClass := range ruleClasses {
126 loadStatement += "\"" + ruleClass + "\""
127 if i != len(ruleClasses)-1 {
128 loadStatement += ", "
129 }
130 }
131 loadStatement += ")"
132 loadStatements = append(loadStatements, loadStatement)
133 }
134 return strings.Join(android.SortedUniqueStrings(loadStatements), "\n")
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800135}
136
137type bpToBuildContext interface {
138 ModuleName(module blueprint.Module) string
139 ModuleDir(module blueprint.Module) string
140 ModuleSubDir(module blueprint.Module) string
141 ModuleType(module blueprint.Module) string
142
Jingwen Chendaa54bc2020-12-14 02:58:54 -0500143 VisitAllModules(visit func(blueprint.Module))
144 VisitDirectDeps(module blueprint.Module, visit func(blueprint.Module))
145}
146
147type CodegenContext struct {
Jingwen Chen16d90a82021-09-17 07:16:13 +0000148 config android.Config
Paul Duffinc6390592022-11-04 13:35:21 +0000149 context *android.Context
Jingwen Chen16d90a82021-09-17 07:16:13 +0000150 mode CodegenMode
151 additionalDeps []string
Liz Kammer6eff3232021-08-26 08:37:59 -0400152 unconvertedDepMode unconvertedDepsMode
Cole Faustb85d1a12022-11-08 18:14:01 -0800153 topDir string
Jingwen Chendaa54bc2020-12-14 02:58:54 -0500154}
155
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400156func (ctx *CodegenContext) Mode() CodegenMode {
157 return ctx.mode
Jingwen Chen164e0862021-02-19 00:48:40 -0500158}
159
Jingwen Chen33832f92021-01-24 22:55:54 -0500160// CodegenMode is an enum to differentiate code-generation modes.
161type CodegenMode int
162
163const (
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400164 // Bp2Build - generate BUILD files with targets buildable by Bazel directly.
Jingwen Chen33832f92021-01-24 22:55:54 -0500165 //
166 // This mode is used for the Soong->Bazel build definition conversion.
167 Bp2Build CodegenMode = iota
168
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400169 // QueryView - generate BUILD files with targets representing fully mutated
Jingwen Chen33832f92021-01-24 22:55:54 -0500170 // Soong modules, representing the fully configured Soong module graph with
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400171 // variants and dependency edges.
Jingwen Chen33832f92021-01-24 22:55:54 -0500172 //
173 // This mode is used for discovering and introspecting the existing Soong
174 // module graph.
175 QueryView
Spandan Das5af0bd32022-09-28 20:43:08 +0000176
177 // ApiBp2build - generate BUILD files for API contribution targets
178 ApiBp2build
Jingwen Chen33832f92021-01-24 22:55:54 -0500179)
180
Liz Kammer6eff3232021-08-26 08:37:59 -0400181type unconvertedDepsMode int
182
183const (
184 // Include a warning in conversion metrics about converted modules with unconverted direct deps
185 warnUnconvertedDeps unconvertedDepsMode = iota
186 // Error and fail conversion if encountering a module with unconverted direct deps
187 // Enabled by setting environment variable `BP2BUILD_ERROR_UNCONVERTED`
188 errorModulesUnconvertedDeps
189)
190
Jingwen Chendcc329a2021-01-26 02:49:03 -0500191func (mode CodegenMode) String() string {
192 switch mode {
193 case Bp2Build:
194 return "Bp2Build"
195 case QueryView:
196 return "QueryView"
Spandan Das5af0bd32022-09-28 20:43:08 +0000197 case ApiBp2build:
198 return "ApiBp2build"
Jingwen Chendcc329a2021-01-26 02:49:03 -0500199 default:
200 return fmt.Sprintf("%d", mode)
201 }
202}
203
Liz Kammerba3ea162021-02-17 13:22:03 -0500204// AddNinjaFileDeps adds dependencies on the specified files to be added to the ninja manifest. The
205// primary builder will be rerun whenever the specified files are modified. Allows us to fulfill the
206// PathContext interface in order to add dependencies on hand-crafted BUILD files. Note: must also
207// call AdditionalNinjaDeps and add them manually to the ninja file.
208func (ctx *CodegenContext) AddNinjaFileDeps(deps ...string) {
209 ctx.additionalDeps = append(ctx.additionalDeps, deps...)
210}
211
212// AdditionalNinjaDeps returns additional ninja deps added by CodegenContext
213func (ctx *CodegenContext) AdditionalNinjaDeps() []string {
214 return ctx.additionalDeps
215}
216
Paul Duffinc6390592022-11-04 13:35:21 +0000217func (ctx *CodegenContext) Config() android.Config { return ctx.config }
218func (ctx *CodegenContext) Context() *android.Context { return ctx.context }
Jingwen Chendaa54bc2020-12-14 02:58:54 -0500219
220// NewCodegenContext creates a wrapper context that conforms to PathContext for
221// writing BUILD files in the output directory.
Cole Faustb85d1a12022-11-08 18:14:01 -0800222func NewCodegenContext(config android.Config, context *android.Context, mode CodegenMode, topDir string) *CodegenContext {
Liz Kammer6eff3232021-08-26 08:37:59 -0400223 var unconvertedDeps unconvertedDepsMode
224 if config.IsEnvTrue("BP2BUILD_ERROR_UNCONVERTED") {
225 unconvertedDeps = errorModulesUnconvertedDeps
226 }
Liz Kammerba3ea162021-02-17 13:22:03 -0500227 return &CodegenContext{
Liz Kammer6eff3232021-08-26 08:37:59 -0400228 context: context,
229 config: config,
230 mode: mode,
231 unconvertedDepMode: unconvertedDeps,
Cole Faustb85d1a12022-11-08 18:14:01 -0800232 topDir: topDir,
Jingwen Chendaa54bc2020-12-14 02:58:54 -0500233 }
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800234}
235
236// props is an unsorted map. This function ensures that
237// the generated attributes are sorted to ensure determinism.
238func propsToAttributes(props map[string]string) string {
239 var attributes string
Cole Faust18994c72023-02-28 16:02:16 -0800240 for _, propName := range android.SortedKeys(props) {
Liz Kammer0eae52e2021-10-06 10:32:26 -0400241 attributes += fmt.Sprintf(" %s = %s,\n", propName, props[propName])
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800242 }
243 return attributes
244}
245
Liz Kammer6eff3232021-08-26 08:37:59 -0400246type conversionResults struct {
247 buildFileToTargets map[string]BazelTargets
248 metrics CodegenMetrics
Liz Kammer6eff3232021-08-26 08:37:59 -0400249}
250
251func (r conversionResults) BuildDirToTargets() map[string]BazelTargets {
252 return r.buildFileToTargets
253}
254
255func GenerateBazelTargets(ctx *CodegenContext, generateFilegroups bool) (conversionResults, []error) {
Jingwen Chen40067de2021-01-26 21:58:43 -0500256 buildFileToTargets := make(map[string]BazelTargets)
Jingwen Chen164e0862021-02-19 00:48:40 -0500257
258 // Simple metrics tracking for bp2build
usta4f5d2c12022-10-28 23:32:01 -0400259 metrics := CreateCodegenMetrics()
Jingwen Chen164e0862021-02-19 00:48:40 -0500260
Rupert Shuttleworth2a4fc3e2021-04-21 07:10:09 -0400261 dirs := make(map[string]bool)
262
Liz Kammer6eff3232021-08-26 08:37:59 -0400263 var errs []error
264
Jingwen Chen164e0862021-02-19 00:48:40 -0500265 bpCtx := ctx.Context()
266 bpCtx.VisitAllModules(func(m blueprint.Module) {
267 dir := bpCtx.ModuleDir(m)
Chris Parsons492bd912022-01-20 12:55:05 -0500268 moduleType := bpCtx.ModuleType(m)
Rupert Shuttleworth2a4fc3e2021-04-21 07:10:09 -0400269 dirs[dir] = true
270
Liz Kammer2ada09a2021-08-11 00:17:36 -0400271 var targets []BazelTarget
Jingwen Chen73850672020-12-14 08:25:34 -0500272
Jingwen Chen164e0862021-02-19 00:48:40 -0500273 switch ctx.Mode() {
Jingwen Chen33832f92021-01-24 22:55:54 -0500274 case Bp2Build:
Jingwen Chen310bc8f2021-09-20 10:54:27 +0000275 // There are two main ways of converting a Soong module to Bazel:
276 // 1) Manually handcrafting a Bazel target and associating the module with its label
277 // 2) Automatically generating with bp2build converters
278 //
279 // bp2build converters are used for the majority of modules.
Liz Kammerba3ea162021-02-17 13:22:03 -0500280 if b, ok := m.(android.Bazelable); ok && b.HasHandcraftedLabel() {
Jingwen Chen310bc8f2021-09-20 10:54:27 +0000281 // Handle modules converted to handcrafted targets.
282 //
283 // Since these modules are associated with some handcrafted
Cole Faustea602c52022-08-31 14:48:26 -0700284 // target in a BUILD file, we don't autoconvert them.
Jingwen Chen310bc8f2021-09-20 10:54:27 +0000285
286 // Log the module.
Chris Parsons39a16972023-06-08 14:28:51 +0000287 metrics.AddUnconvertedModule(m, moduleType, dir,
288 android.UnconvertedReason{
289 ReasonType: int(bp2build_metrics_proto.UnconvertedReasonType_DEFINED_IN_BUILD_FILE),
290 })
Liz Kammer2ada09a2021-08-11 00:17:36 -0400291 } else if aModule, ok := m.(android.Module); ok && aModule.IsConvertedByBp2build() {
Jingwen Chen310bc8f2021-09-20 10:54:27 +0000292 // Handle modules converted to generated targets.
293
294 // Log the module.
Chris Parsons39a16972023-06-08 14:28:51 +0000295 metrics.AddConvertedModule(aModule, moduleType, dir)
Jingwen Chen310bc8f2021-09-20 10:54:27 +0000296
297 // Handle modules with unconverted deps. By default, emit a warning.
Liz Kammer6eff3232021-08-26 08:37:59 -0400298 if unconvertedDeps := aModule.GetUnconvertedBp2buildDeps(); len(unconvertedDeps) > 0 {
Sasha Smundakf2bb26f2022-08-04 11:28:15 -0700299 msg := fmt.Sprintf("%s %s:%s depends on unconverted modules: %s",
300 moduleType, bpCtx.ModuleDir(m), m.Name(), strings.Join(unconvertedDeps, ", "))
Usta Shresthac6057152022-09-24 00:23:31 -0400301 switch ctx.unconvertedDepMode {
302 case warnUnconvertedDeps:
Liz Kammer6eff3232021-08-26 08:37:59 -0400303 metrics.moduleWithUnconvertedDepsMsgs = append(metrics.moduleWithUnconvertedDepsMsgs, msg)
Usta Shresthac6057152022-09-24 00:23:31 -0400304 case errorModulesUnconvertedDeps:
Liz Kammer6eff3232021-08-26 08:37:59 -0400305 errs = append(errs, fmt.Errorf(msg))
306 return
307 }
308 }
Liz Kammerdaa09ef2021-12-15 15:35:38 -0500309 if unconvertedDeps := aModule.GetMissingBp2buildDeps(); len(unconvertedDeps) > 0 {
Sasha Smundakf2bb26f2022-08-04 11:28:15 -0700310 msg := fmt.Sprintf("%s %s:%s depends on missing modules: %s",
311 moduleType, bpCtx.ModuleDir(m), m.Name(), strings.Join(unconvertedDeps, ", "))
Usta Shresthac6057152022-09-24 00:23:31 -0400312 switch ctx.unconvertedDepMode {
313 case warnUnconvertedDeps:
Liz Kammerdaa09ef2021-12-15 15:35:38 -0500314 metrics.moduleWithMissingDepsMsgs = append(metrics.moduleWithMissingDepsMsgs, msg)
Usta Shresthac6057152022-09-24 00:23:31 -0400315 case errorModulesUnconvertedDeps:
Liz Kammerdaa09ef2021-12-15 15:35:38 -0500316 errs = append(errs, fmt.Errorf(msg))
317 return
318 }
319 }
Alix94e26032022-08-16 20:37:33 +0000320 var targetErrs []error
321 targets, targetErrs = generateBazelTargets(bpCtx, aModule)
322 errs = append(errs, targetErrs...)
Liz Kammer2ada09a2021-08-11 00:17:36 -0400323 for _, t := range targets {
Jingwen Chen310bc8f2021-09-20 10:54:27 +0000324 // A module can potentially generate more than 1 Bazel
325 // target, each of a different rule class.
326 metrics.IncrementRuleClassCount(t.ruleClass)
Liz Kammer2ada09a2021-08-11 00:17:36 -0400327 }
MarkDacek9c094ca2023-03-16 19:15:19 +0000328 } else if _, ok := ctx.Config().BazelModulesForceEnabledByFlag()[m.Name()]; ok && m.Name() != "" {
329 err := fmt.Errorf("Force Enabled Module %s not converted", m.Name())
330 errs = append(errs, err)
Chris Parsons39a16972023-06-08 14:28:51 +0000331 } else if aModule, ok := m.(android.Module); ok {
332 reason := aModule.GetUnconvertedReason()
333 if reason == nil {
334 panic(fmt.Errorf("module '%s' was neither converted nor marked unconvertible with bp2build", aModule.Name()))
335 } else {
336 metrics.AddUnconvertedModule(m, moduleType, dir, *reason)
337 }
338 return
Liz Kammerfc46bc12021-02-19 11:06:17 -0500339 } else {
Chris Parsons39a16972023-06-08 14:28:51 +0000340 metrics.AddUnconvertedModule(m, moduleType, dir, android.UnconvertedReason{
341 ReasonType: int(bp2build_metrics_proto.UnconvertedReasonType_TYPE_UNSUPPORTED),
342 })
Liz Kammerba3ea162021-02-17 13:22:03 -0500343 return
Jingwen Chen73850672020-12-14 08:25:34 -0500344 }
Jingwen Chen33832f92021-01-24 22:55:54 -0500345 case QueryView:
Jingwen Chen96af35b2021-02-08 00:49:32 -0500346 // Blocklist certain module types from being generated.
Jingwen Chen164e0862021-02-19 00:48:40 -0500347 if canonicalizeModuleType(bpCtx.ModuleType(m)) == "package" {
Jingwen Chen96af35b2021-02-08 00:49:32 -0500348 // package module name contain slashes, and thus cannot
349 // be mapped cleanly to a bazel label.
350 return
351 }
Alix94e26032022-08-16 20:37:33 +0000352 t, err := generateSoongModuleTarget(bpCtx, m)
353 if err != nil {
354 errs = append(errs, err)
355 }
Liz Kammer2ada09a2021-08-11 00:17:36 -0400356 targets = append(targets, t)
Spandan Das5af0bd32022-09-28 20:43:08 +0000357 case ApiBp2build:
358 if aModule, ok := m.(android.Module); ok && aModule.IsConvertedByBp2build() {
359 targets, errs = generateBazelTargets(bpCtx, aModule)
360 }
Jingwen Chen33832f92021-01-24 22:55:54 -0500361 default:
Liz Kammer6eff3232021-08-26 08:37:59 -0400362 errs = append(errs, fmt.Errorf("Unknown code-generation mode: %s", ctx.Mode()))
363 return
Jingwen Chen73850672020-12-14 08:25:34 -0500364 }
365
Spandan Dasabedff02023-03-07 19:24:34 +0000366 for _, target := range targets {
367 targetDir := target.PackageName()
368 buildFileToTargets[targetDir] = append(buildFileToTargets[targetDir], target)
369 }
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800370 })
Liz Kammer6eff3232021-08-26 08:37:59 -0400371
372 if len(errs) > 0 {
373 return conversionResults{}, errs
374 }
375
Rupert Shuttleworth2a4fc3e2021-04-21 07:10:09 -0400376 if generateFilegroups {
377 // Add a filegroup target that exposes all sources in the subtree of this package
378 // NOTE: This also means we generate a BUILD file for every Android.bp file (as long as it has at least one module)
Cole Faust324a92e2022-08-23 15:29:05 -0700379 //
380 // This works because: https://bazel.build/reference/be/functions#exports_files
381 // "As a legacy behaviour, also files mentioned as input to a rule are exported with the
382 // default visibility until the flag --incompatible_no_implicit_file_export is flipped. However, this behavior
383 // should not be relied upon and actively migrated away from."
384 //
385 // TODO(b/198619163): We should change this to export_files(glob(["**/*"])) instead, but doing that causes these errors:
386 // "Error in exports_files: generated label '//external/avb:avbtool' conflicts with existing py_binary rule"
387 // So we need to solve all the "target ... is both a rule and a file" warnings first.
Usta Shresthac6057152022-09-24 00:23:31 -0400388 for dir := range dirs {
Rupert Shuttleworth2a4fc3e2021-04-21 07:10:09 -0400389 buildFileToTargets[dir] = append(buildFileToTargets[dir], BazelTarget{
390 name: "bp2build_all_srcs",
391 content: `filegroup(name = "bp2build_all_srcs", srcs = glob(["**/*"]))`,
392 ruleClass: "filegroup",
393 })
394 }
395 }
Jingwen Chen164e0862021-02-19 00:48:40 -0500396
Liz Kammer6eff3232021-08-26 08:37:59 -0400397 return conversionResults{
398 buildFileToTargets: buildFileToTargets,
399 metrics: metrics,
Liz Kammer6eff3232021-08-26 08:37:59 -0400400 }, errs
Jingwen Chen164e0862021-02-19 00:48:40 -0500401}
402
Alix94e26032022-08-16 20:37:33 +0000403func generateBazelTargets(ctx bpToBuildContext, m android.Module) ([]BazelTarget, []error) {
Liz Kammer2ada09a2021-08-11 00:17:36 -0400404 var targets []BazelTarget
Alix94e26032022-08-16 20:37:33 +0000405 var errs []error
Liz Kammer2ada09a2021-08-11 00:17:36 -0400406 for _, m := range m.Bp2buildTargets() {
Alix94e26032022-08-16 20:37:33 +0000407 target, err := generateBazelTarget(ctx, m)
408 if err != nil {
409 errs = append(errs, err)
410 return targets, errs
411 }
412 targets = append(targets, target)
Liz Kammer2ada09a2021-08-11 00:17:36 -0400413 }
Alix94e26032022-08-16 20:37:33 +0000414 return targets, errs
Liz Kammer2ada09a2021-08-11 00:17:36 -0400415}
416
417type bp2buildModule interface {
418 TargetName() string
419 TargetPackage() string
420 BazelRuleClass() string
421 BazelRuleLoadLocation() string
Alex Márquez Pérez Muñíz Díaz Púras Thaureaux447f6c92021-08-31 20:30:36 +0000422 BazelAttributes() []interface{}
Liz Kammer2ada09a2021-08-11 00:17:36 -0400423}
424
Alix94e26032022-08-16 20:37:33 +0000425func generateBazelTarget(ctx bpToBuildContext, m bp2buildModule) (BazelTarget, error) {
Liz Kammer2ada09a2021-08-11 00:17:36 -0400426 ruleClass := m.BazelRuleClass()
427 bzlLoadLocation := m.BazelRuleLoadLocation()
Jingwen Chen40067de2021-01-26 21:58:43 -0500428
Jingwen Chen73850672020-12-14 08:25:34 -0500429 // extract the bazel attributes from the module.
Alex Márquez Pérez Muñíz Díaz Púras Thaureaux447f6c92021-08-31 20:30:36 +0000430 attrs := m.BazelAttributes()
Alix94e26032022-08-16 20:37:33 +0000431 props, err := extractModuleProperties(attrs, true)
432 if err != nil {
433 return BazelTarget{}, err
434 }
Jingwen Chen73850672020-12-14 08:25:34 -0500435
Liz Kammer0eae52e2021-10-06 10:32:26 -0400436 // name is handled in a special manner
437 delete(props.Attrs, "name")
Jingwen Chen77e8b7b2021-02-05 03:03:24 -0500438
Jingwen Chen73850672020-12-14 08:25:34 -0500439 // Return the Bazel target with rule class and attributes, ready to be
440 // code-generated.
441 attributes := propsToAttributes(props.Attrs)
Sasha Smundakfb589492022-08-04 11:13:27 -0700442 var content string
Liz Kammer2ada09a2021-08-11 00:17:36 -0400443 targetName := m.TargetName()
Sasha Smundakfb589492022-08-04 11:13:27 -0700444 if targetName != "" {
445 content = fmt.Sprintf(ruleTargetTemplate, ruleClass, targetName, attributes)
446 } else {
447 content = fmt.Sprintf(unnamedRuleTargetTemplate, ruleClass, attributes)
448 }
Jingwen Chen73850672020-12-14 08:25:34 -0500449 return BazelTarget{
Jingwen Chen40067de2021-01-26 21:58:43 -0500450 name: targetName,
Liz Kammer2ada09a2021-08-11 00:17:36 -0400451 packageName: m.TargetPackage(),
Jingwen Chen40067de2021-01-26 21:58:43 -0500452 ruleClass: ruleClass,
453 bzlLoadLocation: bzlLoadLocation,
Sasha Smundakfb589492022-08-04 11:13:27 -0700454 content: content,
Alix94e26032022-08-16 20:37:33 +0000455 }, nil
Jingwen Chen73850672020-12-14 08:25:34 -0500456}
457
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800458// Convert a module and its deps and props into a Bazel macro/rule
459// representation in the BUILD file.
Alix94e26032022-08-16 20:37:33 +0000460func generateSoongModuleTarget(ctx bpToBuildContext, m blueprint.Module) (BazelTarget, error) {
461 props, err := getBuildProperties(ctx, m)
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800462
463 // TODO(b/163018919): DirectDeps can have duplicate (module, variant)
464 // items, if the modules are added using different DependencyTag. Figure
465 // out the implications of that.
466 depLabels := map[string]bool{}
467 if aModule, ok := m.(android.Module); ok {
Jingwen Chendaa54bc2020-12-14 02:58:54 -0500468 ctx.VisitDirectDeps(aModule, func(depModule blueprint.Module) {
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800469 depLabels[qualifiedTargetLabel(ctx, depModule)] = true
470 })
471 }
Liz Kammer0eae52e2021-10-06 10:32:26 -0400472
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400473 for p := range ignoredPropNames {
Liz Kammer0eae52e2021-10-06 10:32:26 -0400474 delete(props.Attrs, p)
475 }
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800476 attributes := propsToAttributes(props.Attrs)
477
478 depLabelList := "[\n"
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400479 for depLabel := range depLabels {
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800480 depLabelList += fmt.Sprintf(" %q,\n", depLabel)
481 }
482 depLabelList += " ]"
483
484 targetName := targetNameWithVariant(ctx, m)
485 return BazelTarget{
Spandan Dasabedff02023-03-07 19:24:34 +0000486 name: targetName,
487 packageName: ctx.ModuleDir(m),
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800488 content: fmt.Sprintf(
Sasha Smundakfb589492022-08-04 11:13:27 -0700489 soongModuleTargetTemplate,
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800490 targetName,
491 ctx.ModuleName(m),
492 canonicalizeModuleType(ctx.ModuleType(m)),
493 ctx.ModuleSubDir(m),
494 depLabelList,
495 attributes),
Alix94e26032022-08-16 20:37:33 +0000496 }, err
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800497}
498
Alix94e26032022-08-16 20:37:33 +0000499func getBuildProperties(ctx bpToBuildContext, m blueprint.Module) (BazelAttributes, error) {
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800500 // TODO: this omits properties for blueprint modules (blueprint_go_binary,
501 // bootstrap_go_binary, bootstrap_go_package), which will have to be handled separately.
502 if aModule, ok := m.(android.Module); ok {
Alex Márquez Pérez Muñíz Díaz Púras Thaureaux447f6c92021-08-31 20:30:36 +0000503 return extractModuleProperties(aModule.GetProperties(), false)
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800504 }
505
Alix94e26032022-08-16 20:37:33 +0000506 return BazelAttributes{}, nil
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800507}
508
509// Generically extract module properties and types into a map, keyed by the module property name.
Alix94e26032022-08-16 20:37:33 +0000510func extractModuleProperties(props []interface{}, checkForDuplicateProperties bool) (BazelAttributes, error) {
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800511 ret := map[string]string{}
512
513 // Iterate over this android.Module's property structs.
Liz Kammer2ada09a2021-08-11 00:17:36 -0400514 for _, properties := range props {
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800515 propertiesValue := reflect.ValueOf(properties)
516 // Check that propertiesValue is a pointer to the Properties struct, like
517 // *cc.BaseLinkerProperties or *java.CompilerProperties.
518 //
519 // propertiesValue can also be type-asserted to the structs to
520 // manipulate internal props, if needed.
521 if isStructPtr(propertiesValue.Type()) {
522 structValue := propertiesValue.Elem()
Alix94e26032022-08-16 20:37:33 +0000523 ok, err := extractStructProperties(structValue, 0)
524 if err != nil {
525 return BazelAttributes{}, err
526 }
527 for k, v := range ok {
Alex Márquez Pérez Muñíz Díaz Púras Thaureaux447f6c92021-08-31 20:30:36 +0000528 if existing, exists := ret[k]; checkForDuplicateProperties && exists {
Alix94e26032022-08-16 20:37:33 +0000529 return BazelAttributes{}, fmt.Errorf(
Alex Márquez Pérez Muñíz Díaz Púras Thaureaux447f6c92021-08-31 20:30:36 +0000530 "%s (%v) is present in properties whereas it should be consolidated into a commonAttributes",
Alix94e26032022-08-16 20:37:33 +0000531 k, existing)
Alex Márquez Pérez Muñíz Díaz Púras Thaureaux447f6c92021-08-31 20:30:36 +0000532 }
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800533 ret[k] = v
534 }
535 } else {
Alix94e26032022-08-16 20:37:33 +0000536 return BazelAttributes{},
537 fmt.Errorf(
538 "properties must be a pointer to a struct, got %T",
539 propertiesValue.Interface())
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800540 }
541 }
542
Liz Kammer2ada09a2021-08-11 00:17:36 -0400543 return BazelAttributes{
544 Attrs: ret,
Alix94e26032022-08-16 20:37:33 +0000545 }, nil
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800546}
547
548func isStructPtr(t reflect.Type) bool {
549 return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
550}
551
552// prettyPrint a property value into the equivalent Starlark representation
553// recursively.
Jingwen Chen58ff6802021-11-17 12:14:41 +0000554func prettyPrint(propertyValue reflect.Value, indent int, emitZeroValues bool) (string, error) {
555 if !emitZeroValues && isZero(propertyValue) {
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800556 // A property value being set or unset actually matters -- Soong does set default
557 // values for unset properties, like system_shared_libs = ["libc", "libm", "libdl"] at
558 // https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/linker.go;l=281-287;drc=f70926eef0b9b57faf04c17a1062ce50d209e480
559 //
Jingwen Chenfc490bd2021-03-30 10:24:19 +0000560 // In Bazel-parlance, we would use "attr.<type>(default = <default
561 // value>)" to set the default value of unset attributes. In the cases
562 // where the bp2build converter didn't set the default value within the
563 // mutator when creating the BazelTargetModule, this would be a zero
Jingwen Chen63930982021-03-24 10:04:33 -0400564 // value. For those cases, we return an empty string so we don't
565 // unnecessarily generate empty values.
566 return "", nil
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800567 }
568
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800569 switch propertyValue.Kind() {
570 case reflect.String:
Liz Kammer72beb342022-02-03 08:42:10 -0500571 return fmt.Sprintf("\"%v\"", escapeString(propertyValue.String())), nil
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800572 case reflect.Bool:
Liz Kammer72beb342022-02-03 08:42:10 -0500573 return starlark_fmt.PrintBool(propertyValue.Bool()), nil
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800574 case reflect.Int, reflect.Uint, reflect.Int64:
Liz Kammer72beb342022-02-03 08:42:10 -0500575 return fmt.Sprintf("%v", propertyValue.Interface()), nil
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800576 case reflect.Ptr:
Jingwen Chen58ff6802021-11-17 12:14:41 +0000577 return prettyPrint(propertyValue.Elem(), indent, emitZeroValues)
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800578 case reflect.Slice:
Liz Kammer72beb342022-02-03 08:42:10 -0500579 elements := make([]string, 0, propertyValue.Len())
580 for i := 0; i < propertyValue.Len(); i++ {
581 val, err := prettyPrint(propertyValue.Index(i), indent, emitZeroValues)
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800582 if err != nil {
583 return "", err
584 }
Liz Kammer72beb342022-02-03 08:42:10 -0500585 if val != "" {
586 elements = append(elements, val)
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800587 }
588 }
Sam Delmerico932c01c2022-03-25 16:33:26 +0000589 return starlark_fmt.PrintList(elements, indent, func(s string) string {
590 return "%s"
591 }), nil
Jingwen Chenb4628eb2021-04-08 14:40:57 +0000592
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800593 case reflect.Struct:
Jingwen Chen5d864492021-02-24 07:20:12 -0500594 // Special cases where the bp2build sends additional information to the codegenerator
595 // by wrapping the attributes in a custom struct type.
Jingwen Chenc1c26502021-04-05 10:35:13 +0000596 if attr, ok := propertyValue.Interface().(bazel.Attribute); ok {
597 return prettyPrintAttribute(attr, indent)
Liz Kammer356f7d42021-01-26 09:18:53 -0500598 } else if label, ok := propertyValue.Interface().(bazel.Label); ok {
599 return fmt.Sprintf("%q", label.Label), nil
600 }
601
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800602 // Sort and print the struct props by the key.
Alix94e26032022-08-16 20:37:33 +0000603 structProps, err := extractStructProperties(propertyValue, indent)
604
605 if err != nil {
606 return "", err
607 }
608
Jingwen Chen3d383bb2021-06-09 07:18:37 +0000609 if len(structProps) == 0 {
610 return "", nil
611 }
Liz Kammer72beb342022-02-03 08:42:10 -0500612 return starlark_fmt.PrintDict(structProps, indent), nil
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800613 case reflect.Interface:
614 // TODO(b/164227191): implement pretty print for interfaces.
615 // Interfaces are used for for arch, multilib and target properties.
616 return "", nil
Spandan Das6a448ec2023-04-19 17:36:12 +0000617 case reflect.Map:
618 if v, ok := propertyValue.Interface().(bazel.StringMapAttribute); ok {
619 return starlark_fmt.PrintStringStringDict(v, indent), nil
620 }
621 return "", fmt.Errorf("bp2build expects map of type map[string]string for field: %s", propertyValue)
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800622 default:
623 return "", fmt.Errorf(
624 "unexpected kind for property struct field: %s", propertyValue.Kind())
625 }
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800626}
627
628// Converts a reflected property struct value into a map of property names and property values,
629// which each property value correctly pretty-printed and indented at the right nest level,
630// since property structs can be nested. In Starlark, nested structs are represented as nested
631// dicts: https://docs.bazel.build/skylark/lib/dict.html
Alix94e26032022-08-16 20:37:33 +0000632func extractStructProperties(structValue reflect.Value, indent int) (map[string]string, error) {
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800633 if structValue.Kind() != reflect.Struct {
Alix94e26032022-08-16 20:37:33 +0000634 return map[string]string{}, fmt.Errorf("Expected a reflect.Struct type, but got %s", structValue.Kind())
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800635 }
636
Alix94e26032022-08-16 20:37:33 +0000637 var err error
638
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800639 ret := map[string]string{}
640 structType := structValue.Type()
641 for i := 0; i < structValue.NumField(); i++ {
642 field := structType.Field(i)
643 if shouldSkipStructField(field) {
644 continue
645 }
646
647 fieldValue := structValue.Field(i)
648 if isZero(fieldValue) {
649 // Ignore zero-valued fields
650 continue
651 }
Liz Kammer7a210ac2021-09-22 15:52:58 -0400652
Liz Kammer32a03392021-09-14 11:17:21 -0400653 // if the struct is embedded (anonymous), flatten the properties into the containing struct
654 if field.Anonymous {
655 if field.Type.Kind() == reflect.Ptr {
656 fieldValue = fieldValue.Elem()
657 }
658 if fieldValue.Type().Kind() == reflect.Struct {
Alix94e26032022-08-16 20:37:33 +0000659 propsToMerge, err := extractStructProperties(fieldValue, indent)
660 if err != nil {
661 return map[string]string{}, err
662 }
Liz Kammer32a03392021-09-14 11:17:21 -0400663 for prop, value := range propsToMerge {
664 ret[prop] = value
665 }
666 continue
667 }
668 }
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800669
670 propertyName := proptools.PropertyNameForField(field.Name)
Alix94e26032022-08-16 20:37:33 +0000671 var prettyPrintedValue string
672 prettyPrintedValue, err = prettyPrint(fieldValue, indent+1, false)
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800673 if err != nil {
Alix94e26032022-08-16 20:37:33 +0000674 return map[string]string{}, fmt.Errorf(
675 "Error while parsing property: %q. %s",
676 propertyName,
677 err)
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800678 }
679 if prettyPrintedValue != "" {
680 ret[propertyName] = prettyPrintedValue
681 }
682 }
683
Alix94e26032022-08-16 20:37:33 +0000684 return ret, nil
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800685}
686
687func isZero(value reflect.Value) bool {
688 switch value.Kind() {
689 case reflect.Func, reflect.Map, reflect.Slice:
690 return value.IsNil()
691 case reflect.Array:
692 valueIsZero := true
693 for i := 0; i < value.Len(); i++ {
694 valueIsZero = valueIsZero && isZero(value.Index(i))
695 }
696 return valueIsZero
697 case reflect.Struct:
698 valueIsZero := true
699 for i := 0; i < value.NumField(); i++ {
Lukacs T. Berki1353e592021-04-30 15:35:09 +0200700 valueIsZero = valueIsZero && isZero(value.Field(i))
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800701 }
702 return valueIsZero
703 case reflect.Ptr:
704 if !value.IsNil() {
705 return isZero(reflect.Indirect(value))
706 } else {
707 return true
708 }
Liz Kammer46fb7ab2021-12-01 10:09:34 -0500709 // Always print bool/strings, if you want a bool/string attribute to be able to take the default value, use a
710 // pointer instead
711 case reflect.Bool, reflect.String:
Liz Kammerd366c902021-06-03 13:43:01 -0400712 return false
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800713 default:
Rupert Shuttleworthc194ffb2021-05-19 06:49:02 -0400714 if !value.IsValid() {
715 return true
716 }
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800717 zeroValue := reflect.Zero(value.Type())
718 result := value.Interface() == zeroValue.Interface()
719 return result
720 }
721}
722
723func escapeString(s string) string {
724 s = strings.ReplaceAll(s, "\\", "\\\\")
Jingwen Chen58a12b82021-03-30 13:08:36 +0000725
726 // b/184026959: Reverse the application of some common control sequences.
727 // These must be generated literally in the BUILD file.
728 s = strings.ReplaceAll(s, "\t", "\\t")
729 s = strings.ReplaceAll(s, "\n", "\\n")
730 s = strings.ReplaceAll(s, "\r", "\\r")
731
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800732 return strings.ReplaceAll(s, "\"", "\\\"")
733}
734
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800735func targetNameWithVariant(c bpToBuildContext, logicModule blueprint.Module) string {
736 name := ""
737 if c.ModuleSubDir(logicModule) != "" {
738 // TODO(b/162720883): Figure out a way to drop the "--" variant suffixes.
739 name = c.ModuleName(logicModule) + "--" + c.ModuleSubDir(logicModule)
740 } else {
741 name = c.ModuleName(logicModule)
742 }
743
744 return strings.Replace(name, "//", "", 1)
745}
746
747func qualifiedTargetLabel(c bpToBuildContext, logicModule blueprint.Module) string {
748 return fmt.Sprintf("//%s:%s", c.ModuleDir(logicModule), targetNameWithVariant(c, logicModule))
749}