blob: a7c3adb65318d61ea02cb0e71827c5cfbb916978 [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
17import (
18 "android/soong/android"
19 "fmt"
20 "reflect"
21 "strings"
22
23 "github.com/google/blueprint"
24 "github.com/google/blueprint/proptools"
25)
26
27type BazelAttributes struct {
28 Attrs map[string]string
29}
30
31type BazelTarget struct {
32 name string
33 content string
34}
35
36type bpToBuildContext interface {
37 ModuleName(module blueprint.Module) string
38 ModuleDir(module blueprint.Module) string
39 ModuleSubDir(module blueprint.Module) string
40 ModuleType(module blueprint.Module) string
41
Jingwen Chendaa54bc2020-12-14 02:58:54 -050042 VisitAllModules(visit func(blueprint.Module))
43 VisitDirectDeps(module blueprint.Module, visit func(blueprint.Module))
44}
45
46type CodegenContext struct {
47 config android.Config
48 context android.Context
Jingwen Chen33832f92021-01-24 22:55:54 -050049 mode CodegenMode
Jingwen Chendaa54bc2020-12-14 02:58:54 -050050}
51
Jingwen Chen33832f92021-01-24 22:55:54 -050052// CodegenMode is an enum to differentiate code-generation modes.
53type CodegenMode int
54
55const (
56 // Bp2Build: generate BUILD files with targets buildable by Bazel directly.
57 //
58 // This mode is used for the Soong->Bazel build definition conversion.
59 Bp2Build CodegenMode = iota
60
61 // QueryView: generate BUILD files with targets representing fully mutated
62 // Soong modules, representing the fully configured Soong module graph with
63 // variants and dependency endges.
64 //
65 // This mode is used for discovering and introspecting the existing Soong
66 // module graph.
67 QueryView
68)
69
Jingwen Chendcc329a2021-01-26 02:49:03 -050070func (mode CodegenMode) String() string {
71 switch mode {
72 case Bp2Build:
73 return "Bp2Build"
74 case QueryView:
75 return "QueryView"
76 default:
77 return fmt.Sprintf("%d", mode)
78 }
79}
80
Jingwen Chendaa54bc2020-12-14 02:58:54 -050081func (ctx CodegenContext) AddNinjaFileDeps(...string) {}
82func (ctx CodegenContext) Config() android.Config { return ctx.config }
83func (ctx CodegenContext) Context() android.Context { return ctx.context }
84
85// NewCodegenContext creates a wrapper context that conforms to PathContext for
86// writing BUILD files in the output directory.
Jingwen Chen33832f92021-01-24 22:55:54 -050087func NewCodegenContext(config android.Config, context android.Context, mode CodegenMode) CodegenContext {
Jingwen Chendaa54bc2020-12-14 02:58:54 -050088 return CodegenContext{
89 context: context,
90 config: config,
Jingwen Chen33832f92021-01-24 22:55:54 -050091 mode: mode,
Jingwen Chendaa54bc2020-12-14 02:58:54 -050092 }
Liz Kammer2dd9ca42020-11-25 16:06:39 -080093}
94
95// props is an unsorted map. This function ensures that
96// the generated attributes are sorted to ensure determinism.
97func propsToAttributes(props map[string]string) string {
98 var attributes string
99 for _, propName := range android.SortedStringKeys(props) {
100 if shouldGenerateAttribute(propName) {
101 attributes += fmt.Sprintf(" %s = %s,\n", propName, props[propName])
102 }
103 }
104 return attributes
105}
106
Jingwen Chen33832f92021-01-24 22:55:54 -0500107func GenerateSoongModuleTargets(ctx bpToBuildContext, codegenMode CodegenMode) map[string][]BazelTarget {
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800108 buildFileToTargets := make(map[string][]BazelTarget)
Jingwen Chendaa54bc2020-12-14 02:58:54 -0500109 ctx.VisitAllModules(func(m blueprint.Module) {
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800110 dir := ctx.ModuleDir(m)
Jingwen Chen73850672020-12-14 08:25:34 -0500111 var t BazelTarget
112
Jingwen Chen33832f92021-01-24 22:55:54 -0500113 switch codegenMode {
114 case Bp2Build:
Jingwen Chen73850672020-12-14 08:25:34 -0500115 if _, ok := m.(android.BazelTargetModule); !ok {
116 return
117 }
118 t = generateBazelTarget(ctx, m)
Jingwen Chen33832f92021-01-24 22:55:54 -0500119 case QueryView:
Jingwen Chen73850672020-12-14 08:25:34 -0500120 t = generateSoongModuleTarget(ctx, m)
Jingwen Chen33832f92021-01-24 22:55:54 -0500121 default:
122 panic(fmt.Errorf("Unknown code-generation mode: %s", codegenMode))
Jingwen Chen73850672020-12-14 08:25:34 -0500123 }
124
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800125 buildFileToTargets[ctx.ModuleDir(m)] = append(buildFileToTargets[dir], t)
126 })
127 return buildFileToTargets
128}
129
Jingwen Chen73850672020-12-14 08:25:34 -0500130func generateBazelTarget(ctx bpToBuildContext, m blueprint.Module) BazelTarget {
131 // extract the bazel attributes from the module.
132 props := getBuildProperties(ctx, m)
133
134 // extract the rule class name from the attributes. Since the string value
135 // will be string-quoted, remove the quotes here.
136 ruleClass := strings.Replace(props.Attrs["rule_class"], "\"", "", 2)
137 // Delete it from being generated in the BUILD file.
138 delete(props.Attrs, "rule_class")
139
140 // Return the Bazel target with rule class and attributes, ready to be
141 // code-generated.
142 attributes := propsToAttributes(props.Attrs)
143 targetName := targetNameForBp2Build(ctx, m)
144 return BazelTarget{
145 name: targetName,
146 content: fmt.Sprintf(
147 bazelTarget,
148 ruleClass,
149 targetName,
150 attributes,
151 ),
152 }
153}
154
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800155// Convert a module and its deps and props into a Bazel macro/rule
156// representation in the BUILD file.
157func generateSoongModuleTarget(ctx bpToBuildContext, m blueprint.Module) BazelTarget {
158 props := getBuildProperties(ctx, m)
159
160 // TODO(b/163018919): DirectDeps can have duplicate (module, variant)
161 // items, if the modules are added using different DependencyTag. Figure
162 // out the implications of that.
163 depLabels := map[string]bool{}
164 if aModule, ok := m.(android.Module); ok {
Jingwen Chendaa54bc2020-12-14 02:58:54 -0500165 ctx.VisitDirectDeps(aModule, func(depModule blueprint.Module) {
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800166 depLabels[qualifiedTargetLabel(ctx, depModule)] = true
167 })
168 }
169 attributes := propsToAttributes(props.Attrs)
170
171 depLabelList := "[\n"
172 for depLabel, _ := range depLabels {
173 depLabelList += fmt.Sprintf(" %q,\n", depLabel)
174 }
175 depLabelList += " ]"
176
177 targetName := targetNameWithVariant(ctx, m)
178 return BazelTarget{
179 name: targetName,
180 content: fmt.Sprintf(
181 soongModuleTarget,
182 targetName,
183 ctx.ModuleName(m),
184 canonicalizeModuleType(ctx.ModuleType(m)),
185 ctx.ModuleSubDir(m),
186 depLabelList,
187 attributes),
188 }
189}
190
191func getBuildProperties(ctx bpToBuildContext, m blueprint.Module) BazelAttributes {
192 var allProps map[string]string
193 // TODO: this omits properties for blueprint modules (blueprint_go_binary,
194 // bootstrap_go_binary, bootstrap_go_package), which will have to be handled separately.
195 if aModule, ok := m.(android.Module); ok {
196 allProps = ExtractModuleProperties(aModule)
197 }
198
199 return BazelAttributes{
200 Attrs: allProps,
201 }
202}
203
204// Generically extract module properties and types into a map, keyed by the module property name.
205func ExtractModuleProperties(aModule android.Module) map[string]string {
206 ret := map[string]string{}
207
208 // Iterate over this android.Module's property structs.
209 for _, properties := range aModule.GetProperties() {
210 propertiesValue := reflect.ValueOf(properties)
211 // Check that propertiesValue is a pointer to the Properties struct, like
212 // *cc.BaseLinkerProperties or *java.CompilerProperties.
213 //
214 // propertiesValue can also be type-asserted to the structs to
215 // manipulate internal props, if needed.
216 if isStructPtr(propertiesValue.Type()) {
217 structValue := propertiesValue.Elem()
218 for k, v := range extractStructProperties(structValue, 0) {
219 ret[k] = v
220 }
221 } else {
222 panic(fmt.Errorf(
223 "properties must be a pointer to a struct, got %T",
224 propertiesValue.Interface()))
225 }
226 }
227
228 return ret
229}
230
231func isStructPtr(t reflect.Type) bool {
232 return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
233}
234
235// prettyPrint a property value into the equivalent Starlark representation
236// recursively.
237func prettyPrint(propertyValue reflect.Value, indent int) (string, error) {
238 if isZero(propertyValue) {
239 // A property value being set or unset actually matters -- Soong does set default
240 // values for unset properties, like system_shared_libs = ["libc", "libm", "libdl"] at
241 // https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/linker.go;l=281-287;drc=f70926eef0b9b57faf04c17a1062ce50d209e480
242 //
243 // In Bazel-parlance, we would use "attr.<type>(default = <default value>)" to set the default
244 // value of unset attributes.
245 return "", nil
246 }
247
248 var ret string
249 switch propertyValue.Kind() {
250 case reflect.String:
251 ret = fmt.Sprintf("\"%v\"", escapeString(propertyValue.String()))
252 case reflect.Bool:
253 ret = strings.Title(fmt.Sprintf("%v", propertyValue.Interface()))
254 case reflect.Int, reflect.Uint, reflect.Int64:
255 ret = fmt.Sprintf("%v", propertyValue.Interface())
256 case reflect.Ptr:
257 return prettyPrint(propertyValue.Elem(), indent)
258 case reflect.Slice:
259 ret = "[\n"
260 for i := 0; i < propertyValue.Len(); i++ {
261 indexedValue, err := prettyPrint(propertyValue.Index(i), indent+1)
262 if err != nil {
263 return "", err
264 }
265
266 if indexedValue != "" {
267 ret += makeIndent(indent + 1)
268 ret += indexedValue
269 ret += ",\n"
270 }
271 }
272 ret += makeIndent(indent)
273 ret += "]"
274 case reflect.Struct:
275 ret = "{\n"
276 // Sort and print the struct props by the key.
277 structProps := extractStructProperties(propertyValue, indent)
278 for _, k := range android.SortedStringKeys(structProps) {
279 ret += makeIndent(indent + 1)
280 ret += fmt.Sprintf("%q: %s,\n", k, structProps[k])
281 }
282 ret += makeIndent(indent)
283 ret += "}"
284 case reflect.Interface:
285 // TODO(b/164227191): implement pretty print for interfaces.
286 // Interfaces are used for for arch, multilib and target properties.
287 return "", nil
288 default:
289 return "", fmt.Errorf(
290 "unexpected kind for property struct field: %s", propertyValue.Kind())
291 }
292 return ret, nil
293}
294
295// Converts a reflected property struct value into a map of property names and property values,
296// which each property value correctly pretty-printed and indented at the right nest level,
297// since property structs can be nested. In Starlark, nested structs are represented as nested
298// dicts: https://docs.bazel.build/skylark/lib/dict.html
299func extractStructProperties(structValue reflect.Value, indent int) map[string]string {
300 if structValue.Kind() != reflect.Struct {
301 panic(fmt.Errorf("Expected a reflect.Struct type, but got %s", structValue.Kind()))
302 }
303
304 ret := map[string]string{}
305 structType := structValue.Type()
306 for i := 0; i < structValue.NumField(); i++ {
307 field := structType.Field(i)
308 if shouldSkipStructField(field) {
309 continue
310 }
311
312 fieldValue := structValue.Field(i)
313 if isZero(fieldValue) {
314 // Ignore zero-valued fields
315 continue
316 }
317
318 propertyName := proptools.PropertyNameForField(field.Name)
319 prettyPrintedValue, err := prettyPrint(fieldValue, indent+1)
320 if err != nil {
321 panic(
322 fmt.Errorf(
323 "Error while parsing property: %q. %s",
324 propertyName,
325 err))
326 }
327 if prettyPrintedValue != "" {
328 ret[propertyName] = prettyPrintedValue
329 }
330 }
331
332 return ret
333}
334
335func isZero(value reflect.Value) bool {
336 switch value.Kind() {
337 case reflect.Func, reflect.Map, reflect.Slice:
338 return value.IsNil()
339 case reflect.Array:
340 valueIsZero := true
341 for i := 0; i < value.Len(); i++ {
342 valueIsZero = valueIsZero && isZero(value.Index(i))
343 }
344 return valueIsZero
345 case reflect.Struct:
346 valueIsZero := true
347 for i := 0; i < value.NumField(); i++ {
348 if value.Field(i).CanSet() {
349 valueIsZero = valueIsZero && isZero(value.Field(i))
350 }
351 }
352 return valueIsZero
353 case reflect.Ptr:
354 if !value.IsNil() {
355 return isZero(reflect.Indirect(value))
356 } else {
357 return true
358 }
359 default:
360 zeroValue := reflect.Zero(value.Type())
361 result := value.Interface() == zeroValue.Interface()
362 return result
363 }
364}
365
366func escapeString(s string) string {
367 s = strings.ReplaceAll(s, "\\", "\\\\")
368 return strings.ReplaceAll(s, "\"", "\\\"")
369}
370
371func makeIndent(indent int) string {
372 if indent < 0 {
373 panic(fmt.Errorf("indent column cannot be less than 0, but got %d", indent))
374 }
375 return strings.Repeat(" ", indent)
376}
377
Jingwen Chen73850672020-12-14 08:25:34 -0500378func targetNameForBp2Build(c bpToBuildContext, logicModule blueprint.Module) string {
379 return strings.Replace(c.ModuleName(logicModule), "__bp2build__", "", 1)
380}
381
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800382func targetNameWithVariant(c bpToBuildContext, logicModule blueprint.Module) string {
383 name := ""
384 if c.ModuleSubDir(logicModule) != "" {
385 // TODO(b/162720883): Figure out a way to drop the "--" variant suffixes.
386 name = c.ModuleName(logicModule) + "--" + c.ModuleSubDir(logicModule)
387 } else {
388 name = c.ModuleName(logicModule)
389 }
390
391 return strings.Replace(name, "//", "", 1)
392}
393
394func qualifiedTargetLabel(c bpToBuildContext, logicModule blueprint.Module) string {
395 return fmt.Sprintf("//%s:%s", c.ModuleDir(logicModule), targetNameWithVariant(c, logicModule))
396}