1// 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 := `{ 112 "soong_module_name": attr.string(mandatory = True), 113 "soong_module_variant": attr.string(), 114 "soong_module_deps": attr.label_list(providers = [SoongModuleInfo]), 115` 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 } 163 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 } 172 } 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} 238