From be6f81d61e25753eeecdb65bdf7dd2bb6d4c1a5c Mon Sep 17 00:00:00 2001 From: Justin Yun Date: Tue, 17 Dec 2024 21:15:59 +0900 Subject: Generic configuration for generic system modules. Config object includes a copy of itself for the generic configuration. The generic configuration replaces any product-specific configurations with the generic information. When a module context gets Config() object, it returns the generic configuration if the module sets use_generic_config to true. Otherwise, Config() returns the original config object as before. By adding `generic:""` annotation to the product variable, the variables will be initialized with the for the generic configs. If the is "unset", the variable will be unset. The generic modules can be included in the shared system image to be installed in multiple targets. Bug: 361816274 Test: m nothing --no-skip-soong-tests Change-Id: I15e4ade17ad1a8969f8e0e91d994b60545dc412f --- android/config.go | 158 +++++++++++++++++++++++++++++++--------- android/config_test.go | 69 ++++++++++++++++++ android/early_module_context.go | 7 ++ android/module.go | 11 +++ android/module_proxy.go | 4 + android/paths.go | 2 +- android/test_config.go | 17 ++++- android/variable.go | 4 +- 8 files changed, 233 insertions(+), 39 deletions(-) (limited to 'android') diff --git a/android/config.go b/android/config.go index d47f0d44a..e02678220 100644 --- a/android/config.go +++ b/android/config.go @@ -22,6 +22,7 @@ import ( "fmt" "os" "path/filepath" + "reflect" "runtime" "strconv" "strings" @@ -108,6 +109,10 @@ const ( const testKeyDir = "build/make/target/product/security" +func (c Config) genericConfig() Config { + return Config{c.config.genericConfig} +} + // SoongOutDir returns the build output directory for the configuration. func (c Config) SoongOutDir() string { return c.soongOutDir @@ -372,7 +377,7 @@ type config struct { // regenerate build.ninja. ninjaFileDepsSet sync.Map - OncePer + *OncePer // If buildFromSourceStub is true then the Java API stubs are // built from the source Java files, not the signature text files. @@ -382,6 +387,17 @@ type config struct { // modules that aren't mixed-built for at least one variant will cause a build // failure ensureAllowlistIntegrity bool + + // If isGeneric is true, this config is the generic config. + isGeneric bool + + // InstallPath requires the device name. + // This is only for the installPath. + deviceNameToInstall *string + + // Copy of this config struct but some product-specific variables are + // replaced with the generic configuration values. + genericConfig *config } type partialCompileFlags struct { @@ -639,11 +655,9 @@ func NullConfig(outDir, soongOutDir string) Config { } } -// NewConfig creates a new Config object. The srcDir argument specifies the path -// to the root source directory. It also loads the config file, if found. -func NewConfig(cmdArgs CmdArgs, availableEnv map[string]string) (Config, error) { +func initConfig(cmdArgs CmdArgs, availableEnv map[string]string) (*config, error) { // Make a config with default options. - config := &config{ + newConfig := &config{ ProductVariablesFileName: cmdArgs.SoongVariables, env: availableEnv, @@ -657,70 +671,72 @@ func NewConfig(cmdArgs CmdArgs, availableEnv map[string]string) (Config, error) moduleListFile: cmdArgs.ModuleListFile, fs: pathtools.NewOsFs(absSrcDir), + OncePer: &OncePer{}, + buildFromSourceStub: cmdArgs.BuildFromSourceStub, } variant, ok := os.LookupEnv("TARGET_BUILD_VARIANT") isEngBuild := !ok || variant == "eng" - config.deviceConfig = &deviceConfig{ - config: config, + newConfig.deviceConfig = &deviceConfig{ + config: newConfig, } // Soundness check of the build and source directories. This won't catch strange // configurations with symlinks, but at least checks the obvious case. absBuildDir, err := filepath.Abs(cmdArgs.SoongOutDir) if err != nil { - return Config{}, err + return &config{}, err } absSrcDir, err := filepath.Abs(".") if err != nil { - return Config{}, err + return &config{}, err } if strings.HasPrefix(absSrcDir, absBuildDir) { - return Config{}, fmt.Errorf("Build dir must not contain source directory") + return &config{}, fmt.Errorf("Build dir must not contain source directory") } // Load any configurable options from the configuration file - err = loadConfig(config) + err = loadConfig(newConfig) if err != nil { - return Config{}, err + return &config{}, err } KatiEnabledMarkerFile := filepath.Join(cmdArgs.SoongOutDir, ".soong.kati_enabled") if _, err := os.Stat(absolutePath(KatiEnabledMarkerFile)); err == nil { - config.katiEnabled = true + newConfig.katiEnabled = true } - determineBuildOS(config) + determineBuildOS(newConfig) // Sets up the map of target OSes to the finer grained compilation targets // that are configured from the product variables. - targets, err := decodeTargetProductVariables(config) + targets, err := decodeTargetProductVariables(newConfig) if err != nil { - return Config{}, err + return &config{}, err } - config.partialCompileFlags, err = config.parsePartialCompileFlags(isEngBuild) + newConfig.partialCompileFlags, err = newConfig.parsePartialCompileFlags(isEngBuild) if err != nil { - return Config{}, err + return &config{}, err } // Make the CommonOS OsType available for all products. targets[CommonOS] = []Target{commonTargetMap[CommonOS.Name]} var archConfig []archConfig - if config.NdkAbis() { + if newConfig.NdkAbis() { archConfig = getNdkAbisConfig() - } else if config.AmlAbis() { + } else if newConfig.AmlAbis() { archConfig = getAmlAbisConfig() } if archConfig != nil { androidTargets, err := decodeAndroidArchSettings(archConfig) if err != nil { - return Config{}, err + return &config{}, err } targets[Android] = androidTargets } @@ -728,37 +744,113 @@ func NewConfig(cmdArgs CmdArgs, availableEnv map[string]string) (Config, error) multilib := make(map[string]bool) for _, target := range targets[Android] { if seen := multilib[target.Arch.ArchType.Multilib]; seen { - config.multilibConflicts[target.Arch.ArchType] = true + newConfig.multilibConflicts[target.Arch.ArchType] = true } multilib[target.Arch.ArchType.Multilib] = true } // Map of OS to compilation targets. - config.Targets = targets + newConfig.Targets = targets // Compilation targets for host tools. - config.BuildOSTarget = config.Targets[config.BuildOS][0] - config.BuildOSCommonTarget = getCommonTargets(config.Targets[config.BuildOS])[0] + newConfig.BuildOSTarget = newConfig.Targets[newConfig.BuildOS][0] + newConfig.BuildOSCommonTarget = getCommonTargets(newConfig.Targets[newConfig.BuildOS])[0] // Compilation targets for Android. - if len(config.Targets[Android]) > 0 { - config.AndroidCommonTarget = getCommonTargets(config.Targets[Android])[0] - config.AndroidFirstDeviceTarget = FirstTarget(config.Targets[Android], "lib64", "lib32")[0] + if len(newConfig.Targets[Android]) > 0 { + newConfig.AndroidCommonTarget = getCommonTargets(newConfig.Targets[Android])[0] + newConfig.AndroidFirstDeviceTarget = FirstTarget(newConfig.Targets[Android], "lib64", "lib32")[0] } setBuildMode := func(arg string, mode SoongBuildMode) { if arg != "" { - if config.BuildMode != AnalysisNoBazel { + if newConfig.BuildMode != AnalysisNoBazel { fmt.Fprintf(os.Stderr, "buildMode is already set, illegal argument: %s", arg) os.Exit(1) } - config.BuildMode = mode + newConfig.BuildMode = mode } } setBuildMode(cmdArgs.ModuleGraphFile, GenerateModuleGraph) setBuildMode(cmdArgs.DocFile, GenerateDocFile) - config.productVariables.Build_from_text_stub = boolPtr(config.BuildFromTextStub()) + newConfig.productVariables.Build_from_text_stub = boolPtr(newConfig.BuildFromTextStub()) + + newConfig.deviceNameToInstall = newConfig.productVariables.DeviceName + + return newConfig, err +} + +// Replace variables in config.productVariables that have tags with "generic" key. +// A generic tag may have a string or an int value for the generic configuration. +// If the value is "unset", generic configuration will unset the variable. +func overrideGenericConfig(config *config) { + config.genericConfig.isGeneric = true + type_pv := reflect.TypeOf(config.genericConfig.productVariables) + value_pv := reflect.ValueOf(&config.genericConfig.productVariables) + for i := range type_pv.NumField() { + type_pv_field := type_pv.Field(i) + generic_value := type_pv_field.Tag.Get("generic") + // If a product variable has an annotation of "generic" tag, use the + // value of the tag to set the generic variable. + if generic_value != "" { + value_pv_field := value_pv.Elem().Field(i) + + if generic_value == "unset" { + // unset the product variable + value_pv_field.SetZero() + continue + } + + kind_of_type_pv := type_pv_field.Type.Kind() + is_pointer := false + if kind_of_type_pv == reflect.Pointer { + is_pointer = true + kind_of_type_pv = type_pv_field.Type.Elem().Kind() + } + + switch kind_of_type_pv { + case reflect.String: + if is_pointer { + value_pv_field.Set(reflect.ValueOf(stringPtr(generic_value))) + } else { + value_pv_field.Set(reflect.ValueOf(generic_value)) + } + case reflect.Int: + generic_int, err := strconv.Atoi(generic_value) + if err != nil { + panic(fmt.Errorf("Only an int value can be assigned to int variable: %s", err)) + } + if is_pointer { + value_pv_field.Set(reflect.ValueOf(intPtr(generic_int))) + } else { + value_pv_field.Set(reflect.ValueOf(generic_int)) + } + default: + panic(fmt.Errorf("Unknown type to replace for generic variable: %s", &kind_of_type_pv)) + } + } + } + + // OncePer must be a singleton. + config.genericConfig.OncePer = config.OncePer + // keep the device name to get the install path. + config.genericConfig.deviceNameToInstall = config.deviceNameToInstall +} + +// NewConfig creates a new Config object. It also loads the config file, if +// found. The Config object includes a duplicated Config object in it for the +// generic configuration that does not provide any product specific information. +func NewConfig(cmdArgs CmdArgs, availableEnv map[string]string) (Config, error) { + config, err := initConfig(cmdArgs, availableEnv) + if err != nil { + return Config{}, err + } + + // Initialize generic configuration. + config.genericConfig, err = initConfig(cmdArgs, availableEnv) + // Update product specific variables with the generic configuration. + overrideGenericConfig(config) return Config{config}, err } @@ -946,7 +1038,7 @@ func (c *config) DisplayBuildNumber() bool { // require them to run and get the current build fingerprint. This ensures they // don't rebuild on every incremental build when the build number changes. func (c *config) BuildFingerprintFile(ctx PathContext) Path { - return PathForArbitraryOutput(ctx, "target", "product", c.DeviceName(), String(c.productVariables.BuildFingerprintFile)) + return PathForArbitraryOutput(ctx, "target", "product", *c.deviceNameToInstall, String(c.productVariables.BuildFingerprintFile)) } // BuildNumberFile returns the path to a text file containing metadata @@ -974,7 +1066,7 @@ func (c *config) BuildHostnameFile(ctx PathContext) Path { // require them to run and get the current build thumbprint. This ensures they // don't rebuild on every incremental build when the build thumbprint changes. func (c *config) BuildThumbprintFile(ctx PathContext) Path { - return PathForArbitraryOutput(ctx, "target", "product", c.DeviceName(), String(c.productVariables.BuildThumbprintFile)) + return PathForArbitraryOutput(ctx, "target", "product", *c.deviceNameToInstall, String(c.productVariables.BuildThumbprintFile)) } // DeviceName returns the name of the current device target. diff --git a/android/config_test.go b/android/config_test.go index d1b26c12a..81b7c3eb5 100644 --- a/android/config_test.go +++ b/android/config_test.go @@ -281,3 +281,72 @@ func TestPartialCompile(t *testing.T) { }) } } + +type configTestProperties struct { + Use_generic_config *bool +} + +type configTestModule struct { + ModuleBase + properties configTestProperties +} + +func (d *configTestModule) GenerateAndroidBuildActions(ctx ModuleContext) { + deviceName := ctx.Config().DeviceName() + if ctx.ModuleName() == "foo" { + if ctx.Module().UseGenericConfig() { + ctx.PropertyErrorf("use_generic_config", "must not be set for this test") + } + } else if ctx.ModuleName() == "bar" { + if !ctx.Module().UseGenericConfig() { + ctx.ModuleErrorf("\"use_generic_config: true\" must be set for this test") + } + } + + if ctx.Module().UseGenericConfig() { + if deviceName != "generic" { + ctx.ModuleErrorf("Device name for this module must be \"generic\" but %q\n", deviceName) + } + } else { + if deviceName == "generic" { + ctx.ModuleErrorf("Device name for this module must not be \"generic\"\n") + } + } +} + +func configTestModuleFactory() Module { + module := &configTestModule{} + module.AddProperties(&module.properties) + InitAndroidModule(module) + return module +} + +var prepareForConfigTest = GroupFixturePreparers( + FixtureRegisterWithContext(func(ctx RegistrationContext) { + ctx.RegisterModuleType("test", configTestModuleFactory) + }), +) + +func TestGenericConfig(t *testing.T) { + bp := ` + test { + name: "foo", + } + + test { + name: "bar", + use_generic_config: true, + } + ` + + result := GroupFixturePreparers( + prepareForConfigTest, + FixtureWithRootAndroidBp(bp), + ).RunTest(t) + + foo := result.Module("foo", "").(*configTestModule) + bar := result.Module("bar", "").(*configTestModule) + + AssertBoolEquals(t, "Do not use generic config", false, foo.UseGenericConfig()) + AssertBoolEquals(t, "Use generic config", true, bar.UseGenericConfig()) +} diff --git a/android/early_module_context.go b/android/early_module_context.go index 8d2828545..300edf194 100644 --- a/android/early_module_context.go +++ b/android/early_module_context.go @@ -146,6 +146,13 @@ func (e *earlyModuleContext) Module() Module { } func (e *earlyModuleContext) Config() Config { + // Only the system image may use the generic config. + // If a module builds multiple image variations, provide the generic config only for the core + // variant which is installed in the system partition. Other image variant may still read the + // original configurations. + if e.Module().base().UseGenericConfig() && e.Module().base().commonProperties.ImageVariation == "" { + return e.EarlyModuleContext.Config().(Config).genericConfig() + } return e.EarlyModuleContext.Config().(Config) } diff --git a/android/module.go b/android/module.go index a3fe837a5..87377cc75 100644 --- a/android/module.go +++ b/android/module.go @@ -128,6 +128,9 @@ type Module interface { // WARNING: This should not be used outside build/soong/fsgen // Overrides returns the list of modules which should not be installed if this module is installed. Overrides() []string + + // If this is true, the module must not read product-specific configurations. + UseGenericConfig() bool } // Qualified id for a module @@ -507,6 +510,10 @@ type commonProperties struct { // List of module names that are prevented from being installed when this module gets // installed. Overrides []string + + // Set to true if this module must be generic and does not require product-specific information. + // To be included in the system image, this property must be set to true. + Use_generic_config *bool } // Properties common to all modules inheriting from ModuleBase. Unlike commonProperties, these @@ -2588,6 +2595,10 @@ func (m *ModuleBase) Overrides() []string { return m.commonProperties.Overrides } +func (m *ModuleBase) UseGenericConfig() bool { + return proptools.Bool(m.commonProperties.Use_generic_config) +} + type ConfigContext interface { Config() Config } diff --git a/android/module_proxy.go b/android/module_proxy.go index 77abc11e6..561c4770c 100644 --- a/android/module_proxy.go +++ b/android/module_proxy.go @@ -231,3 +231,7 @@ func (m ModuleProxy) Overrides() []string { func (m ModuleProxy) VintfFragments(ctx ConfigurableEvaluatorContext) []string { panic("method is not implemented on ModuleProxy") } + +func (m ModuleProxy) UseGenericConfig() bool { + panic("method is not implemented on ModuleProxy") +} diff --git a/android/paths.go b/android/paths.go index 977473fbd..6612d37d3 100644 --- a/android/paths.go +++ b/android/paths.go @@ -2054,7 +2054,7 @@ func pathForInstall(ctx PathContext, os OsType, arch ArchType, partition string, var partitionPaths []string if os.Class == Device { - partitionPaths = []string{"target", "product", ctx.Config().DeviceName(), partition} + partitionPaths = []string{"target", "product", *ctx.Config().deviceNameToInstall, partition} } else { osName := os.String() if os == Linux { diff --git a/android/test_config.go b/android/test_config.go index 3609e6b78..5d79df099 100644 --- a/android/test_config.go +++ b/android/test_config.go @@ -23,8 +23,7 @@ import ( "github.com/google/blueprint/proptools" ) -// TestConfig returns a Config object for testing. -func TestConfig(buildDir string, env map[string]string, bp string, fs map[string][]byte) Config { +func initTestConfig(buildDir string, env map[string]string) *config { envCopy := make(map[string]string) for k, v := range env { envCopy[k] = v @@ -58,6 +57,7 @@ func TestConfig(buildDir string, env map[string]string, bp string, fs map[string soongOutDir: filepath.Join(buildDir, "soong"), captureBuild: true, env: envCopy, + OncePer: &OncePer{}, // Set testAllowNonExistentPaths so that test contexts don't need to specify every path // passed to PathForSource or PathForModuleSrc. @@ -69,10 +69,21 @@ func TestConfig(buildDir string, env map[string]string, bp string, fs map[string config: config, } config.TestProductVariables = &config.productVariables + config.deviceNameToInstall = config.TestProductVariables.DeviceName + + determineBuildOS(config) + + return config +} + +// TestConfig returns a Config object for testing. +func TestConfig(buildDir string, env map[string]string, bp string, fs map[string][]byte) Config { + config := initTestConfig(buildDir, env) config.mockFileSystem(bp, fs) - determineBuildOS(config) + config.genericConfig = initTestConfig(buildDir, env) + overrideGenericConfig(config) return Config{config} } diff --git a/android/variable.go b/android/variable.go index 3d5a6ae12..8c9a0b1a5 100644 --- a/android/variable.go +++ b/android/variable.go @@ -225,8 +225,8 @@ type ProductVariables struct { Platform_version_last_stable *string `json:",omitempty"` Platform_version_known_codenames *string `json:",omitempty"` - DeviceName *string `json:",omitempty"` - DeviceProduct *string `json:",omitempty"` + DeviceName *string `json:",omitempty" generic:"generic"` + DeviceProduct *string `json:",omitempty" generic:"generic"` DeviceArch *string `json:",omitempty"` DeviceArchVariant *string `json:",omitempty"` DeviceCpuVariant *string `json:",omitempty"` -- cgit v1.2.3-59-g8ed1b