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