blob: 992cc1c8c972c1f6ec2f5a6d0b6a255edad56805 [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 "runtime"
22 "sort"
23 "strings"
24
25 "github.com/google/blueprint/proptools"
26)
27
28var (
29 // An allowlist of prop types that are surfaced from module props to rule
30 // attributes. (nested) dictionaries are notably absent here, because while
31 // Soong supports multi value typed and nested dictionaries, Bazel's rule
32 // attr() API supports only single-level string_dicts.
33 allowedPropTypes = map[string]bool{
34 "int": true, // e.g. 42
35 "bool": true, // e.g. True
36 "string_list": true, // e.g. ["a", "b"]
37 "string": true, // e.g. "a"
38 }
39)
40
41type rule struct {
42 name string
43 attrs string
44}
45
46type RuleShim struct {
47 // The rule class shims contained in a bzl file. e.g. ["cc_object", "cc_library", ..]
48 rules []string
49
50 // The generated string content of the bzl file.
51 content string
52}
53
54// Create <module>.bzl containing Bazel rule shims for every module type available in Soong and
55// user-specified Go plugins.
56//
57// This function reuses documentation generation APIs to ensure parity between modules-as-docs
58// and modules-as-code, including the names and types of morule properties.
59func CreateRuleShims(moduleTypeFactories map[string]android.ModuleFactory) map[string]RuleShim {
60 ruleShims := map[string]RuleShim{}
61 for pkg, rules := range generateRules(moduleTypeFactories) {
62 shim := RuleShim{
63 rules: make([]string, 0, len(rules)),
64 }
65 shim.content = "load(\"//build/bazel/queryview_rules:providers.bzl\", \"SoongModuleInfo\")\n"
66
67 bzlFileName := strings.ReplaceAll(pkg, "android/soong/", "")
68 bzlFileName = strings.ReplaceAll(bzlFileName, ".", "_")
69 bzlFileName = strings.ReplaceAll(bzlFileName, "/", "_")
70
71 for _, r := range rules {
72 shim.content += fmt.Sprintf(moduleRuleShim, r.name, r.attrs)
73 shim.rules = append(shim.rules, r.name)
74 }
75 sort.Strings(shim.rules)
76 ruleShims[bzlFileName] = shim
77 }
78 return ruleShims
79}
80
81// Generate the content of soong_module.bzl with the rule shim load statements
82// and mapping of module_type to rule shim map for every module type in Soong.
83func generateSoongModuleBzl(bzlLoads map[string]RuleShim) string {
84 var loadStmts string
85 var moduleRuleMap string
86 for _, bzlFileName := range android.SortedStringKeys(bzlLoads) {
87 loadStmt := "load(\"//build/bazel/queryview_rules:"
88 loadStmt += bzlFileName
89 loadStmt += ".bzl\""
90 ruleShim := bzlLoads[bzlFileName]
91 for _, rule := range ruleShim.rules {
92 loadStmt += fmt.Sprintf(", %q", rule)
93 moduleRuleMap += " \"" + rule + "\": " + rule + ",\n"
94 }
95 loadStmt += ")\n"
96 loadStmts += loadStmt
97 }
98
99 return fmt.Sprintf(soongModuleBzl, loadStmts, moduleRuleMap)
100}
101
102func generateRules(moduleTypeFactories map[string]android.ModuleFactory) map[string][]rule {
103 // TODO: add shims for bootstrap/blueprint go modules types
104
105 rules := make(map[string][]rule)
106 // TODO: allow registration of a bzl rule when registring a factory
107 for _, moduleType := range android.SortedStringKeys(moduleTypeFactories) {
108 factory := moduleTypeFactories[moduleType]
109 factoryName := runtime.FuncForPC(reflect.ValueOf(factory).Pointer()).Name()
110 pkg := strings.Split(factoryName, ".")[0]
111 attrs := `{
Jingwen Chen288e2ba2021-01-25 04:36:04 -0500112 "soong_module_name": attr.string(mandatory = True),
113 "soong_module_variant": attr.string(),
114 "soong_module_deps": attr.label_list(providers = [SoongModuleInfo]),
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800115`
116 attrs += getAttributes(factory)
117 attrs += " },"
118
119 r := rule{
120 name: canonicalizeModuleType(moduleType),
121 attrs: attrs,
122 }
123
124 rules[pkg] = append(rules[pkg], r)
125 }
126 return rules
127}
128
129type property struct {
130 name string
131 starlarkAttrType string
132 properties []property
133}
134
135const (
136 attributeIndent = " "
137)
138
139func (p *property) attributeString() string {
140 if !shouldGenerateAttribute(p.name) {
141 return ""
142 }
143
144 if _, ok := allowedPropTypes[p.starlarkAttrType]; !ok {
145 // a struct -- let's just comment out sub-props
146 s := fmt.Sprintf(attributeIndent+"# %s start\n", p.name)
147 for _, nestedP := range p.properties {
148 s += "# " + nestedP.attributeString()
149 }
150 s += fmt.Sprintf(attributeIndent+"# %s end\n", p.name)
151 return s
152 }
153 return fmt.Sprintf(attributeIndent+"%q: attr.%s(),\n", p.name, p.starlarkAttrType)
154}
155
156func extractPropertyDescriptionsFromStruct(structType reflect.Type) []property {
157 properties := make([]property, 0)
158 for i := 0; i < structType.NumField(); i++ {
159 field := structType.Field(i)
160 if shouldSkipStructField(field) {
161 continue
162 }
Liz Kammer32a03392021-09-14 11:17:21 -0400163 subProps := extractPropertyDescriptions(field.Name, field.Type)
164 // if the struct is embedded (anonymous), flatten the properties into the containing struct
165 if field.Anonymous {
166 for _, prop := range subProps {
167 properties = append(properties, prop.properties...)
168 }
169 } else {
170 properties = append(properties, subProps...)
171 }
Liz Kammer2dd9ca42020-11-25 16:06:39 -0800172 }
173 return properties
174}
175
176func extractPropertyDescriptions(name string, t reflect.Type) []property {
177 name = proptools.PropertyNameForField(name)
178
179 // TODO: handle android:paths tags, they should be changed to label types
180
181 starlarkAttrType := fmt.Sprintf("%s", t.Name())
182 props := make([]property, 0)
183
184 switch t.Kind() {
185 case reflect.Bool, reflect.String:
186 // do nothing
187 case reflect.Uint, reflect.Int, reflect.Int64:
188 starlarkAttrType = "int"
189 case reflect.Slice:
190 if t.Elem().Kind() != reflect.String {
191 // TODO: handle lists of non-strings (currently only list of Dist)
192 return []property{}
193 }
194 starlarkAttrType = "string_list"
195 case reflect.Struct:
196 props = extractPropertyDescriptionsFromStruct(t)
197 case reflect.Ptr:
198 return extractPropertyDescriptions(name, t.Elem())
199 case reflect.Interface:
200 // Interfaces are used for for arch, multilib and target properties, which are handled at runtime.
201 // These will need to be handled in a bazel-specific version of the arch mutator.
202 return []property{}
203 }
204
205 prop := property{
206 name: name,
207 starlarkAttrType: starlarkAttrType,
208 properties: props,
209 }
210
211 return []property{prop}
212}
213
214func getPropertyDescriptions(props []interface{}) []property {
215 // there may be duplicate properties, e.g. from defaults libraries
216 propertiesByName := make(map[string]property)
217 for _, p := range props {
218 for _, prop := range extractPropertyDescriptionsFromStruct(reflect.ValueOf(p).Elem().Type()) {
219 propertiesByName[prop.name] = prop
220 }
221 }
222
223 properties := make([]property, 0, len(propertiesByName))
224 for _, key := range android.SortedStringKeys(propertiesByName) {
225 properties = append(properties, propertiesByName[key])
226 }
227
228 return properties
229}
230
231func getAttributes(factory android.ModuleFactory) string {
232 attrs := ""
233 for _, p := range getPropertyDescriptions(factory().GetProperties()) {
234 attrs += p.attributeString()
235 }
236 return attrs
237}