blob: e29b9e1372f4053e8422ad40121e0c26928fbe7d [file] [log] [blame]
// 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
import (
"fmt"
"path/filepath"
"regexp"
"sort"
"strings"
"github.com/google/blueprint"
)
// BazelTargetModuleProperties contain properties and metadata used for
// Blueprint to BUILD file conversion.
type BazelTargetModuleProperties struct {
// The Bazel rule class for this target.
Rule_class string `blueprint:"mutated"`
// The target label for the bzl file containing the definition of the rule class.
Bzl_load_location string `blueprint:"mutated"`
}
var productVariableSubstitutionPattern = regexp.MustCompile("%(d|s)")
// Label is used to represent a Bazel compatible Label. Also stores the original
// bp text to support string replacement.
type Label struct {
// 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
}
// LabelList is used to represent a list of Bazel labels.
type LabelList struct {
Includes []Label
Excludes []Label
}
// MakeLabelList creates a LabelList from a list Label
func MakeLabelList(labels []Label) LabelList {
return LabelList{
Includes: labels,
Excludes: nil,
}
}
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
}
func (ll *LabelList) IsNil() bool {
return ll.Includes == nil && ll.Excludes == nil
}
func (ll *LabelList) deepCopy() LabelList {
return LabelList{
Includes: ll.Includes[:],
Excludes: ll.Excludes[:],
}
}
// 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
}
// Add inserts the label Label at the end of the LabelList.
func (ll *LabelList) Add(label *Label) {
if label == nil {
return
}
ll.Includes = append(ll.Includes, *label)
}
// 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 {
ll.Excludes = append(other.Excludes, other.Excludes...)
}
}
// UniqueSortedBazelLabels takes a []Label and deduplicates the labels, and returns
// the slice in a sorted order.
func UniqueSortedBazelLabels(originalLabels []Label) []Label {
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
}
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 {
var uniqueLabelList LabelList
uniqueLabelList.Includes = UniqueSortedBazelLabels(originalLabelList.Includes)
uniqueLabelList.Excludes = UniqueSortedBazelLabels(originalLabelList.Excludes)
return uniqueLabelList
}
// Subtract needle from haystack
func SubtractStrings(haystack []string, needle []string) []string {
// This is really a set
needleMap := make(map[string]bool)
for _, s := range needle {
needleMap[s] = true
}
var strings []string
for _, s := range haystack {
if exclude := needleMap[s]; !exclude {
strings = append(strings, s)
}
}
return strings
}
// Subtract needle from haystack
func SubtractBazelLabels(haystack []Label, needle []Label) []Label {
// This is really a set
needleMap := make(map[Label]bool)
for _, s := range needle {
needleMap[s] = true
}
var labels []Label
for _, label := range haystack {
if exclude := needleMap[label]; !exclude {
labels = append(labels, label)
}
}
return labels
}
// 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
}
// 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
}
type Attribute interface {
HasConfigurableValues() bool
}
type labelSelectValues map[string]*Label
type configurableLabels map[ConfigurationAxis]labelSelectValues
func (cl configurableLabels) setValueForAxis(axis ConfigurationAxis, config string, value *Label) {
if cl[axis] == nil {
cl[axis] = make(labelSelectValues)
}
cl[axis][config] = value
}
// Represents an attribute whose value is a single label
type LabelAttribute struct {
Value *Label
ConfigurableValues configurableLabels
}
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 {
return fmt.Errorf("label 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 := 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
}
// HasConfigurableValues returns whether there are configurable values set for this label.
func (la LabelAttribute) HasConfigurableValues() bool {
for _, selectValues := range la.ConfigurableValues {
if len(selectValues) > 0 {
return true
}
}
return false
}
// SetValue sets the base, non-configured value for the Label
func (la *LabelAttribute) SetValue(value Label) {
la.SetSelectValue(NoConfigAxis, "", value)
}
// 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
case arch, os, osArch, productVariables:
if la.ConfigurableValues == nil {
la.ConfigurableValues = make(configurableLabels)
}
la.ConfigurableValues.setValueForAxis(axis, config, &value)
default:
panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
}
}
// SelectValue gets a value for a bazel select for the given axis and config.
func (la *LabelAttribute) SelectValue(axis ConfigurationAxis, config string) *Label {
axis.validateConfig(config)
switch axis.configurationType {
case noConfig:
return la.Value
case arch, os, osArch, productVariables:
return la.ConfigurableValues[axis][config]
default:
panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
}
}
// SortedConfigurationAxes returns all the used ConfigurationAxis in sorted order.
func (la *LabelAttribute) SortedConfigurationAxes() []ConfigurationAxis {
keys := make([]ConfigurationAxis, 0, len(la.ConfigurableValues))
for k := range la.ConfigurableValues {
keys = append(keys, k)
}
sort.Slice(keys, func(i, j int) bool { return keys[i].less(keys[j]) })
return keys
}
// MakeLabelAttribute turns a string into a LabelAttribute
func MakeLabelAttribute(label string) *LabelAttribute {
return &LabelAttribute{
Value: &Label{
Label: label,
},
}
}
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 {
for _, cfgToBools := range ba.ConfigurableValues {
if len(cfgToBools) > 0 {
return true
}
}
return false
}
// SetValue sets value for the no config axis
func (ba *BoolAttribute) SetValue(value *bool) {
ba.SetSelectValue(NoConfigAxis, "", value)
}
// 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
case arch, os, osArch, productVariables:
if ba.ConfigurableValues == nil {
ba.ConfigurableValues = make(configurableBools)
}
ba.ConfigurableValues.setValueForAxis(axis, config, value)
default:
panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
}
}
// 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
}
// 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 {
panic(fmt.Errorf("error in collapsing attribute: %#v", ba))
}
}
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
}
// 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
case arch, os, osArch, productVariables:
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 {
keys := make([]ConfigurationAxis, 0, len(ba.ConfigurableValues))
for k := range ba.ConfigurableValues {
keys = append(keys, k)
}
sort.Slice(keys, func(i, j int) bool { return keys[i].less(keys[j]) })
return keys
}
// labelListSelectValues supports config-specific label_list typed Bazel attribute values.
type labelListSelectValues map[string]LabelList
func (ll labelListSelectValues) addSelects(label labelSelectValues) {
for k, v := range label {
if label == nil {
continue
}
l := ll[k]
(&l).Add(v)
ll[k] = l
}
}
func (ll labelListSelectValues) appendSelects(other labelListSelectValues, forceSpecifyEmptyList bool) {
for k, v := range other {
l := ll[k]
if forceSpecifyEmptyList && l.IsNil() && !v.IsNil() {
l.Includes = []Label{}
}
(&l).Append(v)
ll[k] = l
}
}
// HasConfigurableValues returns whether there are configurable values within this set of selects.
func (ll labelListSelectValues) HasConfigurableValues() bool {
for _, v := range ll {
if v.Includes != nil {
return true
}
}
return false
}
// LabelListAttribute is used to represent a list of Bazel labels as an
// attribute.
type LabelListAttribute struct {
// The non-configured attribute label list Value. Required.
Value LabelList
// The configured attribute label list Values. Optional
// a map of independent configurability axes
ConfigurableValues configurableLabelLists
// 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
// 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
}
type configurableLabelLists map[ConfigurationAxis]labelListSelectValues
func (cll configurableLabelLists) setValueForAxis(axis ConfigurationAxis, config string, list LabelList) {
if list.IsNil() {
if _, ok := cll[axis][config]; ok {
delete(cll[axis], config)
}
return
}
if cll[axis] == nil {
cll[axis] = make(labelListSelectValues)
}
cll[axis][config] = list
}
func (cll configurableLabelLists) Append(other configurableLabelLists, forceSpecifyEmptyList bool) {
for axis, otherSelects := range other {
selects := cll[axis]
if selects == nil {
selects = make(labelListSelectValues, len(otherSelects))
}
selects.appendSelects(otherSelects, forceSpecifyEmptyList)
cll[axis] = selects
}
}
func (lla *LabelListAttribute) Clone() *LabelListAttribute {
result := &LabelListAttribute{ForceSpecifyEmptyList: lla.ForceSpecifyEmptyList}
return result.Append(*lla)
}
// MakeLabelListAttribute initializes a LabelListAttribute with the non-arch specific value.
func MakeLabelListAttribute(value LabelList) LabelListAttribute {
return LabelListAttribute{
Value: value,
ConfigurableValues: make(configurableLabelLists),
}
}
// 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}))
}
func (lla *LabelListAttribute) SetValue(list LabelList) {
lla.SetSelectValue(NoConfigAxis, "", list)
}
// 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
case arch, os, osArch, productVariables:
if lla.ConfigurableValues == nil {
lla.ConfigurableValues = make(configurableLabelLists)
}
lla.ConfigurableValues.setValueForAxis(axis, config, list)
default:
panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
}
}
// 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
case arch, os, osArch, productVariables:
return lla.ConfigurableValues[axis][config]
default:
panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
}
}
// SortedConfigurationAxes returns all the used ConfigurationAxis in sorted order.
func (lla *LabelListAttribute) SortedConfigurationAxes() []ConfigurationAxis {
keys := make([]ConfigurationAxis, 0, len(lla.ConfigurableValues))
for k := range lla.ConfigurableValues {
keys = append(keys, k)
}
sort.Slice(keys, func(i, j int) bool { return keys[i].less(keys[j]) })
return keys
}
// Append all values, including os and arch specific ones, from another
// 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() {
lla.Value.Includes = []Label{}
}
lla.Value.Append(other.Value)
if lla.ConfigurableValues == nil {
lla.ConfigurableValues = make(configurableLabelLists)
}
lla.ConfigurableValues.Append(other.ConfigurableValues, forceSpecifyEmptyList)
return lla
}
// 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])
}
}
// HasConfigurableValues returns true if the attribute contains axis-specific label list values.
func (lla LabelListAttribute) HasConfigurableValues() bool {
for _, selectValues := range lla.ConfigurableValues {
if len(selectValues) > 0 {
return true
}
}
return false
}
// 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
}
// 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)
}
// 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() {
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)
lla.ConfigurableValues[axis][config] = SubtractBazelLabelList(allLabels, LabelList{Includes: val.Excludes})
}
// 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)
}
// 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)
// if everything ends up without includes, just delete the axis
if !lla.ConfigurableValues[axis].HasConfigurableValues() {
delete(lla.ConfigurableValues, axis)
}
}
}
// 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.
type LabelMapper func(OtherModuleContext, Label) (string, bool)
// 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 {
if newLabel, changed := lf.LabelMapper(ctx, label); changed {
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
}
// 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
// The configured attribute label list Values. Optional
// a map of independent configurability axes
ConfigurableValues configurableStringLists
}
type configurableStringLists map[ConfigurationAxis]stringListSelectValues
func (csl configurableStringLists) Append(other configurableStringLists) {
for axis, otherSelects := range other {
selects := csl[axis]
if selects == nil {
selects = make(stringListSelectValues, len(otherSelects))
}
selects.appendSelects(otherSelects)
csl[axis] = selects
}
}
func (csl configurableStringLists) setValueForAxis(axis ConfigurationAxis, config string, list []string) {
if csl[axis] == nil {
csl[axis] = make(stringListSelectValues)
}
csl[axis][config] = list
}
type stringListSelectValues map[string][]string
func (sl stringListSelectValues) appendSelects(other stringListSelectValues) {
for k, v := range other {
sl[k] = append(sl[k], v...)
}
}
func (sl stringListSelectValues) hasConfigurableValues(other stringListSelectValues) bool {
for _, val := range sl {
if len(val) > 0 {
return true
}
}
return false
}
// 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),
}
}
// HasConfigurableValues returns true if the attribute contains axis-specific string_list values.
func (sla StringListAttribute) HasConfigurableValues() bool {
for _, selectValues := range sla.ConfigurableValues {
if len(selectValues) > 0 {
return true
}
}
return false
}
// Append appends all values, including os and arch specific ones, from another
// StringListAttribute to this StringListAttribute
func (sla *StringListAttribute) Append(other StringListAttribute) *StringListAttribute {
sla.Value = append(sla.Value, other.Value...)
if sla.ConfigurableValues == nil {
sla.ConfigurableValues = make(configurableStringLists)
}
sla.ConfigurableValues.Append(other.ConfigurableValues)
return sla
}
func (sla *StringListAttribute) Clone() *StringListAttribute {
result := &StringListAttribute{}
return result.Append(*sla)
}
// 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
case arch, os, osArch, productVariables:
if sla.ConfigurableValues == nil {
sla.ConfigurableValues = make(configurableStringLists)
}
sla.ConfigurableValues.setValueForAxis(axis, config, list)
default:
panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
}
}
// 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
case arch, os, osArch, productVariables:
return sla.ConfigurableValues[axis][config]
default:
panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
}
}
// SortedConfigurationAxes returns all the used ConfigurationAxis in sorted order.
func (sla *StringListAttribute) SortedConfigurationAxes() []ConfigurationAxis {
keys := make([]ConfigurationAxis, 0, len(sla.ConfigurableValues))
for k := range sla.ConfigurableValues {
keys = append(keys, k)
}
sort.Slice(keys, func(i, j int) bool { return keys[i].less(keys[j]) })
return keys
}
// DeduplicateAxesFromBase ensures no duplication of items between the no-configuration value and
// configuration-specific values. For example, if we would convert this StringListAttribute as:
// ["a", "b", "c"] + select({
// "//condition:one": ["a", "d"],
// "//conditions:default": [],
// })
// after this function, we would convert this StringListAttribute as:
// ["a", "b", "c"] + select({
// "//condition:one": ["d"],
// "//conditions:default": [],
// })
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
}
}
}
}
// 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) {
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) {
sub := productVariableSubstitutionPattern.ReplaceAllString(s, "$("+productVariable+")")
return sub, s != sub
}