Merge "Refactor snapshot module creation"
diff --git a/android/sdk.go b/android/sdk.go
index d13ad7d..f28c392 100644
--- a/android/sdk.go
+++ b/android/sdk.go
@@ -302,7 +302,46 @@
//
// The SdkMember is guaranteed to contain variants for which the
// IsInstance(Module) method returned true.
+ //
+ // deprecated Use AddPrebuiltModule() instead.
BuildSnapshot(sdkModuleContext ModuleContext, builder SnapshotBuilder, member SdkMember)
+
+ // Add a prebuilt module that the sdk will populate.
+ //
+ // Returning nil from this will cause the sdk module type to use the deprecated BuildSnapshot
+ // method to build the snapshot. That method is deprecated because it requires the SdkMemberType
+ // implementation to do all the word.
+ //
+ // Otherwise, returning a non-nil value from this will cause the sdk module type to do the
+ // majority of the work to generate the snapshot. The sdk module code generates the snapshot
+ // as follows:
+ //
+ // * A properties struct of type SdkMemberProperties is created for each variant and
+ // populated with information from the variant by calling PopulateFromVariant(SdkAware)
+ // on the struct.
+ //
+ // * An additional properties struct is created into which the common properties will be
+ // added.
+ //
+ // * The variant property structs are analysed to find exported (capitalized) fields which
+ // have common values. Those fields are cleared and the common value added to the common
+ // properties.
+ //
+ // * The sdk module type populates the BpModule structure, creating the arch specific
+ // structure and calls AddToPropertySet(...) on the properties struct to add the member
+ // specific properties in the correct place in the structure.
+ //
+ // * Finally, the FinalizeModule(...) method is called to add any additional properties.
+ // This was created to allow the property ordering in existing tests to be maintained so
+ // as to avoid having to change tests while refactoring.
+ //
+ AddPrebuiltModule(sdkModuleContext ModuleContext, builder SnapshotBuilder, member SdkMember) BpModule
+
+ // Add any additional properties to the end of the module.
+ FinalizeModule(sdkModuleContext ModuleContext, builder SnapshotBuilder, member SdkMember, bpModule BpModule)
+
+ // Create a structure into which variant specific properties can be added.
+ CreateVariantPropertiesStruct() SdkMemberProperties
}
// Base type for SdkMemberType implementations.
@@ -324,6 +363,23 @@
return b.TransitiveSdkMembers
}
+func (b *SdkMemberTypeBase) BuildSnapshot(sdkModuleContext ModuleContext, builder SnapshotBuilder, member SdkMember) {
+ panic("override AddPrebuiltModule")
+}
+
+func (b *SdkMemberTypeBase) AddPrebuiltModule(sdkModuleContext ModuleContext, builder SnapshotBuilder, member SdkMember) BpModule {
+ // Returning nil causes the legacy BuildSnapshot method to be used.
+ return nil
+}
+
+func (b *SdkMemberTypeBase) FinalizeModule(sdkModuleContext ModuleContext, builder SnapshotBuilder, member SdkMember, module BpModule) {
+ // Do nothing by default
+}
+
+func (b *SdkMemberTypeBase) CreateVariantPropertiesStruct() SdkMemberProperties {
+ panic("override me")
+}
+
// Encapsulates the information about registered SdkMemberTypes.
type SdkMemberTypesRegistry struct {
// The list of types sorted by property name.
@@ -389,3 +445,31 @@
SdkMemberTypes = SdkMemberTypes.copyAndAppend(memberType)
}
}
+
+// Base structure for all implementations of SdkMemberProperties.
+//
+// Contains common properties that apply across many different member types.
+type SdkMemberPropertiesBase struct {
+ // The setting to use for the compile_multilib property.
+ Compile_multilib string
+}
+
+func (b *SdkMemberPropertiesBase) Base() *SdkMemberPropertiesBase {
+ return b
+}
+
+// Interface to be implemented on top of a structure that contains variant specific
+// information.
+//
+// Struct fields that are capitalized are examined for common values to extract. Fields
+// that are not capitalized are assumed to be arch specific.
+type SdkMemberProperties interface {
+ // Access the base structure.
+ Base() *SdkMemberPropertiesBase
+
+ // Populate the structure with information from the variant.
+ PopulateFromVariant(variant SdkAware)
+
+ // Add the information from the structure to the property set.
+ AddToPropertySet(sdkModuleContext ModuleContext, builder SnapshotBuilder, propertySet BpPropertySet)
+}
diff --git a/cc/binary_sdk_member.go b/cc/binary_sdk_member.go
index 58d6ad0..eddf5b8 100644
--- a/cc/binary_sdk_member.go
+++ b/cc/binary_sdk_member.go
@@ -16,7 +16,6 @@
import (
"path/filepath"
- "strings"
"android/soong/android"
"github.com/google/blueprint"
@@ -64,65 +63,13 @@
return false
}
-func (mt *binarySdkMemberType) BuildSnapshot(sdkModuleContext android.ModuleContext, builder android.SnapshotBuilder, member android.SdkMember) {
- info := mt.organizeVariants(member)
- buildSharedNativeBinarySnapshot(info, builder, member)
-}
-
-// Organize the variants by architecture.
-func (mt *binarySdkMemberType) organizeVariants(member android.SdkMember) *nativeBinaryInfo {
- memberName := member.Name()
- info := &nativeBinaryInfo{
- name: memberName,
- memberType: mt,
- }
-
- for _, variant := range member.Variants() {
- ccModule := variant.(*Module)
-
- info.archVariantProperties = append(info.archVariantProperties, nativeBinaryInfoProperties{
- name: memberName,
- archType: ccModule.Target().Arch.ArchType.String(),
- outputFile: ccModule.OutputFile().Path(),
- })
- }
-
- // Initialize the unexported properties that will not be set during the
- // extraction process.
- info.commonProperties.name = memberName
-
- // Extract common properties from the arch specific properties.
- extractCommonProperties(&info.commonProperties, info.archVariantProperties)
-
- return info
-}
-
-func buildSharedNativeBinarySnapshot(info *nativeBinaryInfo, builder android.SnapshotBuilder, member android.SdkMember) {
+func (mt *binarySdkMemberType) AddPrebuiltModule(sdkModuleContext android.ModuleContext, builder android.SnapshotBuilder, member android.SdkMember) android.BpModule {
pbm := builder.AddPrebuiltModule(member, "cc_prebuilt_binary")
- archVariantCount := len(info.archVariantProperties)
+ return pbm
+}
- // Choose setting for compile_multilib that is appropriate for the arch variants supplied.
- var multilib string
- if archVariantCount == 2 {
- multilib = "both"
- } else if archVariantCount == 1 {
- if strings.HasSuffix(info.archVariantProperties[0].archType, "64") {
- multilib = "64"
- } else {
- multilib = "32"
- }
- }
- if multilib != "" {
- pbm.AddProperty("compile_multilib", multilib)
- }
-
- archProperties := pbm.AddPropertySet("arch")
- for _, av := range info.archVariantProperties {
- archTypeProperties := archProperties.AddPropertySet(av.archType)
- archTypeProperties.AddProperty("srcs", []string{nativeBinaryPathFor(av)})
-
- builder.CopyToSnapshot(av.outputFile, nativeBinaryPathFor(av))
- }
+func (mt *binarySdkMemberType) CreateVariantPropertiesStruct() android.SdkMemberProperties {
+ return &nativeBinaryInfoProperties{}
}
const (
@@ -140,8 +87,7 @@
// The exported (capitalized) fields will be examined and may be changed during common value extraction.
// The unexported fields will be left untouched.
type nativeBinaryInfoProperties struct {
- // The name of the library, is not exported as this must not be changed during optimization.
- name string
+ android.SdkMemberPropertiesBase
// archType is not exported as if set (to a non default value) it is always arch specific.
// This is "" for common properties.
@@ -151,10 +97,21 @@
outputFile android.Path
}
-// nativeBinaryInfo represents a collection of arch-specific modules having the same name
-type nativeBinaryInfo struct {
- name string
- memberType *binarySdkMemberType
- archVariantProperties []nativeBinaryInfoProperties
- commonProperties nativeBinaryInfoProperties
+func (p *nativeBinaryInfoProperties) PopulateFromVariant(variant android.SdkAware) {
+ ccModule := variant.(*Module)
+
+ p.archType = ccModule.Target().Arch.ArchType.String()
+ p.outputFile = ccModule.OutputFile().Path()
+}
+
+func (p *nativeBinaryInfoProperties) AddToPropertySet(sdkModuleContext android.ModuleContext, builder android.SnapshotBuilder, propertySet android.BpPropertySet) {
+ if p.Compile_multilib != "" {
+ propertySet.AddProperty("compile_multilib", p.Compile_multilib)
+ }
+
+ if p.outputFile != nil {
+ propertySet.AddProperty("srcs", []string{nativeBinaryPathFor(*p)})
+
+ builder.CopyToSnapshot(p.outputFile, nativeBinaryPathFor(*p))
+ }
}
diff --git a/cc/library_sdk_member.go b/cc/library_sdk_member.go
index a36917e..16b9e6f 100644
--- a/cc/library_sdk_member.go
+++ b/cc/library_sdk_member.go
@@ -16,7 +16,6 @@
import (
"path/filepath"
- "reflect"
"android/soong/android"
"github.com/google/blueprint"
@@ -96,48 +95,18 @@
return false
}
-// copy exported header files and stub *.so files
-func (mt *librarySdkMemberType) BuildSnapshot(sdkModuleContext android.ModuleContext, builder android.SnapshotBuilder, member android.SdkMember) {
- info := mt.organizeVariants(member)
- info.generatePrebuiltLibrary(sdkModuleContext, builder, member)
+func (mt *librarySdkMemberType) AddPrebuiltModule(sdkModuleContext android.ModuleContext, builder android.SnapshotBuilder, member android.SdkMember) android.BpModule {
+ pbm := builder.AddPrebuiltModule(member, mt.prebuiltModuleType)
+ return pbm
}
-// Organize the variants by architecture.
-func (mt *librarySdkMemberType) organizeVariants(member android.SdkMember) *nativeLibInfo {
- memberName := member.Name()
- info := &nativeLibInfo{
- name: memberName,
- memberType: mt,
- }
+func (mt *librarySdkMemberType) FinalizeModule(sdkModuleContext android.ModuleContext, builder android.SnapshotBuilder, member android.SdkMember, bpModule android.BpModule) {
+ bpModule.AddProperty("stl", "none")
+ bpModule.AddProperty("system_shared_libs", []string{})
+}
- for _, variant := range member.Variants() {
- ccModule := variant.(*Module)
-
- // Separate out the generated include dirs (which are arch specific) from the
- // include dirs (which may not be).
- exportedIncludeDirs, exportedGeneratedIncludeDirs := android.FilterPathListPredicate(
- ccModule.ExportedIncludeDirs(), isGeneratedHeaderDirectory)
-
- info.archVariantProperties = append(info.archVariantProperties, nativeLibInfoProperties{
- name: memberName,
- archType: ccModule.Target().Arch.ArchType.String(),
- ExportedIncludeDirs: exportedIncludeDirs,
- exportedGeneratedIncludeDirs: exportedGeneratedIncludeDirs,
- ExportedSystemIncludeDirs: ccModule.ExportedSystemIncludeDirs(),
- ExportedFlags: ccModule.ExportedFlags(),
- exportedGeneratedHeaders: ccModule.ExportedGeneratedHeaders(),
- outputFile: ccModule.OutputFile().Path(),
- })
- }
-
- // Initialize the unexported properties that will not be set during the
- // extraction process.
- info.commonProperties.name = memberName
-
- // Extract common properties from the arch specific properties.
- extractCommonProperties(&info.commonProperties, info.archVariantProperties)
-
- return info
+func (mt *librarySdkMemberType) CreateVariantPropertiesStruct() android.SdkMemberProperties {
+ return &nativeLibInfoProperties{memberType: mt}
}
func isGeneratedHeaderDirectory(p android.Path) bool {
@@ -145,94 +114,9 @@
return gen
}
-// Extract common properties from a slice of property structures of the same type.
-//
-// All the property structures must be of the same type.
-// commonProperties - must be a pointer to the structure into which common properties will be added.
-// inputPropertiesSlice - must be a slice of input properties structures.
-//
-// Iterates over each exported field (capitalized name) and checks to see whether they
-// have the same value (using DeepEquals) across all the input properties. If it does not then no
-// change is made. Otherwise, the common value is stored in the field in the commonProperties
-// and the field in each of the input properties structure is set to its default value.
-func extractCommonProperties(commonProperties interface{}, inputPropertiesSlice interface{}) {
- commonStructValue := reflect.ValueOf(commonProperties).Elem()
- propertiesStructType := commonStructValue.Type()
-
- // Create an empty structure from which default values for the field can be copied.
- emptyStructValue := reflect.New(propertiesStructType).Elem()
-
- for f := 0; f < propertiesStructType.NumField(); f++ {
- // Check to see if all the structures have the same value for the field. The commonValue
- // is nil on entry to the loop and if it is nil on exit then there is no common value,
- // otherwise it points to the common value.
- var commonValue *reflect.Value
- sliceValue := reflect.ValueOf(inputPropertiesSlice)
-
- for i := 0; i < sliceValue.Len(); i++ {
- structValue := sliceValue.Index(i)
- fieldValue := structValue.Field(f)
- if !fieldValue.CanInterface() {
- // The field is not exported so ignore it.
- continue
- }
-
- if commonValue == nil {
- // Use the first value as the commonProperties value.
- commonValue = &fieldValue
- } else {
- // If the value does not match the current common value then there is
- // no value in common so break out.
- if !reflect.DeepEqual(fieldValue.Interface(), commonValue.Interface()) {
- commonValue = nil
- break
- }
- }
- }
-
- // If the fields all have a common value then store it in the common struct field
- // and set the input struct's field to the empty value.
- if commonValue != nil {
- emptyValue := emptyStructValue.Field(f)
- commonStructValue.Field(f).Set(*commonValue)
- for i := 0; i < sliceValue.Len(); i++ {
- structValue := sliceValue.Index(i)
- fieldValue := structValue.Field(f)
- fieldValue.Set(emptyValue)
- }
- }
- }
-}
-
-func (info *nativeLibInfo) generatePrebuiltLibrary(sdkModuleContext android.ModuleContext, builder android.SnapshotBuilder, member android.SdkMember) {
-
- pbm := builder.AddPrebuiltModule(member, info.memberType.prebuiltModuleType)
-
- addPossiblyArchSpecificProperties(sdkModuleContext, builder, info.commonProperties, pbm)
-
- archProperties := pbm.AddPropertySet("arch")
- for _, av := range info.archVariantProperties {
- archTypeProperties := archProperties.AddPropertySet(av.archType)
-
- // If the library has some link types then it produces an output binary file, otherwise it
- // is header only.
- if info.memberType.linkTypes != nil {
- // Copy the generated library to the snapshot and add a reference to it in the .bp module.
- nativeLibraryPath := nativeLibraryPathFor(av)
- builder.CopyToSnapshot(av.outputFile, nativeLibraryPath)
- archTypeProperties.AddProperty("srcs", []string{nativeLibraryPath})
- }
-
- // Add any arch specific properties inside the appropriate arch: {<arch>: {...}} block
- addPossiblyArchSpecificProperties(sdkModuleContext, builder, av, archTypeProperties)
- }
- pbm.AddProperty("stl", "none")
- pbm.AddProperty("system_shared_libs", []string{})
-}
-
type includeDirsProperty struct {
// Accessor to retrieve the paths
- pathsGetter func(libInfo nativeLibInfoProperties) android.Paths
+ pathsGetter func(libInfo *nativeLibInfoProperties) android.Paths
// The name of the property in the prebuilt library, "" means there is no property.
propertyName string
@@ -252,7 +136,7 @@
// ExportedIncludeDirs lists directories that contains some header files to be
// copied into a directory in the snapshot. The snapshot directories must be added to
// the export_include_dirs property in the prebuilt module in the snapshot.
- pathsGetter: func(libInfo nativeLibInfoProperties) android.Paths { return libInfo.ExportedIncludeDirs },
+ pathsGetter: func(libInfo *nativeLibInfoProperties) android.Paths { return libInfo.ExportedIncludeDirs },
propertyName: "export_include_dirs",
snapshotDir: nativeIncludeDir,
copy: true,
@@ -262,7 +146,7 @@
// ExportedSystemIncludeDirs lists directories that contains some system header files to
// be copied into a directory in the snapshot. The snapshot directories must be added to
// the export_system_include_dirs property in the prebuilt module in the snapshot.
- pathsGetter: func(libInfo nativeLibInfoProperties) android.Paths { return libInfo.ExportedSystemIncludeDirs },
+ pathsGetter: func(libInfo *nativeLibInfoProperties) android.Paths { return libInfo.ExportedSystemIncludeDirs },
propertyName: "export_system_include_dirs",
snapshotDir: nativeIncludeDir,
copy: true,
@@ -273,7 +157,7 @@
// that are explicitly listed in the exportedGeneratedHeaders property. So, the contents
// of these directories do not need to be copied, but these directories do need adding to
// the export_include_dirs property in the prebuilt module in the snapshot.
- pathsGetter: func(libInfo nativeLibInfoProperties) android.Paths { return libInfo.exportedGeneratedIncludeDirs },
+ pathsGetter: func(libInfo *nativeLibInfoProperties) android.Paths { return libInfo.exportedGeneratedIncludeDirs },
propertyName: "export_include_dirs",
snapshotDir: nativeGeneratedIncludeDir,
copy: false,
@@ -284,7 +168,7 @@
// specified in exportedGeneratedIncludeDirs must be copied into the snapshot.
// As they are in a directory in exportedGeneratedIncludeDirs they do not need adding to a
// property in the prebuilt module in the snapshot.
- pathsGetter: func(libInfo nativeLibInfoProperties) android.Paths { return libInfo.exportedGeneratedHeaders },
+ pathsGetter: func(libInfo *nativeLibInfoProperties) android.Paths { return libInfo.exportedGeneratedHeaders },
propertyName: "",
snapshotDir: nativeGeneratedIncludeDir,
copy: true,
@@ -293,7 +177,14 @@
}
// Add properties that may, or may not, be arch specific.
-func addPossiblyArchSpecificProperties(sdkModuleContext android.ModuleContext, builder android.SnapshotBuilder, libInfo nativeLibInfoProperties, outputProperties android.BpPropertySet) {
+func addPossiblyArchSpecificProperties(sdkModuleContext android.ModuleContext, builder android.SnapshotBuilder, libInfo *nativeLibInfoProperties, outputProperties android.BpPropertySet) {
+
+ // Copy the generated library to the snapshot and add a reference to it in the .bp module.
+ if libInfo.outputFile != nil {
+ nativeLibraryPath := nativeLibraryPathFor(libInfo)
+ builder.CopyToSnapshot(libInfo.outputFile, nativeLibraryPath)
+ outputProperties.AddProperty("srcs", []string{nativeLibraryPath})
+ }
// Map from property name to the include dirs to add to the prebuilt module in the snapshot.
includeDirs := make(map[string][]string)
@@ -355,7 +246,7 @@
)
// path to the native library. Relative to <sdk_root>/<api_dir>
-func nativeLibraryPathFor(lib nativeLibInfoProperties) string {
+func nativeLibraryPathFor(lib *nativeLibInfoProperties) string {
return filepath.Join(lib.archType,
nativeStubDir, lib.outputFile.Base())
}
@@ -365,6 +256,10 @@
// The exported (capitalized) fields will be examined and may be changed during common value extraction.
// The unexported fields will be left untouched.
type nativeLibInfoProperties struct {
+ android.SdkMemberPropertiesBase
+
+ memberType *librarySdkMemberType
+
// The name of the library, is not exported as this must not be changed during optimization.
name string
@@ -401,10 +296,29 @@
outputFile android.Path
}
-// nativeLibInfo represents a collection of arch-specific modules having the same name
-type nativeLibInfo struct {
- name string
- memberType *librarySdkMemberType
- archVariantProperties []nativeLibInfoProperties
- commonProperties nativeLibInfoProperties
+func (p *nativeLibInfoProperties) PopulateFromVariant(variant android.SdkAware) {
+ ccModule := variant.(*Module)
+
+ // If the library has some link types then it produces an output binary file, otherwise it
+ // is header only.
+ if p.memberType.linkTypes != nil {
+ p.outputFile = ccModule.OutputFile().Path()
+ }
+
+ // Separate out the generated include dirs (which are arch specific) from the
+ // include dirs (which may not be).
+ exportedIncludeDirs, exportedGeneratedIncludeDirs := android.FilterPathListPredicate(
+ ccModule.ExportedIncludeDirs(), isGeneratedHeaderDirectory)
+
+ p.name = variant.Name()
+ p.archType = ccModule.Target().Arch.ArchType.String()
+ p.ExportedIncludeDirs = exportedIncludeDirs
+ p.exportedGeneratedIncludeDirs = exportedGeneratedIncludeDirs
+ p.ExportedSystemIncludeDirs = ccModule.ExportedSystemIncludeDirs()
+ p.ExportedFlags = ccModule.ExportedFlags()
+ p.exportedGeneratedHeaders = ccModule.ExportedGeneratedHeaders()
+}
+
+func (p *nativeLibInfoProperties) AddToPropertySet(sdkModuleContext android.ModuleContext, builder android.SnapshotBuilder, propertySet android.BpPropertySet) {
+ addPossiblyArchSpecificProperties(sdkModuleContext, builder, p, propertySet)
}
diff --git a/sdk/update.go b/sdk/update.go
index b335777..352cc32 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -251,7 +251,14 @@
members, multilib := s.organizeMembers(ctx, memberRefs)
for _, member := range members {
- member.memberType.BuildSnapshot(ctx, builder, member)
+ memberType := member.memberType
+ prebuiltModule := memberType.AddPrebuiltModule(ctx, builder, member)
+ if prebuiltModule == nil {
+ // Fall back to legacy method of building a snapshot
+ memberType.BuildSnapshot(ctx, builder, member)
+ } else {
+ s.createMemberSnapshot(ctx, builder, member, prebuiltModule)
+ }
}
// Create a transformer that will transform an unversioned module into a versioned module.
@@ -643,3 +650,137 @@
func (m *sdkMember) Variants() []android.SdkAware {
return m.variants
}
+
+type baseInfo struct {
+ Properties android.SdkMemberProperties
+}
+
+type osTypeSpecificInfo struct {
+ baseInfo
+
+ // The list of arch type specific info for this os type.
+ archTypes []*archTypeSpecificInfo
+}
+
+type archTypeSpecificInfo struct {
+ baseInfo
+
+ archType android.ArchType
+}
+
+func (s *sdk) createMemberSnapshot(sdkModuleContext android.ModuleContext, builder *snapshotBuilder, member *sdkMember, bpModule android.BpModule) {
+
+ memberType := member.memberType
+
+ // Group the properties for each variant by arch type.
+ osInfo := &osTypeSpecificInfo{}
+ osInfo.Properties = memberType.CreateVariantPropertiesStruct()
+ variants := member.Variants()
+ for _, variant := range variants {
+ var properties android.SdkMemberProperties
+
+ // Get the info associated with the arch type inside the os info.
+ archType := variant.Target().Arch.ArchType
+
+ archInfo := &archTypeSpecificInfo{archType: archType}
+ properties = memberType.CreateVariantPropertiesStruct()
+ archInfo.Properties = properties
+
+ osInfo.archTypes = append(osInfo.archTypes, archInfo)
+
+ properties.PopulateFromVariant(variant)
+ }
+
+ var archProperties []android.SdkMemberProperties
+ for _, archInfo := range osInfo.archTypes {
+ archProperties = append(archProperties, archInfo.Properties)
+ }
+
+ extractCommonProperties(osInfo.Properties, archProperties)
+
+ // Choose setting for compile_multilib that is appropriate for the arch variants supplied.
+ var multilib string
+ archVariantCount := len(osInfo.archTypes)
+ if archVariantCount == 2 {
+ multilib = "both"
+ } else if archVariantCount == 1 {
+ if strings.HasSuffix(osInfo.archTypes[0].archType.Name, "64") {
+ multilib = "64"
+ } else {
+ multilib = "32"
+ }
+ }
+
+ osInfo.Properties.Base().Compile_multilib = multilib
+
+ osInfo.Properties.AddToPropertySet(sdkModuleContext, builder, bpModule)
+
+ archPropertySet := bpModule.AddPropertySet("arch")
+ for _, av := range osInfo.archTypes {
+ archTypePropertySet := archPropertySet.AddPropertySet(av.archType.Name)
+
+ av.Properties.AddToPropertySet(sdkModuleContext, builder, archTypePropertySet)
+ }
+
+ memberType.FinalizeModule(sdkModuleContext, builder, member, bpModule)
+}
+
+// Extract common properties from a slice of property structures of the same type.
+//
+// All the property structures must be of the same type.
+// commonProperties - must be a pointer to the structure into which common properties will be added.
+// inputPropertiesSlice - must be a slice of input properties structures.
+//
+// Iterates over each exported field (capitalized name) and checks to see whether they
+// have the same value (using DeepEquals) across all the input properties. If it does not then no
+// change is made. Otherwise, the common value is stored in the field in the commonProperties
+// and the field in each of the input properties structure is set to its default value.
+func extractCommonProperties(commonProperties interface{}, inputPropertiesSlice interface{}) {
+ commonPropertiesValue := reflect.ValueOf(commonProperties)
+ commonStructValue := commonPropertiesValue.Elem()
+ propertiesStructType := commonStructValue.Type()
+
+ // Create an empty structure from which default values for the field can be copied.
+ emptyStructValue := reflect.New(propertiesStructType).Elem()
+
+ for f := 0; f < propertiesStructType.NumField(); f++ {
+ // Check to see if all the structures have the same value for the field. The commonValue
+ // is nil on entry to the loop and if it is nil on exit then there is no common value,
+ // otherwise it points to the common value.
+ var commonValue *reflect.Value
+ sliceValue := reflect.ValueOf(inputPropertiesSlice)
+
+ for i := 0; i < sliceValue.Len(); i++ {
+ structValue := sliceValue.Index(i).Elem().Elem()
+ fieldValue := structValue.Field(f)
+ if !fieldValue.CanInterface() {
+ // The field is not exported so ignore it.
+ continue
+ }
+
+ if commonValue == nil {
+ // Use the first value as the commonProperties value.
+ commonValue = &fieldValue
+ } else {
+ // If the value does not match the current common value then there is
+ // no value in common so break out.
+ if !reflect.DeepEqual(fieldValue.Interface(), commonValue.Interface()) {
+ commonValue = nil
+ break
+ }
+ }
+ }
+
+ // If the fields all have a common value then store it in the common struct field
+ // and set the input struct's field to the empty value.
+ if commonValue != nil {
+ emptyValue := emptyStructValue.Field(f)
+ commonStructValue.Field(f).Set(*commonValue)
+ for i := 0; i < sliceValue.Len(); i++ {
+ structValue := sliceValue.Index(i).Elem().Elem()
+ fieldValue := structValue.Field(f)
+ fieldValue.Set(emptyValue)
+ }
+ }
+ }
+}