2020-11-19 11:38:02 +01:00
|
|
|
// 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 bazel
|
|
|
|
|
2021-03-12 12:04:21 +01:00
|
|
|
import (
|
|
|
|
"fmt"
|
2021-03-24 15:04:33 +01:00
|
|
|
"path/filepath"
|
2023-02-06 22:58:30 +01:00
|
|
|
"reflect"
|
2021-03-24 15:14:47 +01:00
|
|
|
"regexp"
|
2021-03-12 12:04:21 +01:00
|
|
|
"sort"
|
2021-09-20 18:55:02 +02:00
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/google/blueprint"
|
2021-03-12 12:04:21 +01:00
|
|
|
)
|
2021-02-24 13:20:12 +01:00
|
|
|
|
2020-12-14 14:25:34 +01:00
|
|
|
// BazelTargetModuleProperties contain properties and metadata used for
|
|
|
|
// Blueprint to BUILD file conversion.
|
|
|
|
type BazelTargetModuleProperties struct {
|
|
|
|
// The Bazel rule class for this target.
|
2021-02-19 17:06:17 +01:00
|
|
|
Rule_class string `blueprint:"mutated"`
|
2021-01-27 03:58:43 +01:00
|
|
|
|
|
|
|
// The target label for the bzl file containing the definition of the rule class.
|
2021-02-19 17:06:17 +01:00
|
|
|
Bzl_load_location string `blueprint:"mutated"`
|
2020-12-14 14:25:34 +01:00
|
|
|
}
|
2021-01-26 15:18:53 +01:00
|
|
|
|
2021-03-24 15:14:47 +01:00
|
|
|
var productVariableSubstitutionPattern = regexp.MustCompile("%(d|s)")
|
|
|
|
|
2021-04-19 07:00:15 +02:00
|
|
|
// Label is used to represent a Bazel compatible Label. Also stores the original
|
|
|
|
// bp text to support string replacement.
|
2021-01-26 15:18:53 +01:00
|
|
|
type Label struct {
|
2021-04-19 07:00:15 +02:00
|
|
|
// The string representation of a Bazel target label. This can be a relative
|
|
|
|
// or fully qualified label. These labels are used for generating BUILD
|
|
|
|
// files with bp2build.
|
|
|
|
Label string
|
|
|
|
|
|
|
|
// The original Soong/Blueprint module name that the label was derived from.
|
|
|
|
// This is used for replacing references to the original name with the new
|
|
|
|
// label, for example in genrule cmds.
|
|
|
|
//
|
|
|
|
// While there is a reversible 1:1 mapping from the module name to Bazel
|
|
|
|
// label with bp2build that could make computing the original module name
|
|
|
|
// from the label automatic, it is not the case for handcrafted targets,
|
|
|
|
// where modules can have a custom label mapping through the { bazel_module:
|
|
|
|
// { label: <label> } } property.
|
|
|
|
//
|
|
|
|
// With handcrafted labels, those modules don't go through bp2build
|
|
|
|
// conversion, but relies on handcrafted targets in the source tree.
|
|
|
|
OriginalModuleName string
|
2021-01-26 15:18:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// LabelList is used to represent a list of Bazel labels.
|
|
|
|
type LabelList struct {
|
|
|
|
Includes []Label
|
|
|
|
Excludes []Label
|
|
|
|
}
|
|
|
|
|
2022-02-25 22:34:51 +01:00
|
|
|
// MakeLabelList creates a LabelList from a list Label
|
|
|
|
func MakeLabelList(labels []Label) LabelList {
|
|
|
|
return LabelList{
|
|
|
|
Includes: labels,
|
|
|
|
Excludes: nil,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-26 23:30:44 +01:00
|
|
|
func SortedConfigurationAxes[T any](m map[ConfigurationAxis]T) []ConfigurationAxis {
|
|
|
|
keys := make([]ConfigurationAxis, 0, len(m))
|
|
|
|
for k := range m {
|
|
|
|
keys = append(keys, k)
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.Slice(keys, func(i, j int) bool { return keys[i].less(keys[j]) })
|
|
|
|
return keys
|
|
|
|
}
|
|
|
|
|
2022-09-09 03:38:47 +02:00
|
|
|
// MakeLabelListFromTargetNames creates a LabelList from unqualified target names
|
|
|
|
// This is a utiltity function for bp2build converters of Soong modules that have 1:many generated targets
|
|
|
|
func MakeLabelListFromTargetNames(targetNames []string) LabelList {
|
|
|
|
labels := []Label{}
|
|
|
|
for _, name := range targetNames {
|
|
|
|
label := Label{Label: ":" + name}
|
|
|
|
labels = append(labels, label)
|
|
|
|
}
|
|
|
|
return MakeLabelList(labels)
|
|
|
|
}
|
|
|
|
|
bp2build: handle system_shared_libs
- If no system_shared_libs is specified, bp2build writes no attribute
value. In this case, the bazel library macros determine the correct
default behavior.
- If any system_shared_libs is specified for any variant, then bp2build
writes the value verbatim. This includes if an empty list is specified,
as this should override defaulting behavior.
Note this defaulting behavior is incomplete and will be incorrect in
corner cases. For example, if, in an Android.bp, system_shared_libs is
specified for os.linux_bionic but not for os.android, then the bazel
default for os.android will be incorrect. However, there are no current
modules in AOSP which fit this case.
As a related fix, supports static struct for cc_library_static.
Also, removes some elements from the bp2build denylist.
Test: mixed_droid CI
Change-Id: Iee5feeaaf05e8e7209c7a90c913173832ad7bf91
2021-08-04 03:01:05 +02:00
|
|
|
func (ll *LabelList) Equals(other LabelList) bool {
|
|
|
|
if len(ll.Includes) != len(other.Includes) || len(ll.Excludes) != len(other.Excludes) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
for i, _ := range ll.Includes {
|
|
|
|
if ll.Includes[i] != other.Includes[i] {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for i, _ := range ll.Excludes {
|
|
|
|
if ll.Excludes[i] != other.Excludes[i] {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
func (ll *LabelList) IsNil() bool {
|
|
|
|
return ll.Includes == nil && ll.Excludes == nil
|
|
|
|
}
|
|
|
|
|
2022-06-21 21:28:33 +02:00
|
|
|
func (ll *LabelList) IsEmpty() bool {
|
|
|
|
return len(ll.Includes) == 0 && len(ll.Excludes) == 0
|
|
|
|
}
|
|
|
|
|
2021-06-02 19:02:03 +02:00
|
|
|
func (ll *LabelList) deepCopy() LabelList {
|
|
|
|
return LabelList{
|
|
|
|
Includes: ll.Includes[:],
|
|
|
|
Excludes: ll.Excludes[:],
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-24 15:04:33 +01:00
|
|
|
// uniqueParentDirectories returns a list of the unique parent directories for
|
|
|
|
// all files in ll.Includes.
|
|
|
|
func (ll *LabelList) uniqueParentDirectories() []string {
|
|
|
|
dirMap := map[string]bool{}
|
|
|
|
for _, label := range ll.Includes {
|
|
|
|
dirMap[filepath.Dir(label.Label)] = true
|
|
|
|
}
|
|
|
|
dirs := []string{}
|
|
|
|
for dir := range dirMap {
|
|
|
|
dirs = append(dirs, dir)
|
|
|
|
}
|
|
|
|
return dirs
|
|
|
|
}
|
|
|
|
|
2022-08-25 20:43:54 +02:00
|
|
|
// Add inserts the label Label at the end of the LabelList.Includes.
|
2021-09-28 15:19:17 +02:00
|
|
|
func (ll *LabelList) Add(label *Label) {
|
|
|
|
if label == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ll.Includes = append(ll.Includes, *label)
|
|
|
|
}
|
|
|
|
|
2022-08-25 20:43:54 +02:00
|
|
|
// AddExclude inserts the label Label at the end of the LabelList.Excludes.
|
|
|
|
func (ll *LabelList) AddExclude(label *Label) {
|
|
|
|
if label == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ll.Excludes = append(ll.Excludes, *label)
|
|
|
|
}
|
|
|
|
|
2021-01-26 15:18:53 +01:00
|
|
|
// Append appends the fields of other labelList to the corresponding fields of ll.
|
|
|
|
func (ll *LabelList) Append(other LabelList) {
|
|
|
|
if len(ll.Includes) > 0 || len(other.Includes) > 0 {
|
|
|
|
ll.Includes = append(ll.Includes, other.Includes...)
|
|
|
|
}
|
|
|
|
if len(ll.Excludes) > 0 || len(other.Excludes) > 0 {
|
2023-01-25 18:07:43 +01:00
|
|
|
ll.Excludes = append(ll.Excludes, other.Excludes...)
|
2021-01-26 15:18:53 +01:00
|
|
|
}
|
|
|
|
}
|
2021-02-24 13:20:12 +01:00
|
|
|
|
2022-08-25 20:43:54 +02:00
|
|
|
// Partition splits a LabelList into two LabelLists depending on the return value
|
|
|
|
// of the predicate.
|
|
|
|
// This function preserves the Includes and Excludes, but it does not provide
|
|
|
|
// that information to the partition function.
|
|
|
|
func (ll *LabelList) Partition(predicate func(label Label) bool) (LabelList, LabelList) {
|
|
|
|
predicated := LabelList{}
|
|
|
|
unpredicated := LabelList{}
|
|
|
|
for _, include := range ll.Includes {
|
|
|
|
if predicate(include) {
|
|
|
|
predicated.Add(&include)
|
|
|
|
} else {
|
|
|
|
unpredicated.Add(&include)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, exclude := range ll.Excludes {
|
|
|
|
if predicate(exclude) {
|
|
|
|
predicated.AddExclude(&exclude)
|
|
|
|
} else {
|
|
|
|
unpredicated.AddExclude(&exclude)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return predicated, unpredicated
|
|
|
|
}
|
|
|
|
|
2021-04-13 09:14:55 +02:00
|
|
|
// UniqueSortedBazelLabels takes a []Label and deduplicates the labels, and returns
|
|
|
|
// the slice in a sorted order.
|
|
|
|
func UniqueSortedBazelLabels(originalLabels []Label) []Label {
|
2021-03-12 12:04:21 +01:00
|
|
|
uniqueLabelsSet := make(map[Label]bool)
|
|
|
|
for _, l := range originalLabels {
|
|
|
|
uniqueLabelsSet[l] = true
|
|
|
|
}
|
|
|
|
var uniqueLabels []Label
|
|
|
|
for l, _ := range uniqueLabelsSet {
|
|
|
|
uniqueLabels = append(uniqueLabels, l)
|
|
|
|
}
|
|
|
|
sort.SliceStable(uniqueLabels, func(i, j int) bool {
|
|
|
|
return uniqueLabels[i].Label < uniqueLabels[j].Label
|
|
|
|
})
|
|
|
|
return uniqueLabels
|
|
|
|
}
|
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
func FirstUniqueBazelLabels(originalLabels []Label) []Label {
|
|
|
|
var labels []Label
|
|
|
|
found := make(map[Label]bool, len(originalLabels))
|
|
|
|
for _, l := range originalLabels {
|
|
|
|
if _, ok := found[l]; ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
labels = append(labels, l)
|
|
|
|
found[l] = true
|
|
|
|
}
|
|
|
|
return labels
|
|
|
|
}
|
|
|
|
|
|
|
|
func FirstUniqueBazelLabelList(originalLabelList LabelList) LabelList {
|
|
|
|
var uniqueLabelList LabelList
|
|
|
|
uniqueLabelList.Includes = FirstUniqueBazelLabels(originalLabelList.Includes)
|
|
|
|
uniqueLabelList.Excludes = FirstUniqueBazelLabels(originalLabelList.Excludes)
|
|
|
|
return uniqueLabelList
|
|
|
|
}
|
|
|
|
|
|
|
|
func UniqueSortedBazelLabelList(originalLabelList LabelList) LabelList {
|
2021-03-12 12:04:21 +01:00
|
|
|
var uniqueLabelList LabelList
|
2021-04-13 09:14:55 +02:00
|
|
|
uniqueLabelList.Includes = UniqueSortedBazelLabels(originalLabelList.Includes)
|
|
|
|
uniqueLabelList.Excludes = UniqueSortedBazelLabels(originalLabelList.Excludes)
|
2021-03-12 12:04:21 +01:00
|
|
|
return uniqueLabelList
|
|
|
|
}
|
|
|
|
|
2021-04-06 22:06:21 +02:00
|
|
|
// Subtract needle from haystack
|
|
|
|
func SubtractStrings(haystack []string, needle []string) []string {
|
|
|
|
// This is really a set
|
2021-10-11 21:40:35 +02:00
|
|
|
needleMap := make(map[string]bool)
|
2021-04-06 22:06:21 +02:00
|
|
|
for _, s := range needle {
|
2021-10-11 21:40:35 +02:00
|
|
|
needleMap[s] = true
|
2021-04-06 22:06:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
var strings []string
|
2021-10-11 21:40:35 +02:00
|
|
|
for _, s := range haystack {
|
|
|
|
if exclude := needleMap[s]; !exclude {
|
|
|
|
strings = append(strings, s)
|
|
|
|
}
|
2021-04-06 22:06:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return strings
|
|
|
|
}
|
|
|
|
|
|
|
|
// Subtract needle from haystack
|
|
|
|
func SubtractBazelLabels(haystack []Label, needle []Label) []Label {
|
|
|
|
// This is really a set
|
2021-10-11 21:40:35 +02:00
|
|
|
needleMap := make(map[Label]bool)
|
|
|
|
for _, s := range needle {
|
|
|
|
needleMap[s] = true
|
2021-04-06 22:06:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
var labels []Label
|
2021-10-11 21:40:35 +02:00
|
|
|
for _, label := range haystack {
|
|
|
|
if exclude := needleMap[label]; !exclude {
|
|
|
|
labels = append(labels, label)
|
|
|
|
}
|
2021-04-06 22:06:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return labels
|
|
|
|
}
|
|
|
|
|
2021-05-13 21:13:04 +02:00
|
|
|
// Appends two LabelLists, returning the combined list.
|
|
|
|
func AppendBazelLabelLists(a LabelList, b LabelList) LabelList {
|
|
|
|
var result LabelList
|
|
|
|
result.Includes = append(a.Includes, b.Includes...)
|
|
|
|
result.Excludes = append(a.Excludes, b.Excludes...)
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2021-04-06 22:06:21 +02:00
|
|
|
// Subtract needle from haystack
|
|
|
|
func SubtractBazelLabelList(haystack LabelList, needle LabelList) LabelList {
|
|
|
|
var result LabelList
|
|
|
|
result.Includes = SubtractBazelLabels(haystack.Includes, needle.Includes)
|
|
|
|
// NOTE: Excludes are intentionally not subtracted
|
|
|
|
result.Excludes = haystack.Excludes
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2021-04-05 12:35:13 +02:00
|
|
|
type Attribute interface {
|
|
|
|
HasConfigurableValues() bool
|
|
|
|
}
|
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
type labelSelectValues map[string]*Label
|
2021-05-27 08:15:54 +02:00
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
type configurableLabels map[ConfigurationAxis]labelSelectValues
|
2021-05-27 08:15:54 +02:00
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
func (cl configurableLabels) setValueForAxis(axis ConfigurationAxis, config string, value *Label) {
|
|
|
|
if cl[axis] == nil {
|
|
|
|
cl[axis] = make(labelSelectValues)
|
|
|
|
}
|
|
|
|
cl[axis][config] = value
|
2021-05-27 08:15:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Represents an attribute whose value is a single label
|
|
|
|
type LabelAttribute struct {
|
2021-05-21 14:37:59 +02:00
|
|
|
Value *Label
|
2021-05-27 08:15:54 +02:00
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
ConfigurableValues configurableLabels
|
2021-04-30 15:35:09 +02:00
|
|
|
}
|
|
|
|
|
2021-12-10 00:10:18 +01:00
|
|
|
func (la *LabelAttribute) axisTypes() map[configurationType]bool {
|
|
|
|
types := map[configurationType]bool{}
|
|
|
|
for k := range la.ConfigurableValues {
|
|
|
|
if len(la.ConfigurableValues[k]) > 0 {
|
|
|
|
types[k.configurationType] = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return types
|
|
|
|
}
|
|
|
|
|
|
|
|
// Collapse reduces the configurable axes of the label attribute to a single axis.
|
|
|
|
// This is necessary for final writing to bp2build, as a configurable label
|
|
|
|
// attribute can only be comprised by a single select.
|
|
|
|
func (la *LabelAttribute) Collapse() error {
|
|
|
|
axisTypes := la.axisTypes()
|
|
|
|
_, containsOs := axisTypes[os]
|
|
|
|
_, containsArch := axisTypes[arch]
|
|
|
|
_, containsOsArch := axisTypes[osArch]
|
|
|
|
_, containsProductVariables := axisTypes[productVariables]
|
|
|
|
if containsProductVariables {
|
|
|
|
if containsOs || containsArch || containsOsArch {
|
2022-06-09 20:52:05 +02:00
|
|
|
if containsArch {
|
|
|
|
allProductVariablesAreArchVariant := true
|
|
|
|
for k := range la.ConfigurableValues {
|
|
|
|
if k.configurationType == productVariables && k.outerAxisType != arch {
|
|
|
|
allProductVariablesAreArchVariant = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !allProductVariablesAreArchVariant {
|
|
|
|
return fmt.Errorf("label attribute could not be collapsed as it has two or more unrelated axes")
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return fmt.Errorf("label attribute could not be collapsed as it has two or more unrelated axes")
|
|
|
|
}
|
2021-12-10 00:10:18 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (containsOs && containsArch) || (containsOsArch && (containsOs || containsArch)) {
|
|
|
|
// If a bool attribute has both os and arch configuration axes, the only
|
|
|
|
// way to successfully union their values is to increase the granularity
|
|
|
|
// of the configuration criteria to os_arch.
|
|
|
|
for osType, supportedArchs := range osToArchMap {
|
|
|
|
for _, supportedArch := range supportedArchs {
|
|
|
|
osArch := osArchString(osType, supportedArch)
|
|
|
|
if archOsVal := la.SelectValue(OsArchConfigurationAxis, osArch); archOsVal != nil {
|
|
|
|
// Do nothing, as the arch_os is explicitly defined already.
|
|
|
|
} else {
|
|
|
|
archVal := la.SelectValue(ArchConfigurationAxis, supportedArch)
|
|
|
|
osVal := la.SelectValue(OsConfigurationAxis, osType)
|
|
|
|
if osVal != nil && archVal != nil {
|
|
|
|
// In this case, arch takes precedence. (This fits legacy Soong behavior, as arch mutator
|
|
|
|
// runs after os mutator.
|
|
|
|
la.SetSelectValue(OsArchConfigurationAxis, osArch, *archVal)
|
|
|
|
} else if osVal != nil && archVal == nil {
|
|
|
|
la.SetSelectValue(OsArchConfigurationAxis, osArch, *osVal)
|
|
|
|
} else if osVal == nil && archVal != nil {
|
|
|
|
la.SetSelectValue(OsArchConfigurationAxis, osArch, *archVal)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// All os_arch values are now set. Clear os and arch axes.
|
|
|
|
delete(la.ConfigurableValues, ArchConfigurationAxis)
|
|
|
|
delete(la.ConfigurableValues, OsConfigurationAxis)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
// HasConfigurableValues returns whether there are configurable values set for this label.
|
|
|
|
func (la LabelAttribute) HasConfigurableValues() bool {
|
2021-12-10 00:10:18 +01:00
|
|
|
for _, selectValues := range la.ConfigurableValues {
|
|
|
|
if len(selectValues) > 0 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
2021-05-27 08:15:54 +02:00
|
|
|
}
|
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
// SetValue sets the base, non-configured value for the Label
|
|
|
|
func (la *LabelAttribute) SetValue(value Label) {
|
|
|
|
la.SetSelectValue(NoConfigAxis, "", value)
|
2021-05-27 08:15:54 +02:00
|
|
|
}
|
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
// SetSelectValue set a value for a bazel select for the given axis, config and value.
|
|
|
|
func (la *LabelAttribute) SetSelectValue(axis ConfigurationAxis, config string, value Label) {
|
|
|
|
axis.validateConfig(config)
|
|
|
|
switch axis.configurationType {
|
|
|
|
case noConfig:
|
|
|
|
la.Value = &value
|
2022-07-27 09:22:06 +02:00
|
|
|
case arch, os, osArch, productVariables, osAndInApex:
|
2021-05-21 14:37:59 +02:00
|
|
|
if la.ConfigurableValues == nil {
|
|
|
|
la.ConfigurableValues = make(configurableLabels)
|
2021-05-27 08:15:54 +02:00
|
|
|
}
|
2021-05-21 14:37:59 +02:00
|
|
|
la.ConfigurableValues.setValueForAxis(axis, config, &value)
|
2021-05-05 09:00:01 +02:00
|
|
|
default:
|
2021-05-21 14:37:59 +02:00
|
|
|
panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
|
2021-05-05 09:00:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
// SelectValue gets a value for a bazel select for the given axis and config.
|
2021-12-10 00:10:18 +01:00
|
|
|
func (la *LabelAttribute) SelectValue(axis ConfigurationAxis, config string) *Label {
|
2021-05-21 14:37:59 +02:00
|
|
|
axis.validateConfig(config)
|
|
|
|
switch axis.configurationType {
|
|
|
|
case noConfig:
|
2021-12-10 00:10:18 +01:00
|
|
|
return la.Value
|
2022-07-27 09:22:06 +02:00
|
|
|
case arch, os, osArch, productVariables, osAndInApex:
|
2021-12-10 00:10:18 +01:00
|
|
|
return la.ConfigurableValues[axis][config]
|
2021-05-05 09:00:01 +02:00
|
|
|
default:
|
2021-05-21 14:37:59 +02:00
|
|
|
panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
|
2021-05-05 09:00:01 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
// SortedConfigurationAxes returns all the used ConfigurationAxis in sorted order.
|
|
|
|
func (la *LabelAttribute) SortedConfigurationAxes() []ConfigurationAxis {
|
2023-01-26 23:30:44 +01:00
|
|
|
return SortedConfigurationAxes(la.ConfigurableValues)
|
Add os/target configurable selects for label list attributes.
This CL is pretty large, so I recommend starting with reading the newly
added tests for the expected behavior.
This change works in conjunction with the linked CLs in the Gerrit topic.
Those CLs add support for new platform() definitions for OS targets
specified in Soong's arch.go, which are configurable through
Android.bp's `target {}` property. It works similary to previous CLs
adding support for the `arch {}` property.
These configurable props are keyed by the OS: android, linux_bionic,
windows, and so on. They map to `select` statements in label list
attributes, which this CL enables for cc_library_headers' header_libs
and export_header_lib_headers props.
This enables //bionic/libc:libc_headers to be generated correctly, from:
cc_library_headers {
name: "libc_headers",
target: {
android: {
header_libs: ["libc_headers_arch"],
export_header_lib_headers: ["libc_headers_arch"],
},
linux_bionic: {
header_libs: ["libc_headers_arch"],
export_header_lib_headers: ["libc_headers_arch"],
},
},
// omitted props
}
to:
cc_library_headers(
name = "libc_headers",
deps = [] + select({
"//build/bazel/platforms/os:android": [
":libc_headers_arch",
],
"//build/bazel/platforms/os:linux_bionic": [
":libc_headers_arch",
],
"//conditions:default": [],
}),
)
Test: TH
Test: Verify generated //bionic/libc:libc_headers
Fixes: 183597786
Change-Id: I01016cc2cc9a71449f02300d747f01decebf3f6e
2021-03-24 07:18:33 +01:00
|
|
|
}
|
|
|
|
|
2022-02-25 22:34:51 +01:00
|
|
|
// MakeLabelAttribute turns a string into a LabelAttribute
|
|
|
|
func MakeLabelAttribute(label string) *LabelAttribute {
|
|
|
|
return &LabelAttribute{
|
|
|
|
Value: &Label{
|
|
|
|
Label: label,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-03 19:43:01 +02:00
|
|
|
type configToBools map[string]bool
|
|
|
|
|
|
|
|
func (ctb configToBools) setValue(config string, value *bool) {
|
|
|
|
if value == nil {
|
|
|
|
if _, ok := ctb[config]; ok {
|
|
|
|
delete(ctb, config)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ctb[config] = *value
|
|
|
|
}
|
|
|
|
|
|
|
|
type configurableBools map[ConfigurationAxis]configToBools
|
|
|
|
|
|
|
|
func (cb configurableBools) setValueForAxis(axis ConfigurationAxis, config string, value *bool) {
|
|
|
|
if cb[axis] == nil {
|
|
|
|
cb[axis] = make(configToBools)
|
|
|
|
}
|
|
|
|
cb[axis].setValue(config, value)
|
|
|
|
}
|
|
|
|
|
|
|
|
// BoolAttribute represents an attribute whose value is a single bool but may be configurable..
|
|
|
|
type BoolAttribute struct {
|
|
|
|
Value *bool
|
|
|
|
|
|
|
|
ConfigurableValues configurableBools
|
|
|
|
}
|
|
|
|
|
|
|
|
// HasConfigurableValues returns whether there are configurable values for this attribute.
|
|
|
|
func (ba BoolAttribute) HasConfigurableValues() bool {
|
2021-12-10 00:10:18 +01:00
|
|
|
for _, cfgToBools := range ba.ConfigurableValues {
|
|
|
|
if len(cfgToBools) > 0 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
2021-06-03 19:43:01 +02:00
|
|
|
}
|
|
|
|
|
2022-05-13 23:20:20 +02:00
|
|
|
// SetValue sets value for the no config axis
|
|
|
|
func (ba *BoolAttribute) SetValue(value *bool) {
|
|
|
|
ba.SetSelectValue(NoConfigAxis, "", value)
|
|
|
|
}
|
|
|
|
|
2021-06-03 19:43:01 +02:00
|
|
|
// SetSelectValue sets value for the given axis/config.
|
|
|
|
func (ba *BoolAttribute) SetSelectValue(axis ConfigurationAxis, config string, value *bool) {
|
|
|
|
axis.validateConfig(config)
|
|
|
|
switch axis.configurationType {
|
|
|
|
case noConfig:
|
|
|
|
ba.Value = value
|
2022-07-27 09:22:06 +02:00
|
|
|
case arch, os, osArch, productVariables, osAndInApex:
|
2021-06-03 19:43:01 +02:00
|
|
|
if ba.ConfigurableValues == nil {
|
|
|
|
ba.ConfigurableValues = make(configurableBools)
|
|
|
|
}
|
|
|
|
ba.ConfigurableValues.setValueForAxis(axis, config, value)
|
|
|
|
default:
|
|
|
|
panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-10 00:10:18 +01:00
|
|
|
// ToLabelListAttribute creates and returns a LabelListAttribute from this
|
|
|
|
// bool attribute, where each bool in this attribute corresponds to a
|
|
|
|
// label list value in the resultant attribute.
|
|
|
|
func (ba *BoolAttribute) ToLabelListAttribute(falseVal LabelList, trueVal LabelList) (LabelListAttribute, error) {
|
|
|
|
getLabelList := func(boolPtr *bool) LabelList {
|
|
|
|
if boolPtr == nil {
|
|
|
|
return LabelList{nil, nil}
|
|
|
|
} else if *boolPtr {
|
|
|
|
return trueVal
|
|
|
|
} else {
|
|
|
|
return falseVal
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mainVal := getLabelList(ba.Value)
|
|
|
|
if !ba.HasConfigurableValues() {
|
|
|
|
return MakeLabelListAttribute(mainVal), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
result := LabelListAttribute{}
|
|
|
|
if err := ba.Collapse(); err != nil {
|
|
|
|
return result, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for axis, configToBools := range ba.ConfigurableValues {
|
|
|
|
if len(configToBools) < 1 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
for config, boolPtr := range configToBools {
|
|
|
|
val := getLabelList(&boolPtr)
|
|
|
|
if !val.Equals(mainVal) {
|
|
|
|
result.SetSelectValue(axis, config, val)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
result.SetSelectValue(axis, ConditionsDefaultConfigKey, mainVal)
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
2023-02-06 22:58:30 +01:00
|
|
|
// ToStringListAttribute creates a StringListAttribute from this BoolAttribute,
|
|
|
|
// where each bool corresponds to a string list value generated by the provided
|
|
|
|
// function.
|
|
|
|
// TODO(b/271425661): Generalize this
|
|
|
|
func (ba *BoolAttribute) ToStringListAttribute(valueFunc func(boolPtr *bool, axis ConfigurationAxis, config string) []string) (StringListAttribute, error) {
|
|
|
|
mainVal := valueFunc(ba.Value, NoConfigAxis, "")
|
|
|
|
if !ba.HasConfigurableValues() {
|
|
|
|
return MakeStringListAttribute(mainVal), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
result := StringListAttribute{}
|
|
|
|
if err := ba.Collapse(); err != nil {
|
|
|
|
return result, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for axis, configToBools := range ba.ConfigurableValues {
|
|
|
|
if len(configToBools) < 1 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
for config, boolPtr := range configToBools {
|
|
|
|
val := valueFunc(&boolPtr, axis, config)
|
|
|
|
if !reflect.DeepEqual(val, mainVal) {
|
|
|
|
result.SetSelectValue(axis, config, val)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
result.SetSelectValue(axis, ConditionsDefaultConfigKey, mainVal)
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
2021-12-10 00:10:18 +01:00
|
|
|
// Collapse reduces the configurable axes of the boolean attribute to a single axis.
|
|
|
|
// This is necessary for final writing to bp2build, as a configurable boolean
|
|
|
|
// attribute can only be comprised by a single select.
|
|
|
|
func (ba *BoolAttribute) Collapse() error {
|
|
|
|
axisTypes := ba.axisTypes()
|
|
|
|
_, containsOs := axisTypes[os]
|
|
|
|
_, containsArch := axisTypes[arch]
|
|
|
|
_, containsOsArch := axisTypes[osArch]
|
|
|
|
_, containsProductVariables := axisTypes[productVariables]
|
|
|
|
if containsProductVariables {
|
|
|
|
if containsOs || containsArch || containsOsArch {
|
|
|
|
return fmt.Errorf("boolean attribute could not be collapsed as it has two or more unrelated axes")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (containsOs && containsArch) || (containsOsArch && (containsOs || containsArch)) {
|
|
|
|
// If a bool attribute has both os and arch configuration axes, the only
|
|
|
|
// way to successfully union their values is to increase the granularity
|
|
|
|
// of the configuration criteria to os_arch.
|
|
|
|
for osType, supportedArchs := range osToArchMap {
|
|
|
|
for _, supportedArch := range supportedArchs {
|
|
|
|
osArch := osArchString(osType, supportedArch)
|
|
|
|
if archOsVal := ba.SelectValue(OsArchConfigurationAxis, osArch); archOsVal != nil {
|
|
|
|
// Do nothing, as the arch_os is explicitly defined already.
|
|
|
|
} else {
|
|
|
|
archVal := ba.SelectValue(ArchConfigurationAxis, supportedArch)
|
|
|
|
osVal := ba.SelectValue(OsConfigurationAxis, osType)
|
|
|
|
if osVal != nil && archVal != nil {
|
|
|
|
// In this case, arch takes precedence. (This fits legacy Soong behavior, as arch mutator
|
|
|
|
// runs after os mutator.
|
|
|
|
ba.SetSelectValue(OsArchConfigurationAxis, osArch, archVal)
|
|
|
|
} else if osVal != nil && archVal == nil {
|
|
|
|
ba.SetSelectValue(OsArchConfigurationAxis, osArch, osVal)
|
|
|
|
} else if osVal == nil && archVal != nil {
|
|
|
|
ba.SetSelectValue(OsArchConfigurationAxis, osArch, archVal)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// All os_arch values are now set. Clear os and arch axes.
|
|
|
|
delete(ba.ConfigurableValues, ArchConfigurationAxis)
|
|
|
|
delete(ba.ConfigurableValues, OsConfigurationAxis)
|
|
|
|
// Verify post-condition; this should never fail, provided no additional
|
|
|
|
// axes are introduced.
|
|
|
|
if len(ba.ConfigurableValues) > 1 {
|
2022-01-13 23:00:10 +01:00
|
|
|
panic(fmt.Errorf("error in collapsing attribute: %#v", ba))
|
2021-12-10 00:10:18 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ba *BoolAttribute) axisTypes() map[configurationType]bool {
|
|
|
|
types := map[configurationType]bool{}
|
|
|
|
for k := range ba.ConfigurableValues {
|
|
|
|
if len(ba.ConfigurableValues[k]) > 0 {
|
|
|
|
types[k.configurationType] = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return types
|
|
|
|
}
|
|
|
|
|
2021-06-03 19:43:01 +02:00
|
|
|
// SelectValue gets the value for the given axis/config.
|
|
|
|
func (ba BoolAttribute) SelectValue(axis ConfigurationAxis, config string) *bool {
|
|
|
|
axis.validateConfig(config)
|
|
|
|
switch axis.configurationType {
|
|
|
|
case noConfig:
|
|
|
|
return ba.Value
|
2022-07-27 09:22:06 +02:00
|
|
|
case arch, os, osArch, productVariables, osAndInApex:
|
2021-06-03 19:43:01 +02:00
|
|
|
if v, ok := ba.ConfigurableValues[axis][config]; ok {
|
|
|
|
return &v
|
|
|
|
} else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// SortedConfigurationAxes returns all the used ConfigurationAxis in sorted order.
|
|
|
|
func (ba *BoolAttribute) SortedConfigurationAxes() []ConfigurationAxis {
|
2023-01-26 23:30:44 +01:00
|
|
|
return SortedConfigurationAxes(ba.ConfigurableValues)
|
2021-06-03 19:43:01 +02:00
|
|
|
}
|
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
// labelListSelectValues supports config-specific label_list typed Bazel attribute values.
|
|
|
|
type labelListSelectValues map[string]LabelList
|
2021-04-23 11:17:24 +02:00
|
|
|
|
2021-09-28 15:19:17 +02:00
|
|
|
func (ll labelListSelectValues) addSelects(label labelSelectValues) {
|
|
|
|
for k, v := range label {
|
|
|
|
if label == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
l := ll[k]
|
|
|
|
(&l).Add(v)
|
|
|
|
ll[k] = l
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-03 23:27:16 +01:00
|
|
|
func (ll labelListSelectValues) appendSelects(other labelListSelectValues, forceSpecifyEmptyList bool) {
|
2021-05-21 14:37:59 +02:00
|
|
|
for k, v := range other {
|
|
|
|
l := ll[k]
|
2021-12-03 23:27:16 +01:00
|
|
|
if forceSpecifyEmptyList && l.IsNil() && !v.IsNil() {
|
|
|
|
l.Includes = []Label{}
|
|
|
|
}
|
2021-05-21 14:37:59 +02:00
|
|
|
(&l).Append(v)
|
|
|
|
ll[k] = l
|
|
|
|
}
|
2021-05-19 12:49:02 +02:00
|
|
|
}
|
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
// HasConfigurableValues returns whether there are configurable values within this set of selects.
|
|
|
|
func (ll labelListSelectValues) HasConfigurableValues() bool {
|
|
|
|
for _, v := range ll {
|
bp2build: handle system_shared_libs
- If no system_shared_libs is specified, bp2build writes no attribute
value. In this case, the bazel library macros determine the correct
default behavior.
- If any system_shared_libs is specified for any variant, then bp2build
writes the value verbatim. This includes if an empty list is specified,
as this should override defaulting behavior.
Note this defaulting behavior is incomplete and will be incorrect in
corner cases. For example, if, in an Android.bp, system_shared_libs is
specified for os.linux_bionic but not for os.android, then the bazel
default for os.android will be incorrect. However, there are no current
modules in AOSP which fit this case.
As a related fix, supports static struct for cc_library_static.
Also, removes some elements from the bp2build denylist.
Test: mixed_droid CI
Change-Id: Iee5feeaaf05e8e7209c7a90c913173832ad7bf91
2021-08-04 03:01:05 +02:00
|
|
|
if v.Includes != nil {
|
2021-05-21 14:37:59 +02:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
2021-03-15 11:02:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// LabelListAttribute is used to represent a list of Bazel labels as an
|
|
|
|
// attribute.
|
|
|
|
type LabelListAttribute struct {
|
2021-05-21 14:37:59 +02:00
|
|
|
// The non-configured attribute label list Value. Required.
|
2021-03-15 11:02:43 +01:00
|
|
|
Value LabelList
|
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
// The configured attribute label list Values. Optional
|
|
|
|
// a map of independent configurability axes
|
|
|
|
ConfigurableValues configurableLabelLists
|
bp2build: handle system_shared_libs
- If no system_shared_libs is specified, bp2build writes no attribute
value. In this case, the bazel library macros determine the correct
default behavior.
- If any system_shared_libs is specified for any variant, then bp2build
writes the value verbatim. This includes if an empty list is specified,
as this should override defaulting behavior.
Note this defaulting behavior is incomplete and will be incorrect in
corner cases. For example, if, in an Android.bp, system_shared_libs is
specified for os.linux_bionic but not for os.android, then the bazel
default for os.android will be incorrect. However, there are no current
modules in AOSP which fit this case.
As a related fix, supports static struct for cc_library_static.
Also, removes some elements from the bp2build denylist.
Test: mixed_droid CI
Change-Id: Iee5feeaaf05e8e7209c7a90c913173832ad7bf91
2021-08-04 03:01:05 +02:00
|
|
|
|
|
|
|
// If true, differentiate between "nil" and "empty" list. nil means that
|
|
|
|
// this attribute should not be specified at all, and "empty" means that
|
|
|
|
// the attribute should be explicitly specified as an empty list.
|
|
|
|
// This mode facilitates use of attribute defaults: an empty list should
|
|
|
|
// override the default.
|
|
|
|
ForceSpecifyEmptyList bool
|
2021-11-17 13:14:41 +01:00
|
|
|
|
|
|
|
// If true, signal the intent to the code generator to emit all select keys,
|
|
|
|
// even if the Includes list for that key is empty. This mode facilitates
|
|
|
|
// specific select statements where an empty list for a non-default select
|
|
|
|
// key has a meaning.
|
|
|
|
EmitEmptyList bool
|
2023-01-04 20:06:54 +01:00
|
|
|
|
|
|
|
// If a property has struct tag "variant_prepend", this value should
|
|
|
|
// be set to True, so that when bp2build generates BUILD.bazel, variant
|
|
|
|
// properties(select ...) come before general properties.
|
|
|
|
Prepend bool
|
2021-03-24 15:04:33 +01:00
|
|
|
}
|
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
type configurableLabelLists map[ConfigurationAxis]labelListSelectValues
|
Add os/target configurable selects for label list attributes.
This CL is pretty large, so I recommend starting with reading the newly
added tests for the expected behavior.
This change works in conjunction with the linked CLs in the Gerrit topic.
Those CLs add support for new platform() definitions for OS targets
specified in Soong's arch.go, which are configurable through
Android.bp's `target {}` property. It works similary to previous CLs
adding support for the `arch {}` property.
These configurable props are keyed by the OS: android, linux_bionic,
windows, and so on. They map to `select` statements in label list
attributes, which this CL enables for cc_library_headers' header_libs
and export_header_lib_headers props.
This enables //bionic/libc:libc_headers to be generated correctly, from:
cc_library_headers {
name: "libc_headers",
target: {
android: {
header_libs: ["libc_headers_arch"],
export_header_lib_headers: ["libc_headers_arch"],
},
linux_bionic: {
header_libs: ["libc_headers_arch"],
export_header_lib_headers: ["libc_headers_arch"],
},
},
// omitted props
}
to:
cc_library_headers(
name = "libc_headers",
deps = [] + select({
"//build/bazel/platforms/os:android": [
":libc_headers_arch",
],
"//build/bazel/platforms/os:linux_bionic": [
":libc_headers_arch",
],
"//conditions:default": [],
}),
)
Test: TH
Test: Verify generated //bionic/libc:libc_headers
Fixes: 183597786
Change-Id: I01016cc2cc9a71449f02300d747f01decebf3f6e
2021-03-24 07:18:33 +01:00
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
func (cll configurableLabelLists) setValueForAxis(axis ConfigurationAxis, config string, list LabelList) {
|
|
|
|
if list.IsNil() {
|
|
|
|
if _, ok := cll[axis][config]; ok {
|
|
|
|
delete(cll[axis], config)
|
2021-05-19 12:49:02 +02:00
|
|
|
}
|
2021-05-21 14:37:59 +02:00
|
|
|
return
|
2021-03-15 11:02:43 +01:00
|
|
|
}
|
2021-05-21 14:37:59 +02:00
|
|
|
if cll[axis] == nil {
|
|
|
|
cll[axis] = make(labelListSelectValues)
|
Add os/target configurable selects for label list attributes.
This CL is pretty large, so I recommend starting with reading the newly
added tests for the expected behavior.
This change works in conjunction with the linked CLs in the Gerrit topic.
Those CLs add support for new platform() definitions for OS targets
specified in Soong's arch.go, which are configurable through
Android.bp's `target {}` property. It works similary to previous CLs
adding support for the `arch {}` property.
These configurable props are keyed by the OS: android, linux_bionic,
windows, and so on. They map to `select` statements in label list
attributes, which this CL enables for cc_library_headers' header_libs
and export_header_lib_headers props.
This enables //bionic/libc:libc_headers to be generated correctly, from:
cc_library_headers {
name: "libc_headers",
target: {
android: {
header_libs: ["libc_headers_arch"],
export_header_lib_headers: ["libc_headers_arch"],
},
linux_bionic: {
header_libs: ["libc_headers_arch"],
export_header_lib_headers: ["libc_headers_arch"],
},
},
// omitted props
}
to:
cc_library_headers(
name = "libc_headers",
deps = [] + select({
"//build/bazel/platforms/os:android": [
":libc_headers_arch",
],
"//build/bazel/platforms/os:linux_bionic": [
":libc_headers_arch",
],
"//conditions:default": [],
}),
)
Test: TH
Test: Verify generated //bionic/libc:libc_headers
Fixes: 183597786
Change-Id: I01016cc2cc9a71449f02300d747f01decebf3f6e
2021-03-24 07:18:33 +01:00
|
|
|
}
|
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
cll[axis][config] = list
|
2021-03-15 11:02:43 +01:00
|
|
|
}
|
|
|
|
|
2021-12-03 23:27:16 +01:00
|
|
|
func (cll configurableLabelLists) Append(other configurableLabelLists, forceSpecifyEmptyList bool) {
|
2021-05-21 14:37:59 +02:00
|
|
|
for axis, otherSelects := range other {
|
|
|
|
selects := cll[axis]
|
|
|
|
if selects == nil {
|
|
|
|
selects = make(labelListSelectValues, len(otherSelects))
|
|
|
|
}
|
2021-12-03 23:27:16 +01:00
|
|
|
selects.appendSelects(otherSelects, forceSpecifyEmptyList)
|
2021-05-21 14:37:59 +02:00
|
|
|
cll[axis] = selects
|
2021-03-15 11:02:43 +01:00
|
|
|
}
|
Add os/target configurable selects for label list attributes.
This CL is pretty large, so I recommend starting with reading the newly
added tests for the expected behavior.
This change works in conjunction with the linked CLs in the Gerrit topic.
Those CLs add support for new platform() definitions for OS targets
specified in Soong's arch.go, which are configurable through
Android.bp's `target {}` property. It works similary to previous CLs
adding support for the `arch {}` property.
These configurable props are keyed by the OS: android, linux_bionic,
windows, and so on. They map to `select` statements in label list
attributes, which this CL enables for cc_library_headers' header_libs
and export_header_lib_headers props.
This enables //bionic/libc:libc_headers to be generated correctly, from:
cc_library_headers {
name: "libc_headers",
target: {
android: {
header_libs: ["libc_headers_arch"],
export_header_lib_headers: ["libc_headers_arch"],
},
linux_bionic: {
header_libs: ["libc_headers_arch"],
export_header_lib_headers: ["libc_headers_arch"],
},
},
// omitted props
}
to:
cc_library_headers(
name = "libc_headers",
deps = [] + select({
"//build/bazel/platforms/os:android": [
":libc_headers_arch",
],
"//build/bazel/platforms/os:linux_bionic": [
":libc_headers_arch",
],
"//conditions:default": [],
}),
)
Test: TH
Test: Verify generated //bionic/libc:libc_headers
Fixes: 183597786
Change-Id: I01016cc2cc9a71449f02300d747f01decebf3f6e
2021-03-24 07:18:33 +01:00
|
|
|
}
|
|
|
|
|
2021-12-03 23:27:16 +01:00
|
|
|
func (lla *LabelListAttribute) Clone() *LabelListAttribute {
|
|
|
|
result := &LabelListAttribute{ForceSpecifyEmptyList: lla.ForceSpecifyEmptyList}
|
|
|
|
return result.Append(*lla)
|
|
|
|
}
|
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
// MakeLabelListAttribute initializes a LabelListAttribute with the non-arch specific value.
|
|
|
|
func MakeLabelListAttribute(value LabelList) LabelListAttribute {
|
|
|
|
return LabelListAttribute{
|
|
|
|
Value: value,
|
|
|
|
ConfigurableValues: make(configurableLabelLists),
|
Add os/target configurable selects for label list attributes.
This CL is pretty large, so I recommend starting with reading the newly
added tests for the expected behavior.
This change works in conjunction with the linked CLs in the Gerrit topic.
Those CLs add support for new platform() definitions for OS targets
specified in Soong's arch.go, which are configurable through
Android.bp's `target {}` property. It works similary to previous CLs
adding support for the `arch {}` property.
These configurable props are keyed by the OS: android, linux_bionic,
windows, and so on. They map to `select` statements in label list
attributes, which this CL enables for cc_library_headers' header_libs
and export_header_lib_headers props.
This enables //bionic/libc:libc_headers to be generated correctly, from:
cc_library_headers {
name: "libc_headers",
target: {
android: {
header_libs: ["libc_headers_arch"],
export_header_lib_headers: ["libc_headers_arch"],
},
linux_bionic: {
header_libs: ["libc_headers_arch"],
export_header_lib_headers: ["libc_headers_arch"],
},
},
// omitted props
}
to:
cc_library_headers(
name = "libc_headers",
deps = [] + select({
"//build/bazel/platforms/os:android": [
":libc_headers_arch",
],
"//build/bazel/platforms/os:linux_bionic": [
":libc_headers_arch",
],
"//conditions:default": [],
}),
)
Test: TH
Test: Verify generated //bionic/libc:libc_headers
Fixes: 183597786
Change-Id: I01016cc2cc9a71449f02300d747f01decebf3f6e
2021-03-24 07:18:33 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-13 00:37:02 +02:00
|
|
|
// MakeSingleLabelListAttribute initializes a LabelListAttribute as a non-arch specific list with 1 element, the given Label.
|
|
|
|
func MakeSingleLabelListAttribute(value Label) LabelListAttribute {
|
|
|
|
return MakeLabelListAttribute(MakeLabelList([]Label{value}))
|
|
|
|
}
|
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
func (lla *LabelListAttribute) SetValue(list LabelList) {
|
|
|
|
lla.SetSelectValue(NoConfigAxis, "", list)
|
Add os/target configurable selects for label list attributes.
This CL is pretty large, so I recommend starting with reading the newly
added tests for the expected behavior.
This change works in conjunction with the linked CLs in the Gerrit topic.
Those CLs add support for new platform() definitions for OS targets
specified in Soong's arch.go, which are configurable through
Android.bp's `target {}` property. It works similary to previous CLs
adding support for the `arch {}` property.
These configurable props are keyed by the OS: android, linux_bionic,
windows, and so on. They map to `select` statements in label list
attributes, which this CL enables for cc_library_headers' header_libs
and export_header_lib_headers props.
This enables //bionic/libc:libc_headers to be generated correctly, from:
cc_library_headers {
name: "libc_headers",
target: {
android: {
header_libs: ["libc_headers_arch"],
export_header_lib_headers: ["libc_headers_arch"],
},
linux_bionic: {
header_libs: ["libc_headers_arch"],
export_header_lib_headers: ["libc_headers_arch"],
},
},
// omitted props
}
to:
cc_library_headers(
name = "libc_headers",
deps = [] + select({
"//build/bazel/platforms/os:android": [
":libc_headers_arch",
],
"//build/bazel/platforms/os:linux_bionic": [
":libc_headers_arch",
],
"//conditions:default": [],
}),
)
Test: TH
Test: Verify generated //bionic/libc:libc_headers
Fixes: 183597786
Change-Id: I01016cc2cc9a71449f02300d747f01decebf3f6e
2021-03-24 07:18:33 +01:00
|
|
|
}
|
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
// SetSelectValue set a value for a bazel select for the given axis, config and value.
|
|
|
|
func (lla *LabelListAttribute) SetSelectValue(axis ConfigurationAxis, config string, list LabelList) {
|
|
|
|
axis.validateConfig(config)
|
|
|
|
switch axis.configurationType {
|
|
|
|
case noConfig:
|
|
|
|
lla.Value = list
|
2022-09-16 22:17:48 +02:00
|
|
|
case arch, os, osArch, productVariables, osAndInApex, inApex:
|
2021-05-21 14:37:59 +02:00
|
|
|
if lla.ConfigurableValues == nil {
|
|
|
|
lla.ConfigurableValues = make(configurableLabelLists)
|
|
|
|
}
|
|
|
|
lla.ConfigurableValues.setValueForAxis(axis, config, list)
|
|
|
|
default:
|
|
|
|
panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
|
2021-05-19 12:49:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
// SelectValue gets a value for a bazel select for the given axis and config.
|
|
|
|
func (lla *LabelListAttribute) SelectValue(axis ConfigurationAxis, config string) LabelList {
|
|
|
|
axis.validateConfig(config)
|
|
|
|
switch axis.configurationType {
|
|
|
|
case noConfig:
|
|
|
|
return lla.Value
|
2022-09-16 22:17:48 +02:00
|
|
|
case arch, os, osArch, productVariables, osAndInApex, inApex:
|
2022-08-03 03:06:50 +02:00
|
|
|
return lla.ConfigurableValues[axis][config]
|
2021-05-19 12:49:02 +02:00
|
|
|
default:
|
2021-05-21 14:37:59 +02:00
|
|
|
panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
|
2021-05-19 12:49:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
// SortedConfigurationAxes returns all the used ConfigurationAxis in sorted order.
|
|
|
|
func (lla *LabelListAttribute) SortedConfigurationAxes() []ConfigurationAxis {
|
2023-01-26 23:30:44 +01:00
|
|
|
return SortedConfigurationAxes(lla.ConfigurableValues)
|
2021-03-15 11:02:43 +01:00
|
|
|
}
|
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
// Append all values, including os and arch specific ones, from another
|
2021-12-03 23:27:16 +01:00
|
|
|
// LabelListAttribute to this LabelListAttribute. Returns this LabelListAttribute.
|
|
|
|
func (lla *LabelListAttribute) Append(other LabelListAttribute) *LabelListAttribute {
|
|
|
|
forceSpecifyEmptyList := lla.ForceSpecifyEmptyList || other.ForceSpecifyEmptyList
|
|
|
|
if forceSpecifyEmptyList && lla.Value.IsNil() && !other.Value.IsNil() {
|
bp2build: handle system_shared_libs
- If no system_shared_libs is specified, bp2build writes no attribute
value. In this case, the bazel library macros determine the correct
default behavior.
- If any system_shared_libs is specified for any variant, then bp2build
writes the value verbatim. This includes if an empty list is specified,
as this should override defaulting behavior.
Note this defaulting behavior is incomplete and will be incorrect in
corner cases. For example, if, in an Android.bp, system_shared_libs is
specified for os.linux_bionic but not for os.android, then the bazel
default for os.android will be incorrect. However, there are no current
modules in AOSP which fit this case.
As a related fix, supports static struct for cc_library_static.
Also, removes some elements from the bp2build denylist.
Test: mixed_droid CI
Change-Id: Iee5feeaaf05e8e7209c7a90c913173832ad7bf91
2021-08-04 03:01:05 +02:00
|
|
|
lla.Value.Includes = []Label{}
|
|
|
|
}
|
2021-05-21 14:37:59 +02:00
|
|
|
lla.Value.Append(other.Value)
|
|
|
|
if lla.ConfigurableValues == nil {
|
|
|
|
lla.ConfigurableValues = make(configurableLabelLists)
|
2021-05-19 12:49:02 +02:00
|
|
|
}
|
2021-12-03 23:27:16 +01:00
|
|
|
lla.ConfigurableValues.Append(other.ConfigurableValues, forceSpecifyEmptyList)
|
|
|
|
return lla
|
2021-05-19 12:49:02 +02:00
|
|
|
}
|
|
|
|
|
2021-09-28 15:19:17 +02:00
|
|
|
// Add inserts the labels for each axis of LabelAttribute at the end of corresponding axis's
|
|
|
|
// LabelList within the LabelListAttribute
|
|
|
|
func (lla *LabelListAttribute) Add(label *LabelAttribute) {
|
|
|
|
if label == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
lla.Value.Add(label.Value)
|
|
|
|
if lla.ConfigurableValues == nil && label.ConfigurableValues != nil {
|
|
|
|
lla.ConfigurableValues = make(configurableLabelLists)
|
|
|
|
}
|
|
|
|
for axis, _ := range label.ConfigurableValues {
|
|
|
|
if _, exists := lla.ConfigurableValues[axis]; !exists {
|
|
|
|
lla.ConfigurableValues[axis] = make(labelListSelectValues)
|
|
|
|
}
|
|
|
|
lla.ConfigurableValues[axis].addSelects(label.ConfigurableValues[axis])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
// HasConfigurableValues returns true if the attribute contains axis-specific label list values.
|
|
|
|
func (lla LabelListAttribute) HasConfigurableValues() bool {
|
2021-12-10 00:10:18 +01:00
|
|
|
for _, selectValues := range lla.ConfigurableValues {
|
|
|
|
if len(selectValues) > 0 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
2021-05-19 12:49:02 +02:00
|
|
|
}
|
|
|
|
|
2022-12-12 23:26:34 +01:00
|
|
|
// HasAxisSpecificValues returns true if the attribute contains axis specific label list values from a given axis
|
|
|
|
func (lla LabelListAttribute) HasAxisSpecificValues(axis ConfigurationAxis) bool {
|
|
|
|
for _, values := range lla.ConfigurableValues[axis] {
|
|
|
|
if !values.IsNil() {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2021-07-13 17:47:44 +02:00
|
|
|
// IsEmpty returns true if the attribute has no values under any configuration.
|
|
|
|
func (lla LabelListAttribute) IsEmpty() bool {
|
|
|
|
if len(lla.Value.Includes) > 0 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
for axis, _ := range lla.ConfigurableValues {
|
|
|
|
if lla.ConfigurableValues[axis].HasConfigurableValues() {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2021-12-14 18:21:22 +01:00
|
|
|
// IsNil returns true if the attribute has not been set for any configuration.
|
|
|
|
func (lla LabelListAttribute) IsNil() bool {
|
|
|
|
if lla.Value.Includes != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return !lla.HasConfigurableValues()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Exclude for the given axis, config, removes Includes in labelList from Includes and appends them
|
|
|
|
// to Excludes. This is to special case any excludes that are not specified in a bp file but need to
|
|
|
|
// be removed, e.g. if they could cause duplicate element failures.
|
|
|
|
func (lla *LabelListAttribute) Exclude(axis ConfigurationAxis, config string, labelList LabelList) {
|
|
|
|
val := lla.SelectValue(axis, config)
|
|
|
|
newList := SubtractBazelLabelList(val, labelList)
|
|
|
|
newList.Excludes = append(newList.Excludes, labelList.Includes...)
|
|
|
|
lla.SetSelectValue(axis, config, newList)
|
|
|
|
}
|
|
|
|
|
2021-06-02 19:02:03 +02:00
|
|
|
// ResolveExcludes handles excludes across the various axes, ensuring that items are removed from
|
|
|
|
// the base value and included in default values as appropriate.
|
|
|
|
func (lla *LabelListAttribute) ResolveExcludes() {
|
2022-11-23 15:42:05 +01:00
|
|
|
// If there are OsAndInApexAxis, we need to use
|
|
|
|
// * includes from the OS & in APEX Axis for non-Android configs for libraries that need to be
|
|
|
|
// included in non-Android OSes
|
|
|
|
// * excludes from the OS Axis for non-Android configs, to exclude libraries that should _not_
|
|
|
|
// be included in the non-Android OSes
|
|
|
|
if _, ok := lla.ConfigurableValues[OsAndInApexAxis]; ok {
|
|
|
|
inApexLabels := lla.ConfigurableValues[OsAndInApexAxis][ConditionsDefaultConfigKey]
|
|
|
|
for config, labels := range lla.ConfigurableValues[OsConfigurationAxis] {
|
|
|
|
// OsAndroid has already handled its excludes.
|
|
|
|
// We only need to copy the excludes from other arches, so if there are none, skip it.
|
|
|
|
if config == OsAndroid || len(labels.Excludes) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
lla.ConfigurableValues[OsAndInApexAxis][config] = LabelList{
|
|
|
|
Includes: inApexLabels.Includes,
|
|
|
|
Excludes: labels.Excludes,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-02 19:02:03 +02:00
|
|
|
for axis, configToLabels := range lla.ConfigurableValues {
|
|
|
|
baseLabels := lla.Value.deepCopy()
|
|
|
|
for config, val := range configToLabels {
|
|
|
|
// Exclude config-specific excludes from base value
|
|
|
|
lla.Value = SubtractBazelLabelList(lla.Value, LabelList{Includes: val.Excludes})
|
|
|
|
|
|
|
|
// add base values to config specific to add labels excluded by others in this axis
|
|
|
|
// then remove all config-specific excludes
|
|
|
|
allLabels := baseLabels.deepCopy()
|
|
|
|
allLabels.Append(val)
|
2023-01-25 18:07:43 +01:00
|
|
|
lla.ConfigurableValues[axis][config] = SubtractBazelLabelList(allLabels, LabelList{Includes: allLabels.Excludes})
|
2021-06-02 19:02:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// After going through all configs, delete the duplicates in the config
|
|
|
|
// values that are already in the base Value.
|
|
|
|
for config, val := range configToLabels {
|
|
|
|
lla.ConfigurableValues[axis][config] = SubtractBazelLabelList(val, lla.Value)
|
|
|
|
}
|
|
|
|
|
2021-11-02 11:27:17 +01:00
|
|
|
// Now that the Value list is finalized for this axis, compare it with
|
|
|
|
// the original list, and union the difference with the default
|
|
|
|
// condition for the axis.
|
|
|
|
difference := SubtractBazelLabelList(baseLabels, lla.Value)
|
|
|
|
existingDefaults := lla.ConfigurableValues[axis][ConditionsDefaultConfigKey]
|
|
|
|
existingDefaults.Append(difference)
|
|
|
|
lla.ConfigurableValues[axis][ConditionsDefaultConfigKey] = FirstUniqueBazelLabelList(existingDefaults)
|
2021-06-02 19:02:03 +02:00
|
|
|
|
|
|
|
// if everything ends up without includes, just delete the axis
|
|
|
|
if !lla.ConfigurableValues[axis].HasConfigurableValues() {
|
|
|
|
delete(lla.ConfigurableValues, axis)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-25 20:43:54 +02:00
|
|
|
// Partition splits a LabelListAttribute into two LabelListAttributes depending
|
|
|
|
// on the return value of the predicate.
|
|
|
|
// This function preserves the Includes and Excludes, but it does not provide
|
|
|
|
// that information to the partition function.
|
|
|
|
func (lla LabelListAttribute) Partition(predicate func(label Label) bool) (LabelListAttribute, LabelListAttribute) {
|
|
|
|
predicated := LabelListAttribute{}
|
|
|
|
unpredicated := LabelListAttribute{}
|
|
|
|
|
|
|
|
valuePartitionTrue, valuePartitionFalse := lla.Value.Partition(predicate)
|
|
|
|
predicated.SetValue(valuePartitionTrue)
|
|
|
|
unpredicated.SetValue(valuePartitionFalse)
|
|
|
|
|
|
|
|
for axis, selectValueLabelLists := range lla.ConfigurableValues {
|
|
|
|
for config, labelList := range selectValueLabelLists {
|
|
|
|
configPredicated, configUnpredicated := labelList.Partition(predicate)
|
|
|
|
predicated.SetSelectValue(axis, config, configPredicated)
|
|
|
|
unpredicated.SetSelectValue(axis, config, configUnpredicated)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return predicated, unpredicated
|
|
|
|
}
|
|
|
|
|
2021-09-20 18:55:02 +02:00
|
|
|
// OtherModuleContext is a limited context that has methods with information about other modules.
|
|
|
|
type OtherModuleContext interface {
|
|
|
|
ModuleFromName(name string) (blueprint.Module, bool)
|
|
|
|
OtherModuleType(m blueprint.Module) string
|
|
|
|
OtherModuleName(m blueprint.Module) string
|
|
|
|
OtherModuleDir(m blueprint.Module) string
|
|
|
|
ModuleErrorf(fmt string, args ...interface{})
|
|
|
|
}
|
|
|
|
|
|
|
|
// LabelMapper is a function that takes a OtherModuleContext and returns a (potentially changed)
|
|
|
|
// label and whether it was changed.
|
2021-09-28 15:19:17 +02:00
|
|
|
type LabelMapper func(OtherModuleContext, Label) (string, bool)
|
2021-09-20 18:55:02 +02:00
|
|
|
|
|
|
|
// LabelPartition contains descriptions of a partition for labels
|
|
|
|
type LabelPartition struct {
|
|
|
|
// Extensions to include in this partition
|
|
|
|
Extensions []string
|
|
|
|
// LabelMapper is a function that can map a label to a new label, and indicate whether to include
|
|
|
|
// the mapped label in the partition
|
|
|
|
LabelMapper LabelMapper
|
|
|
|
// Whether to store files not included in any other partition in a group of LabelPartitions
|
|
|
|
// Only one partition in a group of LabelPartitions can enabled Keep_remainder
|
|
|
|
Keep_remainder bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// LabelPartitions is a map of partition name to a LabelPartition describing the elements of the
|
|
|
|
// partition
|
|
|
|
type LabelPartitions map[string]LabelPartition
|
|
|
|
|
|
|
|
// filter returns a pointer to a label if the label should be included in the partition or nil if
|
|
|
|
// not.
|
|
|
|
func (lf LabelPartition) filter(ctx OtherModuleContext, label Label) *Label {
|
|
|
|
if lf.LabelMapper != nil {
|
2021-09-28 15:19:17 +02:00
|
|
|
if newLabel, changed := lf.LabelMapper(ctx, label); changed {
|
2021-09-20 18:55:02 +02:00
|
|
|
return &Label{newLabel, label.OriginalModuleName}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, ext := range lf.Extensions {
|
|
|
|
if strings.HasSuffix(label.Label, ext) {
|
|
|
|
return &label
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// PartitionToLabelListAttribute is map of partition name to a LabelListAttribute
|
|
|
|
type PartitionToLabelListAttribute map[string]LabelListAttribute
|
|
|
|
|
|
|
|
type partitionToLabelList map[string]*LabelList
|
|
|
|
|
|
|
|
func (p partitionToLabelList) appendIncludes(partition string, label Label) {
|
|
|
|
if _, ok := p[partition]; !ok {
|
|
|
|
p[partition] = &LabelList{}
|
|
|
|
}
|
|
|
|
p[partition].Includes = append(p[partition].Includes, label)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p partitionToLabelList) excludes(partition string, excludes []Label) {
|
|
|
|
if _, ok := p[partition]; !ok {
|
|
|
|
p[partition] = &LabelList{}
|
|
|
|
}
|
|
|
|
p[partition].Excludes = excludes
|
|
|
|
}
|
|
|
|
|
|
|
|
// PartitionLabelListAttribute partitions a LabelListAttribute into the requested partitions
|
|
|
|
func PartitionLabelListAttribute(ctx OtherModuleContext, lla *LabelListAttribute, partitions LabelPartitions) PartitionToLabelListAttribute {
|
|
|
|
ret := PartitionToLabelListAttribute{}
|
|
|
|
var partitionNames []string
|
|
|
|
// Stored as a pointer to distinguish nil (no remainder partition) from empty string partition
|
|
|
|
var remainderPartition *string
|
|
|
|
for p, f := range partitions {
|
|
|
|
partitionNames = append(partitionNames, p)
|
|
|
|
if f.Keep_remainder {
|
|
|
|
if remainderPartition != nil {
|
|
|
|
panic("only one partition can store the remainder")
|
|
|
|
}
|
|
|
|
// If we take the address of p in a loop, we'll end up with the last value of p in
|
|
|
|
// remainderPartition, we want the requested partition
|
|
|
|
capturePartition := p
|
|
|
|
remainderPartition = &capturePartition
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
partitionLabelList := func(axis ConfigurationAxis, config string) {
|
|
|
|
value := lla.SelectValue(axis, config)
|
|
|
|
partitionToLabels := partitionToLabelList{}
|
|
|
|
for _, item := range value.Includes {
|
|
|
|
wasFiltered := false
|
|
|
|
var inPartition *string
|
|
|
|
for partition, f := range partitions {
|
|
|
|
filtered := f.filter(ctx, item)
|
|
|
|
if filtered == nil {
|
|
|
|
// did not match this filter, keep looking
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
wasFiltered = true
|
|
|
|
partitionToLabels.appendIncludes(partition, *filtered)
|
|
|
|
// don't need to check other partitions if this filter used the item,
|
|
|
|
// continue checking if mapped to another name
|
|
|
|
if *filtered == item {
|
|
|
|
if inPartition != nil {
|
|
|
|
ctx.ModuleErrorf("%q was found in multiple partitions: %q, %q", item.Label, *inPartition, partition)
|
|
|
|
}
|
|
|
|
capturePartition := partition
|
|
|
|
inPartition = &capturePartition
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if not specified in a partition, add to remainder partition if one exists
|
|
|
|
if !wasFiltered && remainderPartition != nil {
|
|
|
|
partitionToLabels.appendIncludes(*remainderPartition, item)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ensure empty lists are maintained
|
|
|
|
if value.Excludes != nil {
|
|
|
|
for _, partition := range partitionNames {
|
|
|
|
partitionToLabels.excludes(partition, value.Excludes)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for partition, list := range partitionToLabels {
|
|
|
|
val := ret[partition]
|
|
|
|
(&val).SetSelectValue(axis, config, *list)
|
|
|
|
ret[partition] = val
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
partitionLabelList(NoConfigAxis, "")
|
|
|
|
for axis, configToList := range lla.ConfigurableValues {
|
|
|
|
for config, _ := range configToList {
|
|
|
|
partitionLabelList(axis, config)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
2022-06-23 18:02:44 +02:00
|
|
|
// StringAttribute corresponds to the string Bazel attribute type with
|
|
|
|
// support for additional metadata, like configurations.
|
|
|
|
type StringAttribute struct {
|
|
|
|
// The base value of the string attribute.
|
|
|
|
Value *string
|
|
|
|
|
|
|
|
// The configured attribute label list Values. Optional
|
|
|
|
// a map of independent configurability axes
|
|
|
|
ConfigurableValues configurableStrings
|
|
|
|
}
|
|
|
|
|
|
|
|
type configurableStrings map[ConfigurationAxis]stringSelectValues
|
|
|
|
|
|
|
|
func (cs configurableStrings) setValueForAxis(axis ConfigurationAxis, config string, str *string) {
|
|
|
|
if cs[axis] == nil {
|
|
|
|
cs[axis] = make(stringSelectValues)
|
|
|
|
}
|
2022-12-21 20:51:37 +01:00
|
|
|
cs[axis][config] = str
|
2022-06-23 18:02:44 +02:00
|
|
|
}
|
|
|
|
|
2022-12-21 20:51:37 +01:00
|
|
|
type stringSelectValues map[string]*string
|
2022-06-23 18:02:44 +02:00
|
|
|
|
|
|
|
// HasConfigurableValues returns true if the attribute contains axis-specific string values.
|
|
|
|
func (sa StringAttribute) HasConfigurableValues() bool {
|
|
|
|
for _, selectValues := range sa.ConfigurableValues {
|
|
|
|
if len(selectValues) > 0 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2022-12-21 20:51:37 +01:00
|
|
|
// SetValue sets the base, non-configured value for the Label
|
|
|
|
func (sa *StringAttribute) SetValue(value string) {
|
|
|
|
sa.SetSelectValue(NoConfigAxis, "", &value)
|
|
|
|
}
|
|
|
|
|
2022-06-23 18:02:44 +02:00
|
|
|
// SetSelectValue set a value for a bazel select for the given axis, config and value.
|
|
|
|
func (sa *StringAttribute) SetSelectValue(axis ConfigurationAxis, config string, str *string) {
|
|
|
|
axis.validateConfig(config)
|
|
|
|
switch axis.configurationType {
|
|
|
|
case noConfig:
|
|
|
|
sa.Value = str
|
|
|
|
case arch, os, osArch, productVariables:
|
|
|
|
if sa.ConfigurableValues == nil {
|
|
|
|
sa.ConfigurableValues = make(configurableStrings)
|
|
|
|
}
|
|
|
|
sa.ConfigurableValues.setValueForAxis(axis, config, str)
|
|
|
|
default:
|
|
|
|
panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// SelectValue gets a value for a bazel select for the given axis and config.
|
|
|
|
func (sa *StringAttribute) SelectValue(axis ConfigurationAxis, config string) *string {
|
|
|
|
axis.validateConfig(config)
|
|
|
|
switch axis.configurationType {
|
|
|
|
case noConfig:
|
|
|
|
return sa.Value
|
|
|
|
case arch, os, osArch, productVariables:
|
|
|
|
if v, ok := sa.ConfigurableValues[axis][config]; ok {
|
2022-12-21 20:51:37 +01:00
|
|
|
return v
|
2022-06-23 18:02:44 +02:00
|
|
|
} else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// SortedConfigurationAxes returns all the used ConfigurationAxis in sorted order.
|
|
|
|
func (sa *StringAttribute) SortedConfigurationAxes() []ConfigurationAxis {
|
2023-01-26 23:30:44 +01:00
|
|
|
return SortedConfigurationAxes(sa.ConfigurableValues)
|
2022-06-23 18:02:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Collapse reduces the configurable axes of the string attribute to a single axis.
|
|
|
|
// This is necessary for final writing to bp2build, as a configurable string
|
|
|
|
// attribute can only be comprised by a single select.
|
|
|
|
func (sa *StringAttribute) Collapse() error {
|
|
|
|
axisTypes := sa.axisTypes()
|
|
|
|
_, containsOs := axisTypes[os]
|
|
|
|
_, containsArch := axisTypes[arch]
|
|
|
|
_, containsOsArch := axisTypes[osArch]
|
|
|
|
_, containsProductVariables := axisTypes[productVariables]
|
|
|
|
if containsProductVariables {
|
|
|
|
if containsOs || containsArch || containsOsArch {
|
2022-12-21 20:51:37 +01:00
|
|
|
return fmt.Errorf("string attribute could not be collapsed as it has two or more unrelated axes")
|
2022-06-23 18:02:44 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (containsOs && containsArch) || (containsOsArch && (containsOs || containsArch)) {
|
|
|
|
// If a bool attribute has both os and arch configuration axes, the only
|
|
|
|
// way to successfully union their values is to increase the granularity
|
|
|
|
// of the configuration criteria to os_arch.
|
|
|
|
for osType, supportedArchs := range osToArchMap {
|
|
|
|
for _, supportedArch := range supportedArchs {
|
|
|
|
osArch := osArchString(osType, supportedArch)
|
|
|
|
if archOsVal := sa.SelectValue(OsArchConfigurationAxis, osArch); archOsVal != nil {
|
|
|
|
// Do nothing, as the arch_os is explicitly defined already.
|
|
|
|
} else {
|
|
|
|
archVal := sa.SelectValue(ArchConfigurationAxis, supportedArch)
|
|
|
|
osVal := sa.SelectValue(OsConfigurationAxis, osType)
|
|
|
|
if osVal != nil && archVal != nil {
|
|
|
|
// In this case, arch takes precedence. (This fits legacy Soong behavior, as arch mutator
|
|
|
|
// runs after os mutator.
|
|
|
|
sa.SetSelectValue(OsArchConfigurationAxis, osArch, archVal)
|
|
|
|
} else if osVal != nil && archVal == nil {
|
|
|
|
sa.SetSelectValue(OsArchConfigurationAxis, osArch, osVal)
|
|
|
|
} else if osVal == nil && archVal != nil {
|
|
|
|
sa.SetSelectValue(OsArchConfigurationAxis, osArch, archVal)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-12-21 20:51:37 +01:00
|
|
|
/// All os_arch values are now set. Clear os and arch axes.
|
2022-06-23 18:02:44 +02:00
|
|
|
delete(sa.ConfigurableValues, ArchConfigurationAxis)
|
|
|
|
delete(sa.ConfigurableValues, OsConfigurationAxis)
|
|
|
|
// Verify post-condition; this should never fail, provided no additional
|
|
|
|
// axes are introduced.
|
|
|
|
if len(sa.ConfigurableValues) > 1 {
|
|
|
|
panic(fmt.Errorf("error in collapsing attribute: %#v", sa))
|
|
|
|
}
|
2022-12-21 20:51:37 +01:00
|
|
|
} else if containsProductVariables {
|
|
|
|
usedBaseValue := false
|
|
|
|
for a, configToProp := range sa.ConfigurableValues {
|
|
|
|
if a.configurationType == productVariables {
|
|
|
|
for c, p := range configToProp {
|
|
|
|
if p == nil {
|
|
|
|
sa.SetSelectValue(a, c, sa.Value)
|
|
|
|
usedBaseValue = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if usedBaseValue {
|
|
|
|
sa.Value = nil
|
|
|
|
}
|
2022-06-23 18:02:44 +02:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sa *StringAttribute) axisTypes() map[configurationType]bool {
|
|
|
|
types := map[configurationType]bool{}
|
|
|
|
for k := range sa.ConfigurableValues {
|
|
|
|
if strs := sa.ConfigurableValues[k]; len(strs) > 0 {
|
|
|
|
types[k.configurationType] = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return types
|
|
|
|
}
|
|
|
|
|
2021-02-24 13:20:12 +01:00
|
|
|
// StringListAttribute corresponds to the string_list Bazel attribute type with
|
|
|
|
// support for additional metadata, like configurations.
|
|
|
|
type StringListAttribute struct {
|
|
|
|
// The base value of the string list attribute.
|
|
|
|
Value []string
|
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
// The configured attribute label list Values. Optional
|
|
|
|
// a map of independent configurability axes
|
|
|
|
ConfigurableValues configurableStringLists
|
2022-12-10 01:08:54 +01:00
|
|
|
|
|
|
|
// If a property has struct tag "variant_prepend", this value should
|
|
|
|
// be set to True, so that when bp2build generates BUILD.bazel, variant
|
|
|
|
// properties(select ...) come before general properties.
|
|
|
|
Prepend bool
|
2021-05-19 12:49:02 +02:00
|
|
|
}
|
|
|
|
|
2022-09-09 03:38:47 +02:00
|
|
|
// IsEmpty returns true if the attribute has no values under any configuration.
|
|
|
|
func (sla StringListAttribute) IsEmpty() bool {
|
|
|
|
return len(sla.Value) == 0 && !sla.HasConfigurableValues()
|
|
|
|
}
|
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
type configurableStringLists map[ConfigurationAxis]stringListSelectValues
|
2021-05-19 12:49:02 +02:00
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
func (csl configurableStringLists) Append(other configurableStringLists) {
|
|
|
|
for axis, otherSelects := range other {
|
|
|
|
selects := csl[axis]
|
|
|
|
if selects == nil {
|
|
|
|
selects = make(stringListSelectValues, len(otherSelects))
|
2021-02-24 13:20:12 +01:00
|
|
|
}
|
2021-05-21 14:37:59 +02:00
|
|
|
selects.appendSelects(otherSelects)
|
|
|
|
csl[axis] = selects
|
2021-02-24 13:20:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
func (csl configurableStringLists) setValueForAxis(axis ConfigurationAxis, config string, list []string) {
|
|
|
|
if csl[axis] == nil {
|
|
|
|
csl[axis] = make(stringListSelectValues)
|
Add os/target configurable selects for label list attributes.
This CL is pretty large, so I recommend starting with reading the newly
added tests for the expected behavior.
This change works in conjunction with the linked CLs in the Gerrit topic.
Those CLs add support for new platform() definitions for OS targets
specified in Soong's arch.go, which are configurable through
Android.bp's `target {}` property. It works similary to previous CLs
adding support for the `arch {}` property.
These configurable props are keyed by the OS: android, linux_bionic,
windows, and so on. They map to `select` statements in label list
attributes, which this CL enables for cc_library_headers' header_libs
and export_header_lib_headers props.
This enables //bionic/libc:libc_headers to be generated correctly, from:
cc_library_headers {
name: "libc_headers",
target: {
android: {
header_libs: ["libc_headers_arch"],
export_header_lib_headers: ["libc_headers_arch"],
},
linux_bionic: {
header_libs: ["libc_headers_arch"],
export_header_lib_headers: ["libc_headers_arch"],
},
},
// omitted props
}
to:
cc_library_headers(
name = "libc_headers",
deps = [] + select({
"//build/bazel/platforms/os:android": [
":libc_headers_arch",
],
"//build/bazel/platforms/os:linux_bionic": [
":libc_headers_arch",
],
"//conditions:default": [],
}),
)
Test: TH
Test: Verify generated //bionic/libc:libc_headers
Fixes: 183597786
Change-Id: I01016cc2cc9a71449f02300d747f01decebf3f6e
2021-03-24 07:18:33 +01:00
|
|
|
}
|
2021-05-21 14:37:59 +02:00
|
|
|
csl[axis][config] = list
|
Add os/target configurable selects for label list attributes.
This CL is pretty large, so I recommend starting with reading the newly
added tests for the expected behavior.
This change works in conjunction with the linked CLs in the Gerrit topic.
Those CLs add support for new platform() definitions for OS targets
specified in Soong's arch.go, which are configurable through
Android.bp's `target {}` property. It works similary to previous CLs
adding support for the `arch {}` property.
These configurable props are keyed by the OS: android, linux_bionic,
windows, and so on. They map to `select` statements in label list
attributes, which this CL enables for cc_library_headers' header_libs
and export_header_lib_headers props.
This enables //bionic/libc:libc_headers to be generated correctly, from:
cc_library_headers {
name: "libc_headers",
target: {
android: {
header_libs: ["libc_headers_arch"],
export_header_lib_headers: ["libc_headers_arch"],
},
linux_bionic: {
header_libs: ["libc_headers_arch"],
export_header_lib_headers: ["libc_headers_arch"],
},
},
// omitted props
}
to:
cc_library_headers(
name = "libc_headers",
deps = [] + select({
"//build/bazel/platforms/os:android": [
":libc_headers_arch",
],
"//build/bazel/platforms/os:linux_bionic": [
":libc_headers_arch",
],
"//conditions:default": [],
}),
)
Test: TH
Test: Verify generated //bionic/libc:libc_headers
Fixes: 183597786
Change-Id: I01016cc2cc9a71449f02300d747f01decebf3f6e
2021-03-24 07:18:33 +01:00
|
|
|
}
|
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
type stringListSelectValues map[string][]string
|
2021-02-24 13:20:12 +01:00
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
func (sl stringListSelectValues) appendSelects(other stringListSelectValues) {
|
|
|
|
for k, v := range other {
|
|
|
|
sl[k] = append(sl[k], v...)
|
2021-02-24 13:20:12 +01:00
|
|
|
}
|
|
|
|
}
|
2021-03-24 15:14:47 +01:00
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
func (sl stringListSelectValues) hasConfigurableValues(other stringListSelectValues) bool {
|
|
|
|
for _, val := range sl {
|
|
|
|
if len(val) > 0 {
|
|
|
|
return true
|
|
|
|
}
|
2021-04-05 12:35:13 +02:00
|
|
|
}
|
2021-05-21 14:37:59 +02:00
|
|
|
return false
|
2021-04-05 12:35:13 +02:00
|
|
|
}
|
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
// MakeStringListAttribute initializes a StringListAttribute with the non-arch specific value.
|
|
|
|
func MakeStringListAttribute(value []string) StringListAttribute {
|
|
|
|
// NOTE: These strings are not necessarily unique or sorted.
|
|
|
|
return StringListAttribute{
|
|
|
|
Value: value,
|
|
|
|
ConfigurableValues: make(configurableStringLists),
|
2021-04-05 12:35:13 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
// HasConfigurableValues returns true if the attribute contains axis-specific string_list values.
|
|
|
|
func (sla StringListAttribute) HasConfigurableValues() bool {
|
2021-12-10 00:10:18 +01:00
|
|
|
for _, selectValues := range sla.ConfigurableValues {
|
|
|
|
if len(selectValues) > 0 {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
2021-05-19 12:49:02 +02:00
|
|
|
}
|
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
// Append appends all values, including os and arch specific ones, from another
|
|
|
|
// StringListAttribute to this StringListAttribute
|
2021-12-03 23:27:16 +01:00
|
|
|
func (sla *StringListAttribute) Append(other StringListAttribute) *StringListAttribute {
|
2021-05-21 14:37:59 +02:00
|
|
|
sla.Value = append(sla.Value, other.Value...)
|
|
|
|
if sla.ConfigurableValues == nil {
|
|
|
|
sla.ConfigurableValues = make(configurableStringLists)
|
|
|
|
}
|
|
|
|
sla.ConfigurableValues.Append(other.ConfigurableValues)
|
2021-12-03 23:27:16 +01:00
|
|
|
return sla
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sla *StringListAttribute) Clone() *StringListAttribute {
|
|
|
|
result := &StringListAttribute{}
|
|
|
|
return result.Append(*sla)
|
2021-05-21 14:37:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// SetSelectValue set a value for a bazel select for the given axis, config and value.
|
|
|
|
func (sla *StringListAttribute) SetSelectValue(axis ConfigurationAxis, config string, list []string) {
|
|
|
|
axis.validateConfig(config)
|
|
|
|
switch axis.configurationType {
|
|
|
|
case noConfig:
|
|
|
|
sla.Value = list
|
2022-07-27 09:22:06 +02:00
|
|
|
case arch, os, osArch, productVariables, osAndInApex:
|
2021-05-21 14:37:59 +02:00
|
|
|
if sla.ConfigurableValues == nil {
|
|
|
|
sla.ConfigurableValues = make(configurableStringLists)
|
|
|
|
}
|
|
|
|
sla.ConfigurableValues.setValueForAxis(axis, config, list)
|
2021-05-19 12:49:02 +02:00
|
|
|
default:
|
2021-05-21 14:37:59 +02:00
|
|
|
panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
|
2021-05-19 12:49:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
// SelectValue gets a value for a bazel select for the given axis and config.
|
|
|
|
func (sla *StringListAttribute) SelectValue(axis ConfigurationAxis, config string) []string {
|
|
|
|
axis.validateConfig(config)
|
|
|
|
switch axis.configurationType {
|
|
|
|
case noConfig:
|
|
|
|
return sla.Value
|
2022-07-27 09:22:06 +02:00
|
|
|
case arch, os, osArch, productVariables, osAndInApex:
|
2021-05-21 14:37:59 +02:00
|
|
|
return sla.ConfigurableValues[axis][config]
|
2021-05-19 12:49:02 +02:00
|
|
|
default:
|
2021-05-21 14:37:59 +02:00
|
|
|
panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
|
2021-05-19 12:49:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-21 14:37:59 +02:00
|
|
|
// SortedConfigurationAxes returns all the used ConfigurationAxis in sorted order.
|
|
|
|
func (sla *StringListAttribute) SortedConfigurationAxes() []ConfigurationAxis {
|
2023-01-26 23:30:44 +01:00
|
|
|
return SortedConfigurationAxes(sla.ConfigurableValues)
|
2021-04-13 09:14:55 +02:00
|
|
|
}
|
|
|
|
|
2021-09-09 20:08:21 +02:00
|
|
|
// DeduplicateAxesFromBase ensures no duplication of items between the no-configuration value and
|
|
|
|
// configuration-specific values. For example, if we would convert this StringListAttribute as:
|
2022-08-16 19:27:33 +02:00
|
|
|
//
|
|
|
|
// ["a", "b", "c"] + select({
|
|
|
|
// "//condition:one": ["a", "d"],
|
|
|
|
// "//conditions:default": [],
|
|
|
|
// })
|
|
|
|
//
|
2021-09-09 20:08:21 +02:00
|
|
|
// after this function, we would convert this StringListAttribute as:
|
2022-08-16 19:27:33 +02:00
|
|
|
//
|
|
|
|
// ["a", "b", "c"] + select({
|
|
|
|
// "//condition:one": ["d"],
|
|
|
|
// "//conditions:default": [],
|
|
|
|
// })
|
2021-09-09 20:08:21 +02:00
|
|
|
func (sla *StringListAttribute) DeduplicateAxesFromBase() {
|
|
|
|
base := sla.Value
|
|
|
|
for axis, configToList := range sla.ConfigurableValues {
|
|
|
|
for config, list := range configToList {
|
|
|
|
remaining := SubtractStrings(list, base)
|
|
|
|
if len(remaining) == 0 {
|
|
|
|
delete(sla.ConfigurableValues[axis], config)
|
|
|
|
} else {
|
|
|
|
sla.ConfigurableValues[axis][config] = remaining
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-24 15:14:47 +01:00
|
|
|
// TryVariableSubstitution, replace string substitution formatting within each string in slice with
|
|
|
|
// Starlark string.format compatible tag for productVariable.
|
|
|
|
func TryVariableSubstitutions(slice []string, productVariable string) ([]string, bool) {
|
2022-12-21 20:47:18 +01:00
|
|
|
if len(slice) == 0 {
|
|
|
|
return slice, false
|
|
|
|
}
|
2021-03-24 15:14:47 +01:00
|
|
|
ret := make([]string, 0, len(slice))
|
|
|
|
changesMade := false
|
|
|
|
for _, s := range slice {
|
|
|
|
newS, changed := TryVariableSubstitution(s, productVariable)
|
|
|
|
ret = append(ret, newS)
|
|
|
|
changesMade = changesMade || changed
|
|
|
|
}
|
|
|
|
return ret, changesMade
|
|
|
|
}
|
|
|
|
|
|
|
|
// TryVariableSubstitution, replace string substitution formatting within s with Starlark
|
|
|
|
// string.format compatible tag for productVariable.
|
|
|
|
func TryVariableSubstitution(s string, productVariable string) (string, bool) {
|
2021-05-26 14:45:30 +02:00
|
|
|
sub := productVariableSubstitutionPattern.ReplaceAllString(s, "$("+productVariable+")")
|
2021-03-24 15:14:47 +01:00
|
|
|
return sub, s != sub
|
|
|
|
}
|