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