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