platform_build_soong/android/defaults.go

636 lines
21 KiB
Go
Raw Normal View History

// Copyright 2015 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 android
import (
"bytes"
"fmt"
"reflect"
"github.com/google/blueprint"
"github.com/google/blueprint/proptools"
)
type defaultsDependencyTag struct {
blueprint.BaseDependencyTag
}
var DefaultsDepTag defaultsDependencyTag
type defaultsProperties struct {
Defaults []string
}
type DefaultableModuleBase struct {
defaultsProperties defaultsProperties
defaultableProperties []interface{}
defaultableVariableProperties interface{}
// The optional hook to call after any defaults have been applied.
hook DefaultableHook
}
func (d *DefaultableModuleBase) defaults() *defaultsProperties {
return &d.defaultsProperties
}
func (d *DefaultableModuleBase) setProperties(props []interface{}, variableProperties interface{}) {
d.defaultableProperties = props
d.defaultableVariableProperties = variableProperties
}
func (d *DefaultableModuleBase) SetDefaultableHook(hook DefaultableHook) {
d.hook = hook
}
func (d *DefaultableModuleBase) callHookIfAvailable(ctx DefaultableHookContext) {
if d.hook != nil {
d.hook(ctx)
}
}
// Interface that must be supported by any module to which defaults can be applied.
type Defaultable interface {
// Get a pointer to the struct containing the Defaults property.
defaults() *defaultsProperties
// Set the property structures into which defaults will be added.
setProperties(props []interface{}, variableProperties interface{})
// Apply defaults from the supplied DefaultsModule to the property structures supplied to
// setProperties(...).
applyDefaults(TopDownMutatorContext, []DefaultsModule)
applySingleDefaultsWithTracker(EarlyModuleContext, DefaultsModule, defaultsTrackerFunc)
// Set the hook to be called after any defaults have been applied.
//
// Should be used in preference to a AddLoadHook when the behavior of the load
// hook is dependent on properties supplied in the Android.bp file.
SetDefaultableHook(hook DefaultableHook)
// Call the hook if specified.
callHookIfAvailable(context DefaultableHookContext)
}
type DefaultableModule interface {
Module
Defaultable
}
var _ Defaultable = (*DefaultableModuleBase)(nil)
func InitDefaultableModule(module DefaultableModule) {
if module.base().module == nil {
panic("InitAndroidModule must be called before InitDefaultableModule")
}
module.setProperties(module.GetProperties(), module.base().variableProperties)
module.AddProperties(module.defaults())
}
// A restricted subset of context methods, similar to LoadHookContext.
type DefaultableHookContext interface {
EarlyModuleContext
CreateModule(ModuleFactory, ...interface{}) Module
AddMissingDependencies(missingDeps []string)
}
type DefaultableHook func(ctx DefaultableHookContext)
// The Defaults_visibility property.
type DefaultsVisibilityProperties struct {
// Controls the visibility of the defaults module itself.
Defaults_visibility []string
}
// AdditionalDefaultsProperties contains properties of defaults modules which
// can have other defaults applied.
type AdditionalDefaultsProperties struct {
// The list of properties set by the default whose values must not be changed by any module that
// applies these defaults. It is an error if a property is not supported by the defaults module or
// has not been set to a non-zero value. If this contains "*" then that must be the only entry in
// which case all properties that are set on this defaults will be protected (except the
// protected_properties and visibility.
Protected_properties []string
}
type DefaultsModuleBase struct {
DefaultableModuleBase
defaultsProperties AdditionalDefaultsProperties
// Included to support setting bazel_module.label for multiple Soong modules to the same Bazel
// target. This is primarily useful for modules that were architecture specific and instead are
// handled in Bazel as a select().
BazelModuleBase
}
// The common pattern for defaults modules is to register separate instances of
// the xxxProperties structs in the AddProperties calls, rather than reusing the
// ones inherited from Module.
//
// The effect is that e.g. myDefaultsModuleInstance.base().xxxProperties won't
// contain the values that have been set for the defaults module. Rather, to
// retrieve the values it is necessary to iterate over properties(). E.g. to get
// the commonProperties instance that have the real values:
//
// d := myModule.(Defaults)
// for _, props := range d.properties() {
// if cp, ok := props.(*commonProperties); ok {
// ... access property values in cp ...
// }
// }
//
// The rationale is that the properties on a defaults module apply to the
// defaultable modules using it, not to the defaults module itself. E.g. setting
// the "enabled" property false makes inheriting modules disabled by default,
// rather than disabling the defaults module itself.
type Defaults interface {
Defaultable
// Although this function is unused it is actually needed to ensure that only modules that embed
// DefaultsModuleBase will type-assert to the Defaults interface.
isDefaults() bool
// additionalDefaultableProperties returns additional properties provided by the defaults which
// can themselves have defaults applied.
additionalDefaultableProperties() []interface{}
// protectedProperties returns the names of the properties whose values cannot be changed by a
// module that applies these defaults.
protectedProperties() []string
// setProtectedProperties sets the names of the properties whose values cannot be changed by a
// module that applies these defaults.
setProtectedProperties(protectedProperties []string)
// Get the structures containing the properties for which defaults can be provided.
properties() []interface{}
Refactor visibility to support visibility on defaults modules Existing modules, either general one or package ones have a single visibility property, called visibility in general, and default_visibility on package, that controls access to that module, or in the case of package sets the default visibility of all modules in that package. The property is checked and gathered during the similarly named phases of visibility processing. The defaults module will be different as it will have two properties. The first, visibility, will not affect the visibility of the module, it only affects the visibility of modules that 'extend' the defaults. So, it will need checking but not parsing. The second property, defaults_visibility, will affect the visibility of the module and so will need both checking and parsing. The current implementation does not handle those cases because: 1) It does not differentiate between the property that affects the module and those that do not. It checks and gathers all of them with the last property gathered overriding the rules for the previous properties. 2) It relies on overriding methods in MethodBase in order to change the default behavior for the package module. That works because packageModule embeds ModuleBase but will not work for DefaultsModuleBase as it does not embed ModuleBase and instead is embedded alongside it so attempting to override a method in MethodBase leads to ambiguity. This change addresses the issues as follows: 1) It adds a new visibility() []string method to get access to the primary visibility rules, i.e. the ones that affect the module. 2) It adds two fields, 'visibilityPropertyInfo []visibilityProperty' to provide information about all the properties that need checking, and 'primaryVisibilityProperty visibilityProperty' to specify the property that affects the module. The PackageFactory() and InitAndroidModule(Module) functions are modified to initialize the fields. The override of the visibilityProperties() method for packageModule is removed and the default implementations of visibilityProperties() and visibility() on ModuleBase return information from the two new fields. The InitDefaultsModule is updated to also initialize the two new fields. It uses nil for primaryVisibilityProperty for now but that will be changed to return defaults_visibility. It also uses the commonProperties structure created for the defaults directly instead of having to search for it through properties(). Changed the visibilityProperty to take a pointer to the property that can be used to retrieve the value rather than a lambda function. Bug: 130796911 Test: m nothing Change-Id: Icadd470a5f692a48ec61de02bf3dfde3e2eea2ef
2019-07-24 15:24:38 +02:00
productVariableProperties() interface{}
}
func (d *DefaultsModuleBase) isDefaults() bool {
return true
}
type DefaultsModule interface {
Module
Defaults
Bazelable
}
func (d *DefaultsModuleBase) additionalDefaultableProperties() []interface{} {
return []interface{}{&d.defaultsProperties}
}
func (d *DefaultsModuleBase) protectedProperties() []string {
return d.defaultsProperties.Protected_properties
}
func (d *DefaultsModuleBase) setProtectedProperties(protectedProperties []string) {
d.defaultsProperties.Protected_properties = protectedProperties
}
func (d *DefaultsModuleBase) properties() []interface{} {
return d.defaultableProperties
}
func (d *DefaultsModuleBase) productVariableProperties() interface{} {
return d.defaultableVariableProperties
}
func (d *DefaultsModuleBase) GenerateAndroidBuildActions(ctx ModuleContext) {}
// ConvertWithBp2build to fulfill Bazelable interface; however, at this time defaults module are
// *NOT* converted with bp2build
func (defaultable *DefaultsModuleBase) ConvertWithBp2build(ctx TopDownMutatorContext) {}
func InitDefaultsModule(module DefaultsModule) {
commonProperties := &commonProperties{}
Refactor visibility to support visibility on defaults modules Existing modules, either general one or package ones have a single visibility property, called visibility in general, and default_visibility on package, that controls access to that module, or in the case of package sets the default visibility of all modules in that package. The property is checked and gathered during the similarly named phases of visibility processing. The defaults module will be different as it will have two properties. The first, visibility, will not affect the visibility of the module, it only affects the visibility of modules that 'extend' the defaults. So, it will need checking but not parsing. The second property, defaults_visibility, will affect the visibility of the module and so will need both checking and parsing. The current implementation does not handle those cases because: 1) It does not differentiate between the property that affects the module and those that do not. It checks and gathers all of them with the last property gathered overriding the rules for the previous properties. 2) It relies on overriding methods in MethodBase in order to change the default behavior for the package module. That works because packageModule embeds ModuleBase but will not work for DefaultsModuleBase as it does not embed ModuleBase and instead is embedded alongside it so attempting to override a method in MethodBase leads to ambiguity. This change addresses the issues as follows: 1) It adds a new visibility() []string method to get access to the primary visibility rules, i.e. the ones that affect the module. 2) It adds two fields, 'visibilityPropertyInfo []visibilityProperty' to provide information about all the properties that need checking, and 'primaryVisibilityProperty visibilityProperty' to specify the property that affects the module. The PackageFactory() and InitAndroidModule(Module) functions are modified to initialize the fields. The override of the visibilityProperties() method for packageModule is removed and the default implementations of visibilityProperties() and visibility() on ModuleBase return information from the two new fields. The InitDefaultsModule is updated to also initialize the two new fields. It uses nil for primaryVisibilityProperty for now but that will be changed to return defaults_visibility. It also uses the commonProperties structure created for the defaults directly instead of having to search for it through properties(). Changed the visibilityProperty to take a pointer to the property that can be used to retrieve the value rather than a lambda function. Bug: 130796911 Test: m nothing Change-Id: Icadd470a5f692a48ec61de02bf3dfde3e2eea2ef
2019-07-24 15:24:38 +02:00
module.AddProperties(
&hostAndDeviceProperties{},
Refactor visibility to support visibility on defaults modules Existing modules, either general one or package ones have a single visibility property, called visibility in general, and default_visibility on package, that controls access to that module, or in the case of package sets the default visibility of all modules in that package. The property is checked and gathered during the similarly named phases of visibility processing. The defaults module will be different as it will have two properties. The first, visibility, will not affect the visibility of the module, it only affects the visibility of modules that 'extend' the defaults. So, it will need checking but not parsing. The second property, defaults_visibility, will affect the visibility of the module and so will need both checking and parsing. The current implementation does not handle those cases because: 1) It does not differentiate between the property that affects the module and those that do not. It checks and gathers all of them with the last property gathered overriding the rules for the previous properties. 2) It relies on overriding methods in MethodBase in order to change the default behavior for the package module. That works because packageModule embeds ModuleBase but will not work for DefaultsModuleBase as it does not embed ModuleBase and instead is embedded alongside it so attempting to override a method in MethodBase leads to ambiguity. This change addresses the issues as follows: 1) It adds a new visibility() []string method to get access to the primary visibility rules, i.e. the ones that affect the module. 2) It adds two fields, 'visibilityPropertyInfo []visibilityProperty' to provide information about all the properties that need checking, and 'primaryVisibilityProperty visibilityProperty' to specify the property that affects the module. The PackageFactory() and InitAndroidModule(Module) functions are modified to initialize the fields. The override of the visibilityProperties() method for packageModule is removed and the default implementations of visibilityProperties() and visibility() on ModuleBase return information from the two new fields. The InitDefaultsModule is updated to also initialize the two new fields. It uses nil for primaryVisibilityProperty for now but that will be changed to return defaults_visibility. It also uses the commonProperties structure created for the defaults directly instead of having to search for it through properties(). Changed the visibilityProperty to take a pointer to the property that can be used to retrieve the value rather than a lambda function. Bug: 130796911 Test: m nothing Change-Id: Icadd470a5f692a48ec61de02bf3dfde3e2eea2ef
2019-07-24 15:24:38 +02:00
commonProperties,
&ApexProperties{},
&distProperties{})
// Additional properties of defaults modules that can themselves have
// defaults applied.
module.AddProperties(module.additionalDefaultableProperties()...)
// Bazel module must be initialized _before_ Defaults to be included in cc_defaults module.
InitBazelModule(module)
initAndroidModuleBase(module)
initProductVariableModule(module)
initArchModule(module)
InitDefaultableModule(module)
// Add properties that will not have defaults applied to them.
base := module.base()
defaultsVisibility := &DefaultsVisibilityProperties{}
module.AddProperties(&base.nameProperties, defaultsVisibility)
Refactor visibility to support visibility on defaults modules Existing modules, either general one or package ones have a single visibility property, called visibility in general, and default_visibility on package, that controls access to that module, or in the case of package sets the default visibility of all modules in that package. The property is checked and gathered during the similarly named phases of visibility processing. The defaults module will be different as it will have two properties. The first, visibility, will not affect the visibility of the module, it only affects the visibility of modules that 'extend' the defaults. So, it will need checking but not parsing. The second property, defaults_visibility, will affect the visibility of the module and so will need both checking and parsing. The current implementation does not handle those cases because: 1) It does not differentiate between the property that affects the module and those that do not. It checks and gathers all of them with the last property gathered overriding the rules for the previous properties. 2) It relies on overriding methods in MethodBase in order to change the default behavior for the package module. That works because packageModule embeds ModuleBase but will not work for DefaultsModuleBase as it does not embed ModuleBase and instead is embedded alongside it so attempting to override a method in MethodBase leads to ambiguity. This change addresses the issues as follows: 1) It adds a new visibility() []string method to get access to the primary visibility rules, i.e. the ones that affect the module. 2) It adds two fields, 'visibilityPropertyInfo []visibilityProperty' to provide information about all the properties that need checking, and 'primaryVisibilityProperty visibilityProperty' to specify the property that affects the module. The PackageFactory() and InitAndroidModule(Module) functions are modified to initialize the fields. The override of the visibilityProperties() method for packageModule is removed and the default implementations of visibilityProperties() and visibility() on ModuleBase return information from the two new fields. The InitDefaultsModule is updated to also initialize the two new fields. It uses nil for primaryVisibilityProperty for now but that will be changed to return defaults_visibility. It also uses the commonProperties structure created for the defaults directly instead of having to search for it through properties(). Changed the visibilityProperty to take a pointer to the property that can be used to retrieve the value rather than a lambda function. Bug: 130796911 Test: m nothing Change-Id: Icadd470a5f692a48ec61de02bf3dfde3e2eea2ef
2019-07-24 15:24:38 +02:00
// Unlike non-defaults modules the visibility property is not stored in m.base().commonProperties.
// Instead it is stored in a separate instance of commonProperties created above so clear the
// existing list of properties.
clearVisibilityProperties(module)
// The defaults_visibility property controls the visibility of a defaults module so it must be
// set as the primary property, which also adds it to the list.
setPrimaryVisibilityProperty(module, "defaults_visibility", &defaultsVisibility.Defaults_visibility)
Refactor visibility to support visibility on defaults modules Existing modules, either general one or package ones have a single visibility property, called visibility in general, and default_visibility on package, that controls access to that module, or in the case of package sets the default visibility of all modules in that package. The property is checked and gathered during the similarly named phases of visibility processing. The defaults module will be different as it will have two properties. The first, visibility, will not affect the visibility of the module, it only affects the visibility of modules that 'extend' the defaults. So, it will need checking but not parsing. The second property, defaults_visibility, will affect the visibility of the module and so will need both checking and parsing. The current implementation does not handle those cases because: 1) It does not differentiate between the property that affects the module and those that do not. It checks and gathers all of them with the last property gathered overriding the rules for the previous properties. 2) It relies on overriding methods in MethodBase in order to change the default behavior for the package module. That works because packageModule embeds ModuleBase but will not work for DefaultsModuleBase as it does not embed ModuleBase and instead is embedded alongside it so attempting to override a method in MethodBase leads to ambiguity. This change addresses the issues as follows: 1) It adds a new visibility() []string method to get access to the primary visibility rules, i.e. the ones that affect the module. 2) It adds two fields, 'visibilityPropertyInfo []visibilityProperty' to provide information about all the properties that need checking, and 'primaryVisibilityProperty visibilityProperty' to specify the property that affects the module. The PackageFactory() and InitAndroidModule(Module) functions are modified to initialize the fields. The override of the visibilityProperties() method for packageModule is removed and the default implementations of visibilityProperties() and visibility() on ModuleBase return information from the two new fields. The InitDefaultsModule is updated to also initialize the two new fields. It uses nil for primaryVisibilityProperty for now but that will be changed to return defaults_visibility. It also uses the commonProperties structure created for the defaults directly instead of having to search for it through properties(). Changed the visibilityProperty to take a pointer to the property that can be used to retrieve the value rather than a lambda function. Bug: 130796911 Test: m nothing Change-Id: Icadd470a5f692a48ec61de02bf3dfde3e2eea2ef
2019-07-24 15:24:38 +02:00
// The visibility property needs to be checked (but not parsed) by the visibility module during
// its checking phase and parsing phase so add it to the list as a normal property.
AddVisibilityProperty(module, "visibility", &commonProperties.Visibility)
// The applicable licenses property for defaults is 'licenses'.
setPrimaryLicensesProperty(module, "licenses", &commonProperties.Licenses)
AddLoadHook(module, func(ctx LoadHookContext) {
protectedProperties := module.protectedProperties()
if len(protectedProperties) == 0 {
return
}
propertiesAvailable := map[string]struct{}{}
propertiesSet := map[string]struct{}{}
// A defaults tracker which will keep track of which properties have been set on this module.
collector := func(defaults DefaultsModule, property string, dstValue interface{}, srcValue interface{}) bool {
value := reflect.ValueOf(dstValue)
propertiesAvailable[property] = struct{}{}
if !value.IsZero() {
propertiesSet[property] = struct{}{}
}
// Skip all the properties so that there are no changes to the defaults.
return false
}
// Try and apply this module's defaults to itself, so that the properties can be collected but
// skip all the properties so it doesn't actually do anything.
module.applySingleDefaultsWithTracker(ctx, module, collector)
if InList("*", protectedProperties) {
if len(protectedProperties) != 1 {
ctx.PropertyErrorf("protected_properties", `if specified then "*" must be the only property listed`)
return
}
// Do not automatically protect the protected_properties property.
delete(propertiesSet, "protected_properties")
// Or the visibility property.
delete(propertiesSet, "visibility")
// Replace the "*" with the names of all the properties that have been set.
protectedProperties = SortedStringKeys(propertiesSet)
module.setProtectedProperties(protectedProperties)
} else {
for _, property := range protectedProperties {
if _, ok := propertiesAvailable[property]; !ok {
ctx.PropertyErrorf(property, "property is not supported by this module type %q",
ctx.ModuleType())
} else if _, ok := propertiesSet[property]; !ok {
ctx.PropertyErrorf(property, "is not set; protected properties must be explicitly set")
}
}
}
})
}
var _ Defaults = (*DefaultsModuleBase)(nil)
// applyNamespacedVariableDefaults only runs in bp2build mode for
// defaultable/defaults modules. Its purpose is to merge namespaced product
// variable props from defaults deps, even if those defaults are custom module
// types created from soong_config_module_type, e.g. one that's wrapping a
// cc_defaults or java_defaults.
func applyNamespacedVariableDefaults(defaultDep Defaults, ctx TopDownMutatorContext) {
var dep, b Bazelable
dep, ok := defaultDep.(Bazelable)
if !ok {
if depMod, ok := defaultDep.(Module); ok {
// Track that this dependency hasn't been converted to bp2build yet.
ctx.AddUnconvertedBp2buildDep(depMod.Name())
return
} else {
panic("Expected default dep to be a Module.")
}
}
b, ok = ctx.Module().(Bazelable)
if !ok {
return
}
// namespacedVariableProps is a map from namespaces (e.g. acme, android,
// vendor_foo) to a slice of soong_config_variable struct pointers,
// containing properties for that particular module.
src := dep.namespacedVariableProps()
dst := b.namespacedVariableProps()
if dst == nil {
dst = make(namespacedVariableProperties)
}
// Propagate all soong_config_variable structs from the dep. We'll merge the
// actual property values later in variable.go.
for namespace := range src {
if dst[namespace] == nil {
dst[namespace] = []interface{}{}
}
for _, i := range src[namespace] {
dst[namespace] = append(dst[namespace], i)
}
}
b.setNamespacedVariableProps(dst)
}
// defaultValueInfo contains information about each default value that applies to a protected
// property.
type defaultValueInfo struct {
// The DefaultsModule providing the value, which may be defined on that module or applied as a
// default from other modules.
module Module
// The default value, as returned by getComparableValue
defaultValue reflect.Value
}
// protectedPropertyInfo contains information about each property that has to be protected when
// applying defaults.
type protectedPropertyInfo struct {
// True if the property was set on the module to which defaults are applied, this is an error.
propertySet bool
// The original value of the property on the module, as returned by getComparableValue.
originalValue reflect.Value
// A list of defaults for the property that are being applied.
defaultValues []defaultValueInfo
}
// getComparableValue takes a reflect.Value that may be a pointer to another value and returns a
// reflect.Value to the underlying data or the original if was not a pointer or was nil. The
// returned values can then be compared for equality.
func getComparableValue(value reflect.Value) reflect.Value {
if value.IsZero() {
return value
}
for value.Kind() == reflect.Ptr {
value = value.Elem()
}
return value
}
func (defaultable *DefaultableModuleBase) applyDefaults(ctx TopDownMutatorContext,
defaultsList []DefaultsModule) {
// Collate information on all the properties protected by each of the default modules applied
// to this module.
allProtectedProperties := map[string]*protectedPropertyInfo{}
for _, defaults := range defaultsList {
for _, property := range defaults.protectedProperties() {
info := allProtectedProperties[property]
if info == nil {
info = &protectedPropertyInfo{}
allProtectedProperties[property] = info
}
}
}
// If there are any protected properties then collate information about attempts to change them.
var protectedPropertyInfoCollector defaultsTrackerFunc
if len(allProtectedProperties) > 0 {
protectedPropertyInfoCollector = func(defaults DefaultsModule, property string,
dstValue interface{}, srcValue interface{}) bool {
// If the property is not protected then return immediately.
info := allProtectedProperties[property]
if info == nil {
return true
}
currentValue := reflect.ValueOf(dstValue)
if info.defaultValues == nil {
info.propertySet = !currentValue.IsZero()
info.originalValue = getComparableValue(currentValue)
}
defaultValue := reflect.ValueOf(srcValue)
if !defaultValue.IsZero() {
info.defaultValues = append(info.defaultValues,
defaultValueInfo{defaults, getComparableValue(defaultValue)})
}
return true
}
}
for _, defaults := range defaultsList {
if ctx.Config().BuildMode == Bp2build {
applyNamespacedVariableDefaults(defaults, ctx)
}
defaultable.applySingleDefaultsWithTracker(ctx, defaults, protectedPropertyInfoCollector)
}
// Check the status of any protected properties.
for property, info := range allProtectedProperties {
if len(info.defaultValues) == 0 {
// No defaults were applied to the protected properties. Possibly because this module type
// does not support any of them.
continue
}
// Check to make sure that there are no conflicts between the defaults.
conflictingDefaults := false
previousDefaultValue := reflect.ValueOf(false)
for _, defaultInfo := range info.defaultValues {
defaultValue := defaultInfo.defaultValue
if previousDefaultValue.IsZero() {
previousDefaultValue = defaultValue
} else if !reflect.DeepEqual(previousDefaultValue.Interface(), defaultValue.Interface()) {
conflictingDefaults = true
break
}
}
if conflictingDefaults {
var buf bytes.Buffer
for _, defaultInfo := range info.defaultValues {
buf.WriteString(fmt.Sprintf("\n defaults module %q provides value %#v",
ctx.OtherModuleName(defaultInfo.module), defaultInfo.defaultValue))
}
result := buf.String()
ctx.ModuleErrorf("has conflicting default values for protected property %q:%s", property, result)
continue
}
// Now check to see whether there the current module tried to override/append to the defaults.
if info.propertySet {
originalValue := info.originalValue
// Just compare against the first defaults.
defaultValue := info.defaultValues[0].defaultValue
defaults := info.defaultValues[0].module
if originalValue.Kind() == reflect.Slice {
ctx.ModuleErrorf("attempts to append %q to protected property %q's value of %q defined in module %q",
originalValue,
property,
defaultValue,
ctx.OtherModuleName(defaults))
} else {
same := reflect.DeepEqual(originalValue.Interface(), defaultValue.Interface())
message := ""
if same {
message = fmt.Sprintf(" with a matching value (%#v) so this property can simply be removed.", originalValue)
} else {
message = fmt.Sprintf(" with a different value (override %#v with %#v) so removing the property may necessitate other changes.", defaultValue, originalValue)
}
ctx.ModuleErrorf("attempts to override protected property %q defined in module %q%s",
property,
ctx.OtherModuleName(defaults), message)
}
}
}
}
func (defaultable *DefaultableModuleBase) applySingleDefaultsWithTracker(ctx EarlyModuleContext, defaults DefaultsModule, tracker defaultsTrackerFunc) {
for _, prop := range defaultable.defaultableProperties {
var err error
if prop == defaultable.defaultableVariableProperties {
err = defaultable.applyDefaultVariableProperties(defaults, prop, tracker)
} else {
err = defaultable.applyDefaultProperties(defaults, prop, tracker)
}
if err != nil {
if propertyErr, ok := err.(*proptools.ExtendPropertyError); ok {
ctx.PropertyErrorf(propertyErr.Property, "%s", propertyErr.Err.Error())
} else {
panic(err)
}
}
}
}
// defaultsTrackerFunc is the type of a function that can be used to track how defaults are applied.
type defaultsTrackerFunc func(defaults DefaultsModule, property string,
dstValue interface{}, srcValue interface{}) bool
// filterForTracker wraps a defaultsTrackerFunc in a proptools.ExtendPropertyFilterFunc
func filterForTracker(defaults DefaultsModule, tracker defaultsTrackerFunc) proptools.ExtendPropertyFilterFunc {
if tracker == nil {
return nil
}
return func(property string,
dstField, srcField reflect.StructField,
dstValue, srcValue interface{}) (bool, error) {
apply := tracker(defaults, property, dstValue, srcValue)
return apply, nil
}
}
// Product variable properties need special handling, the type of the filtered product variable
// property struct may not be identical between the defaults module and the defaultable module.
// Use PrependMatchingProperties to apply whichever properties match.
func (defaultable *DefaultableModuleBase) applyDefaultVariableProperties(defaults DefaultsModule,
defaultableProp interface{}, tracker defaultsTrackerFunc) error {
if defaultableProp == nil {
return nil
}
defaultsProp := defaults.productVariableProperties()
if defaultsProp == nil {
return nil
}
dst := []interface{}{
defaultableProp,
// Put an empty copy of the src properties into dst so that properties in src that are not in dst
// don't cause a "failed to find property to extend" error.
proptools.CloneEmptyProperties(reflect.ValueOf(defaultsProp)).Interface(),
}
filter := filterForTracker(defaults, tracker)
return proptools.PrependMatchingProperties(dst, defaultsProp, filter)
}
func (defaultable *DefaultableModuleBase) applyDefaultProperties(defaults DefaultsModule,
defaultableProp interface{}, checker defaultsTrackerFunc) error {
filter := filterForTracker(defaults, checker)
for _, def := range defaults.properties() {
if proptools.TypeEqual(defaultableProp, def) {
err := proptools.PrependProperties(defaultableProp, def, filter)
if err != nil {
return err
}
}
}
return nil
}
func RegisterDefaultsPreArchMutators(ctx RegisterMutatorsContext) {
ctx.BottomUp("defaults_deps", defaultsDepsMutator).Parallel()
ctx.TopDown("defaults", defaultsMutator).Parallel()
}
func defaultsDepsMutator(ctx BottomUpMutatorContext) {
if defaultable, ok := ctx.Module().(Defaultable); ok {
ctx.AddDependency(ctx.Module(), DefaultsDepTag, defaultable.defaults().Defaults...)
}
}
func defaultsMutator(ctx TopDownMutatorContext) {
if defaultable, ok := ctx.Module().(Defaultable); ok {
if len(defaultable.defaults().Defaults) > 0 {
var defaultsList []DefaultsModule
seen := make(map[Defaults]bool)
ctx.WalkDeps(func(module, parent Module) bool {
if ctx.OtherModuleDependencyTag(module) == DefaultsDepTag {
if defaults, ok := module.(DefaultsModule); ok {
if !seen[defaults] {
seen[defaults] = true
defaultsList = append(defaultsList, defaults)
return len(defaults.defaults().Defaults) > 0
}
} else {
ctx.PropertyErrorf("defaults", "module %s is not an defaults module",
ctx.OtherModuleName(module))
}
}
return false
})
defaultable.applyDefaults(ctx, defaultsList)
}
defaultable.callHookIfAvailable(ctx)
}
}