• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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