• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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