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