Support building boot images with disabled dexpreopt.

Bug: 176171716
Test: build_mainline_modules.sh and ensure that the ART boot image is
    built: `find out -type f -name '*.art'` is nonempty for every arch.
Change-Id: Ibc45581eef2b205c750a30709780cf659ba7cfa1
diff --git a/apex/apex.go b/apex/apex.go
index 4a9a9a3..ceaeaa0 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -29,6 +29,7 @@
 	"android/soong/android"
 	"android/soong/bpf"
 	"android/soong/cc"
+	"android/soong/dexpreopt"
 	prebuilt_etc "android/soong/etc"
 	"android/soong/filesystem"
 	"android/soong/java"
@@ -720,9 +721,15 @@
 	ctx.AddFarVariationDependencies(commonVariation, bpfTag, a.properties.Bpfs...)
 	ctx.AddFarVariationDependencies(commonVariation, fsTag, a.properties.Filesystems...)
 
-	// With EMMA_INSTRUMENT_FRAMEWORK=true the ART boot image includes jacoco library.
-	if a.artApex && ctx.Config().IsEnvTrue("EMMA_INSTRUMENT_FRAMEWORK") {
-		ctx.AddFarVariationDependencies(commonVariation, javaLibTag, "jacocoagent")
+	if a.artApex {
+		// With EMMA_INSTRUMENT_FRAMEWORK=true the ART boot image includes jacoco library.
+		if ctx.Config().IsEnvTrue("EMMA_INSTRUMENT_FRAMEWORK") {
+			ctx.AddFarVariationDependencies(commonVariation, javaLibTag, "jacocoagent")
+		}
+		// The ART boot image depends on dex2oat to compile it.
+		if !java.SkipDexpreoptBootJars(ctx) {
+			dexpreopt.RegisterToolDeps(ctx)
+		}
 	}
 
 	// Dependencies for signing
@@ -1520,6 +1527,9 @@
 		if dt, ok := depTag.(dependencyTag); ok && !dt.payload {
 			return false
 		}
+		if depTag == dexpreopt.Dex2oatDepTag {
+			return false
+		}
 
 		ai := ctx.OtherModuleProvider(child, android.ApexInfoProvider).(android.ApexInfo)
 		externalDep := !android.InList(ctx.ModuleName(), ai.InApexes)
@@ -1841,10 +1851,10 @@
 		return
 	}
 
-	// Specific to the ART apex: dexpreopt artifacts for libcore Java libraries. Build rules are
-	// generated by the dexpreopt singleton, and here we access build artifacts via the global
-	// boot image config.
 	if a.artApex {
+		// Specific to the ART apex: dexpreopt artifacts for libcore Java libraries. Build rules are
+		// generated by the dexpreopt singleton, and here we access build artifacts via the global
+		// boot image config.
 		for arch, files := range java.DexpreoptedArtApexJars(ctx) {
 			dirInApex := filepath.Join("javalib", arch.String())
 			for _, f := range files {
@@ -1853,6 +1863,11 @@
 				filesInfo = append(filesInfo, af)
 			}
 		}
+		// Call GetGlobalSoongConfig to initialize it, which may be necessary if dexpreopt is
+		// disabled for libraries/apps, but boot images are still needed.
+		if !java.SkipDexpreoptBootJars(ctx) {
+			dexpreopt.GetGlobalSoongConfig(ctx)
+		}
 	}
 
 	// Remove duplicates in filesInfo
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index f52ecb4..6a80649 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -27,8 +27,9 @@
 // GlobalConfig stores the configuration for dex preopting. The fields are set
 // from product variables via dex_preopt_config.mk.
 type GlobalConfig struct {
-	DisablePreopt        bool     // disable preopt for all modules
-	DisablePreoptModules []string // modules with preopt disabled by product-specific config
+	DisablePreopt           bool     // disable preopt for all modules (excluding boot images)
+	DisablePreoptBootImages bool     // disable prepot for boot images
+	DisablePreoptModules    []string // modules with preopt disabled by product-specific config
 
 	OnlyPreoptBootImageAndSystemServer bool // only preopt jars in the boot image or system server
 
@@ -234,8 +235,9 @@
 		return ctx.Config().Once(testGlobalConfigOnceKey, func() interface{} {
 			// Nope, return a config with preopting disabled
 			return globalConfigAndRaw{&GlobalConfig{
-				DisablePreopt:          true,
-				DisableGenerateProfile: true,
+				DisablePreopt:           true,
+				DisablePreoptBootImages: true,
+				DisableGenerateProfile:  true,
 			}, nil}
 		})
 	}).(globalConfigAndRaw)
