blob: bece8f65c1e91153a1e43f35a05680e612ee4346 [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
76func GenerateSoongModuleTargets(ctx bpToBuildContext) map[string][]BazelTarget {
77 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)
80 t := generateSoongModuleTarget(ctx, m)
81 buildFileToTargets[ctx.ModuleDir(m)] = append(buildFileToTargets[dir], t)
82 })
83 return buildFileToTargets
84}
85
86// Convert a module and its deps and props into a Bazel macro/rule
87// representation in the BUILD file.
88func generateSoongModuleTarget(ctx bpToBuildContext, m blueprint.Module) BazelTarget {
89 props := getBuildProperties(ctx, m)
90
91 // TODO(b/163018919): DirectDeps can have duplicate (module, variant)
92 // items, if the modules are added using different DependencyTag. Figure
93 // out the implications of that.
94 depLabels := map[string]bool{}
95 if aModule, ok := m.(android.Module); ok {
Jingwen Chendaa54bc2020-12-14 02:58:54 -050096 ctx.VisitDirectDeps(aModule, func(depModule blueprint.Module) {
Liz Kammer2dd9ca42020-11-25 16:06:39 -080097 depLabels[qualifiedTargetLabel(ctx, depModule)] = true
98 })
99 }
100 attributes := propsToAttributes(props.Attrs)
101
102 depLabelList := "[\n"
103 for depLabel, _ := range depLabels {
104 depLabelList += fmt.Sprintf(" %q,\n", depLabel)
105 }
106 depLabelList += " ]"
107
108 targetName := targetNameWithVariant(ctx, m)
109 return BazelTarget{
110 name: targetName,
111 content: fmt.Sprintf(
112 soongModuleTarget,
113 targetName,
114 ctx.ModuleName(m),
115 canonicalizeModuleType(ctx.ModuleType(m)),
116 ctx.ModuleSubDir(m),
117 depLabelList,
118 attributes),
119 }
120}
121
122func getBuildProperties(ctx bpToBuildContext, m blueprint.Module) BazelAttributes {
123 var allProps map[string]string
124 // TODO: this omits properties for blueprint modules (blueprint_go_binary,
125 // bootstrap_go_binary, bootstrap_go_package), which will have to be handled separately.
126 if aModule, ok := m.(android.Module); ok {
127 allProps = ExtractModuleProperties(aModule)
128 }
129
130 return BazelAttributes{
131 Attrs: allProps,
132 }
133}
134
135// Generically extract module properties and types into a map, keyed by the module property name.
136func ExtractModuleProperties(aModule android.Module) map[string]string {
137 ret := map[string]string{}
138
139 // Iterate over this android.Module's property structs.
140 for _, properties := range aModule.GetProperties() {
141 propertiesValue := reflect.ValueOf(properties)
142 // Check that propertiesValue is a pointer to the Properties struct, like
143 // *cc.BaseLinkerProperties or *java.CompilerProperties.
144 //
145 // propertiesValue can also be type-asserted to the structs to
146 // manipulate internal props, if needed.
147 if isStructPtr(propertiesValue.Type()) {
148 structValue := propertiesValue.Elem()
149 for k, v := range extractStructProperties(structValue, 0) {
150 ret[k] = v
151 }
152 } else {
153 panic(fmt.Errorf(
154 "properties must be a pointer to a struct, got %T",
155 propertiesValue.Interface()))
156 }
157 }
158
159 return ret
160}
161
162func isStructPtr(t reflect.Type) bool {
163 return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
164}
165
166// prettyPrint a property value into the equivalent Starlark representation
167// recursively.
168func prettyPrint(propertyValue reflect.Value, indent int) (string, error) {
169 if isZero(propertyValue) {
170 // A property value being set or unset actually matters -- Soong does set default
171 // values for unset properties, like system_shared_libs = ["libc", "libm", "libdl"] at
172 // https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/linker.go;l=281-287;drc=f70926eef0b9b57faf04c17a1062ce50d209e480
173 //
174 // In Bazel-parlance, we would use "attr.<type>(default = <default value>)" to set the default
175 // value of unset attributes.
176 return "", nil
177 }
178
179 var ret string
180 switch propertyValue.Kind() {
181 case reflect.String:
182 ret = fmt.Sprintf("\"%v\"", escapeString(propertyValue.String()))
183 case reflect.Bool:
184 ret = strings.Title(fmt.Sprintf("%v", propertyValue.Interface()))
185 case reflect.Int, reflect.Uint, reflect.Int64:
186 ret = fmt.Sprintf("%v", propertyValue.Interface())
187 case reflect.Ptr:
188 return prettyPrint(propertyValue.Elem(), indent)
189 case reflect.Slice:
190 ret = "[\n"
191 for i := 0; i < propertyValue.Len(); i++ {
192 indexedValue, err := prettyPrint(propertyValue.Index(i), indent+1)
193 if err != nil {
194 return "", err
195 }
196
197 if indexedValue != "" {
198 ret += makeIndent(indent + 1)
199 ret += indexedValue
200 ret += ",\n"
201 }
202 }
203 ret += makeIndent(indent)
204 ret += "]"
205 case reflect.Struct:
206 ret = "{\n"
207 // Sort and print the struct props by the key.
208 structProps := extractStructProperties(propertyValue, indent)
209 for _, k := range android.SortedStringKeys(structProps) {
210 ret += makeIndent(indent + 1)
211 ret += fmt.Sprintf("%q: %s,\n", k, structProps[k])
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
226// Converts a reflected property struct value into a map of property names and property values,
227// which each property value correctly pretty-printed and indented at the right nest level,
228// since property structs can be nested. In Starlark, nested structs are represented as nested
229// dicts: https://docs.bazel.build/skylark/lib/dict.html
230func extractStructProperties(structValue reflect.Value, indent int) map[string]string {
231 if structValue.Kind() != reflect.Struct {
232 panic(fmt.Errorf("Expected a reflect.Struct type, but got %s", structValue.Kind()))
233 }
234
235 ret := map[string]string{}
236 structType := structValue.Type()
237 for i := 0; i < structValue.NumField(); i++ {
238 field := structType.Field(i)
239 if shouldSkipStructField(field) {
240 continue
241 }
242
243 fieldValue := structValue.Field(i)
244 if isZero(fieldValue) {
245 // Ignore zero-valued fields
246 continue
247 }
248
249 propertyName := proptools.PropertyNameForField(field.Name)
250 prettyPrintedValue, err := prettyPrint(fieldValue, indent+1)
251 if err != nil {
252 panic(
253 fmt.Errorf(
254 "Error while parsing property: %q. %s",
255 propertyName,
256 err))
257 }
258 if prettyPrintedValue != "" {
259 ret[propertyName] = prettyPrintedValue
260 }
261 }
262
263 return ret
264}
265
266func isZero(value reflect.Value) bool {
267 switch value.Kind() {
268 case reflect.Func, reflect.Map, reflect.Slice:
269 return value.IsNil()
270 case reflect.Array:
271 valueIsZero := true
272 for i := 0; i < value.Len(); i++ {
273 valueIsZero = valueIsZero && isZero(value.Index(i))
274 }
275 return valueIsZero
276 case reflect.Struct:
277 valueIsZero := true
278 for i := 0; i < value.NumField(); i++ {
279 if value.Field(i).CanSet() {
280 valueIsZero = valueIsZero && isZero(value.Field(i))
281 }
282 }
283 return valueIsZero
284 case reflect.Ptr:
285 if !value.IsNil() {
286 return isZero(reflect.Indirect(value))
287 } else {
288 return true
289 }
290 default:
291 zeroValue := reflect.Zero(value.Type())
292 result := value.Interface() == zeroValue.Interface()
293 return result
294 }
295}
296
297func escapeString(s string) string {
298 s = strings.ReplaceAll(s, "\\", "\\\\")
299 return strings.ReplaceAll(s, "\"", "\\\"")
300}
301
302func makeIndent(indent int) string {
303 if indent < 0 {
304 panic(fmt.Errorf("indent column cannot be less than 0, but got %d", indent))
305 }
306 return strings.Repeat(" ", indent)
307}
308
309func targetNameWithVariant(c bpToBuildContext, logicModule blueprint.Module) string {
310 name := ""
311 if c.ModuleSubDir(logicModule) != "" {
312 // TODO(b/162720883): Figure out a way to drop the "--" variant suffixes.
313 name = c.ModuleName(logicModule) + "--" + c.ModuleSubDir(logicModule)
314 } else {
315 name = c.ModuleName(logicModule)
316 }
317
318 return strings.Replace(name, "//", "", 1)
319}
320
321func qualifiedTargetLabel(c bpToBuildContext, logicModule blueprint.Module) string {
322 return fmt.Sprintf("//%s:%s", c.ModuleDir(logicModule), targetNameWithVariant(c, logicModule))
323}