blob: 308076d527123e3e1d47ab4d5f79f594457db5d6 [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 Chen69d4cbe2020-08-07 14:16:34 +000027 "github.com/google/blueprint/proptools"
Jingwen Chen5ba7e472020-07-15 10:06:41 +000028)
29
30const (
31 soongModuleLoad = `package(default_visibility = ["//visibility:public"])
32load("//:soong_module.bzl", "soong_module")
Jingwen Chen69d4cbe2020-08-07 14:16:34 +000033
Jingwen Chen5ba7e472020-07-15 10:06:41 +000034`
35
36 // A BUILD file target snippet representing a Soong module
37 soongModuleTarget = `soong_module(
38 name = "%s",
39 module_name = "%s",
40 module_type = "%s",
41 module_variant = "%s",
Jingwen Chence3d46f2020-08-25 05:34:43 +000042 module_deps = %s,
Jingwen Chen69d4cbe2020-08-07 14:16:34 +000043%s)`
Jingwen Chen5ba7e472020-07-15 10:06:41 +000044
45 // The soong_module rule implementation in a .bzl file
Jingwen Chen69d4cbe2020-08-07 14:16:34 +000046 soongModuleBzl = `SoongModuleInfo = provider(
Jingwen Chen5ba7e472020-07-15 10:06:41 +000047 fields = {
48 "name": "Name of module",
49 "type": "Type of module",
50 "variant": "Variant of module",
51 },
52)
53
Jingwen Chen69d4cbe2020-08-07 14:16:34 +000054def _merge_dicts(*dicts):
55 """Adds a list of dictionaries into a single dictionary."""
56
57 # If keys are repeated in multiple dictionaries, the latter one "wins".
58 result = {}
59 for d in dicts:
60 result.update(d)
61
62 return result
63
64def _generic_soong_module_impl(ctx):
Jingwen Chen5ba7e472020-07-15 10:06:41 +000065 return [
66 SoongModuleInfo(
67 name = ctx.attr.module_name,
68 type = ctx.attr.module_type,
69 variant = ctx.attr.module_variant,
70 ),
71 ]
72
Jingwen Chen69d4cbe2020-08-07 14:16:34 +000073_COMMON_ATTRS = {
74 "module_name": attr.string(mandatory = True),
75 "module_type": attr.string(mandatory = True),
76 "module_variant": attr.string(),
Jingwen Chence3d46f2020-08-25 05:34:43 +000077 "module_deps": attr.label_list(providers = [SoongModuleInfo]),
Jingwen Chen69d4cbe2020-08-07 14:16:34 +000078}
79
80
81generic_soong_module = rule(
82 implementation = _generic_soong_module_impl,
83 attrs = _COMMON_ATTRS,
Jingwen Chen5ba7e472020-07-15 10:06:41 +000084)
Jingwen Chen69d4cbe2020-08-07 14:16:34 +000085
86# TODO(jingwen): auto generate Soong module shims
87def _soong_filegroup_impl(ctx):
88 return [SoongModuleInfo(),]
89
90soong_filegroup = rule(
91 implementation = _soong_filegroup_impl,
92 # Matches https://cs.android.com/android/platform/superproject/+/master:build/soong/android/filegroup.go;l=25-40;drc=6a6478d49e78703ba22a432c41d819c8df79ef6c
93 attrs = _merge_dicts(_COMMON_ATTRS, {
94 "srcs": attr.string_list(doc = "srcs lists files that will be included in this filegroup"),
95 "exclude_srcs": attr.string_list(),
96 "path": attr.string(doc = "The base path to the files. May be used by other modules to determine which portion of the path to use. For example, when a filegroup is used as data in a cc_test rule, the base path is stripped off the path and the remaining path is used as the installation directory."),
97 "export_to_make_var": attr.string(doc = "Create a make variable with the specified name that contains the list of files in the filegroup, relative to the root of the source tree."),
98 })
99)
100
101soong_module_rule_map = {
102 "filegroup": soong_filegroup,
103}
104
105# soong_module is a macro that supports arbitrary kwargs, and uses module_type to
106# expand to the right underlying shim.
107def soong_module(name, module_type, **kwargs):
108 soong_module_rule = soong_module_rule_map.get(module_type)
109
110 if soong_module_rule == None:
111 # This module type does not have an existing rule to map to, so use the
112 # generic_soong_module rule instead.
113 generic_soong_module(
114 name = name,
115 module_type = module_type,
116 module_name = kwargs.pop("module_name", ""),
117 module_variant = kwargs.pop("module_variant", ""),
Jingwen Chence3d46f2020-08-25 05:34:43 +0000118 module_deps = kwargs.pop("module_deps", []),
Jingwen Chen69d4cbe2020-08-07 14:16:34 +0000119 )
120 else:
121 soong_module_rule(
122 name = name,
123 module_type = module_type,
124 **kwargs,
125 )
Jingwen Chen5ba7e472020-07-15 10:06:41 +0000126`
127)
128
129func targetNameWithVariant(c *blueprint.Context, logicModule blueprint.Module) string {
130 name := ""
131 if c.ModuleSubDir(logicModule) != "" {
Jingwen Chen69d4cbe2020-08-07 14:16:34 +0000132 // TODO(b/162720883): Figure out a way to drop the "--" variant suffixes.
Jingwen Chen5ba7e472020-07-15 10:06:41 +0000133 name = c.ModuleName(logicModule) + "--" + c.ModuleSubDir(logicModule)
134 } else {
135 name = c.ModuleName(logicModule)
136 }
137
138 return strings.Replace(name, "//", "", 1)
139}
140
141func qualifiedTargetLabel(c *blueprint.Context, logicModule blueprint.Module) string {
142 return "//" +
143 packagePath(c, logicModule) +
144 ":" +
145 targetNameWithVariant(c, logicModule)
146}
147
148func packagePath(c *blueprint.Context, logicModule blueprint.Module) string {
149 return filepath.Dir(c.BlueprintFile(logicModule))
150}
151
Jingwen Chen69d4cbe2020-08-07 14:16:34 +0000152func escapeString(s string) string {
153 s = strings.ReplaceAll(s, "\\", "\\\\")
154 return strings.ReplaceAll(s, "\"", "\\\"")
155}
156
157func makeIndent(indent int) string {
158 if indent < 0 {
159 panic(fmt.Errorf("indent column cannot be less than 0, but got %d", indent))
160 }
161 return strings.Repeat(" ", indent)
162}
163
164// prettyPrint a property value into the equivalent Starlark representation
165// recursively.
166func prettyPrint(propertyValue reflect.Value, indent int) (string, error) {
167 if isZero(propertyValue) {
168 // A property value being set or unset actually matters -- Soong does set default
169 // values for unset properties, like system_shared_libs = ["libc", "libm", "libdl"] at
170 // https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/linker.go;l=281-287;drc=f70926eef0b9b57faf04c17a1062ce50d209e480
171 //
172 // In Bazel-parlance, we would use "attr.<type>(default = <default value>)" to set the default
173 // value of unset attributes.
174 return "", nil
175 }
176
177 var ret string
178 switch propertyValue.Kind() {
179 case reflect.String:
180 ret = fmt.Sprintf("\"%v\"", escapeString(propertyValue.String()))
181 case reflect.Bool:
182 ret = strings.Title(fmt.Sprintf("%v", propertyValue.Interface()))
183 case reflect.Int, reflect.Uint, reflect.Int64:
184 ret = fmt.Sprintf("%v", propertyValue.Interface())
185 case reflect.Ptr:
186 return prettyPrint(propertyValue.Elem(), indent)
187 case reflect.Slice:
188 ret = "[\n"
189 for i := 0; i < propertyValue.Len(); i++ {
190 indexedValue, err := prettyPrint(propertyValue.Index(i), indent+1)
191 if err != nil {
192 return "", err
193 }
194
195 if indexedValue != "" {
196 ret += makeIndent(indent + 1)
197 ret += indexedValue
198 ret += ",\n"
199 }
200 }
201 ret += makeIndent(indent)
202 ret += "]"
203 case reflect.Struct:
204 ret = "{\n"
205 // Sort and print the struct props by the key.
206 structProps := extractStructProperties(propertyValue, indent)
207 for _, k := range android.SortedStringKeys(structProps) {
208 ret += makeIndent(indent + 1)
209 ret += "\"" + k + "\": "
210 ret += structProps[k]
211 ret += ",\n"
212 }
213 ret += makeIndent(indent)
214 ret += "}"
215 case reflect.Interface:
216 // TODO(b/164227191): implement pretty print for interfaces.
217 // Interfaces are used for for arch, multilib and target properties.
218 return "", nil
219 default:
220 return "", fmt.Errorf(
221 "unexpected kind for property struct field: %s", propertyValue.Kind())
222 }
223 return ret, nil
224}
225
226func extractStructProperties(structValue reflect.Value, indent int) map[string]string {
227 if structValue.Kind() != reflect.Struct {
228 panic(fmt.Errorf("Expected a reflect.Struct type, but got %s", structValue.Kind()))
229 }
230
231 ret := map[string]string{}
232 structType := structValue.Type()
233 for i := 0; i < structValue.NumField(); i++ {
234 field := structType.Field(i)
235 if field.PkgPath != "" {
236 // Skip unexported fields. Some properties are
237 // internal to Soong only, and these fields do not have PkgPath.
238 continue
239 }
240 if proptools.HasTag(field, "blueprint", "mutated") {
241 continue
242 }
243
244 fieldValue := structValue.Field(i)
245 if isZero(fieldValue) {
246 // Ignore zero-valued fields
247 continue
248 }
249
250 propertyName := proptools.PropertyNameForField(field.Name)
251 prettyPrintedValue, err := prettyPrint(fieldValue, indent+1)
252 if err != nil {
253 panic(
254 fmt.Errorf(
255 "Error while parsing property: %q. %s",
256 propertyName,
257 err))
258 }
259 if prettyPrintedValue != "" {
260 ret[propertyName] = prettyPrintedValue
261 }
262 }
263
264 return ret
265}
266
267func isStructPtr(t reflect.Type) bool {
268 return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
269}
270
271// Generically extract module properties and types into a map, keyed by the module property name.
272func extractModuleProperties(aModule android.Module) map[string]string {
273 ret := map[string]string{}
274
275 // Iterate over this android.Module's property structs.
276 for _, properties := range aModule.GetProperties() {
277 propertiesValue := reflect.ValueOf(properties)
278 // Check that propertiesValue is a pointer to the Properties struct, like
279 // *cc.BaseLinkerProperties or *java.CompilerProperties.
280 //
281 // propertiesValue can also be type-asserted to the structs to
282 // manipulate internal props, if needed.
283 if isStructPtr(propertiesValue.Type()) {
284 structValue := propertiesValue.Elem()
285 for k, v := range extractStructProperties(structValue, 0) {
286 ret[k] = v
287 }
288 } else {
289 panic(fmt.Errorf(
290 "properties must be a pointer to a struct, got %T",
291 propertiesValue.Interface()))
292 }
293
294 }
295
296 return ret
297}
298
Jingwen Chen5ba7e472020-07-15 10:06:41 +0000299func createBazelOverlay(ctx *android.Context, bazelOverlayDir string) error {
300 blueprintCtx := ctx.Context
301 blueprintCtx.VisitAllModules(func(module blueprint.Module) {
302 buildFile, err := buildFileForModule(blueprintCtx, module)
303 if err != nil {
304 panic(err)
305 }
306
Jingwen Chen69d4cbe2020-08-07 14:16:34 +0000307 buildFile.Write([]byte(generateSoongModuleTarget(blueprintCtx, module) + "\n\n"))
Jingwen Chen5ba7e472020-07-15 10:06:41 +0000308 buildFile.Close()
309 })
310
311 if err := writeReadOnlyFile(bazelOverlayDir, "WORKSPACE", ""); err != nil {
312 return err
313 }
314
315 if err := writeReadOnlyFile(bazelOverlayDir, "BUILD", ""); err != nil {
316 return err
317 }
318
319 return writeReadOnlyFile(bazelOverlayDir, "soong_module.bzl", soongModuleBzl)
320}
321
Jingwen Chen69d4cbe2020-08-07 14:16:34 +0000322var ignoredProps map[string]bool = map[string]bool{
323 "name": true, // redundant, since this is explicitly generated for every target
324 "from": true, // reserved keyword
325 "in": true, // reserved keyword
326 "arch": true, // interface prop type is not supported yet.
327 "multilib": true, // interface prop type is not supported yet.
328 "target": true, // interface prop type is not supported yet.
329 "visibility": true, // Bazel has native visibility semantics. Handle later.
330}
331
332func shouldGenerateAttribute(prop string) bool {
333 return !ignoredProps[prop]
334}
335
336// props is an unsorted map. This function ensures that
337// the generated attributes are sorted to ensure determinism.
338func propsToAttributes(props map[string]string) string {
339 var attributes string
340 for _, propName := range android.SortedStringKeys(props) {
341 if shouldGenerateAttribute(propName) {
342 attributes += fmt.Sprintf(" %s = %s,\n", propName, props[propName])
343 }
344 }
345 return attributes
346}
347
348// Convert a module and its deps and props into a Bazel macro/rule
349// representation in the BUILD file.
350func generateSoongModuleTarget(
351 blueprintCtx *blueprint.Context,
352 module blueprint.Module) string {
353
354 var props map[string]string
355 if aModule, ok := module.(android.Module); ok {
356 props = extractModuleProperties(aModule)
357 }
358 attributes := propsToAttributes(props)
359
360 // TODO(b/163018919): DirectDeps can have duplicate (module, variant)
361 // items, if the modules are added using different DependencyTag. Figure
362 // out the implications of that.
363 depLabels := map[string]bool{}
364 blueprintCtx.VisitDirectDeps(module, func(depModule blueprint.Module) {
365 depLabels[qualifiedTargetLabel(blueprintCtx, depModule)] = true
366 })
367
368 depLabelList := "[\n"
369 for depLabel, _ := range depLabels {
370 depLabelList += " \""
371 depLabelList += depLabel
372 depLabelList += "\",\n"
373 }
374 depLabelList += " ]"
375
376 return fmt.Sprintf(
377 soongModuleTarget,
378 targetNameWithVariant(blueprintCtx, module),
379 blueprintCtx.ModuleName(module),
380 blueprintCtx.ModuleType(module),
381 blueprintCtx.ModuleSubDir(module),
382 depLabelList,
383 attributes)
384}
385
Jingwen Chen5ba7e472020-07-15 10:06:41 +0000386func buildFileForModule(ctx *blueprint.Context, module blueprint.Module) (*os.File, error) {
387 // Create nested directories for the BUILD file
388 dirPath := filepath.Join(bazelOverlayDir, packagePath(ctx, module))
389 if _, err := os.Stat(dirPath); os.IsNotExist(err) {
390 os.MkdirAll(dirPath, os.ModePerm)
391 }
392 // Open the file for appending, and create it if it doesn't exist
393 f, err := os.OpenFile(
394 filepath.Join(dirPath, "BUILD.bazel"),
395 os.O_APPEND|os.O_CREATE|os.O_WRONLY,
396 0644)
397 if err != nil {
398 return nil, err
399 }
400
401 // If the file is empty, add the load statement for the `soong_module` rule
402 fi, err := f.Stat()
403 if err != nil {
404 return nil, err
405 }
406 if fi.Size() == 0 {
407 f.Write([]byte(soongModuleLoad + "\n"))
408 }
409
410 return f, nil
411}
412
413// The overlay directory should be read-only, sufficient for bazel query.
414func writeReadOnlyFile(dir string, baseName string, content string) error {
415 workspaceFile := filepath.Join(bazelOverlayDir, baseName)
416 // 0444 is read-only
417 return ioutil.WriteFile(workspaceFile, []byte(content), 0444)
418}
Jingwen Chen69d4cbe2020-08-07 14:16:34 +0000419
420func isZero(value reflect.Value) bool {
421 switch value.Kind() {
422 case reflect.Func, reflect.Map, reflect.Slice:
423 return value.IsNil()
424 case reflect.Array:
425 valueIsZero := true
426 for i := 0; i < value.Len(); i++ {
427 valueIsZero = valueIsZero && isZero(value.Index(i))
428 }
429 return valueIsZero
430 case reflect.Struct:
431 valueIsZero := true
432 for i := 0; i < value.NumField(); i++ {
433 if value.Field(i).CanSet() {
434 valueIsZero = valueIsZero && isZero(value.Field(i))
435 }
436 }
437 return valueIsZero
438 case reflect.Ptr:
439 if !value.IsNil() {
440 return isZero(reflect.Indirect(value))
441 } else {
442 return true
443 }
444 default:
445 zeroValue := reflect.Zero(value.Type())
446 result := value.Interface() == zeroValue.Interface()
447 return result
448 }
449}