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