1// Copyright 2015 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package android 16 17import ( 18 "fmt" 19 "reflect" 20 "runtime" 21 "strings" 22 23 "github.com/google/blueprint/proptools" 24) 25 26func init() { 27 registerVariableBuildComponents(InitRegistrationContext) 28} 29 30func registerVariableBuildComponents(ctx RegistrationContext) { 31 ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) { 32 ctx.BottomUp("variable", VariableMutator).Parallel() 33 }) 34} 35 36var PrepareForTestWithVariables = FixtureRegisterWithContext(registerVariableBuildComponents) 37 38type variableProperties struct { 39 Product_variables struct { 40 Platform_sdk_version struct { 41 Asflags []string 42 Cflags []string 43 } 44 45 // unbundled_build is a catch-all property to annotate modules that don't build in one or 46 // more unbundled branches, usually due to dependencies missing from the manifest. 47 Unbundled_build struct { 48 Enabled *bool `android:"arch_variant"` 49 } `android:"arch_variant"` 50 51 Malloc_not_svelte struct { 52 Cflags []string `android:"arch_variant"` 53 Shared_libs []string `android:"arch_variant"` 54 Whole_static_libs []string `android:"arch_variant"` 55 Exclude_static_libs []string `android:"arch_variant"` 56 } `android:"arch_variant"` 57 58 Malloc_zero_contents struct { 59 Cflags []string `android:"arch_variant"` 60 } `android:"arch_variant"` 61 62 Malloc_pattern_fill_contents struct { 63 Cflags []string `android:"arch_variant"` 64 } `android:"arch_variant"` 65 66 Safestack struct { 67 Cflags []string `android:"arch_variant"` 68 } `android:"arch_variant"` 69 70 Binder32bit struct { 71 Cflags []string 72 } 73 74 Override_rs_driver struct { 75 Cflags []string 76 } 77 78 // treble_linker_namespaces is true when the system/vendor linker namespace separation is 79 // enabled. 80 Treble_linker_namespaces struct { 81 Cflags []string 82 } 83 // enforce_vintf_manifest is true when a device is required to have a vintf manifest. 84 Enforce_vintf_manifest struct { 85 Cflags []string 86 } 87 88 // debuggable is true for eng and userdebug builds, and can be used to turn on additional 89 // debugging features that don't significantly impact runtime behavior. userdebug builds 90 // are used for dogfooding and performance testing, and should be as similar to user builds 91 // as possible. 92 Debuggable struct { 93 Cflags []string 94 Cppflags []string 95 Init_rc []string 96 Required []string 97 Host_required []string 98 Target_required []string 99 Strip struct { 100 All *bool 101 Keep_symbols *bool 102 Keep_symbols_and_debug_frame *bool 103 } 104 } 105 106 // eng is true for -eng builds, and can be used to turn on additionaly heavyweight debugging 107 // features. 108 Eng struct { 109 Cflags []string 110 Cppflags []string 111 Lto struct { 112 Never *bool 113 } 114 Sanitize struct { 115 Address *bool 116 } 117 Optimize struct { 118 Enabled *bool 119 } 120 } 121 122 Pdk struct { 123 Enabled *bool `android:"arch_variant"` 124 } `android:"arch_variant"` 125 126 Uml struct { 127 Cppflags []string 128 } 129 130 Arc struct { 131 Cflags []string `android:"arch_variant"` 132 Exclude_srcs []string `android:"arch_variant"` 133 Header_libs []string `android:"arch_variant"` 134 Include_dirs []string `android:"arch_variant"` 135 Shared_libs []string `android:"arch_variant"` 136 Static_libs []string `android:"arch_variant"` 137 Srcs []string `android:"arch_variant"` 138 Whole_static_libs []string `android:"arch_variant"` 139 } `android:"arch_variant"` 140 141 Flatten_apex struct { 142 Enabled *bool 143 } 144 145 Native_coverage struct { 146 Src *string `android:"arch_variant"` 147 Srcs []string `android:"arch_variant"` 148 Exclude_srcs []string `android:"arch_variant"` 149 } `android:"arch_variant"` 150 } `android:"arch_variant"` 151} 152 153var defaultProductVariables interface{} = variableProperties{} 154 155type productVariables struct { 156 // Suffix to add to generated Makefiles 157 Make_suffix *string `json:",omitempty"` 158 159 BuildId *string `json:",omitempty"` 160 BuildNumberFile *string `json:",omitempty"` 161 162 Platform_version_name *string `json:",omitempty"` 163 Platform_sdk_version *int `json:",omitempty"` 164 Platform_sdk_codename *string `json:",omitempty"` 165 Platform_sdk_final *bool `json:",omitempty"` 166 Platform_version_active_codenames []string `json:",omitempty"` 167 Platform_vndk_version *string `json:",omitempty"` 168 Platform_systemsdk_versions []string `json:",omitempty"` 169 Platform_security_patch *string `json:",omitempty"` 170 Platform_preview_sdk_version *string `json:",omitempty"` 171 Platform_min_supported_target_sdk_version *string `json:",omitempty"` 172 Platform_base_os *string `json:",omitempty"` 173 174 DeviceName *string `json:",omitempty"` 175 DeviceArch *string `json:",omitempty"` 176 DeviceArchVariant *string `json:",omitempty"` 177 DeviceCpuVariant *string `json:",omitempty"` 178 DeviceAbi []string `json:",omitempty"` 179 DeviceVndkVersion *string `json:",omitempty"` 180 DeviceCurrentApiLevelForVendorModules *string `json:",omitempty"` 181 DeviceSystemSdkVersions []string `json:",omitempty"` 182 183 RecoverySnapshotVersion *string `json:",omitempty"` 184 185 DeviceSecondaryArch *string `json:",omitempty"` 186 DeviceSecondaryArchVariant *string `json:",omitempty"` 187 DeviceSecondaryCpuVariant *string `json:",omitempty"` 188 DeviceSecondaryAbi []string `json:",omitempty"` 189 190 NativeBridgeArch *string `json:",omitempty"` 191 NativeBridgeArchVariant *string `json:",omitempty"` 192 NativeBridgeCpuVariant *string `json:",omitempty"` 193 NativeBridgeAbi []string `json:",omitempty"` 194 NativeBridgeRelativePath *string `json:",omitempty"` 195 196 NativeBridgeSecondaryArch *string `json:",omitempty"` 197 NativeBridgeSecondaryArchVariant *string `json:",omitempty"` 198 NativeBridgeSecondaryCpuVariant *string `json:",omitempty"` 199 NativeBridgeSecondaryAbi []string `json:",omitempty"` 200 NativeBridgeSecondaryRelativePath *string `json:",omitempty"` 201 202 HostArch *string `json:",omitempty"` 203 HostSecondaryArch *string `json:",omitempty"` 204 205 CrossHost *string `json:",omitempty"` 206 CrossHostArch *string `json:",omitempty"` 207 CrossHostSecondaryArch *string `json:",omitempty"` 208 209 DeviceResourceOverlays []string `json:",omitempty"` 210 ProductResourceOverlays []string `json:",omitempty"` 211 EnforceRROTargets []string `json:",omitempty"` 212 EnforceRROExcludedOverlays []string `json:",omitempty"` 213 214 AAPTCharacteristics *string `json:",omitempty"` 215 AAPTConfig []string `json:",omitempty"` 216 AAPTPreferredConfig *string `json:",omitempty"` 217 AAPTPrebuiltDPI []string `json:",omitempty"` 218 219 DefaultAppCertificate *string `json:",omitempty"` 220 221 AppsDefaultVersionName *string `json:",omitempty"` 222 223 Allow_missing_dependencies *bool `json:",omitempty"` 224 Unbundled_build *bool `json:",omitempty"` 225 Unbundled_build_apps *bool `json:",omitempty"` 226 Always_use_prebuilt_sdks *bool `json:",omitempty"` 227 Skip_boot_jars_check *bool `json:",omitempty"` 228 Malloc_not_svelte *bool `json:",omitempty"` 229 Malloc_zero_contents *bool `json:",omitempty"` 230 Malloc_pattern_fill_contents *bool `json:",omitempty"` 231 Safestack *bool `json:",omitempty"` 232 HostStaticBinaries *bool `json:",omitempty"` 233 Binder32bit *bool `json:",omitempty"` 234 UseGoma *bool `json:",omitempty"` 235 UseRBE *bool `json:",omitempty"` 236 UseRBEJAVAC *bool `json:",omitempty"` 237 UseRBER8 *bool `json:",omitempty"` 238 UseRBED8 *bool `json:",omitempty"` 239 Debuggable *bool `json:",omitempty"` 240 Eng *bool `json:",omitempty"` 241 Treble_linker_namespaces *bool `json:",omitempty"` 242 Enforce_vintf_manifest *bool `json:",omitempty"` 243 Uml *bool `json:",omitempty"` 244 Arc *bool `json:",omitempty"` 245 MinimizeJavaDebugInfo *bool `json:",omitempty"` 246 247 Check_elf_files *bool `json:",omitempty"` 248 249 UncompressPrivAppDex *bool `json:",omitempty"` 250 ModulesLoadedByPrivilegedModules []string `json:",omitempty"` 251 252 BootJars ConfiguredJarList `json:",omitempty"` 253 UpdatableBootJars ConfiguredJarList `json:",omitempty"` 254 255 IntegerOverflowExcludePaths []string `json:",omitempty"` 256 257 EnableCFI *bool `json:",omitempty"` 258 CFIExcludePaths []string `json:",omitempty"` 259 CFIIncludePaths []string `json:",omitempty"` 260 261 DisableScudo *bool `json:",omitempty"` 262 263 MemtagHeapExcludePaths []string `json:",omitempty"` 264 MemtagHeapAsyncIncludePaths []string `json:",omitempty"` 265 MemtagHeapSyncIncludePaths []string `json:",omitempty"` 266 267 VendorPath *string `json:",omitempty"` 268 OdmPath *string `json:",omitempty"` 269 ProductPath *string `json:",omitempty"` 270 SystemExtPath *string `json:",omitempty"` 271 272 ClangTidy *bool `json:",omitempty"` 273 TidyChecks *string `json:",omitempty"` 274 275 SamplingPGO *bool `json:",omitempty"` 276 277 JavaCoveragePaths []string `json:",omitempty"` 278 JavaCoverageExcludePaths []string `json:",omitempty"` 279 280 GcovCoverage *bool `json:",omitempty"` 281 ClangCoverage *bool `json:",omitempty"` 282 NativeCoveragePaths []string `json:",omitempty"` 283 NativeCoverageExcludePaths []string `json:",omitempty"` 284 285 // Set by NewConfig 286 Native_coverage *bool 287 288 SanitizeHost []string `json:",omitempty"` 289 SanitizeDevice []string `json:",omitempty"` 290 SanitizeDeviceDiag []string `json:",omitempty"` 291 SanitizeDeviceArch []string `json:",omitempty"` 292 293 ArtUseReadBarrier *bool `json:",omitempty"` 294 295 BtConfigIncludeDir *string `json:",omitempty"` 296 297 Override_rs_driver *string `json:",omitempty"` 298 299 Fuchsia *bool `json:",omitempty"` 300 301 DeviceKernelHeaders []string `json:",omitempty"` 302 303 ExtraVndkVersions []string `json:",omitempty"` 304 305 NamespacesToExport []string `json:",omitempty"` 306 307 PgoAdditionalProfileDirs []string `json:",omitempty"` 308 309 VndkUseCoreVariant *bool `json:",omitempty"` 310 VndkSnapshotBuildArtifacts *bool `json:",omitempty"` 311 312 DirectedVendorSnapshot bool `json:",omitempty"` 313 VendorSnapshotModules map[string]bool `json:",omitempty"` 314 315 DirectedRecoverySnapshot bool `json:",omitempty"` 316 RecoverySnapshotModules map[string]bool `json:",omitempty"` 317 318 VendorSnapshotDirsIncluded []string `json:",omitempty"` 319 VendorSnapshotDirsExcluded []string `json:",omitempty"` 320 RecoverySnapshotDirsExcluded []string `json:",omitempty"` 321 RecoverySnapshotDirsIncluded []string `json:",omitempty"` 322 323 BoardVendorSepolicyDirs []string `json:",omitempty"` 324 BoardOdmSepolicyDirs []string `json:",omitempty"` 325 BoardReqdMaskPolicy []string `json:",omitempty"` 326 SystemExtPublicSepolicyDirs []string `json:",omitempty"` 327 SystemExtPrivateSepolicyDirs []string `json:",omitempty"` 328 BoardSepolicyM4Defs []string `json:",omitempty"` 329 330 BoardSepolicyVers *string `json:",omitempty"` 331 PlatformSepolicyVersion *string `json:",omitempty"` 332 333 VendorVars map[string]map[string]string `json:",omitempty"` 334 335 Ndk_abis *bool `json:",omitempty"` 336 337 Flatten_apex *bool `json:",omitempty"` 338 ForceApexSymlinkOptimization *bool `json:",omitempty"` 339 CompressedApex *bool `json:",omitempty"` 340 Aml_abis *bool `json:",omitempty"` 341 342 DexpreoptGlobalConfig *string `json:",omitempty"` 343 344 WithDexpreopt bool `json:",omitempty"` 345 346 ManifestPackageNameOverrides []string `json:",omitempty"` 347 CertificateOverrides []string `json:",omitempty"` 348 PackageNameOverrides []string `json:",omitempty"` 349 350 EnforceSystemCertificate *bool `json:",omitempty"` 351 EnforceSystemCertificateAllowList []string `json:",omitempty"` 352 353 ProductHiddenAPIStubs []string `json:",omitempty"` 354 ProductHiddenAPIStubsSystem []string `json:",omitempty"` 355 ProductHiddenAPIStubsTest []string `json:",omitempty"` 356 357 ProductPublicSepolicyDirs []string `json:",omitempty"` 358 ProductPrivateSepolicyDirs []string `json:",omitempty"` 359 360 ProductVndkVersion *string `json:",omitempty"` 361 362 TargetFSConfigGen []string `json:",omitempty"` 363 364 MissingUsesLibraries []string `json:",omitempty"` 365 366 EnforceProductPartitionInterface *bool `json:",omitempty"` 367 368 EnforceInterPartitionJavaSdkLibrary *bool `json:",omitempty"` 369 InterPartitionJavaLibraryAllowList []string `json:",omitempty"` 370 371 InstallExtraFlattenedApexes *bool `json:",omitempty"` 372 373 BoardUsesRecoveryAsBoot *bool `json:",omitempty"` 374 375 BoardKernelBinaries []string `json:",omitempty"` 376 BoardKernelModuleInterfaceVersions []string `json:",omitempty"` 377 378 BoardMoveRecoveryResourcesToVendorBoot *bool `json:",omitempty"` 379 380 PrebuiltHiddenApiDir *string `json:",omitempty"` 381 382 ShippingApiLevel *string `json:",omitempty"` 383 384 BuildBrokenEnforceSyspropOwner bool `json:",omitempty"` 385 BuildBrokenTrebleSyspropNeverallow bool `json:",omitempty"` 386 BuildBrokenVendorPropertyNamespace bool `json:",omitempty"` 387 388 BuildDebugfsRestrictionsEnabled bool `json:",omitempty"` 389 390 RequiresInsecureExecmemForSwiftshader bool `json:",omitempty"` 391 392 SelinuxIgnoreNeverallows bool `json:",omitempty"` 393 394 SepolicySplit bool `json:",omitempty"` 395} 396 397func boolPtr(v bool) *bool { 398 return &v 399} 400 401func intPtr(v int) *int { 402 return &v 403} 404 405func stringPtr(v string) *string { 406 return &v 407} 408 409func (v *productVariables) SetDefaultConfig() { 410 *v = productVariables{ 411 BuildNumberFile: stringPtr("build_number.txt"), 412 413 Platform_version_name: stringPtr("S"), 414 Platform_sdk_version: intPtr(30), 415 Platform_sdk_codename: stringPtr("S"), 416 Platform_sdk_final: boolPtr(false), 417 Platform_version_active_codenames: []string{"S"}, 418 Platform_vndk_version: stringPtr("S"), 419 420 HostArch: stringPtr("x86_64"), 421 HostSecondaryArch: stringPtr("x86"), 422 DeviceName: stringPtr("generic_arm64"), 423 DeviceArch: stringPtr("arm64"), 424 DeviceArchVariant: stringPtr("armv8-a"), 425 DeviceCpuVariant: stringPtr("generic"), 426 DeviceAbi: []string{"arm64-v8a"}, 427 DeviceSecondaryArch: stringPtr("arm"), 428 DeviceSecondaryArchVariant: stringPtr("armv8-a"), 429 DeviceSecondaryCpuVariant: stringPtr("generic"), 430 DeviceSecondaryAbi: []string{"armeabi-v7a", "armeabi"}, 431 432 AAPTConfig: []string{"normal", "large", "xlarge", "hdpi", "xhdpi", "xxhdpi"}, 433 AAPTPreferredConfig: stringPtr("xhdpi"), 434 AAPTCharacteristics: stringPtr("nosdcard"), 435 AAPTPrebuiltDPI: []string{"xhdpi", "xxhdpi"}, 436 437 Malloc_not_svelte: boolPtr(true), 438 Malloc_zero_contents: boolPtr(true), 439 Malloc_pattern_fill_contents: boolPtr(false), 440 Safestack: boolPtr(false), 441 442 BootJars: ConfiguredJarList{apexes: []string{}, jars: []string{}}, 443 UpdatableBootJars: ConfiguredJarList{apexes: []string{}, jars: []string{}}, 444 } 445 446 if runtime.GOOS == "linux" { 447 v.CrossHost = stringPtr("windows") 448 v.CrossHostArch = stringPtr("x86") 449 v.CrossHostSecondaryArch = stringPtr("x86_64") 450 } 451} 452 453// ProductConfigContext requires the access to the Module to get product config properties. 454type ProductConfigContext interface { 455 Module() Module 456} 457 458// ProductConfigProperty contains the information for a single property (may be a struct) paired 459// with the appropriate ProductConfigVariable. 460type ProductConfigProperty struct { 461 ProductConfigVariable string 462 Property interface{} 463} 464 465// ProductConfigProperties is a map of property name to a slice of ProductConfigProperty such that 466// all it all product variable-specific versions of a property are easily accessed together 467type ProductConfigProperties map[string][]ProductConfigProperty 468 469// ProductVariableProperties returns a ProductConfigProperties containing only the properties which 470// have been set for the module in the given context. 471func ProductVariableProperties(ctx ProductConfigContext) ProductConfigProperties { 472 module := ctx.Module() 473 moduleBase := module.base() 474 475 productConfigProperties := ProductConfigProperties{} 476 477 if moduleBase.variableProperties == nil { 478 return productConfigProperties 479 } 480 481 variableValues := reflect.ValueOf(moduleBase.variableProperties).Elem().FieldByName("Product_variables") 482 for i := 0; i < variableValues.NumField(); i++ { 483 variableValue := variableValues.Field(i) 484 // Check if any properties were set for the module 485 if variableValue.IsZero() { 486 continue 487 } 488 // e.g. Platform_sdk_version, Unbundled_build, Malloc_not_svelte, etc. 489 productVariableName := variableValues.Type().Field(i).Name 490 for j := 0; j < variableValue.NumField(); j++ { 491 property := variableValue.Field(j) 492 // If the property wasn't set, no need to pass it along 493 if property.IsZero() { 494 continue 495 } 496 497 // e.g. Asflags, Cflags, Enabled, etc. 498 propertyName := variableValue.Type().Field(j).Name 499 productConfigProperties[propertyName] = append(productConfigProperties[propertyName], 500 ProductConfigProperty{ 501 ProductConfigVariable: productVariableName, 502 Property: property.Interface(), 503 }) 504 } 505 } 506 507 return productConfigProperties 508} 509 510func VariableMutator(mctx BottomUpMutatorContext) { 511 var module Module 512 var ok bool 513 if module, ok = mctx.Module().(Module); !ok { 514 return 515 } 516 517 // TODO: depend on config variable, create variants, propagate variants up tree 518 a := module.base() 519 520 if a.variableProperties == nil { 521 return 522 } 523 524 variableValues := reflect.ValueOf(a.variableProperties).Elem().FieldByName("Product_variables") 525 526 productVariables := reflect.ValueOf(mctx.Config().productVariables) 527 528 for i := 0; i < variableValues.NumField(); i++ { 529 variableValue := variableValues.Field(i) 530 name := variableValues.Type().Field(i).Name 531 property := "product_variables." + proptools.PropertyNameForField(name) 532 533 // Check that the variable was set for the product 534 val := productVariables.FieldByName(name) 535 if !val.IsValid() || val.Kind() != reflect.Ptr || val.IsNil() { 536 continue 537 } 538 539 val = val.Elem() 540 541 // For bools, check that the value is true 542 if val.Kind() == reflect.Bool && val.Bool() == false { 543 continue 544 } 545 546 // Check if any properties were set for the module 547 if variableValue.IsZero() { 548 continue 549 } 550 a.setVariableProperties(mctx, property, variableValue, val.Interface()) 551 } 552} 553 554func (m *ModuleBase) setVariableProperties(ctx BottomUpMutatorContext, 555 prefix string, productVariablePropertyValue reflect.Value, variableValue interface{}) { 556 557 printfIntoProperties(ctx, prefix, productVariablePropertyValue, variableValue) 558 559 err := proptools.AppendMatchingProperties(m.generalProperties, 560 productVariablePropertyValue.Addr().Interface(), nil) 561 if err != nil { 562 if propertyErr, ok := err.(*proptools.ExtendPropertyError); ok { 563 ctx.PropertyErrorf(propertyErr.Property, "%s", propertyErr.Err.Error()) 564 } else { 565 panic(err) 566 } 567 } 568} 569 570func printfIntoPropertiesError(ctx BottomUpMutatorContext, prefix string, 571 productVariablePropertyValue reflect.Value, i int, err error) { 572 573 field := productVariablePropertyValue.Type().Field(i).Name 574 property := prefix + "." + proptools.PropertyNameForField(field) 575 ctx.PropertyErrorf(property, "%s", err) 576} 577 578func printfIntoProperties(ctx BottomUpMutatorContext, prefix string, 579 productVariablePropertyValue reflect.Value, variableValue interface{}) { 580 581 for i := 0; i < productVariablePropertyValue.NumField(); i++ { 582 propertyValue := productVariablePropertyValue.Field(i) 583 kind := propertyValue.Kind() 584 if kind == reflect.Ptr { 585 if propertyValue.IsNil() { 586 continue 587 } 588 propertyValue = propertyValue.Elem() 589 } 590 switch propertyValue.Kind() { 591 case reflect.String: 592 err := printfIntoProperty(propertyValue, variableValue) 593 if err != nil { 594 printfIntoPropertiesError(ctx, prefix, productVariablePropertyValue, i, err) 595 } 596 case reflect.Slice: 597 for j := 0; j < propertyValue.Len(); j++ { 598 err := printfIntoProperty(propertyValue.Index(j), variableValue) 599 if err != nil { 600 printfIntoPropertiesError(ctx, prefix, productVariablePropertyValue, i, err) 601 } 602 } 603 case reflect.Bool: 604 // Nothing 605 case reflect.Struct: 606 printfIntoProperties(ctx, prefix, propertyValue, variableValue) 607 default: 608 panic(fmt.Errorf("unsupported field kind %q", propertyValue.Kind())) 609 } 610 } 611} 612 613func printfIntoProperty(propertyValue reflect.Value, variableValue interface{}) error { 614 s := propertyValue.String() 615 616 count := strings.Count(s, "%") 617 if count == 0 { 618 return nil 619 } 620 621 if count > 1 { 622 return fmt.Errorf("product variable properties only support a single '%%'") 623 } 624 625 if strings.Contains(s, "%d") { 626 switch v := variableValue.(type) { 627 case int: 628 // Nothing 629 case bool: 630 if v { 631 variableValue = 1 632 } else { 633 variableValue = 0 634 } 635 default: 636 return fmt.Errorf("unsupported type %T for %%d", variableValue) 637 } 638 } else if strings.Contains(s, "%s") { 639 switch variableValue.(type) { 640 case string: 641 // Nothing 642 default: 643 return fmt.Errorf("unsupported type %T for %%s", variableValue) 644 } 645 } else { 646 return fmt.Errorf("unsupported %% in product variable property") 647 } 648 649 propertyValue.Set(reflect.ValueOf(fmt.Sprintf(s, variableValue))) 650 651 return nil 652} 653 654var variablePropTypeMap OncePer 655 656// sliceToTypeArray takes a slice of property structs and returns a reflection created array containing the 657// reflect.Types of each property struct. The result can be used as a key in a map. 658func sliceToTypeArray(s []interface{}) interface{} { 659 // Create an array using reflection whose length is the length of the input slice 660 ret := reflect.New(reflect.ArrayOf(len(s), reflect.TypeOf(reflect.TypeOf(0)))).Elem() 661 for i, e := range s { 662 ret.Index(i).Set(reflect.ValueOf(reflect.TypeOf(e))) 663 } 664 return ret.Interface() 665} 666 667func initProductVariableModule(m Module) { 668 base := m.base() 669 670 // Allow tests to override the default product variables 671 if base.variableProperties == nil { 672 base.variableProperties = defaultProductVariables 673 } 674 // Filter the product variables properties to the ones that exist on this module 675 base.variableProperties = createVariableProperties(m.GetProperties(), base.variableProperties) 676 if base.variableProperties != nil { 677 m.AddProperties(base.variableProperties) 678 } 679} 680 681// createVariableProperties takes the list of property structs for a module and returns a property struct that 682// contains the product variable properties that exist in the property structs, or nil if there are none. It 683// caches the result. 684func createVariableProperties(moduleTypeProps []interface{}, productVariables interface{}) interface{} { 685 // Convert the moduleTypeProps to an array of reflect.Types that can be used as a key in the OncePer. 686 key := sliceToTypeArray(moduleTypeProps) 687 688 // Use the variablePropTypeMap OncePer to cache the result for each set of property struct types. 689 typ, _ := variablePropTypeMap.Once(NewCustomOnceKey(key), func() interface{} { 690 // Compute the filtered property struct type. 691 return createVariablePropertiesType(moduleTypeProps, productVariables) 692 }).(reflect.Type) 693 694 if typ == nil { 695 return nil 696 } 697 698 // Create a new pointer to a filtered property struct. 699 return reflect.New(typ).Interface() 700} 701 702// createVariablePropertiesType creates a new type that contains only the product variable properties that exist in 703// a list of property structs. 704func createVariablePropertiesType(moduleTypeProps []interface{}, productVariables interface{}) reflect.Type { 705 typ, _ := proptools.FilterPropertyStruct(reflect.TypeOf(productVariables), 706 func(field reflect.StructField, prefix string) (bool, reflect.StructField) { 707 // Filter function, returns true if the field should be in the resulting struct 708 if prefix == "" { 709 // Keep the top level Product_variables field 710 return true, field 711 } 712 _, rest := splitPrefix(prefix) 713 if rest == "" { 714 // Keep the 2nd level field (i.e. Product_variables.Eng) 715 return true, field 716 } 717 718 // Strip off the first 2 levels of the prefix 719 _, prefix = splitPrefix(rest) 720 721 for _, p := range moduleTypeProps { 722 if fieldExistsByNameRecursive(reflect.TypeOf(p).Elem(), prefix, field.Name) { 723 // Keep any fields that exist in one of the property structs 724 return true, field 725 } 726 } 727 728 return false, field 729 }) 730 return typ 731} 732 733func splitPrefix(prefix string) (first, rest string) { 734 index := strings.IndexByte(prefix, '.') 735 if index == -1 { 736 return prefix, "" 737 } 738 return prefix[:index], prefix[index+1:] 739} 740 741func fieldExistsByNameRecursive(t reflect.Type, prefix, name string) bool { 742 if t.Kind() != reflect.Struct { 743 panic(fmt.Errorf("fieldExistsByNameRecursive can only be called on a reflect.Struct")) 744 } 745 746 if prefix != "" { 747 split := strings.SplitN(prefix, ".", 2) 748 firstPrefix := split[0] 749 rest := "" 750 if len(split) > 1 { 751 rest = split[1] 752 } 753 f, exists := t.FieldByName(firstPrefix) 754 if !exists { 755 return false 756 } 757 ft := f.Type 758 if ft.Kind() == reflect.Ptr { 759 ft = ft.Elem() 760 } 761 if ft.Kind() != reflect.Struct { 762 panic(fmt.Errorf("field %q in %q is not a struct", firstPrefix, t)) 763 } 764 return fieldExistsByNameRecursive(ft, rest, name) 765 } else { 766 _, exists := t.FieldByName(name) 767 return exists 768 } 769} 770