Because we're going to start generating partition images for the product, and the partitions will eventually be checked in, we want them to be in sensible locations. And the platform should be there as well so all the targets for a product are co-located. Bug: 297269187 Test: m nothing && b build --config=android //build/bazel/examples/apex/minimal:build.bazel.examples.apex.minimal Change-Id: Iaa25c44aa00295ada279d5fd49b5498bbafb89d5
1206 lines
39 KiB
Go
1206 lines
39 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
|
|
|
|
/*
|
|
For shareable/common functionality for conversion from soong-module to build files
|
|
for queryview/bp2build
|
|
*/
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
|
|
"android/soong/android"
|
|
"android/soong/bazel"
|
|
"android/soong/starlark_fmt"
|
|
"android/soong/ui/metrics/bp2build_metrics_proto"
|
|
|
|
"github.com/google/blueprint"
|
|
"github.com/google/blueprint/bootstrap"
|
|
"github.com/google/blueprint/proptools"
|
|
)
|
|
|
|
type BazelAttributes struct {
|
|
Attrs map[string]string
|
|
}
|
|
|
|
type BazelLoadSymbol struct {
|
|
// The name of the symbol in the file being loaded
|
|
symbol string
|
|
// The name the symbol wil have in this file. Can be left blank to use the same name as symbol.
|
|
alias string
|
|
}
|
|
|
|
type BazelLoad struct {
|
|
file string
|
|
symbols []BazelLoadSymbol
|
|
}
|
|
|
|
type BazelTarget struct {
|
|
name string
|
|
packageName string
|
|
content string
|
|
ruleClass string
|
|
loads []BazelLoad
|
|
}
|
|
|
|
// Label is the fully qualified Bazel label constructed from the BazelTarget's
|
|
// package name and target name.
|
|
func (t BazelTarget) Label() string {
|
|
if t.packageName == "." {
|
|
return "//:" + t.name
|
|
} else {
|
|
return "//" + t.packageName + ":" + t.name
|
|
}
|
|
}
|
|
|
|
// PackageName returns the package of the Bazel target.
|
|
// Defaults to root of tree.
|
|
func (t BazelTarget) PackageName() string {
|
|
if t.packageName == "" {
|
|
return "."
|
|
}
|
|
return t.packageName
|
|
}
|
|
|
|
// BazelTargets is a typedef for a slice of BazelTarget objects.
|
|
type BazelTargets []BazelTarget
|
|
|
|
func (targets BazelTargets) packageRule() *BazelTarget {
|
|
for _, target := range targets {
|
|
if target.ruleClass == "package" {
|
|
return &target
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// sort a list of BazelTargets in-place, by name, and by generated/handcrafted types.
|
|
func (targets BazelTargets) sort() {
|
|
sort.Slice(targets, func(i, j int) bool {
|
|
return targets[i].name < targets[j].name
|
|
})
|
|
}
|
|
|
|
// String returns the string representation of BazelTargets, without load
|
|
// statements (use LoadStatements for that), since the targets are usually not
|
|
// adjacent to the load statements at the top of the BUILD file.
|
|
func (targets BazelTargets) String() string {
|
|
var res strings.Builder
|
|
for i, target := range targets {
|
|
if target.ruleClass != "package" {
|
|
res.WriteString(target.content)
|
|
}
|
|
if i != len(targets)-1 {
|
|
res.WriteString("\n\n")
|
|
}
|
|
}
|
|
return res.String()
|
|
}
|
|
|
|
// LoadStatements return the string representation of the sorted and deduplicated
|
|
// Starlark rule load statements needed by a group of BazelTargets.
|
|
func (targets BazelTargets) LoadStatements() string {
|
|
// First, merge all the load statements from all the targets onto one list
|
|
bzlToLoadedSymbols := map[string][]BazelLoadSymbol{}
|
|
for _, target := range targets {
|
|
for _, load := range target.loads {
|
|
outer:
|
|
for _, symbol := range load.symbols {
|
|
alias := symbol.alias
|
|
if alias == "" {
|
|
alias = symbol.symbol
|
|
}
|
|
for _, otherSymbol := range bzlToLoadedSymbols[load.file] {
|
|
otherAlias := otherSymbol.alias
|
|
if otherAlias == "" {
|
|
otherAlias = otherSymbol.symbol
|
|
}
|
|
if symbol.symbol == otherSymbol.symbol && alias == otherAlias {
|
|
continue outer
|
|
} else if alias == otherAlias {
|
|
panic(fmt.Sprintf("Conflicting destination (%s) for loads of %s and %s", alias, symbol.symbol, otherSymbol.symbol))
|
|
}
|
|
}
|
|
bzlToLoadedSymbols[load.file] = append(bzlToLoadedSymbols[load.file], symbol)
|
|
}
|
|
}
|
|
}
|
|
|
|
var loadStatements strings.Builder
|
|
for i, bzl := range android.SortedKeys(bzlToLoadedSymbols) {
|
|
symbols := bzlToLoadedSymbols[bzl]
|
|
loadStatements.WriteString("load(\"")
|
|
loadStatements.WriteString(bzl)
|
|
loadStatements.WriteString("\", ")
|
|
sort.Slice(symbols, func(i, j int) bool {
|
|
if symbols[i].symbol < symbols[j].symbol {
|
|
return true
|
|
}
|
|
return symbols[i].alias < symbols[j].alias
|
|
})
|
|
for j, symbol := range symbols {
|
|
if symbol.alias != "" && symbol.alias != symbol.symbol {
|
|
loadStatements.WriteString(symbol.alias)
|
|
loadStatements.WriteString(" = ")
|
|
}
|
|
loadStatements.WriteString("\"")
|
|
loadStatements.WriteString(symbol.symbol)
|
|
loadStatements.WriteString("\"")
|
|
if j != len(symbols)-1 {
|
|
loadStatements.WriteString(", ")
|
|
}
|
|
}
|
|
loadStatements.WriteString(")")
|
|
if i != len(bzlToLoadedSymbols)-1 {
|
|
loadStatements.WriteString("\n")
|
|
}
|
|
}
|
|
return loadStatements.String()
|
|
}
|
|
|
|
type bpToBuildContext interface {
|
|
ModuleName(module blueprint.Module) string
|
|
ModuleDir(module blueprint.Module) string
|
|
ModuleSubDir(module blueprint.Module) string
|
|
ModuleType(module blueprint.Module) string
|
|
|
|
VisitAllModules(visit func(blueprint.Module))
|
|
VisitDirectDeps(module blueprint.Module, visit func(blueprint.Module))
|
|
}
|
|
|
|
type CodegenContext struct {
|
|
config android.Config
|
|
context *android.Context
|
|
mode CodegenMode
|
|
additionalDeps []string
|
|
unconvertedDepMode unconvertedDepsMode
|
|
topDir string
|
|
}
|
|
|
|
func (ctx *CodegenContext) Mode() CodegenMode {
|
|
return ctx.mode
|
|
}
|
|
|
|
// CodegenMode is an enum to differentiate code-generation modes.
|
|
type CodegenMode int
|
|
|
|
const (
|
|
// Bp2Build - generate BUILD files with targets buildable by Bazel directly.
|
|
//
|
|
// This mode is used for the Soong->Bazel build definition conversion.
|
|
Bp2Build CodegenMode = iota
|
|
|
|
// QueryView - generate BUILD files with targets representing fully mutated
|
|
// Soong modules, representing the fully configured Soong module graph with
|
|
// variants and dependency edges.
|
|
//
|
|
// This mode is used for discovering and introspecting the existing Soong
|
|
// module graph.
|
|
QueryView
|
|
)
|
|
|
|
type unconvertedDepsMode int
|
|
|
|
const (
|
|
// Include a warning in conversion metrics about converted modules with unconverted direct deps
|
|
warnUnconvertedDeps unconvertedDepsMode = iota
|
|
// Error and fail conversion if encountering a module with unconverted direct deps
|
|
// Enabled by setting environment variable `BP2BUILD_ERROR_UNCONVERTED`
|
|
errorModulesUnconvertedDeps
|
|
)
|
|
|
|
func (mode CodegenMode) String() string {
|
|
switch mode {
|
|
case Bp2Build:
|
|
return "Bp2Build"
|
|
case QueryView:
|
|
return "QueryView"
|
|
default:
|
|
return fmt.Sprintf("%d", mode)
|
|
}
|
|
}
|
|
|
|
// AddNinjaFileDeps adds dependencies on the specified files to be added to the ninja manifest. The
|
|
// primary builder will be rerun whenever the specified files are modified. Allows us to fulfill the
|
|
// PathContext interface in order to add dependencies on hand-crafted BUILD files. Note: must also
|
|
// call AdditionalNinjaDeps and add them manually to the ninja file.
|
|
func (ctx *CodegenContext) AddNinjaFileDeps(deps ...string) {
|
|
ctx.additionalDeps = append(ctx.additionalDeps, deps...)
|
|
}
|
|
|
|
// AdditionalNinjaDeps returns additional ninja deps added by CodegenContext
|
|
func (ctx *CodegenContext) AdditionalNinjaDeps() []string {
|
|
return ctx.additionalDeps
|
|
}
|
|
|
|
func (ctx *CodegenContext) Config() android.Config { return ctx.config }
|
|
func (ctx *CodegenContext) Context() *android.Context { return ctx.context }
|
|
|
|
// NewCodegenContext creates a wrapper context that conforms to PathContext for
|
|
// writing BUILD files in the output directory.
|
|
func NewCodegenContext(config android.Config, context *android.Context, mode CodegenMode, topDir string) *CodegenContext {
|
|
var unconvertedDeps unconvertedDepsMode
|
|
if config.IsEnvTrue("BP2BUILD_ERROR_UNCONVERTED") {
|
|
unconvertedDeps = errorModulesUnconvertedDeps
|
|
}
|
|
return &CodegenContext{
|
|
context: context,
|
|
config: config,
|
|
mode: mode,
|
|
unconvertedDepMode: unconvertedDeps,
|
|
topDir: topDir,
|
|
}
|
|
}
|
|
|
|
// props is an unsorted map. This function ensures that
|
|
// the generated attributes are sorted to ensure determinism.
|
|
func propsToAttributes(props map[string]string) string {
|
|
var attributes string
|
|
for _, propName := range android.SortedKeys(props) {
|
|
attributes += fmt.Sprintf(" %s = %s,\n", propName, props[propName])
|
|
}
|
|
return attributes
|
|
}
|
|
|
|
type conversionResults struct {
|
|
buildFileToTargets map[string]BazelTargets
|
|
metrics CodegenMetrics
|
|
}
|
|
|
|
func (r conversionResults) BuildDirToTargets() map[string]BazelTargets {
|
|
return r.buildFileToTargets
|
|
}
|
|
|
|
// struct to store state of go bazel targets
|
|
// this implements bp2buildModule interface and is passed to generateBazelTargets
|
|
type goBazelTarget struct {
|
|
targetName string
|
|
targetPackage string
|
|
bazelRuleClass string
|
|
bazelRuleLoadLocation string
|
|
bazelAttributes []interface{}
|
|
}
|
|
|
|
var _ bp2buildModule = (*goBazelTarget)(nil)
|
|
|
|
func (g goBazelTarget) TargetName() string {
|
|
return g.targetName
|
|
}
|
|
|
|
func (g goBazelTarget) TargetPackage() string {
|
|
return g.targetPackage
|
|
}
|
|
|
|
func (g goBazelTarget) BazelRuleClass() string {
|
|
return g.bazelRuleClass
|
|
}
|
|
|
|
func (g goBazelTarget) BazelRuleLoadLocation() string {
|
|
return g.bazelRuleLoadLocation
|
|
}
|
|
|
|
func (g goBazelTarget) BazelAttributes() []interface{} {
|
|
return g.bazelAttributes
|
|
}
|
|
|
|
// Creates a target_compatible_with entry that is *not* compatible with android
|
|
func targetNotCompatibleWithAndroid() bazel.LabelListAttribute {
|
|
ret := bazel.LabelListAttribute{}
|
|
ret.SetSelectValue(bazel.OsConfigurationAxis, bazel.OsAndroid,
|
|
bazel.MakeLabelList(
|
|
[]bazel.Label{
|
|
bazel.Label{
|
|
Label: "@platforms//:incompatible",
|
|
},
|
|
},
|
|
),
|
|
)
|
|
return ret
|
|
}
|
|
|
|
// helper function to return labels for srcs used in bootstrap_go_package and bootstrap_go_binary
|
|
// this function has the following limitations which make it unsuitable for widespread use
|
|
// - wildcard patterns in srcs
|
|
// This is ok for go since build/blueprint does not support it.
|
|
//
|
|
// Prefer to use `BazelLabelForModuleSrc` instead
|
|
func goSrcLabels(cfg android.Config, moduleDir string, srcs []string, linuxSrcs, darwinSrcs []string) bazel.LabelListAttribute {
|
|
labels := func(srcs []string) bazel.LabelList {
|
|
ret := []bazel.Label{}
|
|
for _, src := range srcs {
|
|
srcLabel := bazel.Label{
|
|
Label: src,
|
|
}
|
|
ret = append(ret, srcLabel)
|
|
}
|
|
// Respect package boundaries
|
|
return android.TransformSubpackagePaths(
|
|
cfg,
|
|
moduleDir,
|
|
bazel.MakeLabelList(ret),
|
|
)
|
|
}
|
|
|
|
ret := bazel.LabelListAttribute{}
|
|
// common
|
|
ret.SetSelectValue(bazel.NoConfigAxis, "", labels(srcs))
|
|
// linux
|
|
ret.SetSelectValue(bazel.OsConfigurationAxis, bazel.OsLinux, labels(linuxSrcs))
|
|
// darwin
|
|
ret.SetSelectValue(bazel.OsConfigurationAxis, bazel.OsDarwin, labels(darwinSrcs))
|
|
return ret
|
|
}
|
|
|
|
func goDepLabels(deps []string, goModulesMap nameToGoLibraryModule) bazel.LabelListAttribute {
|
|
labels := []bazel.Label{}
|
|
for _, dep := range deps {
|
|
moduleDir := goModulesMap[dep].Dir
|
|
if moduleDir == "." {
|
|
moduleDir = ""
|
|
}
|
|
label := bazel.Label{
|
|
Label: fmt.Sprintf("//%s:%s", moduleDir, dep),
|
|
}
|
|
labels = append(labels, label)
|
|
}
|
|
return bazel.MakeLabelListAttribute(bazel.MakeLabelList(labels))
|
|
}
|
|
|
|
// attributes common to blueprint_go_binary and bootstap_go_package
|
|
type goAttributes struct {
|
|
Importpath bazel.StringAttribute
|
|
Srcs bazel.LabelListAttribute
|
|
Deps bazel.LabelListAttribute
|
|
Data bazel.LabelListAttribute
|
|
Target_compatible_with bazel.LabelListAttribute
|
|
|
|
// attributes for the dynamically generated go_test target
|
|
Embed bazel.LabelListAttribute
|
|
}
|
|
|
|
type goTestProperties struct {
|
|
name string
|
|
dir string
|
|
testSrcs []string
|
|
linuxTestSrcs []string
|
|
darwinTestSrcs []string
|
|
testData []string
|
|
// Name of the target that should be compiled together with the test
|
|
embedName string
|
|
}
|
|
|
|
// Creates a go_test target for bootstrap_go_package / blueprint_go_binary
|
|
func generateBazelTargetsGoTest(ctx *android.Context, goModulesMap nameToGoLibraryModule, gp goTestProperties) (BazelTarget, error) {
|
|
ca := android.CommonAttributes{
|
|
Name: gp.name,
|
|
}
|
|
ga := goAttributes{
|
|
Srcs: goSrcLabels(ctx.Config(), gp.dir, gp.testSrcs, gp.linuxTestSrcs, gp.darwinTestSrcs),
|
|
Data: goSrcLabels(ctx.Config(), gp.dir, gp.testData, []string{}, []string{}),
|
|
Embed: bazel.MakeLabelListAttribute(
|
|
bazel.MakeLabelList(
|
|
[]bazel.Label{bazel.Label{Label: ":" + gp.embedName}},
|
|
),
|
|
),
|
|
Target_compatible_with: targetNotCompatibleWithAndroid(),
|
|
}
|
|
|
|
libTest := goBazelTarget{
|
|
targetName: gp.name,
|
|
targetPackage: gp.dir,
|
|
bazelRuleClass: "go_test",
|
|
bazelRuleLoadLocation: "@io_bazel_rules_go//go:def.bzl",
|
|
bazelAttributes: []interface{}{&ca, &ga},
|
|
}
|
|
return generateBazelTarget(ctx, libTest)
|
|
}
|
|
|
|
// TODO - b/288491147: testSrcs of certain bootstrap_go_package/blueprint_go_binary are not hermetic and depend on
|
|
// testdata checked into the filesystem.
|
|
// Denylist the generation of go_test targets for these Soong modules.
|
|
// The go_library/go_binary will still be generated, since those are hermitic.
|
|
var (
|
|
goTestsDenylist = []string{
|
|
"android-archive-zip",
|
|
"bazel_notice_gen",
|
|
"blueprint-bootstrap-bpdoc",
|
|
"blueprint-microfactory",
|
|
"blueprint-pathtools",
|
|
"bssl_ar",
|
|
"compliance_checkmetadata",
|
|
"compliance_checkshare",
|
|
"compliance_dumpgraph",
|
|
"compliance_dumpresolutions",
|
|
"compliance_listshare",
|
|
"compliance-module",
|
|
"compliancenotice_bom",
|
|
"compliancenotice_shippedlibs",
|
|
"compliance_rtrace",
|
|
"compliance_sbom",
|
|
"golang-protobuf-internal-fuzz-jsonfuzz",
|
|
"golang-protobuf-internal-fuzz-textfuzz",
|
|
"golang-protobuf-internal-fuzz-wirefuzz",
|
|
"htmlnotice",
|
|
"protoc-gen-go",
|
|
"rbcrun-module",
|
|
"spdx-tools-builder",
|
|
"spdx-tools-builder2v1",
|
|
"spdx-tools-builder2v2",
|
|
"spdx-tools-builder2v3",
|
|
"spdx-tools-idsearcher",
|
|
"spdx-tools-spdx-json",
|
|
"spdx-tools-utils",
|
|
"soong-ui-build",
|
|
"textnotice",
|
|
"xmlnotice",
|
|
}
|
|
)
|
|
|
|
func testOfGoPackageIsIncompatible(g *bootstrap.GoPackage) bool {
|
|
return android.InList(g.Name(), goTestsDenylist) ||
|
|
// Denylist tests of soong_build
|
|
// Theses tests have a guard that prevent usage outside a test environment
|
|
// The guard (`ensureTestOnly`) looks for a `-test` in os.Args, which is present in soong's gotestrunner, but missing in `b test`
|
|
g.IsPluginFor("soong_build") ||
|
|
// soong-android is a dep of soong_build
|
|
// This dependency is created by soong_build by listing it in its deps explicitly in Android.bp, and not via `plugin_for` in `soong-android`
|
|
g.Name() == "soong-android"
|
|
}
|
|
|
|
func testOfGoBinaryIsIncompatible(g *bootstrap.GoBinary) bool {
|
|
return android.InList(g.Name(), goTestsDenylist)
|
|
}
|
|
|
|
func generateBazelTargetsGoPackage(ctx *android.Context, g *bootstrap.GoPackage, goModulesMap nameToGoLibraryModule) ([]BazelTarget, []error) {
|
|
ca := android.CommonAttributes{
|
|
Name: g.Name(),
|
|
}
|
|
|
|
// For this bootstrap_go_package dep chain,
|
|
// A --> B --> C ( ---> depends on)
|
|
// Soong provides the convenience of only listing B as deps of A even if a src file of A imports C
|
|
// Bazel OTOH
|
|
// 1. requires C to be listed in `deps` expllicity.
|
|
// 2. does not require C to be listed if src of A does not import C
|
|
//
|
|
// bp2build does not have sufficient info on whether C is a direct dep of A or not, so for now collect all transitive deps and add them to deps
|
|
transitiveDeps := transitiveGoDeps(g.Deps(), goModulesMap)
|
|
|
|
ga := goAttributes{
|
|
Importpath: bazel.StringAttribute{
|
|
Value: proptools.StringPtr(g.GoPkgPath()),
|
|
},
|
|
Srcs: goSrcLabels(ctx.Config(), ctx.ModuleDir(g), g.Srcs(), g.LinuxSrcs(), g.DarwinSrcs()),
|
|
Deps: goDepLabels(
|
|
android.FirstUniqueStrings(transitiveDeps),
|
|
goModulesMap,
|
|
),
|
|
Target_compatible_with: targetNotCompatibleWithAndroid(),
|
|
}
|
|
|
|
lib := goBazelTarget{
|
|
targetName: g.Name(),
|
|
targetPackage: ctx.ModuleDir(g),
|
|
bazelRuleClass: "go_library",
|
|
bazelRuleLoadLocation: "@io_bazel_rules_go//go:def.bzl",
|
|
bazelAttributes: []interface{}{&ca, &ga},
|
|
}
|
|
retTargets := []BazelTarget{}
|
|
var retErrs []error
|
|
if libTarget, err := generateBazelTarget(ctx, lib); err == nil {
|
|
retTargets = append(retTargets, libTarget)
|
|
} else {
|
|
retErrs = []error{err}
|
|
}
|
|
|
|
// If the library contains test srcs, create an additional go_test target
|
|
if !testOfGoPackageIsIncompatible(g) && (len(g.TestSrcs()) > 0 || len(g.LinuxTestSrcs()) > 0 || len(g.DarwinTestSrcs()) > 0) {
|
|
gp := goTestProperties{
|
|
name: g.Name() + "-test",
|
|
dir: ctx.ModuleDir(g),
|
|
testSrcs: g.TestSrcs(),
|
|
linuxTestSrcs: g.LinuxTestSrcs(),
|
|
darwinTestSrcs: g.DarwinTestSrcs(),
|
|
testData: g.TestData(),
|
|
embedName: g.Name(), // embed the source go_library in the test so that its .go files are included in the compilation unit
|
|
}
|
|
if libTestTarget, err := generateBazelTargetsGoTest(ctx, goModulesMap, gp); err == nil {
|
|
retTargets = append(retTargets, libTestTarget)
|
|
} else {
|
|
retErrs = append(retErrs, err)
|
|
}
|
|
}
|
|
|
|
return retTargets, retErrs
|
|
}
|
|
|
|
type goLibraryModule struct {
|
|
Dir string
|
|
Deps []string
|
|
}
|
|
|
|
type nameToGoLibraryModule map[string]goLibraryModule
|
|
|
|
// Visit each module in the graph
|
|
// If a module is of type `bootstrap_go_package`, return a map containing metadata like its dir and deps
|
|
func createGoLibraryModuleMap(ctx *android.Context) nameToGoLibraryModule {
|
|
ret := nameToGoLibraryModule{}
|
|
ctx.VisitAllModules(func(m blueprint.Module) {
|
|
moduleType := ctx.ModuleType(m)
|
|
// We do not need to store information about blueprint_go_binary since it does not have any rdeps
|
|
if moduleType == "bootstrap_go_package" {
|
|
ret[m.Name()] = goLibraryModule{
|
|
Dir: ctx.ModuleDir(m),
|
|
Deps: m.(*bootstrap.GoPackage).Deps(),
|
|
}
|
|
}
|
|
})
|
|
return ret
|
|
}
|
|
|
|
// Returns the deps in the transitive closure of a go target
|
|
func transitiveGoDeps(directDeps []string, goModulesMap nameToGoLibraryModule) []string {
|
|
allDeps := directDeps
|
|
i := 0
|
|
for i < len(allDeps) {
|
|
curr := allDeps[i]
|
|
allDeps = append(allDeps, goModulesMap[curr].Deps...)
|
|
i += 1
|
|
}
|
|
allDeps = android.SortedUniqueStrings(allDeps)
|
|
return allDeps
|
|
}
|
|
|
|
func generateBazelTargetsGoBinary(ctx *android.Context, g *bootstrap.GoBinary, goModulesMap nameToGoLibraryModule) ([]BazelTarget, []error) {
|
|
ca := android.CommonAttributes{
|
|
Name: g.Name(),
|
|
}
|
|
|
|
retTargets := []BazelTarget{}
|
|
var retErrs []error
|
|
|
|
// For this bootstrap_go_package dep chain,
|
|
// A --> B --> C ( ---> depends on)
|
|
// Soong provides the convenience of only listing B as deps of A even if a src file of A imports C
|
|
// Bazel OTOH
|
|
// 1. requires C to be listed in `deps` expllicity.
|
|
// 2. does not require C to be listed if src of A does not import C
|
|
//
|
|
// bp2build does not have sufficient info on whether C is a direct dep of A or not, so for now collect all transitive deps and add them to deps
|
|
transitiveDeps := transitiveGoDeps(g.Deps(), goModulesMap)
|
|
|
|
goSource := ""
|
|
// If the library contains test srcs, create an additional go_test target
|
|
// The go_test target will embed a go_source containining the source .go files it tests
|
|
if !testOfGoBinaryIsIncompatible(g) && (len(g.TestSrcs()) > 0 || len(g.LinuxTestSrcs()) > 0 || len(g.DarwinTestSrcs()) > 0) {
|
|
// Create a go_source containing the source .go files of go_library
|
|
// This target will be an `embed` of the go_binary and go_test
|
|
goSource = g.Name() + "-source"
|
|
ca := android.CommonAttributes{
|
|
Name: goSource,
|
|
}
|
|
ga := goAttributes{
|
|
Srcs: goSrcLabels(ctx.Config(), ctx.ModuleDir(g), g.Srcs(), g.LinuxSrcs(), g.DarwinSrcs()),
|
|
Deps: goDepLabels(transitiveDeps, goModulesMap),
|
|
Target_compatible_with: targetNotCompatibleWithAndroid(),
|
|
}
|
|
libTestSource := goBazelTarget{
|
|
targetName: goSource,
|
|
targetPackage: ctx.ModuleDir(g),
|
|
bazelRuleClass: "go_source",
|
|
bazelRuleLoadLocation: "@io_bazel_rules_go//go:def.bzl",
|
|
bazelAttributes: []interface{}{&ca, &ga},
|
|
}
|
|
if libSourceTarget, err := generateBazelTarget(ctx, libTestSource); err == nil {
|
|
retTargets = append(retTargets, libSourceTarget)
|
|
} else {
|
|
retErrs = append(retErrs, err)
|
|
}
|
|
|
|
// Create a go_test target
|
|
gp := goTestProperties{
|
|
name: g.Name() + "-test",
|
|
dir: ctx.ModuleDir(g),
|
|
testSrcs: g.TestSrcs(),
|
|
linuxTestSrcs: g.LinuxTestSrcs(),
|
|
darwinTestSrcs: g.DarwinTestSrcs(),
|
|
testData: g.TestData(),
|
|
// embed the go_source in the test
|
|
embedName: g.Name() + "-source",
|
|
}
|
|
if libTestTarget, err := generateBazelTargetsGoTest(ctx, goModulesMap, gp); err == nil {
|
|
retTargets = append(retTargets, libTestTarget)
|
|
} else {
|
|
retErrs = append(retErrs, err)
|
|
}
|
|
|
|
}
|
|
|
|
// Create a go_binary target
|
|
ga := goAttributes{
|
|
Deps: goDepLabels(transitiveDeps, goModulesMap),
|
|
Target_compatible_with: targetNotCompatibleWithAndroid(),
|
|
}
|
|
|
|
// If the binary has testSrcs, embed the common `go_source`
|
|
if goSource != "" {
|
|
ga.Embed = bazel.MakeLabelListAttribute(
|
|
bazel.MakeLabelList(
|
|
[]bazel.Label{bazel.Label{Label: ":" + goSource}},
|
|
),
|
|
)
|
|
} else {
|
|
ga.Srcs = goSrcLabels(ctx.Config(), ctx.ModuleDir(g), g.Srcs(), g.LinuxSrcs(), g.DarwinSrcs())
|
|
}
|
|
|
|
bin := goBazelTarget{
|
|
targetName: g.Name(),
|
|
targetPackage: ctx.ModuleDir(g),
|
|
bazelRuleClass: "go_binary",
|
|
bazelRuleLoadLocation: "@io_bazel_rules_go//go:def.bzl",
|
|
bazelAttributes: []interface{}{&ca, &ga},
|
|
}
|
|
|
|
if binTarget, err := generateBazelTarget(ctx, bin); err == nil {
|
|
retTargets = append(retTargets, binTarget)
|
|
} else {
|
|
retErrs = []error{err}
|
|
}
|
|
|
|
return retTargets, retErrs
|
|
}
|
|
|
|
func GenerateBazelTargets(ctx *CodegenContext, generateFilegroups bool) (conversionResults, []error) {
|
|
ctx.Context().BeginEvent("GenerateBazelTargets")
|
|
defer ctx.Context().EndEvent("GenerateBazelTargets")
|
|
buildFileToTargets := make(map[string]BazelTargets)
|
|
|
|
// Simple metrics tracking for bp2build
|
|
metrics := CreateCodegenMetrics()
|
|
|
|
dirs := make(map[string]bool)
|
|
|
|
var errs []error
|
|
|
|
// Visit go libraries in a pre-run and store its state in a map
|
|
// The time complexity remains O(N), and this does not add significant wall time.
|
|
nameToGoLibMap := createGoLibraryModuleMap(ctx.Context())
|
|
|
|
bpCtx := ctx.Context()
|
|
bpCtx.VisitAllModules(func(m blueprint.Module) {
|
|
dir := bpCtx.ModuleDir(m)
|
|
moduleType := bpCtx.ModuleType(m)
|
|
dirs[dir] = true
|
|
|
|
var targets []BazelTarget
|
|
var targetErrs []error
|
|
|
|
switch ctx.Mode() {
|
|
case Bp2Build:
|
|
// There are two main ways of converting a Soong module to Bazel:
|
|
// 1) Manually handcrafting a Bazel target and associating the module with its label
|
|
// 2) Automatically generating with bp2build converters
|
|
//
|
|
// bp2build converters are used for the majority of modules.
|
|
if b, ok := m.(android.Bazelable); ok && b.HasHandcraftedLabel() {
|
|
if aModule, ok := m.(android.Module); ok && aModule.IsConvertedByBp2build() {
|
|
panic(fmt.Errorf("module %q [%s] [%s] was both converted with bp2build and has a handcrafted label", bpCtx.ModuleName(m), moduleType, dir))
|
|
}
|
|
// Handle modules converted to handcrafted targets.
|
|
//
|
|
// Since these modules are associated with some handcrafted
|
|
// target in a BUILD file, we don't autoconvert them.
|
|
|
|
// Log the module.
|
|
metrics.AddUnconvertedModule(m, moduleType, dir,
|
|
android.UnconvertedReason{
|
|
ReasonType: int(bp2build_metrics_proto.UnconvertedReasonType_DEFINED_IN_BUILD_FILE),
|
|
})
|
|
} else if aModule, ok := m.(android.Module); ok && aModule.IsConvertedByBp2build() {
|
|
// Handle modules converted to generated targets.
|
|
|
|
// Log the module.
|
|
metrics.AddConvertedModule(aModule, moduleType, dir)
|
|
|
|
// Handle modules with unconverted deps. By default, emit a warning.
|
|
if unconvertedDeps := aModule.GetUnconvertedBp2buildDeps(); len(unconvertedDeps) > 0 {
|
|
msg := fmt.Sprintf("%s %s:%s depends on unconverted modules: %s",
|
|
moduleType, bpCtx.ModuleDir(m), m.Name(), strings.Join(unconvertedDeps, ", "))
|
|
switch ctx.unconvertedDepMode {
|
|
case warnUnconvertedDeps:
|
|
metrics.moduleWithUnconvertedDepsMsgs = append(metrics.moduleWithUnconvertedDepsMsgs, msg)
|
|
case errorModulesUnconvertedDeps:
|
|
errs = append(errs, fmt.Errorf(msg))
|
|
return
|
|
}
|
|
}
|
|
if unconvertedDeps := aModule.GetMissingBp2buildDeps(); len(unconvertedDeps) > 0 {
|
|
msg := fmt.Sprintf("%s %s:%s depends on missing modules: %s",
|
|
moduleType, bpCtx.ModuleDir(m), m.Name(), strings.Join(unconvertedDeps, ", "))
|
|
switch ctx.unconvertedDepMode {
|
|
case warnUnconvertedDeps:
|
|
metrics.moduleWithMissingDepsMsgs = append(metrics.moduleWithMissingDepsMsgs, msg)
|
|
case errorModulesUnconvertedDeps:
|
|
errs = append(errs, fmt.Errorf(msg))
|
|
return
|
|
}
|
|
}
|
|
targets, targetErrs = generateBazelTargets(bpCtx, aModule)
|
|
errs = append(errs, targetErrs...)
|
|
for _, t := range targets {
|
|
// A module can potentially generate more than 1 Bazel
|
|
// target, each of a different rule class.
|
|
metrics.IncrementRuleClassCount(t.ruleClass)
|
|
}
|
|
} else if _, ok := ctx.Config().BazelModulesForceEnabledByFlag()[m.Name()]; ok && m.Name() != "" {
|
|
err := fmt.Errorf("Force Enabled Module %s not converted", m.Name())
|
|
errs = append(errs, err)
|
|
} else if aModule, ok := m.(android.Module); ok {
|
|
reason := aModule.GetUnconvertedReason()
|
|
if reason == nil {
|
|
panic(fmt.Errorf("module '%s' was neither converted nor marked unconvertible with bp2build", aModule.Name()))
|
|
} else {
|
|
metrics.AddUnconvertedModule(m, moduleType, dir, *reason)
|
|
}
|
|
return
|
|
} else if glib, ok := m.(*bootstrap.GoPackage); ok {
|
|
targets, targetErrs = generateBazelTargetsGoPackage(bpCtx, glib, nameToGoLibMap)
|
|
errs = append(errs, targetErrs...)
|
|
metrics.IncrementRuleClassCount("go_library")
|
|
metrics.AddConvertedModule(glib, "go_library", dir)
|
|
} else if gbin, ok := m.(*bootstrap.GoBinary); ok {
|
|
targets, targetErrs = generateBazelTargetsGoBinary(bpCtx, gbin, nameToGoLibMap)
|
|
errs = append(errs, targetErrs...)
|
|
metrics.IncrementRuleClassCount("go_binary")
|
|
metrics.AddConvertedModule(gbin, "go_binary", dir)
|
|
} else {
|
|
metrics.AddUnconvertedModule(m, moduleType, dir, android.UnconvertedReason{
|
|
ReasonType: int(bp2build_metrics_proto.UnconvertedReasonType_TYPE_UNSUPPORTED),
|
|
})
|
|
return
|
|
}
|
|
case QueryView:
|
|
// Blocklist certain module types from being generated.
|
|
if canonicalizeModuleType(bpCtx.ModuleType(m)) == "package" {
|
|
// package module name contain slashes, and thus cannot
|
|
// be mapped cleanly to a bazel label.
|
|
return
|
|
}
|
|
t, err := generateSoongModuleTarget(bpCtx, m)
|
|
if err != nil {
|
|
errs = append(errs, err)
|
|
}
|
|
targets = append(targets, t)
|
|
default:
|
|
errs = append(errs, fmt.Errorf("Unknown code-generation mode: %s", ctx.Mode()))
|
|
return
|
|
}
|
|
|
|
for _, target := range targets {
|
|
targetDir := target.PackageName()
|
|
buildFileToTargets[targetDir] = append(buildFileToTargets[targetDir], target)
|
|
}
|
|
})
|
|
|
|
if len(errs) > 0 {
|
|
return conversionResults{}, errs
|
|
}
|
|
|
|
if generateFilegroups {
|
|
// Add a filegroup target that exposes all sources in the subtree of this package
|
|
// NOTE: This also means we generate a BUILD file for every Android.bp file (as long as it has at least one module)
|
|
//
|
|
// This works because: https://bazel.build/reference/be/functions#exports_files
|
|
// "As a legacy behaviour, also files mentioned as input to a rule are exported with the
|
|
// default visibility until the flag --incompatible_no_implicit_file_export is flipped. However, this behavior
|
|
// should not be relied upon and actively migrated away from."
|
|
//
|
|
// TODO(b/198619163): We should change this to export_files(glob(["**/*"])) instead, but doing that causes these errors:
|
|
// "Error in exports_files: generated label '//external/avb:avbtool' conflicts with existing py_binary rule"
|
|
// So we need to solve all the "target ... is both a rule and a file" warnings first.
|
|
for dir := range dirs {
|
|
buildFileToTargets[dir] = append(buildFileToTargets[dir], BazelTarget{
|
|
name: "bp2build_all_srcs",
|
|
content: `filegroup(name = "bp2build_all_srcs", srcs = glob(["**/*"]))`,
|
|
ruleClass: "filegroup",
|
|
})
|
|
}
|
|
}
|
|
|
|
return conversionResults{
|
|
buildFileToTargets: buildFileToTargets,
|
|
metrics: metrics,
|
|
}, errs
|
|
}
|
|
|
|
func generateBazelTargets(ctx bpToBuildContext, m android.Module) ([]BazelTarget, []error) {
|
|
var targets []BazelTarget
|
|
var errs []error
|
|
for _, m := range m.Bp2buildTargets() {
|
|
target, err := generateBazelTarget(ctx, m)
|
|
if err != nil {
|
|
errs = append(errs, err)
|
|
return targets, errs
|
|
}
|
|
targets = append(targets, target)
|
|
}
|
|
return targets, errs
|
|
}
|
|
|
|
type bp2buildModule interface {
|
|
TargetName() string
|
|
TargetPackage() string
|
|
BazelRuleClass() string
|
|
BazelRuleLoadLocation() string
|
|
BazelAttributes() []interface{}
|
|
}
|
|
|
|
func generateBazelTarget(ctx bpToBuildContext, m bp2buildModule) (BazelTarget, error) {
|
|
ruleClass := m.BazelRuleClass()
|
|
bzlLoadLocation := m.BazelRuleLoadLocation()
|
|
|
|
// extract the bazel attributes from the module.
|
|
attrs := m.BazelAttributes()
|
|
props, err := extractModuleProperties(attrs, true)
|
|
if err != nil {
|
|
return BazelTarget{}, err
|
|
}
|
|
|
|
// name is handled in a special manner
|
|
delete(props.Attrs, "name")
|
|
|
|
// Return the Bazel target with rule class and attributes, ready to be
|
|
// code-generated.
|
|
attributes := propsToAttributes(props.Attrs)
|
|
var content string
|
|
targetName := m.TargetName()
|
|
if targetName != "" {
|
|
content = fmt.Sprintf(ruleTargetTemplate, ruleClass, targetName, attributes)
|
|
} else {
|
|
content = fmt.Sprintf(unnamedRuleTargetTemplate, ruleClass, attributes)
|
|
}
|
|
var loads []BazelLoad
|
|
if bzlLoadLocation != "" {
|
|
loads = append(loads, BazelLoad{
|
|
file: bzlLoadLocation,
|
|
symbols: []BazelLoadSymbol{{symbol: ruleClass}},
|
|
})
|
|
}
|
|
return BazelTarget{
|
|
name: targetName,
|
|
packageName: m.TargetPackage(),
|
|
ruleClass: ruleClass,
|
|
loads: loads,
|
|
content: content,
|
|
}, nil
|
|
}
|
|
|
|
// Convert a module and its deps and props into a Bazel macro/rule
|
|
// representation in the BUILD file.
|
|
func generateSoongModuleTarget(ctx bpToBuildContext, m blueprint.Module) (BazelTarget, error) {
|
|
props, err := getBuildProperties(ctx, m)
|
|
|
|
// TODO(b/163018919): DirectDeps can have duplicate (module, variant)
|
|
// items, if the modules are added using different DependencyTag. Figure
|
|
// out the implications of that.
|
|
depLabels := map[string]bool{}
|
|
if aModule, ok := m.(android.Module); ok {
|
|
ctx.VisitDirectDeps(aModule, func(depModule blueprint.Module) {
|
|
depLabels[qualifiedTargetLabel(ctx, depModule)] = true
|
|
})
|
|
}
|
|
|
|
for p := range ignoredPropNames {
|
|
delete(props.Attrs, p)
|
|
}
|
|
attributes := propsToAttributes(props.Attrs)
|
|
|
|
depLabelList := "[\n"
|
|
for depLabel := range depLabels {
|
|
depLabelList += fmt.Sprintf(" %q,\n", depLabel)
|
|
}
|
|
depLabelList += " ]"
|
|
|
|
targetName := targetNameWithVariant(ctx, m)
|
|
return BazelTarget{
|
|
name: targetName,
|
|
packageName: ctx.ModuleDir(m),
|
|
content: fmt.Sprintf(
|
|
soongModuleTargetTemplate,
|
|
targetName,
|
|
ctx.ModuleName(m),
|
|
canonicalizeModuleType(ctx.ModuleType(m)),
|
|
ctx.ModuleSubDir(m),
|
|
depLabelList,
|
|
attributes),
|
|
}, err
|
|
}
|
|
|
|
func getBuildProperties(ctx bpToBuildContext, m blueprint.Module) (BazelAttributes, error) {
|
|
// TODO: this omits properties for blueprint modules (blueprint_go_binary,
|
|
// bootstrap_go_binary, bootstrap_go_package), which will have to be handled separately.
|
|
if aModule, ok := m.(android.Module); ok {
|
|
return extractModuleProperties(aModule.GetProperties(), false)
|
|
}
|
|
|
|
return BazelAttributes{}, nil
|
|
}
|
|
|
|
// Generically extract module properties and types into a map, keyed by the module property name.
|
|
func extractModuleProperties(props []interface{}, checkForDuplicateProperties bool) (BazelAttributes, error) {
|
|
ret := map[string]string{}
|
|
|
|
// Iterate over this android.Module's property structs.
|
|
for _, properties := range props {
|
|
propertiesValue := reflect.ValueOf(properties)
|
|
// Check that propertiesValue is a pointer to the Properties struct, like
|
|
// *cc.BaseLinkerProperties or *java.CompilerProperties.
|
|
//
|
|
// propertiesValue can also be type-asserted to the structs to
|
|
// manipulate internal props, if needed.
|
|
if isStructPtr(propertiesValue.Type()) {
|
|
structValue := propertiesValue.Elem()
|
|
ok, err := extractStructProperties(structValue, 0)
|
|
if err != nil {
|
|
return BazelAttributes{}, err
|
|
}
|
|
for k, v := range ok {
|
|
if existing, exists := ret[k]; checkForDuplicateProperties && exists {
|
|
return BazelAttributes{}, fmt.Errorf(
|
|
"%s (%v) is present in properties whereas it should be consolidated into a commonAttributes",
|
|
k, existing)
|
|
}
|
|
ret[k] = v
|
|
}
|
|
} else {
|
|
return BazelAttributes{},
|
|
fmt.Errorf(
|
|
"properties must be a pointer to a struct, got %T",
|
|
propertiesValue.Interface())
|
|
}
|
|
}
|
|
|
|
return BazelAttributes{
|
|
Attrs: ret,
|
|
}, nil
|
|
}
|
|
|
|
func isStructPtr(t reflect.Type) bool {
|
|
return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
|
|
}
|
|
|
|
// prettyPrint a property value into the equivalent Starlark representation
|
|
// recursively.
|
|
func prettyPrint(propertyValue reflect.Value, indent int, emitZeroValues bool) (string, error) {
|
|
if !emitZeroValues && isZero(propertyValue) {
|
|
// A property value being set or unset actually matters -- Soong does set default
|
|
// values for unset properties, like system_shared_libs = ["libc", "libm", "libdl"] at
|
|
// https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/linker.go;l=281-287;drc=f70926eef0b9b57faf04c17a1062ce50d209e480
|
|
//
|
|
// In Bazel-parlance, we would use "attr.<type>(default = <default
|
|
// value>)" to set the default value of unset attributes. In the cases
|
|
// where the bp2build converter didn't set the default value within the
|
|
// mutator when creating the BazelTargetModule, this would be a zero
|
|
// value. For those cases, we return an empty string so we don't
|
|
// unnecessarily generate empty values.
|
|
return "", nil
|
|
}
|
|
|
|
switch propertyValue.Kind() {
|
|
case reflect.String:
|
|
return fmt.Sprintf("\"%v\"", escapeString(propertyValue.String())), nil
|
|
case reflect.Bool:
|
|
return starlark_fmt.PrintBool(propertyValue.Bool()), nil
|
|
case reflect.Int, reflect.Uint, reflect.Int64:
|
|
return fmt.Sprintf("%v", propertyValue.Interface()), nil
|
|
case reflect.Ptr:
|
|
return prettyPrint(propertyValue.Elem(), indent, emitZeroValues)
|
|
case reflect.Slice:
|
|
elements := make([]string, 0, propertyValue.Len())
|
|
for i := 0; i < propertyValue.Len(); i++ {
|
|
val, err := prettyPrint(propertyValue.Index(i), indent, emitZeroValues)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if val != "" {
|
|
elements = append(elements, val)
|
|
}
|
|
}
|
|
return starlark_fmt.PrintList(elements, indent, func(s string) string {
|
|
return "%s"
|
|
}), nil
|
|
|
|
case reflect.Struct:
|
|
// Special cases where the bp2build sends additional information to the codegenerator
|
|
// by wrapping the attributes in a custom struct type.
|
|
if attr, ok := propertyValue.Interface().(bazel.Attribute); ok {
|
|
return prettyPrintAttribute(attr, indent)
|
|
} else if label, ok := propertyValue.Interface().(bazel.Label); ok {
|
|
return fmt.Sprintf("%q", label.Label), nil
|
|
}
|
|
|
|
// Sort and print the struct props by the key.
|
|
structProps, err := extractStructProperties(propertyValue, indent)
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if len(structProps) == 0 {
|
|
return "", nil
|
|
}
|
|
return starlark_fmt.PrintDict(structProps, indent), nil
|
|
case reflect.Interface:
|
|
// TODO(b/164227191): implement pretty print for interfaces.
|
|
// Interfaces are used for for arch, multilib and target properties.
|
|
return "", nil
|
|
case reflect.Map:
|
|
if v, ok := propertyValue.Interface().(bazel.StringMapAttribute); ok {
|
|
return starlark_fmt.PrintStringStringDict(v, indent), nil
|
|
}
|
|
return "", fmt.Errorf("bp2build expects map of type map[string]string for field: %s", propertyValue)
|
|
default:
|
|
return "", fmt.Errorf(
|
|
"unexpected kind for property struct field: %s", propertyValue.Kind())
|
|
}
|
|
}
|
|
|
|
// Converts a reflected property struct value into a map of property names and property values,
|
|
// which each property value correctly pretty-printed and indented at the right nest level,
|
|
// since property structs can be nested. In Starlark, nested structs are represented as nested
|
|
// dicts: https://docs.bazel.build/skylark/lib/dict.html
|
|
func extractStructProperties(structValue reflect.Value, indent int) (map[string]string, error) {
|
|
if structValue.Kind() != reflect.Struct {
|
|
return map[string]string{}, fmt.Errorf("Expected a reflect.Struct type, but got %s", structValue.Kind())
|
|
}
|
|
|
|
var err error
|
|
|
|
ret := map[string]string{}
|
|
structType := structValue.Type()
|
|
for i := 0; i < structValue.NumField(); i++ {
|
|
field := structType.Field(i)
|
|
if shouldSkipStructField(field) {
|
|
continue
|
|
}
|
|
|
|
fieldValue := structValue.Field(i)
|
|
if isZero(fieldValue) {
|
|
// Ignore zero-valued fields
|
|
continue
|
|
}
|
|
|
|
// if the struct is embedded (anonymous), flatten the properties into the containing struct
|
|
if field.Anonymous {
|
|
if field.Type.Kind() == reflect.Ptr {
|
|
fieldValue = fieldValue.Elem()
|
|
}
|
|
if fieldValue.Type().Kind() == reflect.Struct {
|
|
propsToMerge, err := extractStructProperties(fieldValue, indent)
|
|
if err != nil {
|
|
return map[string]string{}, err
|
|
}
|
|
for prop, value := range propsToMerge {
|
|
ret[prop] = value
|
|
}
|
|
continue
|
|
}
|
|
}
|
|
|
|
propertyName := proptools.PropertyNameForField(field.Name)
|
|
var prettyPrintedValue string
|
|
prettyPrintedValue, err = prettyPrint(fieldValue, indent+1, false)
|
|
if err != nil {
|
|
return map[string]string{}, fmt.Errorf(
|
|
"Error while parsing property: %q. %s",
|
|
propertyName,
|
|
err)
|
|
}
|
|
if prettyPrintedValue != "" {
|
|
ret[propertyName] = prettyPrintedValue
|
|
}
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
func isZero(value reflect.Value) bool {
|
|
switch value.Kind() {
|
|
case reflect.Func, reflect.Map, reflect.Slice:
|
|
return value.IsNil()
|
|
case reflect.Array:
|
|
valueIsZero := true
|
|
for i := 0; i < value.Len(); i++ {
|
|
valueIsZero = valueIsZero && isZero(value.Index(i))
|
|
}
|
|
return valueIsZero
|
|
case reflect.Struct:
|
|
valueIsZero := true
|
|
for i := 0; i < value.NumField(); i++ {
|
|
valueIsZero = valueIsZero && isZero(value.Field(i))
|
|
}
|
|
return valueIsZero
|
|
case reflect.Ptr:
|
|
if !value.IsNil() {
|
|
return isZero(reflect.Indirect(value))
|
|
} else {
|
|
return true
|
|
}
|
|
// Always print bool/strings, if you want a bool/string attribute to be able to take the default value, use a
|
|
// pointer instead
|
|
case reflect.Bool, reflect.String:
|
|
return false
|
|
default:
|
|
if !value.IsValid() {
|
|
return true
|
|
}
|
|
zeroValue := reflect.Zero(value.Type())
|
|
result := value.Interface() == zeroValue.Interface()
|
|
return result
|
|
}
|
|
}
|
|
|
|
func escapeString(s string) string {
|
|
s = strings.ReplaceAll(s, "\\", "\\\\")
|
|
|
|
// b/184026959: Reverse the application of some common control sequences.
|
|
// These must be generated literally in the BUILD file.
|
|
s = strings.ReplaceAll(s, "\t", "\\t")
|
|
s = strings.ReplaceAll(s, "\n", "\\n")
|
|
s = strings.ReplaceAll(s, "\r", "\\r")
|
|
|
|
return strings.ReplaceAll(s, "\"", "\\\"")
|
|
}
|
|
|
|
func targetNameWithVariant(c bpToBuildContext, logicModule blueprint.Module) string {
|
|
name := ""
|
|
if c.ModuleSubDir(logicModule) != "" {
|
|
// TODO(b/162720883): Figure out a way to drop the "--" variant suffixes.
|
|
name = c.ModuleName(logicModule) + "--" + c.ModuleSubDir(logicModule)
|
|
} else {
|
|
name = c.ModuleName(logicModule)
|
|
}
|
|
|
|
return strings.Replace(name, "//", "", 1)
|
|
}
|
|
|
|
func qualifiedTargetLabel(c bpToBuildContext, logicModule blueprint.Module) string {
|
|
return fmt.Sprintf("//%s:%s", c.ModuleDir(logicModule), targetNameWithVariant(c, logicModule))
|
|
}
|