blob: 27856b53a4746fc9fd80349a826efa424b16857a [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
31const (
Jingwen Chend8004ef2020-08-27 09:40:43 +000032 // The default `load` preamble for every generated BUILD file.
Jingwen Chen5ba7e472020-07-15 10:06:41 +000033 soongModuleLoad = `package(default_visibility = ["//visibility:public"])
Jingwen Chen50f93d22020-11-05 07:42:11 -050034load("//build/bazel/queryview_rules:soong_module.bzl", "soong_module")
Jingwen Chen69d4cbe2020-08-07 14:16:34 +000035
Jingwen Chen5ba7e472020-07-15 10:06:41 +000036`
37
Jingwen Chend8004ef2020-08-27 09:40:43 +000038 // A macro call in the BUILD file representing a Soong module, with space
39 // for expanding more attributes.
Jingwen Chen5ba7e472020-07-15 10:06:41 +000040 soongModuleTarget = `soong_module(
41 name = "%s",
42 module_name = "%s",
43 module_type = "%s",
44 module_variant = "%s",
Jingwen Chence3d46f2020-08-25 05:34:43 +000045 module_deps = %s,
Jingwen Chen69d4cbe2020-08-07 14:16:34 +000046%s)`
Jingwen Chen5ba7e472020-07-15 10:06:41 +000047
Jingwen Chend8004ef2020-08-27 09:40:43 +000048 // A simple provider to mark and differentiate Soong module rule shims from
49 // regular Bazel rules. Every Soong module rule shim returns a
50 // SoongModuleInfo provider, and can only depend on rules returning
51 // SoongModuleInfo in the `module_deps` attribute.
52 providersBzl = `SoongModuleInfo = provider(
Jingwen Chen5ba7e472020-07-15 10:06:41 +000053 fields = {
54 "name": "Name of module",
55 "type": "Type of module",
56 "variant": "Variant of module",
57 },
58)
Jingwen Chend8004ef2020-08-27 09:40:43 +000059`
Jingwen Chen5ba7e472020-07-15 10:06:41 +000060
Jingwen Chend8004ef2020-08-27 09:40:43 +000061 // The soong_module rule implementation in a .bzl file.
62 soongModuleBzl = `
63%s
Jingwen Chen69d4cbe2020-08-07 14:16:34 +000064
Jingwen Chen50f93d22020-11-05 07:42:11 -050065load("//build/bazel/queryview_rules:providers.bzl", "SoongModuleInfo")
Jingwen Chen69d4cbe2020-08-07 14:16:34 +000066
67def _generic_soong_module_impl(ctx):
Jingwen Chen5ba7e472020-07-15 10:06:41 +000068 return [
69 SoongModuleInfo(
70 name = ctx.attr.module_name,
71 type = ctx.attr.module_type,
72 variant = ctx.attr.module_variant,
73 ),
74 ]
75
Jingwen Chen69d4cbe2020-08-07 14:16:34 +000076generic_soong_module = rule(
77 implementation = _generic_soong_module_impl,
Jingwen Chend8004ef2020-08-27 09:40:43 +000078 attrs = {
79 "module_name": attr.string(mandatory = True),
80 "module_type": attr.string(mandatory = True),
81 "module_variant": attr.string(),
82 "module_deps": attr.label_list(providers = [SoongModuleInfo]),
83 },
Jingwen Chen69d4cbe2020-08-07 14:16:34 +000084)
85
86soong_module_rule_map = {
Jingwen Chend8004ef2020-08-27 09:40:43 +000087%s}
88
89_SUPPORTED_TYPES = ["bool", "int", "string"]
90
91def _is_supported_type(value):
92 if type(value) in _SUPPORTED_TYPES:
93 return True
94 elif type(value) == "list":
95 supported = True
96 for v in value:
97 supported = supported and type(v) in _SUPPORTED_TYPES
98 return supported
99 else:
100 return False
Jingwen Chen69d4cbe2020-08-07 14:16:34 +0000101
102# soong_module is a macro that supports arbitrary kwargs, and uses module_type to
103# expand to the right underlying shim.
104def soong_module(name, module_type, **kwargs):
105 soong_module_rule = soong_module_rule_map.get(module_type)
106
107 if soong_module_rule == None:
108 # This module type does not have an existing rule to map to, so use the
109 # generic_soong_module rule instead.
110 generic_soong_module(
111 name = name,
112 module_type = module_type,
113 module_name = kwargs.pop("module_name", ""),
114 module_variant = kwargs.pop("module_variant", ""),
Jingwen Chence3d46f2020-08-25 05:34:43 +0000115 module_deps = kwargs.pop("module_deps", []),
Jingwen Chen69d4cbe2020-08-07 14:16:34 +0000116 )
117 else:
Jingwen Chend8004ef2020-08-27 09:40:43 +0000118 supported_kwargs = dict()
119 for key, value in kwargs.items():
120 if _is_supported_type(value):
121 supported_kwargs[key] = value
Jingwen Chen69d4cbe2020-08-07 14:16:34 +0000122 soong_module_rule(
123 name = name,
Jingwen Chend8004ef2020-08-27 09:40:43 +0000124 **supported_kwargs,
Jingwen Chen69d4cbe2020-08-07 14:16:34 +0000125 )
Jingwen Chen5ba7e472020-07-15 10:06:41 +0000126`
Jingwen Chend8004ef2020-08-27 09:40:43 +0000127
128 // A rule shim for representing a Soong module type and its properties.
129 moduleRuleShim = `
130def _%[1]s_impl(ctx):
131 return [SoongModuleInfo()]
132
133%[1]s = rule(
134 implementation = _%[1]s_impl,
135 attrs = %[2]s
136)
137`
138)
139
140var (
141 // An allowlist of prop types that are surfaced from module props to rule
142 // attributes. (nested) dictionaries are notably absent here, because while
143 // Soong supports multi value typed and nested dictionaries, Bazel's rule
144 // attr() API supports only single-level string_dicts.
145 allowedPropTypes = map[string]bool{
146 "int": true, // e.g. 42
147 "bool": true, // e.g. True
148 "string_list": true, // e.g. ["a", "b"]
149 "string": true, // e.g. "a"
150 }
151
Jingwen Chend8004ef2020-08-27 09:40:43 +0000152 // Certain module property names are blocklisted/ignored here, for the reasons commented.
153 ignoredPropNames = map[string]bool{
154 "name": true, // redundant, since this is explicitly generated for every target
155 "from": true, // reserved keyword
156 "in": true, // reserved keyword
157 "arch": true, // interface prop type is not supported yet.
158 "multilib": true, // interface prop type is not supported yet.
159 "target": true, // interface prop type is not supported yet.
160 "visibility": true, // Bazel has native visibility semantics. Handle later.
161 "features": true, // There is already a built-in attribute 'features' which cannot be overridden.
162 }
Jingwen Chen5ba7e472020-07-15 10:06:41 +0000163)
164
165func targetNameWithVariant(c *blueprint.Context, logicModule blueprint.Module) string {
166 name := ""
167 if c.ModuleSubDir(logicModule) != "" {
Jingwen Chen69d4cbe2020-08-07 14:16:34 +0000168 // TODO(b/162720883): Figure out a way to drop the "--" variant suffixes.
Jingwen Chen5ba7e472020-07-15 10:06:41 +0000169 name = c.ModuleName(logicModule) + "--" + c.ModuleSubDir(logicModule)
170 } else {
171 name = c.ModuleName(logicModule)
172 }
173
174 return strings.Replace(name, "//", "", 1)
175}
176
177func qualifiedTargetLabel(c *blueprint.Context, logicModule blueprint.Module) string {
178 return "//" +
179 packagePath(c, logicModule) +
180 ":" +
181 targetNameWithVariant(c, logicModule)
182}
183
184func packagePath(c *blueprint.Context, logicModule blueprint.Module) string {
185 return filepath.Dir(c.BlueprintFile(logicModule))
186}
187
Jingwen Chen69d4cbe2020-08-07 14:16:34 +0000188func escapeString(s string) string {
189 s = strings.ReplaceAll(s, "\\", "\\\\")
190 return strings.ReplaceAll(s, "\"", "\\\"")
191}
192
193func makeIndent(indent int) string {
194 if indent < 0 {
195 panic(fmt.Errorf("indent column cannot be less than 0, but got %d", indent))
196 }
197 return strings.Repeat(" ", indent)
198}
199
200// prettyPrint a property value into the equivalent Starlark representation
201// recursively.
202func prettyPrint(propertyValue reflect.Value, indent int) (string, error) {
203 if isZero(propertyValue) {
204 // A property value being set or unset actually matters -- Soong does set default
205 // values for unset properties, like system_shared_libs = ["libc", "libm", "libdl"] at
206 // https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/linker.go;l=281-287;drc=f70926eef0b9b57faf04c17a1062ce50d209e480
207 //
208 // In Bazel-parlance, we would use "attr.<type>(default = <default value>)" to set the default
209 // value of unset attributes.
210 return "", nil
211 }
212
213 var ret string
214 switch propertyValue.Kind() {
215 case reflect.String:
216 ret = fmt.Sprintf("\"%v\"", escapeString(propertyValue.String()))
217 case reflect.Bool:
218 ret = strings.Title(fmt.Sprintf("%v", propertyValue.Interface()))
219 case reflect.Int, reflect.Uint, reflect.Int64:
220 ret = fmt.Sprintf("%v", propertyValue.Interface())
221 case reflect.Ptr:
222 return prettyPrint(propertyValue.Elem(), indent)
223 case reflect.Slice:
224 ret = "[\n"
225 for i := 0; i < propertyValue.Len(); i++ {
226 indexedValue, err := prettyPrint(propertyValue.Index(i), indent+1)
227 if err != nil {
228 return "", err
229 }
230
231 if indexedValue != "" {
232 ret += makeIndent(indent + 1)
233 ret += indexedValue
234 ret += ",\n"
235 }
236 }
237 ret += makeIndent(indent)
238 ret += "]"
239 case reflect.Struct:
240 ret = "{\n"
241 // Sort and print the struct props by the key.
242 structProps := extractStructProperties(propertyValue, indent)
243 for _, k := range android.SortedStringKeys(structProps) {
244 ret += makeIndent(indent + 1)
Jingwen Chend8004ef2020-08-27 09:40:43 +0000245 ret += fmt.Sprintf("%q: %s,\n", k, structProps[k])
Jingwen Chen69d4cbe2020-08-07 14:16:34 +0000246 }
247 ret += makeIndent(indent)
248 ret += "}"
249 case reflect.Interface:
250 // TODO(b/164227191): implement pretty print for interfaces.
251 // Interfaces are used for for arch, multilib and target properties.
252 return "", nil
253 default:
254 return "", fmt.Errorf(
255 "unexpected kind for property struct field: %s", propertyValue.Kind())
256 }
257 return ret, nil
258}
259
Jingwen Chend8004ef2020-08-27 09:40:43 +0000260// Converts a reflected property struct value into a map of property names and property values,
261// which each property value correctly pretty-printed and indented at the right nest level,
262// since property structs can be nested. In Starlark, nested structs are represented as nested
263// dicts: https://docs.bazel.build/skylark/lib/dict.html
Jingwen Chen69d4cbe2020-08-07 14:16:34 +0000264func extractStructProperties(structValue reflect.Value, indent int) map[string]string {
265 if structValue.Kind() != reflect.Struct {
266 panic(fmt.Errorf("Expected a reflect.Struct type, but got %s", structValue.Kind()))
267 }
268
269 ret := map[string]string{}
270 structType := structValue.Type()
271 for i := 0; i < structValue.NumField(); i++ {
272 field := structType.Field(i)
273 if field.PkgPath != "" {
274 // Skip unexported fields. Some properties are
275 // internal to Soong only, and these fields do not have PkgPath.
276 continue
277 }
278 if proptools.HasTag(field, "blueprint", "mutated") {
279 continue
280 }
281
282 fieldValue := structValue.Field(i)
283 if isZero(fieldValue) {
284 // Ignore zero-valued fields
285 continue
286 }
287
288 propertyName := proptools.PropertyNameForField(field.Name)
289 prettyPrintedValue, err := prettyPrint(fieldValue, indent+1)
290 if err != nil {
291 panic(
292 fmt.Errorf(
293 "Error while parsing property: %q. %s",
294 propertyName,
295 err))
296 }
297 if prettyPrintedValue != "" {
298 ret[propertyName] = prettyPrintedValue
299 }
300 }
301
302 return ret
303}
304
305func isStructPtr(t reflect.Type) bool {
306 return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
307}
308
309// Generically extract module properties and types into a map, keyed by the module property name.
310func extractModuleProperties(aModule android.Module) map[string]string {
311 ret := map[string]string{}
312
313 // Iterate over this android.Module's property structs.
314 for _, properties := range aModule.GetProperties() {
315 propertiesValue := reflect.ValueOf(properties)
316 // Check that propertiesValue is a pointer to the Properties struct, like
317 // *cc.BaseLinkerProperties or *java.CompilerProperties.
318 //
319 // propertiesValue can also be type-asserted to the structs to
320 // manipulate internal props, if needed.
321 if isStructPtr(propertiesValue.Type()) {
322 structValue := propertiesValue.Elem()
323 for k, v := range extractStructProperties(structValue, 0) {
324 ret[k] = v
325 }
326 } else {
327 panic(fmt.Errorf(
328 "properties must be a pointer to a struct, got %T",
329 propertiesValue.Interface()))
330 }
331
332 }
333
334 return ret
335}
336
Jingwen Chend8004ef2020-08-27 09:40:43 +0000337// FIXME(b/168089390): In Bazel, rules ending with "_test" needs to be marked as
338// testonly = True, forcing other rules that depend on _test rules to also be
339// marked as testonly = True. This semantic constraint is not present in Soong.
340// To work around, rename "*_test" rules to "*_test_".
341func canonicalizeModuleType(moduleName string) string {
342 if strings.HasSuffix(moduleName, "_test") {
343 return moduleName + "_"
344 }
345
346 return moduleName
347}
348
349type RuleShim struct {
350 // The rule class shims contained in a bzl file. e.g. ["cc_object", "cc_library", ..]
351 rules []string
352
353 // The generated string content of the bzl file.
354 content string
355}
356
357// Create <module>.bzl containing Bazel rule shims for every module type available in Soong and
358// user-specified Go plugins.
359//
360// This function reuses documentation generation APIs to ensure parity between modules-as-docs
361// and modules-as-code, including the names and types of module properties.
362func createRuleShims(packages []*bpdoc.Package) (map[string]RuleShim, error) {
363 var propToAttr func(prop bpdoc.Property, propName string) string
364 propToAttr = func(prop bpdoc.Property, propName string) string {
365 // dots are not allowed in Starlark attribute names. Substitute them with double underscores.
366 propName = strings.ReplaceAll(propName, ".", "__")
367 if !shouldGenerateAttribute(propName) {
368 return ""
369 }
370
371 // Canonicalize and normalize module property types to Bazel attribute types
372 starlarkAttrType := prop.Type
Jingwen Chen222ff4d2020-11-05 03:52:34 -0500373 if starlarkAttrType == "list of string" {
Jingwen Chend8004ef2020-08-27 09:40:43 +0000374 starlarkAttrType = "string_list"
375 } else if starlarkAttrType == "int64" {
376 starlarkAttrType = "int"
377 } else if starlarkAttrType == "" {
378 var attr string
379 for _, nestedProp := range prop.Properties {
380 nestedAttr := propToAttr(nestedProp, propName+"__"+nestedProp.Name)
381 if nestedAttr != "" {
382 // TODO(b/167662930): Fix nested props resulting in too many attributes.
383 // Let's still generate these, but comment them out.
384 attr += "# " + nestedAttr
385 }
386 }
387 return attr
388 }
389
390 if !allowedPropTypes[starlarkAttrType] {
391 return ""
392 }
393
394 return fmt.Sprintf(" %q: attr.%s(),\n", propName, starlarkAttrType)
395 }
396
397 ruleShims := map[string]RuleShim{}
398 for _, pkg := range packages {
Jingwen Chen50f93d22020-11-05 07:42:11 -0500399 content := "load(\"//build/bazel/queryview_rules:providers.bzl\", \"SoongModuleInfo\")\n"
Jingwen Chend8004ef2020-08-27 09:40:43 +0000400
401 bzlFileName := strings.ReplaceAll(pkg.Path, "android/soong/", "")
402 bzlFileName = strings.ReplaceAll(bzlFileName, ".", "_")
403 bzlFileName = strings.ReplaceAll(bzlFileName, "/", "_")
404
405 rules := []string{}
406
407 for _, moduleTypeTemplate := range moduleTypeDocsToTemplates(pkg.ModuleTypes) {
408 attrs := `{
409 "module_name": attr.string(mandatory = True),
410 "module_variant": attr.string(),
411 "module_deps": attr.label_list(providers = [SoongModuleInfo]),
412`
413 for _, prop := range moduleTypeTemplate.Properties {
414 attrs += propToAttr(prop, prop.Name)
415 }
416
Jingwen Chen6874dbe2020-10-14 04:37:12 -0400417 moduleTypeName := moduleTypeTemplate.Name
418
419 // Certain SDK-related module types dynamically inject properties, instead of declaring
420 // them as structs. These properties are registered in an SdkMemberTypesRegistry. If
421 // the module type name matches, add these properties into the rule definition.
422 var registeredTypes []android.SdkMemberType
423 if moduleTypeName == "module_exports" || moduleTypeName == "module_exports_snapshot" {
424 registeredTypes = android.ModuleExportsMemberTypes.RegisteredTypes()
425 } else if moduleTypeName == "sdk" || moduleTypeName == "sdk_snapshot" {
426 registeredTypes = android.SdkMemberTypes.RegisteredTypes()
427 }
428 for _, memberType := range registeredTypes {
429 attrs += fmt.Sprintf(" %q: attr.string_list(),\n", memberType.SdkPropertyName())
Jingwen Chend8004ef2020-08-27 09:40:43 +0000430 }
431
432 attrs += " },"
433
434 rule := canonicalizeModuleType(moduleTypeTemplate.Name)
435 content += fmt.Sprintf(moduleRuleShim, rule, attrs)
436 rules = append(rules, rule)
437 }
438
439 ruleShims[bzlFileName] = RuleShim{content: content, rules: rules}
440 }
441 return ruleShims, nil
442}
443
Jingwen Chen50f93d22020-11-05 07:42:11 -0500444func createBazelQueryView(ctx *android.Context, bazelQueryViewDir string) error {
Jingwen Chen5ba7e472020-07-15 10:06:41 +0000445 blueprintCtx := ctx.Context
446 blueprintCtx.VisitAllModules(func(module blueprint.Module) {
Jingwen Chen50f93d22020-11-05 07:42:11 -0500447 buildFile, err := buildFileForModule(blueprintCtx, module, bazelQueryViewDir)
Jingwen Chen5ba7e472020-07-15 10:06:41 +0000448 if err != nil {
449 panic(err)
450 }
451
Jingwen Chen69d4cbe2020-08-07 14:16:34 +0000452 buildFile.Write([]byte(generateSoongModuleTarget(blueprintCtx, module) + "\n\n"))
Jingwen Chen5ba7e472020-07-15 10:06:41 +0000453 buildFile.Close()
454 })
Jingwen Chen311bd382020-10-19 07:49:50 -0400455 var err error
Jingwen Chen5ba7e472020-07-15 10:06:41 +0000456
Jingwen Chen311bd382020-10-19 07:49:50 -0400457 // Write top level files: WORKSPACE and BUILD. These files are empty.
Jingwen Chen50f93d22020-11-05 07:42:11 -0500458 if err = writeReadOnlyFile(bazelQueryViewDir, "WORKSPACE", ""); err != nil {
Jingwen Chen5ba7e472020-07-15 10:06:41 +0000459 return err
460 }
461
Jingwen Chen311bd382020-10-19 07:49:50 -0400462 // Used to denote that the top level directory is a package.
Jingwen Chen50f93d22020-11-05 07:42:11 -0500463 if err = writeReadOnlyFile(bazelQueryViewDir, "BUILD", ""); err != nil {
Jingwen Chend8004ef2020-08-27 09:40:43 +0000464 return err
465 }
466
467 packages, err := getPackages(ctx)
468 if err != nil {
469 return err
470 }
471 ruleShims, err := createRuleShims(packages)
472 if err != nil {
473 return err
474 }
475
Jingwen Chen311bd382020-10-19 07:49:50 -0400476 // Write .bzl Starlark files into the bazel_rules top level directory (provider and rule definitions)
Jingwen Chen50f93d22020-11-05 07:42:11 -0500477 bazelRulesDir := bazelQueryViewDir + "/build/bazel/queryview_rules"
Jingwen Chen311bd382020-10-19 07:49:50 -0400478 if err = writeReadOnlyFile(bazelRulesDir, "BUILD", ""); err != nil {
479 return err
480 }
481 if err = writeReadOnlyFile(bazelRulesDir, "providers.bzl", providersBzl); err != nil {
482 return err
483 }
484
Jingwen Chend8004ef2020-08-27 09:40:43 +0000485 for bzlFileName, ruleShim := range ruleShims {
Jingwen Chen311bd382020-10-19 07:49:50 -0400486 if err = writeReadOnlyFile(bazelRulesDir, bzlFileName+".bzl", ruleShim.content); err != nil {
Jingwen Chend8004ef2020-08-27 09:40:43 +0000487 return err
488 }
489 }
490
Jingwen Chen311bd382020-10-19 07:49:50 -0400491 return writeReadOnlyFile(bazelRulesDir, "soong_module.bzl", generateSoongModuleBzl(ruleShims))
Jingwen Chen5ba7e472020-07-15 10:06:41 +0000492}
493
Jingwen Chend8004ef2020-08-27 09:40:43 +0000494// Generate the content of soong_module.bzl with the rule shim load statements
495// and mapping of module_type to rule shim map for every module type in Soong.
496func generateSoongModuleBzl(bzlLoads map[string]RuleShim) string {
497 var loadStmts string
498 var moduleRuleMap string
499 for bzlFileName, ruleShim := range bzlLoads {
Jingwen Chen50f93d22020-11-05 07:42:11 -0500500 loadStmt := "load(\"//build/bazel/queryview_rules:"
Jingwen Chend8004ef2020-08-27 09:40:43 +0000501 loadStmt += bzlFileName
502 loadStmt += ".bzl\""
503 for _, rule := range ruleShim.rules {
504 loadStmt += fmt.Sprintf(", %q", rule)
505 moduleRuleMap += " \"" + rule + "\": " + rule + ",\n"
506 }
507 loadStmt += ")\n"
508 loadStmts += loadStmt
509 }
510
511 return fmt.Sprintf(soongModuleBzl, loadStmts, moduleRuleMap)
Jingwen Chen69d4cbe2020-08-07 14:16:34 +0000512}
513
514func shouldGenerateAttribute(prop string) bool {
Jingwen Chend8004ef2020-08-27 09:40:43 +0000515 return !ignoredPropNames[prop]
Jingwen Chen69d4cbe2020-08-07 14:16:34 +0000516}
517
518// props is an unsorted map. This function ensures that
519// the generated attributes are sorted to ensure determinism.
520func propsToAttributes(props map[string]string) string {
521 var attributes string
522 for _, propName := range android.SortedStringKeys(props) {
523 if shouldGenerateAttribute(propName) {
524 attributes += fmt.Sprintf(" %s = %s,\n", propName, props[propName])
525 }
526 }
527 return attributes
528}
529
530// Convert a module and its deps and props into a Bazel macro/rule
531// representation in the BUILD file.
532func generateSoongModuleTarget(
533 blueprintCtx *blueprint.Context,
534 module blueprint.Module) string {
535
536 var props map[string]string
537 if aModule, ok := module.(android.Module); ok {
538 props = extractModuleProperties(aModule)
539 }
540 attributes := propsToAttributes(props)
541
542 // TODO(b/163018919): DirectDeps can have duplicate (module, variant)
543 // items, if the modules are added using different DependencyTag. Figure
544 // out the implications of that.
545 depLabels := map[string]bool{}
546 blueprintCtx.VisitDirectDeps(module, func(depModule blueprint.Module) {
547 depLabels[qualifiedTargetLabel(blueprintCtx, depModule)] = true
548 })
549
550 depLabelList := "[\n"
551 for depLabel, _ := range depLabels {
Jingwen Chend8004ef2020-08-27 09:40:43 +0000552 depLabelList += fmt.Sprintf(" %q,\n", depLabel)
Jingwen Chen69d4cbe2020-08-07 14:16:34 +0000553 }
554 depLabelList += " ]"
555
556 return fmt.Sprintf(
557 soongModuleTarget,
558 targetNameWithVariant(blueprintCtx, module),
559 blueprintCtx.ModuleName(module),
Jingwen Chend8004ef2020-08-27 09:40:43 +0000560 canonicalizeModuleType(blueprintCtx.ModuleType(module)),
Jingwen Chen69d4cbe2020-08-07 14:16:34 +0000561 blueprintCtx.ModuleSubDir(module),
562 depLabelList,
563 attributes)
564}
565
Jingwen Chen50f93d22020-11-05 07:42:11 -0500566func buildFileForModule(
567 ctx *blueprint.Context, module blueprint.Module, bazelQueryViewDir string) (*os.File, error) {
Jingwen Chen5ba7e472020-07-15 10:06:41 +0000568 // Create nested directories for the BUILD file
Jingwen Chen50f93d22020-11-05 07:42:11 -0500569 dirPath := filepath.Join(bazelQueryViewDir, packagePath(ctx, module))
Jingwen Chen311bd382020-10-19 07:49:50 -0400570 createDirectoryIfNonexistent(dirPath)
Jingwen Chen5ba7e472020-07-15 10:06:41 +0000571 // Open the file for appending, and create it if it doesn't exist
572 f, err := os.OpenFile(
573 filepath.Join(dirPath, "BUILD.bazel"),
574 os.O_APPEND|os.O_CREATE|os.O_WRONLY,
575 0644)
576 if err != nil {
577 return nil, err
578 }
579
580 // If the file is empty, add the load statement for the `soong_module` rule
581 fi, err := f.Stat()
582 if err != nil {
583 return nil, err
584 }
585 if fi.Size() == 0 {
586 f.Write([]byte(soongModuleLoad + "\n"))
587 }
588
589 return f, nil
590}
591
Jingwen Chen311bd382020-10-19 07:49:50 -0400592func createDirectoryIfNonexistent(dir string) {
593 if _, err := os.Stat(dir); os.IsNotExist(err) {
594 os.MkdirAll(dir, os.ModePerm)
595 }
596}
597
Jingwen Chen50f93d22020-11-05 07:42:11 -0500598// The QueryView directory should be read-only, sufficient for bazel query. The files
Jingwen Chend8004ef2020-08-27 09:40:43 +0000599// are not intended to be edited by end users.
Jingwen Chen5ba7e472020-07-15 10:06:41 +0000600func writeReadOnlyFile(dir string, baseName string, content string) error {
Jingwen Chen311bd382020-10-19 07:49:50 -0400601 createDirectoryIfNonexistent(dir)
602 pathToFile := filepath.Join(dir, baseName)
Jingwen Chen5ba7e472020-07-15 10:06:41 +0000603 // 0444 is read-only
Jingwen Chend8004ef2020-08-27 09:40:43 +0000604 return ioutil.WriteFile(pathToFile, []byte(content), 0444)
Jingwen Chen5ba7e472020-07-15 10:06:41 +0000605}
Jingwen Chen69d4cbe2020-08-07 14:16:34 +0000606
607func isZero(value reflect.Value) bool {
608 switch value.Kind() {
609 case reflect.Func, reflect.Map, reflect.Slice:
610 return value.IsNil()
611 case reflect.Array:
612 valueIsZero := true
613 for i := 0; i < value.Len(); i++ {
614 valueIsZero = valueIsZero && isZero(value.Index(i))
615 }
616 return valueIsZero
617 case reflect.Struct:
618 valueIsZero := true
619 for i := 0; i < value.NumField(); i++ {
620 if value.Field(i).CanSet() {
621 valueIsZero = valueIsZero && isZero(value.Field(i))
622 }
623 }
624 return valueIsZero
625 case reflect.Ptr:
626 if !value.IsNil() {
627 return isZero(reflect.Indirect(value))
628 } else {
629 return true
630 }
631 default:
632 zeroValue := reflect.Zero(value.Type())
633 result := value.Interface() == zeroValue.Interface()
634 return result
635 }
636}