blob: f5aa685d5b020cee8fa500604a74ede546071a72 [file] [log] [blame]
Jingwen Chen5ba7e472020-07-15 10:06:41 +00001// 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 main
16
17import (
18 "android/soong/android"
19 "fmt"
20 "io/ioutil"
21 "os"
22 "path/filepath"
Jingwen Chen69d4cbe2020-08-07 14:16:34 +000023 "reflect"
Jingwen Chen5ba7e472020-07-15 10:06:41 +000024 "strings"
25
26 "github.com/google/blueprint"
Jingwen Chend8004ef2020-08-27 09:40:43 +000027 "github.com/google/blueprint/bootstrap/bpdoc"
Jingwen Chen69d4cbe2020-08-07 14:16:34 +000028 "github.com/google/blueprint/proptools"
Jingwen Chen5ba7e472020-07-15 10:06:41 +000029)
30
Jingwen Chend8004ef2020-08-27 09:40:43 +000031var (
32 // An allowlist of prop types that are surfaced from module props to rule
33 // attributes. (nested) dictionaries are notably absent here, because while
34 // Soong supports multi value typed and nested dictionaries, Bazel's rule
35 // attr() API supports only single-level string_dicts.
36 allowedPropTypes = map[string]bool{
37 "int": true, // e.g. 42
38 "bool": true, // e.g. True
39 "string_list": true, // e.g. ["a", "b"]
40 "string": true, // e.g. "a"
41 }
42
Jingwen Chend8004ef2020-08-27 09:40:43 +000043 // Certain module property names are blocklisted/ignored here, for the reasons commented.
44 ignoredPropNames = map[string]bool{
45 "name": true, // redundant, since this is explicitly generated for every target
46 "from": true, // reserved keyword
47 "in": true, // reserved keyword
48 "arch": true, // interface prop type is not supported yet.
49 "multilib": true, // interface prop type is not supported yet.
50 "target": true, // interface prop type is not supported yet.
51 "visibility": true, // Bazel has native visibility semantics. Handle later.
52 "features": true, // There is already a built-in attribute 'features' which cannot be overridden.
53 }
Jingwen Chen5ba7e472020-07-15 10:06:41 +000054)
55
56func targetNameWithVariant(c *blueprint.Context, logicModule blueprint.Module) string {
57 name := ""
58 if c.ModuleSubDir(logicModule) != "" {
Jingwen Chen69d4cbe2020-08-07 14:16:34 +000059 // TODO(b/162720883): Figure out a way to drop the "--" variant suffixes.
Jingwen Chen5ba7e472020-07-15 10:06:41 +000060 name = c.ModuleName(logicModule) + "--" + c.ModuleSubDir(logicModule)
61 } else {
62 name = c.ModuleName(logicModule)
63 }
64
65 return strings.Replace(name, "//", "", 1)
66}
67
68func qualifiedTargetLabel(c *blueprint.Context, logicModule blueprint.Module) string {
69 return "//" +
70 packagePath(c, logicModule) +
71 ":" +
72 targetNameWithVariant(c, logicModule)
73}
74
75func packagePath(c *blueprint.Context, logicModule blueprint.Module) string {
76 return filepath.Dir(c.BlueprintFile(logicModule))
77}
78
Jingwen Chen69d4cbe2020-08-07 14:16:34 +000079func escapeString(s string) string {
80 s = strings.ReplaceAll(s, "\\", "\\\\")
81 return strings.ReplaceAll(s, "\"", "\\\"")
82}
83
84func makeIndent(indent int) string {
85 if indent < 0 {
86 panic(fmt.Errorf("indent column cannot be less than 0, but got %d", indent))
87 }
88 return strings.Repeat(" ", indent)
89}
90
91// prettyPrint a property value into the equivalent Starlark representation
92// recursively.
93func prettyPrint(propertyValue reflect.Value, indent int) (string, error) {
94 if isZero(propertyValue) {
95 // A property value being set or unset actually matters -- Soong does set default
96 // values for unset properties, like system_shared_libs = ["libc", "libm", "libdl"] at
97 // https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/linker.go;l=281-287;drc=f70926eef0b9b57faf04c17a1062ce50d209e480
98 //
99 // In Bazel-parlance, we would use "attr.<type>(default = <default value>)" to set the default
100 // value of unset attributes.
101 return "", nil
102 }
103
104 var ret string
105 switch propertyValue.Kind() {
106 case reflect.String:
107 ret = fmt.Sprintf("\"%v\"", escapeString(propertyValue.String()))
108 case reflect.Bool:
109 ret = strings.Title(fmt.Sprintf("%v", propertyValue.Interface()))
110 case reflect.Int, reflect.Uint, reflect.Int64:
111 ret = fmt.Sprintf("%v", propertyValue.Interface())
112 case reflect.Ptr:
113 return prettyPrint(propertyValue.Elem(), indent)
114 case reflect.Slice:
115 ret = "[\n"
116 for i := 0; i < propertyValue.Len(); i++ {
117 indexedValue, err := prettyPrint(propertyValue.Index(i), indent+1)
118 if err != nil {
119 return "", err
120 }
121
122 if indexedValue != "" {
123 ret += makeIndent(indent + 1)
124 ret += indexedValue
125 ret += ",\n"
126 }
127 }
128 ret += makeIndent(indent)
129 ret += "]"
130 case reflect.Struct:
131 ret = "{\n"
132 // Sort and print the struct props by the key.
133 structProps := extractStructProperties(propertyValue, indent)
134 for _, k := range android.SortedStringKeys(structProps) {
135 ret += makeIndent(indent + 1)
Jingwen Chend8004ef2020-08-27 09:40:43 +0000136 ret += fmt.Sprintf("%q: %s,\n", k, structProps[k])
Jingwen Chen69d4cbe2020-08-07 14:16:34 +0000137 }
138 ret += makeIndent(indent)
139 ret += "}"
140 case reflect.Interface:
141 // TODO(b/164227191): implement pretty print for interfaces.
142 // Interfaces are used for for arch, multilib and target properties.
143 return "", nil
144 default:
145 return "", fmt.Errorf(
146 "unexpected kind for property struct field: %s", propertyValue.Kind())
147 }
148 return ret, nil
149}
150
Jingwen Chend8004ef2020-08-27 09:40:43 +0000151// Converts a reflected property struct value into a map of property names and property values,
152// which each property value correctly pretty-printed and indented at the right nest level,
153// since property structs can be nested. In Starlark, nested structs are represented as nested
154// dicts: https://docs.bazel.build/skylark/lib/dict.html
Jingwen Chen69d4cbe2020-08-07 14:16:34 +0000155func extractStructProperties(structValue reflect.Value, indent int) map[string]string {
156 if structValue.Kind() != reflect.Struct {
157 panic(fmt.Errorf("Expected a reflect.Struct type, but got %s", structValue.Kind()))
158 }
159
160 ret := map[string]string{}
161 structType := structValue.Type()
162 for i := 0; i < structValue.NumField(); i++ {
163 field := structType.Field(i)
164 if field.PkgPath != "" {
165 // Skip unexported fields. Some properties are
166 // internal to Soong only, and these fields do not have PkgPath.
167 continue
168 }
169 if proptools.HasTag(field, "blueprint", "mutated") {
170 continue
171 }
172
173 fieldValue := structValue.Field(i)
174 if isZero(fieldValue) {
175 // Ignore zero-valued fields
176 continue
177 }
178
179 propertyName := proptools.PropertyNameForField(field.Name)
180 prettyPrintedValue, err := prettyPrint(fieldValue, indent+1)
181 if err != nil {
182 panic(
183 fmt.Errorf(
184 "Error while parsing property: %q. %s",
185 propertyName,
186 err))
187 }
188 if prettyPrintedValue != "" {
189 ret[propertyName] = prettyPrintedValue
190 }
191 }
192
193 return ret
194}
195
196func isStructPtr(t reflect.Type) bool {
197 return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
198}
199
200// Generically extract module properties and types into a map, keyed by the module property name.
201func extractModuleProperties(aModule android.Module) map[string]string {
202 ret := map[string]string{}
203
204 // Iterate over this android.Module's property structs.
205 for _, properties := range aModule.GetProperties() {
206 propertiesValue := reflect.ValueOf(properties)
207 // Check that propertiesValue is a pointer to the Properties struct, like
208 // *cc.BaseLinkerProperties or *java.CompilerProperties.
209 //
210 // propertiesValue can also be type-asserted to the structs to
211 // manipulate internal props, if needed.
212 if isStructPtr(propertiesValue.Type()) {
213 structValue := propertiesValue.Elem()
214 for k, v := range extractStructProperties(structValue, 0) {
215 ret[k] = v
216 }
217 } else {
218 panic(fmt.Errorf(
219 "properties must be a pointer to a struct, got %T",
220 propertiesValue.Interface()))
221 }
222
223 }
224
225 return ret
226}
227
Jingwen Chend8004ef2020-08-27 09:40:43 +0000228// FIXME(b/168089390): In Bazel, rules ending with "_test" needs to be marked as
229// testonly = True, forcing other rules that depend on _test rules to also be
230// marked as testonly = True. This semantic constraint is not present in Soong.
231// To work around, rename "*_test" rules to "*_test_".
232func canonicalizeModuleType(moduleName string) string {
233 if strings.HasSuffix(moduleName, "_test") {
234 return moduleName + "_"
235 }
236
237 return moduleName
238}
239
240type RuleShim struct {
241 // The rule class shims contained in a bzl file. e.g. ["cc_object", "cc_library", ..]
242 rules []string
243
244 // The generated string content of the bzl file.
245 content string
246}
247
248// Create <module>.bzl containing Bazel rule shims for every module type available in Soong and
249// user-specified Go plugins.
250//
251// This function reuses documentation generation APIs to ensure parity between modules-as-docs
252// and modules-as-code, including the names and types of module properties.
253func createRuleShims(packages []*bpdoc.Package) (map[string]RuleShim, error) {
254 var propToAttr func(prop bpdoc.Property, propName string) string
255 propToAttr = func(prop bpdoc.Property, propName string) string {
256 // dots are not allowed in Starlark attribute names. Substitute them with double underscores.
257 propName = strings.ReplaceAll(propName, ".", "__")
258 if !shouldGenerateAttribute(propName) {
259 return ""
260 }
261
262 // Canonicalize and normalize module property types to Bazel attribute types
263 starlarkAttrType := prop.Type
Jingwen Chen222ff4d2020-11-05 03:52:34 -0500264 if starlarkAttrType == "list of string" {
Jingwen Chend8004ef2020-08-27 09:40:43 +0000265 starlarkAttrType = "string_list"
266 } else if starlarkAttrType == "int64" {
267 starlarkAttrType = "int"
268 } else if starlarkAttrType == "" {
269 var attr string
270 for _, nestedProp := range prop.Properties {
271 nestedAttr := propToAttr(nestedProp, propName+"__"+nestedProp.Name)
272 if nestedAttr != "" {
273 // TODO(b/167662930): Fix nested props resulting in too many attributes.
274 // Let's still generate these, but comment them out.
275 attr += "# " + nestedAttr
276 }
277 }
278 return attr
279 }
280
281 if !allowedPropTypes[starlarkAttrType] {
282 return ""
283 }
284
285 return fmt.Sprintf(" %q: attr.%s(),\n", propName, starlarkAttrType)
286 }
287
288 ruleShims := map[string]RuleShim{}
289 for _, pkg := range packages {
Jingwen Chen50f93d22020-11-05 07:42:11 -0500290 content := "load(\"//build/bazel/queryview_rules:providers.bzl\", \"SoongModuleInfo\")\n"
Jingwen Chend8004ef2020-08-27 09:40:43 +0000291
292 bzlFileName := strings.ReplaceAll(pkg.Path, "android/soong/", "")
293 bzlFileName = strings.ReplaceAll(bzlFileName, ".", "_")
294 bzlFileName = strings.ReplaceAll(bzlFileName, "/", "_")
295
296 rules := []string{}
297
298 for _, moduleTypeTemplate := range moduleTypeDocsToTemplates(pkg.ModuleTypes) {
299 attrs := `{
300 "module_name": attr.string(mandatory = True),
301 "module_variant": attr.string(),
302 "module_deps": attr.label_list(providers = [SoongModuleInfo]),
303`
304 for _, prop := range moduleTypeTemplate.Properties {
305 attrs += propToAttr(prop, prop.Name)
306 }
307
Jingwen Chen6874dbe2020-10-14 04:37:12 -0400308 moduleTypeName := moduleTypeTemplate.Name
309
310 // Certain SDK-related module types dynamically inject properties, instead of declaring
311 // them as structs. These properties are registered in an SdkMemberTypesRegistry. If
312 // the module type name matches, add these properties into the rule definition.
313 var registeredTypes []android.SdkMemberType
314 if moduleTypeName == "module_exports" || moduleTypeName == "module_exports_snapshot" {
315 registeredTypes = android.ModuleExportsMemberTypes.RegisteredTypes()
316 } else if moduleTypeName == "sdk" || moduleTypeName == "sdk_snapshot" {
317 registeredTypes = android.SdkMemberTypes.RegisteredTypes()
318 }
319 for _, memberType := range registeredTypes {
320 attrs += fmt.Sprintf(" %q: attr.string_list(),\n", memberType.SdkPropertyName())
Jingwen Chend8004ef2020-08-27 09:40:43 +0000321 }
322
323 attrs += " },"
324
325 rule := canonicalizeModuleType(moduleTypeTemplate.Name)
326 content += fmt.Sprintf(moduleRuleShim, rule, attrs)
327 rules = append(rules, rule)
328 }
329
330 ruleShims[bzlFileName] = RuleShim{content: content, rules: rules}
331 }
332 return ruleShims, nil
333}
334
Jingwen Chen50f93d22020-11-05 07:42:11 -0500335func createBazelQueryView(ctx *android.Context, bazelQueryViewDir string) error {
Jingwen Chen5ba7e472020-07-15 10:06:41 +0000336 blueprintCtx := ctx.Context
337 blueprintCtx.VisitAllModules(func(module blueprint.Module) {
Jingwen Chen50f93d22020-11-05 07:42:11 -0500338 buildFile, err := buildFileForModule(blueprintCtx, module, bazelQueryViewDir)
Jingwen Chen5ba7e472020-07-15 10:06:41 +0000339 if err != nil {
340 panic(err)
341 }
342
Jingwen Chen69d4cbe2020-08-07 14:16:34 +0000343 buildFile.Write([]byte(generateSoongModuleTarget(blueprintCtx, module) + "\n\n"))
Jingwen Chen5ba7e472020-07-15 10:06:41 +0000344 buildFile.Close()
345 })
Jingwen Chen311bd382020-10-19 07:49:50 -0400346 var err error
Jingwen Chen5ba7e472020-07-15 10:06:41 +0000347
Jingwen Chen311bd382020-10-19 07:49:50 -0400348 // Write top level files: WORKSPACE and BUILD. These files are empty.
Jingwen Chen50f93d22020-11-05 07:42:11 -0500349 if err = writeReadOnlyFile(bazelQueryViewDir, "WORKSPACE", ""); err != nil {
Jingwen Chen5ba7e472020-07-15 10:06:41 +0000350 return err
351 }
352
Jingwen Chen311bd382020-10-19 07:49:50 -0400353 // Used to denote that the top level directory is a package.
Jingwen Chen50f93d22020-11-05 07:42:11 -0500354 if err = writeReadOnlyFile(bazelQueryViewDir, "BUILD", ""); err != nil {
Jingwen Chend8004ef2020-08-27 09:40:43 +0000355 return err
356 }
357
358 packages, err := getPackages(ctx)
359 if err != nil {
360 return err
361 }
362 ruleShims, err := createRuleShims(packages)
363 if err != nil {
364 return err
365 }
366
Jingwen Chen311bd382020-10-19 07:49:50 -0400367 // Write .bzl Starlark files into the bazel_rules top level directory (provider and rule definitions)
Jingwen Chen50f93d22020-11-05 07:42:11 -0500368 bazelRulesDir := bazelQueryViewDir + "/build/bazel/queryview_rules"
Jingwen Chen311bd382020-10-19 07:49:50 -0400369 if err = writeReadOnlyFile(bazelRulesDir, "BUILD", ""); err != nil {
370 return err
371 }
372 if err = writeReadOnlyFile(bazelRulesDir, "providers.bzl", providersBzl); err != nil {
373 return err
374 }
375
Jingwen Chend8004ef2020-08-27 09:40:43 +0000376 for bzlFileName, ruleShim := range ruleShims {
Jingwen Chen311bd382020-10-19 07:49:50 -0400377 if err = writeReadOnlyFile(bazelRulesDir, bzlFileName+".bzl", ruleShim.content); err != nil {
Jingwen Chend8004ef2020-08-27 09:40:43 +0000378 return err
379 }
380 }
381
Jingwen Chen311bd382020-10-19 07:49:50 -0400382 return writeReadOnlyFile(bazelRulesDir, "soong_module.bzl", generateSoongModuleBzl(ruleShims))
Jingwen Chen5ba7e472020-07-15 10:06:41 +0000383}
384
Jingwen Chend8004ef2020-08-27 09:40:43 +0000385// Generate the content of soong_module.bzl with the rule shim load statements
386// and mapping of module_type to rule shim map for every module type in Soong.
387func generateSoongModuleBzl(bzlLoads map[string]RuleShim) string {
388 var loadStmts string
389 var moduleRuleMap string
390 for bzlFileName, ruleShim := range bzlLoads {
Jingwen Chen50f93d22020-11-05 07:42:11 -0500391 loadStmt := "load(\"//build/bazel/queryview_rules:"
Jingwen Chend8004ef2020-08-27 09:40:43 +0000392 loadStmt += bzlFileName
393 loadStmt += ".bzl\""
394 for _, rule := range ruleShim.rules {
395 loadStmt += fmt.Sprintf(", %q", rule)
396 moduleRuleMap += " \"" + rule + "\": " + rule + ",\n"
397 }
398 loadStmt += ")\n"
399 loadStmts += loadStmt
400 }
401
402 return fmt.Sprintf(soongModuleBzl, loadStmts, moduleRuleMap)
Jingwen Chen69d4cbe2020-08-07 14:16:34 +0000403}
404
405func shouldGenerateAttribute(prop string) bool {
Jingwen Chend8004ef2020-08-27 09:40:43 +0000406 return !ignoredPropNames[prop]
Jingwen Chen69d4cbe2020-08-07 14:16:34 +0000407}
408
409// props is an unsorted map. This function ensures that
410// the generated attributes are sorted to ensure determinism.
411func propsToAttributes(props map[string]string) string {
412 var attributes string
413 for _, propName := range android.SortedStringKeys(props) {
414 if shouldGenerateAttribute(propName) {
415 attributes += fmt.Sprintf(" %s = %s,\n", propName, props[propName])
416 }
417 }
418 return attributes
419}
420
421// Convert a module and its deps and props into a Bazel macro/rule
422// representation in the BUILD file.
423func generateSoongModuleTarget(
424 blueprintCtx *blueprint.Context,
425 module blueprint.Module) string {
426
427 var props map[string]string
428 if aModule, ok := module.(android.Module); ok {
429 props = extractModuleProperties(aModule)
430 }
431 attributes := propsToAttributes(props)
432
433 // TODO(b/163018919): DirectDeps can have duplicate (module, variant)
434 // items, if the modules are added using different DependencyTag. Figure
435 // out the implications of that.
436 depLabels := map[string]bool{}
437 blueprintCtx.VisitDirectDeps(module, func(depModule blueprint.Module) {
438 depLabels[qualifiedTargetLabel(blueprintCtx, depModule)] = true
439 })
440
441 depLabelList := "[\n"
442 for depLabel, _ := range depLabels {
Jingwen Chend8004ef2020-08-27 09:40:43 +0000443 depLabelList += fmt.Sprintf(" %q,\n", depLabel)
Jingwen Chen69d4cbe2020-08-07 14:16:34 +0000444 }
445 depLabelList += " ]"
446
447 return fmt.Sprintf(
448 soongModuleTarget,
449 targetNameWithVariant(blueprintCtx, module),
450 blueprintCtx.ModuleName(module),
Jingwen Chend8004ef2020-08-27 09:40:43 +0000451 canonicalizeModuleType(blueprintCtx.ModuleType(module)),
Jingwen Chen69d4cbe2020-08-07 14:16:34 +0000452 blueprintCtx.ModuleSubDir(module),
453 depLabelList,
454 attributes)
455}
456
Jingwen Chen50f93d22020-11-05 07:42:11 -0500457func buildFileForModule(
458 ctx *blueprint.Context, module blueprint.Module, bazelQueryViewDir string) (*os.File, error) {
Jingwen Chen5ba7e472020-07-15 10:06:41 +0000459 // Create nested directories for the BUILD file
Jingwen Chen50f93d22020-11-05 07:42:11 -0500460 dirPath := filepath.Join(bazelQueryViewDir, packagePath(ctx, module))
Jingwen Chen311bd382020-10-19 07:49:50 -0400461 createDirectoryIfNonexistent(dirPath)
Jingwen Chen5ba7e472020-07-15 10:06:41 +0000462 // Open the file for appending, and create it if it doesn't exist
463 f, err := os.OpenFile(
464 filepath.Join(dirPath, "BUILD.bazel"),
465 os.O_APPEND|os.O_CREATE|os.O_WRONLY,
466 0644)
467 if err != nil {
468 return nil, err
469 }
470
471 // If the file is empty, add the load statement for the `soong_module` rule
472 fi, err := f.Stat()
473 if err != nil {
474 return nil, err
475 }
476 if fi.Size() == 0 {
477 f.Write([]byte(soongModuleLoad + "\n"))
478 }
479
480 return f, nil
481}
482
Jingwen Chen311bd382020-10-19 07:49:50 -0400483func createDirectoryIfNonexistent(dir string) {
484 if _, err := os.Stat(dir); os.IsNotExist(err) {
485 os.MkdirAll(dir, os.ModePerm)
486 }
487}
488
Jingwen Chen50f93d22020-11-05 07:42:11 -0500489// The QueryView directory should be read-only, sufficient for bazel query. The files
Jingwen Chend8004ef2020-08-27 09:40:43 +0000490// are not intended to be edited by end users.
Jingwen Chen5ba7e472020-07-15 10:06:41 +0000491func writeReadOnlyFile(dir string, baseName string, content string) error {
Jingwen Chen311bd382020-10-19 07:49:50 -0400492 createDirectoryIfNonexistent(dir)
493 pathToFile := filepath.Join(dir, baseName)
Jingwen Chen5ba7e472020-07-15 10:06:41 +0000494 // 0444 is read-only
Jingwen Chend8004ef2020-08-27 09:40:43 +0000495 return ioutil.WriteFile(pathToFile, []byte(content), 0444)
Jingwen Chen5ba7e472020-07-15 10:06:41 +0000496}
Jingwen Chen69d4cbe2020-08-07 14:16:34 +0000497
498func isZero(value reflect.Value) bool {
499 switch value.Kind() {
500 case reflect.Func, reflect.Map, reflect.Slice:
501 return value.IsNil()
502 case reflect.Array:
503 valueIsZero := true
504 for i := 0; i < value.Len(); i++ {
505 valueIsZero = valueIsZero && isZero(value.Index(i))
506 }
507 return valueIsZero
508 case reflect.Struct:
509 valueIsZero := true
510 for i := 0; i < value.NumField(); i++ {
511 if value.Field(i).CanSet() {
512 valueIsZero = valueIsZero && isZero(value.Field(i))
513 }
514 }
515 return valueIsZero
516 case reflect.Ptr:
517 if !value.IsNil() {
518 return isZero(reflect.Indirect(value))
519 } else {
520 return true
521 }
522 default:
523 zeroValue := reflect.Zero(value.Type())
524 result := value.Interface() == zeroValue.Interface()
525 return result
526 }
527}