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:"<value>"` annotation to the product variable, the
variables will be initialized with the <value> for the generic configs.
If the <value> 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
diff --git a/android/config.go b/android/config.go
index d47f0d4..e026782 100644
--- a/android/config.go
+++ b/android/config.go
@@ -22,6 +22,7 @@
"fmt"
"os"
"path/filepath"
+ "reflect"
"runtime"
"strconv"
"strings"
@@ -108,6 +109,10 @@
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 @@
// 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 @@
// 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 @@
}
}
-// 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 @@
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 @@
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 @@
// 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 @@
// 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 d1b26c1..81b7c3e 100644
--- a/android/config_test.go
+++ b/android/config_test.go
@@ -281,3 +281,72 @@
})
}
}
+
+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 8d28285..300edf1 100644
--- a/android/early_module_context.go
+++ b/android/early_module_context.go
@@ -146,6 +146,13 @@
}
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 a3fe837..87377cc 100644
--- a/android/module.go
+++ b/android/module.go
@@ -128,6 +128,9 @@
// 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 @@
// 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 @@
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 77abc11..561c477 100644
--- a/android/module_proxy.go
+++ b/android/module_proxy.go
@@ -231,3 +231,7 @@
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 977473f..6612d37 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -2054,7 +2054,7 @@
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 3609e6b..5d79df0 100644
--- a/android/test_config.go
+++ b/android/test_config.go
@@ -23,8 +23,7 @@
"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 @@
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 @@
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 3d5a6ae..8c9a0b1 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -225,8 +225,8 @@
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"`
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index e57384f..af09dbc 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -139,6 +139,11 @@
// Returns all jars that system_server loads.
func (g *GlobalConfig) AllSystemServerJars(ctx android.PathContext) *android.ConfiguredJarList {
+ // dexpreopt does not initialize the soong config.
+ // Initialize the OncePer here.
+ if ctx.Config().OncePer == nil {
+ ctx.Config().OncePer = &android.OncePer{}
+ }
return ctx.Config().Once(allSystemServerJarsKey, func() interface{} {
res := g.AllPlatformSystemServerJars(ctx).AppendList(g.AllApexSystemServerJars(ctx))
return &res