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 164 properties = append(properties, extractPropertyDescriptions(field.Name, field.Type)...) 165 } 166 return properties 167} 168 169func extractPropertyDescriptions(name string, t reflect.Type) []property { 170 name = proptools.PropertyNameForField(name) 171 172 // TODO: handle android:paths tags, they should be changed to label types 173 174 starlarkAttrType := fmt.Sprintf("%s", t.Name()) 175 props := make([]property, 0) 176 177 switch t.Kind() { 178 case reflect.Bool, reflect.String: 179 // do nothing 180 case reflect.Uint, reflect.Int, reflect.Int64: 181 starlarkAttrType = "int" 182 case reflect.Slice: 183 if t.Elem().Kind() != reflect.String { 184 // TODO: handle lists of non-strings (currently only list of Dist) 185 return []property{} 186 } 187 starlarkAttrType = "string_list" 188 case reflect.Struct: 189 props = extractPropertyDescriptionsFromStruct(t) 190 case reflect.Ptr: 191 return extractPropertyDescriptions(name, t.Elem()) 192 case reflect.Interface: 193 // Interfaces are used for for arch, multilib and target properties, which are handled at runtime. 194 // These will need to be handled in a bazel-specific version of the arch mutator. 195 return []property{} 196 } 197 198 prop := property{ 199 name: name, 200 starlarkAttrType: starlarkAttrType, 201 properties: props, 202 } 203 204 return []property{prop} 205} 206 207func getPropertyDescriptions(props []interface{}) []property { 208 // there may be duplicate properties, e.g. from defaults libraries 209 propertiesByName := make(map[string]property) 210 for _, p := range props { 211 for _, prop := range extractPropertyDescriptionsFromStruct(reflect.ValueOf(p).Elem().Type()) { 212 propertiesByName[prop.name] = prop 213 } 214 } 215 216 properties := make([]property, 0, len(propertiesByName)) 217 for _, key := range android.SortedStringKeys(propertiesByName) { 218 properties = append(properties, propertiesByName[key]) 219 } 220 221 return properties 222} 223 224func getAttributes(factory android.ModuleFactory) string { 225 attrs := "" 226 for _, p := range getPropertyDescriptions(factory().GetProperties()) { 227 attrs += p.attributeString() 228 } 229 return attrs 230} 231