@@ -305,7 +307,7 @@
 	}
 }
 
-var dex2oatDepTag = struct {
+var Dex2oatDepTag = struct {
 	blueprint.BaseDependencyTag
 }{}
 
@@ -316,7 +318,7 @@
 func RegisterToolDeps(ctx android.BottomUpMutatorContext) {
 	dex2oatBin := dex2oatModuleName(ctx.Config())
 	v := ctx.Config().BuildOSTarget.Variations()
-	ctx.AddFarVariationDependencies(v, dex2oatDepTag, dex2oatBin)
+	ctx.AddFarVariationDependencies(v, Dex2oatDepTag, dex2oatBin)
 }
 
 func dex2oatPathFromDep(ctx android.ModuleContext) android.Path {
@@ -332,7 +334,7 @@
 	// prebuilt explicitly here instead.
 	var dex2oatModule android.Module
 	ctx.WalkDeps(func(child, parent android.Module) bool {
-		if parent == ctx.Module() && ctx.OtherModuleDependencyTag(child) == dex2oatDepTag {
+		if parent == ctx.Module() && ctx.OtherModuleDependencyTag(child) == Dex2oatDepTag {
 			// Found the source module, or prebuilt module that has replaced the source.
 			dex2oatModule = child
 			if p, ok := child.(android.PrebuiltInterface); ok && p.Prebuilt() != nil {
@@ -385,10 +387,10 @@
 
 // The main reason for this Once cache for GlobalSoongConfig is to make the
 // dex2oat path available to singletons. In ordinary modules we get it through a
-// dex2oatDepTag dependency, but in singletons there's no simple way to do the
+// Dex2oatDepTag dependency, but in singletons there's no simple way to do the
 // same thing and ensure the right variant is selected, hence this cache to make
 // the resolved path available to singletons. This means we depend on there
-// being at least one ordinary module with a dex2oatDepTag dependency.
+// being at least one ordinary module with a Dex2oatDepTag dependency.
 //
 // TODO(b/147613152): Implement a way to deal with dependencies from singletons,
 // and then possibly remove this cache altogether (but the use in
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index 80211fd..0a71df1 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -347,8 +347,8 @@
 	ctx.RegisterSingletonType("dex_bootjars", dexpreoptBootJarsFactory)
 }
 
-func skipDexpreoptBootJars(ctx android.PathContext) bool {
-	return dexpreopt.GetGlobalConfig(ctx).DisablePreopt
+func SkipDexpreoptBootJars(ctx android.PathContext) bool {
+	return dexpreopt.GetGlobalConfig(ctx).DisablePreoptBootImages
 }
 
 // Singleton for generating boot image build rules.
@@ -371,7 +371,7 @@
 
 // Accessor function for the apex package. Returns nil if dexpreopt is disabled.
 func DexpreoptedArtApexJars(ctx android.BuilderContext) map[android.ArchType]android.OutputPaths {
-	if skipDexpreoptBootJars(ctx) {
+	if SkipDexpreoptBootJars(ctx) {
 		return nil
 	}
 	// Include dexpreopt files for the primary boot image.
@@ -387,7 +387,7 @@
 
 // Generate build rules for boot images.
 func (d *dexpreoptBootJars) GenerateBuildActions(ctx android.SingletonContext) {
-	if skipDexpreoptBootJars(ctx) {
+	if SkipDexpreoptBootJars(ctx) {
 		return
 	}
 	if dexpreopt.GetCachedGlobalSoongConfig(ctx) == nil {
@@ -727,7 +727,7 @@
 	globalSoong := dexpreopt.GetCachedGlobalSoongConfig(ctx)
 	global := dexpreopt.GetGlobalConfig(ctx)
 
-	if global.DisableGenerateProfile || ctx.Config().UnbundledBuild() {
+	if global.DisableGenerateProfile {
 		return nil
 	}
 	profile := ctx.Config().Once(bootImageProfileRuleKey, func() interface{} {