231 lines
6.9 KiB
Go
231 lines
6.9 KiB
Go
// Copyright 2020 Google Inc. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package bp2build
|
|
|
|
import (
|
|
"android/soong/android"
|
|
"fmt"
|
|
"reflect"
|
|
"runtime"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/google/blueprint/proptools"
|
|
)
|
|
|
|
var (
|
|
// An allowlist of prop types that are surfaced from module props to rule
|
|
// attributes. (nested) dictionaries are notably absent here, because while
|
|
// Soong supports multi value typed and nested dictionaries, Bazel's rule
|
|
// attr() API supports only single-level string_dicts.
|
|
allowedPropTypes = map[string]bool{
|
|
"int": true, // e.g. 42
|
|
"bool": true, // e.g. True
|
|
"string_list": true, // e.g. ["a", "b"]
|
|
"string": true, // e.g. "a"
|
|
}
|
|
)
|
|
|
|
type rule struct {
|
|
name string
|
|
attrs string
|
|
}
|
|
|
|
type RuleShim struct {
|
|
// The rule class shims contained in a bzl file. e.g. ["cc_object", "cc_library", ..]
|
|
rules []string
|
|
|
|
// The generated string content of the bzl file.
|
|
content string
|
|
}
|
|
|
|
// Create <module>.bzl containing Bazel rule shims for every module type available in Soong and
|
|
// user-specified Go plugins.
|
|
//
|
|
// This function reuses documentation generation APIs to ensure parity between modules-as-docs
|
|
// and modules-as-code, including the names and types of morule properties.
|
|
func CreateRuleShims(moduleTypeFactories map[string]android.ModuleFactory) map[string]RuleShim {
|
|
ruleShims := map[string]RuleShim{}
|
|
for pkg, rules := range generateRules(moduleTypeFactories) {
|
|
shim := RuleShim{
|
|
rules: make([]string, 0, len(rules)),
|
|
}
|
|
shim.content = "load(\"//build/bazel/queryview_rules:providers.bzl\", \"SoongModuleInfo\")\n"
|
|
|
|
bzlFileName := strings.ReplaceAll(pkg, "android/soong/", "")
|
|
bzlFileName = strings.ReplaceAll(bzlFileName, ".", "_")
|
|
bzlFileName = strings.ReplaceAll(bzlFileName, "/", "_")
|
|
|
|
for _, r := range rules {
|
|
shim.content += fmt.Sprintf(moduleRuleShim, r.name, r.attrs)
|
|
shim.rules = append(shim.rules, r.name)
|
|
}
|
|
sort.Strings(shim.rules)
|
|
ruleShims[bzlFileName] = shim
|
|
}
|
|
return ruleShims
|
|
}
|
|
|
|
// Generate the content of soong_module.bzl with the rule shim load statements
|
|
// and mapping of module_type to rule shim map for every module type in Soong.
|
|
func generateSoongModuleBzl(bzlLoads map[string]RuleShim) string {
|
|
var loadStmts string
|
|
var moduleRuleMap string
|
|
for _, bzlFileName := range android.SortedStringKeys(bzlLoads) {
|
|
loadStmt := "load(\"//build/bazel/queryview_rules:"
|
|
loadStmt += bzlFileName
|
|
loadStmt += ".bzl\""
|
|
ruleShim := bzlLoads[bzlFileName]
|
|
for _, rule := range ruleShim.rules {
|
|
loadStmt += fmt.Sprintf(", %q", rule)
|
|
moduleRuleMap += " \"" + rule + "\": " + rule + ",\n"
|
|
}
|
|
loadStmt += ")\n"
|
|
loadStmts += loadStmt
|
|
}
|
|
|
|
return fmt.Sprintf(soongModuleBzl, loadStmts, moduleRuleMap)
|
|
}
|
|
|
|
func generateRules(moduleTypeFactories map[string]android.ModuleFactory) map[string][]rule {
|
|
// TODO: add shims for bootstrap/blueprint go modules types
|
|
|
|
rules := make(map[string][]rule)
|
|
// TODO: allow registration of a bzl rule when registring a factory
|
|
for _, moduleType := range android.SortedStringKeys(moduleTypeFactories) {
|
|
factory := moduleTypeFactories[moduleType]
|
|
factoryName := runtime.FuncForPC(reflect.ValueOf(factory).Pointer()).Name()
|
|
pkg := strings.Split(factoryName, ".")[0]
|
|
attrs := `{
|
|
"module_name": attr.string(mandatory = True),
|
|
"module_variant": attr.string(),
|
|
"module_deps": attr.label_list(providers = [SoongModuleInfo]),
|
|
`
|
|
attrs += getAttributes(factory)
|
|
attrs += " },"
|
|
|
|
r := rule{
|
|
name: canonicalizeModuleType(moduleType),
|
|
attrs: attrs,
|
|
}
|
|
|
|
rules[pkg] = append(rules[pkg], r)
|
|
}
|
|
return rules
|
|
}
|
|
|
|
type property struct {
|
|
name string
|
|
starlarkAttrType string
|
|
properties []property
|
|
}
|
|
|
|
const (
|
|
attributeIndent = " "
|
|
)
|
|
|
|
func (p *property) attributeString() string {
|
|
if !shouldGenerateAttribute(p.name) {
|
|
return ""
|
|
}
|
|
|
|
if _, ok := allowedPropTypes[p.starlarkAttrType]; !ok {
|
|
// a struct -- let's just comment out sub-props
|
|
s := fmt.Sprintf(attributeIndent+"# %s start\n", p.name)
|
|
for _, nestedP := range p.properties {
|
|
s += "# " + nestedP.attributeString()
|
|
}
|
|
s += fmt.Sprintf(attributeIndent+"# %s end\n", p.name)
|
|
return s
|
|
}
|
|
return fmt.Sprintf(attributeIndent+"%q: attr.%s(),\n", p.name, p.starlarkAttrType)
|
|
}
|
|
|
|
func extractPropertyDescriptionsFromStruct(structType reflect.Type) []property {
|
|
properties := make([]property, 0)
|
|
for i := 0; i < structType.NumField(); i++ {
|
|
field := structType.Field(i)
|
|
if shouldSkipStructField(field) {
|
|
continue
|
|
}
|
|
|
|
properties = append(properties, extractPropertyDescriptions(field.Name, field.Type)...)
|
|
}
|
|
return properties
|
|
}
|
|
|
|
func extractPropertyDescriptions(name string, t reflect.Type) []property {
|
|
name = proptools.PropertyNameForField(name)
|
|
|
|
// TODO: handle android:paths tags, they should be changed to label types
|
|
|
|
starlarkAttrType := fmt.Sprintf("%s", t.Name())
|
|
props := make([]property, 0)
|
|
|
|
switch t.Kind() {
|
|
case reflect.Bool, reflect.String:
|
|
// do nothing
|
|
case reflect.Uint, reflect.Int, reflect.Int64:
|
|
starlarkAttrType = "int"
|
|
case reflect.Slice:
|
|
if t.Elem().Kind() != reflect.String {
|
|
// TODO: handle lists of non-strings (currently only list of Dist)
|
|
return []property{}
|
|
}
|
|
starlarkAttrType = "string_list"
|
|
case reflect.Struct:
|
|
props = extractPropertyDescriptionsFromStruct(t)
|
|
case reflect.Ptr:
|
|
return extractPropertyDescriptions(name, t.Elem())
|
|
case reflect.Interface:
|
|
// Interfaces are used for for arch, multilib and target properties, which are handled at runtime.
|
|
// These will need to be handled in a bazel-specific version of the arch mutator.
|
|
return []property{}
|
|
}
|
|
|
|
prop := property{
|
|
name: name,
|
|
starlarkAttrType: starlarkAttrType,
|
|
properties: props,
|
|
}
|
|
|
|
return []property{prop}
|
|
}
|
|
|
|
func getPropertyDescriptions(props []interface{}) []property {
|
|
// there may be duplicate properties, e.g. from defaults libraries
|
|
propertiesByName := make(map[string]property)
|
|
for _, p := range props {
|
|
for _, prop := range extractPropertyDescriptionsFromStruct(reflect.ValueOf(p).Elem().Type()) {
|
|
propertiesByName[prop.name] = prop
|
|
}
|
|
}
|
|
|
|
properties := make([]property, 0, len(propertiesByName))
|
|
for _, key := range android.SortedStringKeys(propertiesByName) {
|
|
properties = append(properties, propertiesByName[key])
|
|
}
|
|
|
|
return properties
|
|
}
|
|
|
|
func getAttributes(factory android.ModuleFactory) string {
|
|
attrs := ""
|
|
for _, p := range getPropertyDescriptions(factory().GetProperties()) {
|
|
attrs += p.attributeString()
|
|
}
|
|
return attrs
|
|
}
|