Merge "Remove Use_clang_lld in lto"
diff --git a/android/allowlists/allowlists.go b/android/allowlists/allowlists.go
index 6405e9f..0dd7dae 100644
--- a/android/allowlists/allowlists.go
+++ b/android/allowlists/allowlists.go
@@ -766,6 +766,9 @@
 		"libauto_value_plugin",
 		"auto_value_plugin_resources",
 		"auto_value_extension",
+
+		// Used by xsd_config
+		"xsdc",
 	}
 
 	Bp2buildModuleTypeAlwaysConvertList = []string{
diff --git a/android/config.go b/android/config.go
index d148012..bed57e3 100644
--- a/android/config.go
+++ b/android/config.go
@@ -80,9 +80,10 @@
 
 type CmdArgs struct {
 	bootstrap.Args
-	RunGoTests  bool
-	OutDir      string
-	SoongOutDir string
+	RunGoTests     bool
+	OutDir         string
+	SoongOutDir    string
+	SoongVariables string
 
 	SymlinkForestMarker string
 	Bp2buildMarker      string
@@ -491,7 +492,7 @@
 func NewConfig(cmdArgs CmdArgs, availableEnv map[string]string) (Config, error) {
 	// Make a config with default options.
 	config := &config{
-		ProductVariablesFileName: filepath.Join(cmdArgs.SoongOutDir, productVariablesFileName),
+		ProductVariablesFileName: cmdArgs.SoongVariables,
 
 		env: availableEnv,
 
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index cf6c1c7..e006c9d 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -78,6 +78,7 @@
 	flag.StringVar(&cmdlineArgs.Bp2buildMarker, "bp2build_marker", "", "If set, run bp2build, touch the specified marker file then exit")
 	flag.StringVar(&cmdlineArgs.SymlinkForestMarker, "symlink_forest_marker", "", "If set, create the bp2build symlink forest, touch the specified marker file, then exit")
 	flag.StringVar(&cmdlineArgs.OutFile, "o", "build.ninja", "the Ninja file to output")
+	flag.StringVar(&cmdlineArgs.SoongVariables, "soong_variables", "soong.variables", "the file contains all build variables")
 	flag.StringVar(&cmdlineArgs.BazelForceEnabledModules, "bazel-force-enabled-modules", "", "additional modules to build with Bazel. Comma-delimited")
 	flag.BoolVar(&cmdlineArgs.EmptyNinjaFile, "empty-ninja-file", false, "write out a 0-byte ninja file")
 	flag.BoolVar(&cmdlineArgs.MultitreeBuild, "multitree-build", false, "this is a multitree build")
@@ -517,6 +518,8 @@
 
 	var finalOutputFile string
 
+	writeSymlink := false
+
 	// Run Soong for a specific activity, like bp2build, queryview
 	// or the actual Soong build for the build.ninja file.
 	switch configuration.BuildMode {
@@ -539,8 +542,13 @@
 					maybeQuit(err, "")
 				}
 			}
+			writeSymlink = true
 		} else {
 			finalOutputFile = runSoongOnlyBuild(ctx, extraNinjaDeps)
+
+			if configuration.BuildMode == android.AnalysisNoBazel {
+				writeSymlink = true
+			}
 		}
 		writeMetrics(configuration, ctx.EventHandler, metricsDir)
 	}
@@ -551,6 +559,24 @@
 	// are ninja inputs to the main output file, then ninja would superfluously
 	// rebuild this output file on the next build invocation.
 	touch(shared.JoinPath(topDir, finalOutputFile))
+
+	// TODO(b/277029044): Remove this function once build.<product>.ninja lands
+	if writeSymlink {
+		writeBuildNinjaSymlink(configuration, finalOutputFile)
+	}
+}
+
+// TODO(b/277029044): Remove this function once build.<product>.ninja lands
+func writeBuildNinjaSymlink(config android.Config, source string) {
+	targetPath := shared.JoinPath(topDir, config.SoongOutDir(), "build.ninja")
+	sourcePath := shared.JoinPath(topDir, source)
+
+	if targetPath == sourcePath {
+		return
+	}
+
+	os.Remove(targetPath)
+	os.Symlink(sourcePath, targetPath)
 }
 
 func writeUsedEnvironmentFile(configuration android.Config) {
diff --git a/dexpreopt/class_loader_context.go b/dexpreopt/class_loader_context.go
index afb3de3..6ead589 100644
--- a/dexpreopt/class_loader_context.go
+++ b/dexpreopt/class_loader_context.go
@@ -17,11 +17,11 @@
 import (
 	"encoding/json"
 	"fmt"
-	"sort"
 	"strconv"
-	"strings"
 
 	"android/soong/android"
+
+	"github.com/google/blueprint/proptools"
 )
 
 // This comment describes the following:
@@ -310,8 +310,8 @@
 	// Nested class loader context shouldn't have conditional part (it is allowed only at the top level).
 	for ver, _ := range nestedClcMap {
 		if ver != AnySdkVersion {
-			clcStr, _ := ComputeClassLoaderContext(nestedClcMap)
-			return fmt.Errorf("nested class loader context shouldn't have conditional part: %s", clcStr)
+			clcPaths := ComputeClassLoaderContextDependencies(nestedClcMap)
+			return fmt.Errorf("nested class loader context shouldn't have conditional part: %+v", clcPaths)
 		}
 	}
 	subcontexts := nestedClcMap[AnySdkVersion]
@@ -418,6 +418,15 @@
 	return string(bytes)
 }
 
+func (clcMap ClassLoaderContextMap) DumpForFlag() string {
+	jsonCLC := toJsonClassLoaderContext(clcMap)
+	bytes, err := json.Marshal(jsonCLC)
+	if err != nil {
+		panic(err)
+	}
+	return proptools.ShellEscapeIncludingSpaces(string(bytes))
+}
+
 // excludeLibsFromCLCList excludes the libraries from the ClassLoaderContext in this list.
 //
 // This treats the supplied list as being immutable (as it may come from a dependency). So, it
@@ -544,67 +553,27 @@
 	return true, nil
 }
 
-// Return the class loader context as a string, and a slice of build paths for all dependencies.
+// Returns a slice of build paths for all possible dependencies that the class loader context may
+// refer to.
 // Perform a depth-first preorder traversal of the class loader context tree for each SDK version.
-// Return the resulting string and a slice of on-host build paths to all library dependencies.
-func ComputeClassLoaderContext(clcMap ClassLoaderContextMap) (clcStr string, paths android.Paths) {
-	// CLC for different SDK versions should come in specific order that agrees with PackageManager.
-	// Since PackageManager processes SDK versions in ascending order and prepends compatibility
-	// libraries at the front, the required order is descending, except for AnySdkVersion that has
-	// numerically the largest order, but must be the last one. Example of correct order: [30, 29,
-	// 28, AnySdkVersion]. There are Soong tests to ensure that someone doesn't change this by
-	// accident, but there is no way to guard against changes in the PackageManager, except for
-	// grepping logcat on the first boot for absence of the following messages:
-	//
-	//   `logcat | grep -E 'ClassLoaderContext [a-z ]+ mismatch`
-	//
-	versions := make([]int, 0, len(clcMap))
-	for ver, _ := range clcMap {
-		if ver != AnySdkVersion {
-			versions = append(versions, ver)
-		}
-	}
-	sort.Sort(sort.Reverse(sort.IntSlice(versions))) // descending order
-	versions = append(versions, AnySdkVersion)
-
-	for _, sdkVer := range versions {
-		sdkVerStr := fmt.Sprintf("%d", sdkVer)
-		if sdkVer == AnySdkVersion {
-			sdkVerStr = "any" // a special keyword that means any SDK version
-		}
-		hostClc, targetClc, hostPaths := computeClassLoaderContextRec(clcMap[sdkVer])
-		if hostPaths != nil {
-			clcStr += fmt.Sprintf(" --host-context-for-sdk %s %s", sdkVerStr, hostClc)
-			clcStr += fmt.Sprintf(" --target-context-for-sdk %s %s", sdkVerStr, targetClc)
-		}
+func ComputeClassLoaderContextDependencies(clcMap ClassLoaderContextMap) android.Paths {
+	var paths android.Paths
+	for _, clcs := range clcMap {
+		hostPaths := ComputeClassLoaderContextDependenciesRec(clcs)
 		paths = append(paths, hostPaths...)
 	}
-	return clcStr, android.FirstUniquePaths(paths)
+	return android.FirstUniquePaths(paths)
 }
 
-// Helper function for ComputeClassLoaderContext() that handles recursion.
-func computeClassLoaderContextRec(clcs []*ClassLoaderContext) (string, string, android.Paths) {
+// Helper function for ComputeClassLoaderContextDependencies() that handles recursion.
+func ComputeClassLoaderContextDependenciesRec(clcs []*ClassLoaderContext) android.Paths {
 	var paths android.Paths
-	var clcsHost, clcsTarget []string
-
 	for _, clc := range clcs {
-		subClcHost, subClcTarget, subPaths := computeClassLoaderContextRec(clc.Subcontexts)
-		if subPaths != nil {
-			subClcHost = "{" + subClcHost + "}"
-			subClcTarget = "{" + subClcTarget + "}"
-		}
-
-		clcsHost = append(clcsHost, "PCL["+clc.Host.String()+"]"+subClcHost)
-		clcsTarget = append(clcsTarget, "PCL["+clc.Device+"]"+subClcTarget)
-
+		subPaths := ComputeClassLoaderContextDependenciesRec(clc.Subcontexts)
 		paths = append(paths, clc.Host)
 		paths = append(paths, subPaths...)
 	}
-
-	clcHost := strings.Join(clcsHost, "#")
-	clcTarget := strings.Join(clcsTarget, "#")
-
-	return clcHost, clcTarget, paths
+	return paths
 }
 
 // Class loader contexts that come from Make via JSON dexpreopt.config. JSON CLC representation is
diff --git a/dexpreopt/class_loader_context_test.go b/dexpreopt/class_loader_context_test.go
index 8b3c013..39b4652 100644
--- a/dexpreopt/class_loader_context_test.go
+++ b/dexpreopt/class_loader_context_test.go
@@ -20,6 +20,7 @@
 import (
 	"fmt"
 	"reflect"
+	"sort"
 	"strings"
 	"testing"
 
@@ -34,7 +35,7 @@
 	// │   └── android.hidl.base
 	// │
 	// └── any
-	//     ├── a
+	//     ├── a'  (a single quotation mark (') is there to test escaping)
 	//     ├── b
 	//     ├── c
 	//     ├── d
@@ -53,7 +54,7 @@
 
 	m := make(ClassLoaderContextMap)
 
-	m.AddContext(ctx, AnySdkVersion, "a", optional, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
+	m.AddContext(ctx, AnySdkVersion, "a'", optional, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
 	m.AddContext(ctx, AnySdkVersion, "b", optional, buildPath(ctx, "b"), installPath(ctx, "b"), nil)
 	m.AddContext(ctx, AnySdkVersion, "c", optional, buildPath(ctx, "c"), installPath(ctx, "c"), nil)
 
@@ -96,11 +97,10 @@
 
 	fixClassLoaderContext(m)
 
-	var haveStr string
 	var havePaths android.Paths
 	var haveUsesLibsReq, haveUsesLibsOpt []string
 	if valid && validationError == nil {
-		haveStr, havePaths = ComputeClassLoaderContext(m)
+		havePaths = ComputeClassLoaderContextDependencies(m)
 		haveUsesLibsReq, haveUsesLibsOpt = m.UsesLibs()
 	}
 
@@ -111,29 +111,6 @@
 		}
 	})
 
-	// Test that class loader context structure is correct.
-	t.Run("string", func(t *testing.T) {
-		wantStr := " --host-context-for-sdk 29 " +
-			"PCL[out/soong/" + AndroidHidlManager + ".jar]#" +
-			"PCL[out/soong/" + AndroidHidlBase + ".jar]" +
-			" --target-context-for-sdk 29 " +
-			"PCL[/system/framework/" + AndroidHidlManager + ".jar]#" +
-			"PCL[/system/framework/" + AndroidHidlBase + ".jar]" +
-			" --host-context-for-sdk any " +
-			"PCL[out/soong/a.jar]#PCL[out/soong/b.jar]#PCL[out/soong/c.jar]#PCL[out/soong/d.jar]" +
-			"{PCL[out/soong/a2.jar]#PCL[out/soong/b2.jar]#PCL[out/soong/c2.jar]" +
-			"{PCL[out/soong/a1.jar]#PCL[out/soong/b1.jar]}}#" +
-			"PCL[out/soong/f.jar]#PCL[out/soong/a3.jar]#PCL[out/soong/b3.jar]" +
-			" --target-context-for-sdk any " +
-			"PCL[/system/a.jar]#PCL[/system/b.jar]#PCL[/system/c.jar]#PCL[/system/d.jar]" +
-			"{PCL[/system/a2.jar]#PCL[/system/b2.jar]#PCL[/system/c2.jar]" +
-			"{PCL[/system/a1.jar]#PCL[/system/b1.jar]}}#" +
-			"PCL[/system/f.jar]#PCL[/system/a3.jar]#PCL[/system/b3.jar]"
-		if wantStr != haveStr {
-			t.Errorf("\nwant class loader context: %s\nhave class loader context: %s", wantStr, haveStr)
-		}
-	})
-
 	// Test that all expected build paths are gathered.
 	t.Run("paths", func(t *testing.T) {
 		wantPaths := []string{
@@ -143,14 +120,28 @@
 			"out/soong/a1.jar", "out/soong/b1.jar",
 			"out/soong/f.jar", "out/soong/a3.jar", "out/soong/b3.jar",
 		}
-		if !reflect.DeepEqual(wantPaths, havePaths.Strings()) {
-			t.Errorf("\nwant paths: %s\nhave paths: %s", wantPaths, havePaths)
-		}
+		actual := havePaths.Strings()
+		// The order does not matter.
+		sort.Strings(wantPaths)
+		sort.Strings(actual)
+		android.AssertArrayString(t, "", wantPaths, actual)
+	})
+
+	// Test the JSON passed to construct_context.py.
+	t.Run("json", func(t *testing.T) {
+		// The tree structure within each SDK version should be kept exactly the same when serialized
+		// to JSON. The order matters because the Python script keeps the order within each SDK version
+		// as is.
+		// The JSON is passed to the Python script as a commandline flag, so quotation ('') and escaping
+		// must be performed.
+		android.AssertStringEquals(t, "", strings.TrimSpace(`
+'{"29":[{"Name":"android.hidl.manager-V1.0-java","Optional":false,"Host":"out/soong/android.hidl.manager-V1.0-java.jar","Device":"/system/framework/android.hidl.manager-V1.0-java.jar","Subcontexts":[]},{"Name":"android.hidl.base-V1.0-java","Optional":false,"Host":"out/soong/android.hidl.base-V1.0-java.jar","Device":"/system/framework/android.hidl.base-V1.0-java.jar","Subcontexts":[]}],"30":[],"42":[],"any":[{"Name":"a'\''","Optional":false,"Host":"out/soong/a.jar","Device":"/system/a.jar","Subcontexts":[]},{"Name":"b","Optional":false,"Host":"out/soong/b.jar","Device":"/system/b.jar","Subcontexts":[]},{"Name":"c","Optional":false,"Host":"out/soong/c.jar","Device":"/system/c.jar","Subcontexts":[]},{"Name":"d","Optional":false,"Host":"out/soong/d.jar","Device":"/system/d.jar","Subcontexts":[{"Name":"a2","Optional":false,"Host":"out/soong/a2.jar","Device":"/system/a2.jar","Subcontexts":[]},{"Name":"b2","Optional":false,"Host":"out/soong/b2.jar","Device":"/system/b2.jar","Subcontexts":[]},{"Name":"c2","Optional":false,"Host":"out/soong/c2.jar","Device":"/system/c2.jar","Subcontexts":[{"Name":"a1","Optional":false,"Host":"out/soong/a1.jar","Device":"/system/a1.jar","Subcontexts":[]},{"Name":"b1","Optional":false,"Host":"out/soong/b1.jar","Device":"/system/b1.jar","Subcontexts":[]}]}]},{"Name":"f","Optional":false,"Host":"out/soong/f.jar","Device":"/system/f.jar","Subcontexts":[]},{"Name":"a3","Optional":false,"Host":"out/soong/a3.jar","Device":"/system/a3.jar","Subcontexts":[]},{"Name":"b3","Optional":false,"Host":"out/soong/b3.jar","Device":"/system/b3.jar","Subcontexts":[]}]}'
+`), m.DumpForFlag())
 	})
 
 	// Test for libraries that are added by the manifest_fixer.
 	t.Run("uses libs", func(t *testing.T) {
-		wantUsesLibsReq := []string{"a", "b", "c", "d", "f", "a3", "b3"}
+		wantUsesLibsReq := []string{"a'", "b", "c", "d", "f", "a3", "b3"}
 		wantUsesLibsOpt := []string{}
 		if !reflect.DeepEqual(wantUsesLibsReq, haveUsesLibsReq) {
 			t.Errorf("\nwant required uses libs: %s\nhave required uses libs: %s", wantUsesLibsReq, haveUsesLibsReq)
@@ -236,49 +227,6 @@
 	checkError(t, err, "nested class loader context shouldn't have conditional part")
 }
 
-// Test for SDK version order in conditional CLC: no matter in what order the libraries are added,
-// they end up in the order that agrees with PackageManager.
-func TestCLCSdkVersionOrder(t *testing.T) {
-	ctx := testContext()
-	optional := false
-	m := make(ClassLoaderContextMap)
-	m.AddContext(ctx, 28, "a", optional, buildPath(ctx, "a"), installPath(ctx, "a"), nil)
-	m.AddContext(ctx, 29, "b", optional, buildPath(ctx, "b"), installPath(ctx, "b"), nil)
-	m.AddContext(ctx, 30, "c", optional, buildPath(ctx, "c"), installPath(ctx, "c"), nil)
-	m.AddContext(ctx, AnySdkVersion, "d", optional, buildPath(ctx, "d"), installPath(ctx, "d"), nil)
-
-	valid, validationError := validateClassLoaderContext(m)
-
-	fixClassLoaderContext(m)
-
-	var haveStr string
-	if valid && validationError == nil {
-		haveStr, _ = ComputeClassLoaderContext(m)
-	}
-
-	// Test that validation is successful (all paths are known).
-	t.Run("validate", func(t *testing.T) {
-		if !(valid && validationError == nil) {
-			t.Errorf("invalid class loader context")
-		}
-	})
-
-	// Test that class loader context structure is correct.
-	t.Run("string", func(t *testing.T) {
-		wantStr := " --host-context-for-sdk 30 PCL[out/soong/c.jar]" +
-			" --target-context-for-sdk 30 PCL[/system/c.jar]" +
-			" --host-context-for-sdk 29 PCL[out/soong/b.jar]" +
-			" --target-context-for-sdk 29 PCL[/system/b.jar]" +
-			" --host-context-for-sdk 28 PCL[out/soong/a.jar]" +
-			" --target-context-for-sdk 28 PCL[/system/a.jar]" +
-			" --host-context-for-sdk any PCL[out/soong/d.jar]" +
-			" --target-context-for-sdk any PCL[/system/d.jar]"
-		if wantStr != haveStr {
-			t.Errorf("\nwant class loader context: %s\nhave class loader context: %s", wantStr, haveStr)
-		}
-	})
-}
-
 func TestCLCMExcludeLibs(t *testing.T) {
 	ctx := testContext()
 	const optional = false
diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go
index 2b38793..20737e3 100644
--- a/dexpreopt/dexpreopt.go
+++ b/dexpreopt/dexpreopt.go
@@ -52,7 +52,8 @@
 // GenerateDexpreoptRule generates a set of commands that will preopt a module based on a GlobalConfig and a
 // ModuleConfig.  The produced files and their install locations will be available through rule.Installs().
 func GenerateDexpreoptRule(ctx android.BuilderContext, globalSoong *GlobalSoongConfig,
-	global *GlobalConfig, module *ModuleConfig) (rule *android.RuleBuilder, err error) {
+	global *GlobalConfig, module *ModuleConfig, productPackages android.Path) (
+	rule *android.RuleBuilder, err error) {
 
 	defer func() {
 		if r := recover(); r != nil {
@@ -92,7 +93,8 @@
 			generateDM := shouldGenerateDM(module, global)
 
 			for archIdx, _ := range module.Archs {
-				dexpreoptCommand(ctx, globalSoong, global, module, rule, archIdx, profile, appImage, generateDM)
+				dexpreoptCommand(ctx, globalSoong, global, module, rule, archIdx, profile, appImage,
+					generateDM, productPackages)
 			}
 		}
 	}
@@ -232,9 +234,9 @@
 		pathtools.ReplaceExtension(filepath.Base(path), "odex"))
 }
 
-func dexpreoptCommand(ctx android.PathContext, globalSoong *GlobalSoongConfig, global *GlobalConfig,
-	module *ModuleConfig, rule *android.RuleBuilder, archIdx int, profile android.WritablePath,
-	appImage bool, generateDM bool) {
+func dexpreoptCommand(ctx android.BuilderContext, globalSoong *GlobalSoongConfig,
+	global *GlobalConfig, module *ModuleConfig, rule *android.RuleBuilder, archIdx int,
+	profile android.WritablePath, appImage bool, generateDM bool, productPackages android.Path) {
 
 	arch := module.Archs[archIdx]
 
@@ -351,11 +353,13 @@
 		}
 
 		// Generate command that saves host and target class loader context in shell variables.
-		clc, paths := ComputeClassLoaderContext(module.ClassLoaderContexts)
+		paths := ComputeClassLoaderContextDependencies(module.ClassLoaderContexts)
 		rule.Command().
 			Text(`eval "$(`).Tool(globalSoong.ConstructContext).
 			Text(` --target-sdk-version ${target_sdk_version}`).
-			Text(clc).Implicits(paths).
+			FlagWithArg("--context-json=", module.ClassLoaderContexts.DumpForFlag()).
+			FlagWithInput("--product-packages=", productPackages).
+			Implicits(paths).
 			Text(`)"`)
 	}
 
diff --git a/dexpreopt/dexpreopt_gen/dexpreopt_gen.go b/dexpreopt/dexpreopt_gen/dexpreopt_gen.go
index ba05d94..8033b48 100644
--- a/dexpreopt/dexpreopt_gen/dexpreopt_gen.go
+++ b/dexpreopt/dexpreopt_gen/dexpreopt_gen.go
@@ -42,7 +42,8 @@
 	// The flag is useful when running dex2oat on system image and vendor image which are built separately.
 	usesTargetFiles = flag.Bool("uses_target_files", false, "whether or not dexpreopt is running on target_files")
 	// basePath indicates the path where target_files.zip is extracted.
-	basePath = flag.String("base_path", ".", "base path where images and tools are extracted")
+	basePath            = flag.String("base_path", ".", "base path where images and tools are extracted")
+	productPackagesPath = flag.String("product_packages", "", "path to product_packages.txt")
 )
 
 type builderContext struct {
@@ -87,6 +88,10 @@
 		usage("--module configuration file is required")
 	}
 
+	if *productPackagesPath == "" {
+		usage("--product_packages configuration file is required")
+	}
+
 	// NOTE: duplicating --out_dir here is incorrect (one should be the another
 	// plus "/soong" but doing so apparently breaks dexpreopt
 	ctx := &builderContext{android.NullConfig(*outDir, *outDir)}
@@ -159,11 +164,12 @@
 			moduleConfig.DexPreoptImageLocationsOnHost[i] = *basePath + location
 		}
 	}
-	writeScripts(ctx, globalSoongConfig, globalConfig, moduleConfig, *dexpreoptScriptPath)
+	writeScripts(ctx, globalSoongConfig, globalConfig, moduleConfig, *dexpreoptScriptPath, *productPackagesPath)
 }
 
 func writeScripts(ctx android.BuilderContext, globalSoong *dexpreopt.GlobalSoongConfig,
-	global *dexpreopt.GlobalConfig, module *dexpreopt.ModuleConfig, dexpreoptScriptPath string) {
+	global *dexpreopt.GlobalConfig, module *dexpreopt.ModuleConfig, dexpreoptScriptPath string,
+	productPackagesPath string) {
 	write := func(rule *android.RuleBuilder, file string) {
 		script := &bytes.Buffer{}
 		script.WriteString(scriptHeader)
@@ -199,7 +205,8 @@
 			panic(err)
 		}
 	}
-	dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(ctx, globalSoong, global, module)
+	dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(
+		ctx, globalSoong, global, module, android.PathForTesting(productPackagesPath))
 	if err != nil {
 		panic(err)
 	}
diff --git a/dexpreopt/dexpreopt_test.go b/dexpreopt/dexpreopt_test.go
index 429b5ff..2b19c9d 100644
--- a/dexpreopt/dexpreopt_test.go
+++ b/dexpreopt/dexpreopt_test.go
@@ -100,8 +100,9 @@
 	globalSoong := globalSoongConfigForTests()
 	global := GlobalConfigForTests(ctx)
 	module := testSystemModuleConfig(ctx, "test")
+	productPackages := android.PathForTesting("product_packages.txt")
 
-	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module)
+	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module, productPackages)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -124,6 +125,7 @@
 	systemModule := testSystemModuleConfig(ctx, "Stest")
 	systemProductModule := testSystemProductModuleConfig(ctx, "SPtest")
 	productModule := testProductModuleConfig(ctx, "Ptest")
+	productPackages := android.PathForTesting("product_packages.txt")
 
 	global.HasSystemOther = true
 
@@ -157,7 +159,7 @@
 	for _, test := range tests {
 		global.PatternsOnSystemOther = test.patterns
 		for _, mt := range test.moduleTests {
-			rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, mt.module)
+			rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, mt.module, productPackages)
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -182,11 +184,12 @@
 	globalSoong := globalSoongConfigForTests()
 	global := GlobalConfigForTests(ctx)
 	module := testApexModuleConfig(ctx, "service-A", "com.android.apex1")
+	productPackages := android.PathForTesting("product_packages.txt")
 
 	global.ApexSystemServerJars = android.CreateTestConfiguredJarList(
 		[]string{"com.android.apex1:service-A"})
 
-	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module)
+	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module, productPackages)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -205,11 +208,12 @@
 	globalSoong := globalSoongConfigForTests()
 	global := GlobalConfigForTests(ctx)
 	module := testPlatformSystemServerModuleConfig(ctx, "service-A")
+	productPackages := android.PathForTesting("product_packages.txt")
 
 	global.StandaloneSystemServerJars = android.CreateTestConfiguredJarList(
 		[]string{"platform:service-A"})
 
-	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module)
+	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module, productPackages)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -228,11 +232,12 @@
 	globalSoong := globalSoongConfigForTests()
 	global := GlobalConfigForTests(ctx)
 	module := testSystemExtSystemServerModuleConfig(ctx, "service-A")
+	productPackages := android.PathForTesting("product_packages.txt")
 
 	global.StandaloneSystemServerJars = android.CreateTestConfiguredJarList(
 		[]string{"system_ext:service-A"})
 
-	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module)
+	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module, productPackages)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -251,11 +256,12 @@
 	globalSoong := globalSoongConfigForTests()
 	global := GlobalConfigForTests(ctx)
 	module := testApexModuleConfig(ctx, "service-A", "com.android.apex1")
+	productPackages := android.PathForTesting("product_packages.txt")
 
 	global.ApexStandaloneSystemServerJars = android.CreateTestConfiguredJarList(
 		[]string{"com.android.apex1:service-A"})
 
-	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module)
+	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module, productPackages)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -274,10 +280,11 @@
 	globalSoong := globalSoongConfigForTests()
 	global := GlobalConfigForTests(ctx)
 	module := testSystemModuleConfig(ctx, "test")
+	productPackages := android.PathForTesting("product_packages.txt")
 
 	module.ProfileClassListing = android.OptionalPathForPath(android.PathForTesting("profile"))
 
-	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module)
+	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module, productPackages)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/java/app_test.go b/java/app_test.go
index 9293da4..7f9f0ed 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -2693,52 +2693,11 @@
 		`--optional-uses-library baz `
 	android.AssertStringDoesContain(t, "verify apk cmd args", verifyApkCmd, verifyApkArgs)
 
-	// Test that all present libraries are preopted, including implicit SDK dependencies, possibly stubs
+	// Test that necessary args are passed for constructing CLC in Ninja phase.
 	cmd := app.Rule("dexpreopt").RuleParams.Command
-	w := `--target-context-for-sdk any ` +
-		`PCL[/system/framework/qux.jar]#` +
-		`PCL[/system/framework/quuz.jar]#` +
-		`PCL[/system/framework/foo.jar]#` +
-		`PCL[/system/framework/non-sdk-lib.jar]#` +
-		`PCL[/system/framework/bar.jar]#` +
-		`PCL[/system/framework/runtime-library.jar]#` +
-		`PCL[/system/framework/runtime-required-x.jar]#` +
-		`PCL[/system/framework/runtime-optional-x.jar]#` +
-		`PCL[/system/framework/runtime-required-y.jar]#` +
-		`PCL[/system/framework/runtime-optional-y.jar] `
-	android.AssertStringDoesContain(t, "dexpreopt app cmd args", cmd, w)
-
-	// Test conditional context for target SDK version 28.
-	android.AssertStringDoesContain(t, "dexpreopt app cmd 28", cmd,
-		`--target-context-for-sdk 28`+
-			` PCL[/system/framework/org.apache.http.legacy.jar] `)
-
-	// Test conditional context for target SDK version 29.
-	android.AssertStringDoesContain(t, "dexpreopt app cmd 29", cmd,
-		`--target-context-for-sdk 29`+
-			` PCL[/system/framework/android.hidl.manager-V1.0-java.jar]`+
-			`#PCL[/system/framework/android.hidl.base-V1.0-java.jar] `)
-
-	// Test conditional context for target SDK version 30.
-	// "android.test.mock" is absent because "android.test.runner" is not used.
-	android.AssertStringDoesContain(t, "dexpreopt app cmd 30", cmd,
-		`--target-context-for-sdk 30`+
-			` PCL[/system/framework/android.test.base.jar] `)
-
-	cmd = prebuilt.Rule("dexpreopt").RuleParams.Command
-	android.AssertStringDoesContain(t, "dexpreopt prebuilt cmd", cmd,
-		`--target-context-for-sdk any`+
-			` PCL[/system/framework/foo.jar]`+
-			`#PCL[/system/framework/non-sdk-lib.jar]`+
-			`#PCL[/system/framework/android.test.runner.jar]`+
-			`#PCL[/system/framework/bar.jar] `)
-
-	// Test conditional context for target SDK version 30.
-	// "android.test.mock" is present because "android.test.runner" is used.
-	android.AssertStringDoesContain(t, "dexpreopt prebuilt cmd 30", cmd,
-		`--target-context-for-sdk 30`+
-			` PCL[/system/framework/android.test.base.jar]`+
-			`#PCL[/system/framework/android.test.mock.jar] `)
+	android.AssertStringDoesContain(t, "dexpreopt app cmd context", cmd, "--context-json=")
+	android.AssertStringDoesContain(t, "dexpreopt app cmd product_packages", cmd,
+		"--product-packages=out/soong/target/product/test_device/product_packages.txt")
 }
 
 func TestDexpreoptBcp(t *testing.T) {
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
index a96b312..e588c9a 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -390,7 +390,11 @@
 
 	globalSoong := dexpreopt.GetGlobalSoongConfig(ctx)
 
-	dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(ctx, globalSoong, global, dexpreoptConfig)
+	// "product_packages.txt" is generated by `build/make/core/Makefile`.
+	productPackages := android.PathForModuleInPartitionInstall(ctx, "", "product_packages.txt")
+
+	dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(
+		ctx, globalSoong, global, dexpreoptConfig, productPackages)
 	if err != nil {
 		ctx.ModuleErrorf("error generating dexpreopt rule: %s", err.Error())
 		return
diff --git a/scripts/construct_context.py b/scripts/construct_context.py
index 3f601c3..fc3a89e 100755
--- a/scripts/construct_context.py
+++ b/scripts/construct_context.py
@@ -19,6 +19,7 @@
 from __future__ import print_function
 
 import argparse
+import json
 import sys
 
 from manifest import compare_version_gt
@@ -33,20 +34,14 @@
         dest='sdk',
         help='specify target SDK version (as it appears in the manifest)')
     parser.add_argument(
-        '--host-context-for-sdk',
-        dest='host_contexts',
-        action='append',
-        nargs=2,
-        metavar=('sdk', 'context'),
-        help='specify context on host for a given SDK version or "any" version')
+        '--context-json',
+        default='',
+        dest='context_json',
+    )
     parser.add_argument(
-        '--target-context-for-sdk',
-        dest='target_contexts',
-        action='append',
-        nargs=2,
-        metavar=('sdk', 'context'),
-        help='specify context on target for a given SDK version or "any" '
-        'version'
+        '--product-packages',
+        default='',
+        dest='product_packages_file',
     )
     return parser.parse_args(args)
 
@@ -55,28 +50,69 @@
 # context regardless of the target SDK version.
 any_sdk = 'any'
 
-
-# We assume that the order of context arguments passed to this script is
-# correct (matches the order computed by package manager). It is possible to
-# sort them here, but Soong needs to use deterministic order anyway, so it can
-# as well use the correct order.
-def construct_context(versioned_contexts, target_sdk):
-    context = []
-    for [sdk, ctx] in versioned_contexts:
-        if sdk == any_sdk or compare_version_gt(sdk, target_sdk):
-            context.append(ctx)
-    return context
+context_sep = '#'
 
 
-def construct_contexts(args):
-    host_context = construct_context(args.host_contexts, args.sdk)
-    target_context = construct_context(args.target_contexts, args.sdk)
-    context_sep = '#'
+def encode_class_loader(context, product_packages):
+    host_sub_contexts, target_sub_contexts = encode_class_loaders(
+            context['Subcontexts'], product_packages)
+
+    return ('PCL[%s]%s' % (context['Host'], host_sub_contexts),
+            'PCL[%s]%s' % (context['Device'], target_sub_contexts))
+
+
+def encode_class_loaders(contexts, product_packages):
+    host_contexts = []
+    target_contexts = []
+
+    for context in contexts:
+        if not context['Optional'] or context['Name'] in product_packages:
+            host_context, target_context = encode_class_loader(
+                    context, product_packages)
+            host_contexts.append(host_context)
+            target_contexts.append(target_context)
+
+    if host_contexts:
+        return ('{%s}' % context_sep.join(host_contexts),
+                '{%s}' % context_sep.join(target_contexts))
+    else:
+        return '', ''
+
+
+def construct_context_args(target_sdk, context_json, product_packages):
+    all_contexts = []
+
+    # CLC for different SDK versions should come in specific order that agrees
+    # with PackageManager. Since PackageManager processes SDK versions in
+    # ascending order and prepends compatibility libraries at the front, the
+    # required order is descending, except for any_sdk that has numerically
+    # the largest order, but must be the last one. Example of correct order:
+    # [30, 29, 28, any_sdk]. There are Python tests to ensure that someone
+    # doesn't change this by accident, but there is no way to guard against
+    # changes in the PackageManager, except for grepping logcat on the first
+    # boot for absence of the following messages:
+    #
+    #   `logcat | grep -E 'ClassLoaderContext [a-z ]+ mismatch`
+
+    for sdk, contexts in sorted(
+            ((sdk, contexts)
+             for sdk, contexts in context_json.items()
+             if sdk != any_sdk and compare_version_gt(sdk, target_sdk)),
+            key=lambda item: int(item[0]), reverse=True):
+        all_contexts += contexts
+
+    if any_sdk in context_json:
+        all_contexts += context_json[any_sdk]
+
+    host_contexts, target_contexts = encode_class_loaders(
+            all_contexts, product_packages)
+
     return (
-        'class_loader_context_arg=--class-loader-context=PCL[]{%s} ; ' %
-        context_sep.join(host_context) +
-        'stored_class_loader_context_arg=--stored-class-loader-context=PCL[]{%s}' #pylint: disable=line-too-long
-        % context_sep.join(target_context))
+        'class_loader_context_arg=--class-loader-context=PCL[]%s ; ' %
+        host_contexts +
+        'stored_class_loader_context_arg='
+        '--stored-class-loader-context=PCL[]%s'
+        % target_contexts)
 
 
 def main():
@@ -85,12 +121,12 @@
         args = parse_args(sys.argv[1:])
         if not args.sdk:
             raise SystemExit('target sdk version is not set')
-        if not args.host_contexts:
-            args.host_contexts = []
-        if not args.target_contexts:
-            args.target_contexts = []
 
-        print(construct_contexts(args))
+        context_json = json.loads(args.context_json)
+        with open(args.product_packages_file, 'r') as f:
+            product_packages = set(line.strip() for line in f if line.strip())
+
+        print(construct_context_args(args.sdk, context_json, product_packages))
 
     # pylint: disable=broad-except
     except Exception as err:
diff --git a/scripts/construct_context_test.py b/scripts/construct_context_test.py
index 2ff5ac5..34a0e16 100755
--- a/scripts/construct_context_test.py
+++ b/scripts/construct_context_test.py
@@ -24,62 +24,249 @@
 sys.dont_write_bytecode = True
 
 
-def construct_contexts(arglist):
-    args = cc.parse_args(arglist)
-    return cc.construct_contexts(args)
+CONTEXT_JSON = {
+    '28': [
+        {
+            'Name': 'z',
+            'Optional': False,
+            'Host': 'out/zdir/z.jar',
+            'Device': '/system/z.jar',
+            'Subcontexts': [],
+        },
+    ],
+    '29': [
+        {
+            'Name': 'x',
+            'Optional': False,
+            'Host': 'out/xdir/x.jar',
+            'Device': '/system/x.jar',
+            'Subcontexts': [],
+        },
+        {
+            'Name': 'y',
+            'Optional': False,
+            'Host': 'out/ydir/y.jar',
+            'Device': '/product/y.jar',
+            'Subcontexts': [],
+        },
+    ],
+    'any': [
+        {
+            'Name': 'a',
+            'Optional': False,
+            'Host': 'out/adir/a.jar',
+            'Device': '/system/a.jar',
+            'Subcontexts': [
+                {  # Not installed optional, being the only child.
+                    'Name': 'a1',
+                    'Optional': True,
+                    'Host': 'out/a1dir/a1.jar',
+                    'Device': '/product/a1.jar',
+                    'Subcontexts': [],
+                },
+            ],
+        },
+        {
+            'Name': 'b',
+            'Optional': True,
+            'Host': 'out/bdir/b.jar',
+            'Device': '/product/b.jar',
+            'Subcontexts': [
+                {  # Not installed but required.
+                    'Name': 'b1',
+                    'Optional': False,
+                    'Host': 'out/b1dir/b1.jar',
+                    'Device': '/product/b1.jar',
+                    'Subcontexts': [],
+                },
+                {  # Installed optional.
+                    'Name': 'b2',
+                    'Optional': True,
+                    'Host': 'out/b2dir/b2.jar',
+                    'Device': '/product/b2.jar',
+                    'Subcontexts': [],
+                },
+                {  # Not installed optional.
+                    'Name': 'b3',
+                    'Optional': True,
+                    'Host': 'out/b3dir/b3.jar',
+                    'Device': '/product/b3.jar',
+                    'Subcontexts': [],
+                },
+                {  # Installed optional with one more level of nested deps.
+                    'Name': 'b4',
+                    'Optional': True,
+                    'Host': 'out/b4dir/b4.jar',
+                    'Device': '/product/b4.jar',
+                    'Subcontexts': [
+                        {
+                            'Name': 'b41',
+                            'Optional': True,
+                            'Host': 'out/b41dir/b41.jar',
+                            'Device': '/product/b41.jar',
+                            'Subcontexts': [],
+                        },
+                        {
+                            'Name': 'b42',
+                            'Optional': True,
+                            'Host': 'out/b42dir/b42.jar',
+                            'Device': '/product/b42.jar',
+                            'Subcontexts': [],
+                        },
+                    ],
+                },
+            ],
+        },
+        {  # Not installed optional, at the top-level.
+            'Name': 'c',
+            'Optional': True,
+            'Host': 'out/cdir/c.jar',
+            'Device': '/product/c.jar',
+            'Subcontexts': [],
+        },
+    ],
+}
 
 
-contexts = [
-    '--host-context-for-sdk',
-    '28',
-    'PCL[out/zdir/z.jar]',
-    '--target-context-for-sdk',
-    '28',
-    'PCL[/system/z.jar]',
-    '--host-context-for-sdk',
-    '29',
-    'PCL[out/xdir/x.jar]#PCL[out/ydir/y.jar]',
-    '--target-context-for-sdk',
-    '29',
-    'PCL[/system/x.jar]#PCL[/product/y.jar]',
-    '--host-context-for-sdk',
-    'any',
-    'PCL[out/adir/a.jar]#PCL[out/bdir/b.jar]',
-    '--target-context-for-sdk',
-    'any',
-    'PCL[/system/a.jar]#PCL[/product/b.jar]',
-]
+PRODUCT_PACKAGES = ['a', 'b', 'b2', 'b4', 'b41', 'b42', 'x', 'y', 'z']
 
-#pylint: disable=line-too-long
+
+def construct_context_args(target_sdk):
+    return cc.construct_context_args(target_sdk, CONTEXT_JSON, PRODUCT_PACKAGES)
+
+
 class ConstructContextTest(unittest.TestCase):
+    def test_construct_context_27(self):
+        actual = construct_context_args('27')
+        # The order matters.
+        expected = (
+            'class_loader_context_arg='
+            '--class-loader-context=PCL[]{'
+            'PCL[out/xdir/x.jar]#'
+            'PCL[out/ydir/y.jar]#'
+            'PCL[out/zdir/z.jar]#'
+            'PCL[out/adir/a.jar]#'
+            'PCL[out/bdir/b.jar]{'
+            'PCL[out/b1dir/b1.jar]#'
+            'PCL[out/b2dir/b2.jar]#'
+            'PCL[out/b4dir/b4.jar]{'
+            'PCL[out/b41dir/b41.jar]#'
+            'PCL[out/b42dir/b42.jar]'
+            '}'
+            '}'
+            '}'
+            ' ; '
+            'stored_class_loader_context_arg='
+            '--stored-class-loader-context=PCL[]{'
+            'PCL[/system/x.jar]#'
+            'PCL[/product/y.jar]#'
+            'PCL[/system/z.jar]#'
+            'PCL[/system/a.jar]#'
+            'PCL[/product/b.jar]{'
+            'PCL[/product/b1.jar]#'
+            'PCL[/product/b2.jar]#'
+            'PCL[/product/b4.jar]{'
+            'PCL[/product/b41.jar]#'
+            'PCL[/product/b42.jar]'
+            '}'
+            '}'
+            '}')
+        self.assertEqual(actual, expected)
 
     def test_construct_context_28(self):
-        args = ['--target-sdk-version', '28'] + contexts
-        result = construct_contexts(args)
-        expect = (
-            'class_loader_context_arg=--class-loader-context=PCL[]{PCL[out/xdir/x.jar]#PCL[out/ydir/y.jar]#PCL[out/adir/a.jar]#PCL[out/bdir/b.jar]}'
+        actual = construct_context_args('28')
+        expected = (
+            'class_loader_context_arg='
+            '--class-loader-context=PCL[]{'
+            'PCL[out/xdir/x.jar]#'
+            'PCL[out/ydir/y.jar]#'
+            'PCL[out/adir/a.jar]#'
+            'PCL[out/bdir/b.jar]{'
+            'PCL[out/b1dir/b1.jar]#'
+            'PCL[out/b2dir/b2.jar]#'
+            'PCL[out/b4dir/b4.jar]{'
+            'PCL[out/b41dir/b41.jar]#'
+            'PCL[out/b42dir/b42.jar]'
+            '}'
+            '}'
+            '}'
             ' ; '
-            'stored_class_loader_context_arg=--stored-class-loader-context=PCL[]{PCL[/system/x.jar]#PCL[/product/y.jar]#PCL[/system/a.jar]#PCL[/product/b.jar]}')
-        self.assertEqual(result, expect)
+            'stored_class_loader_context_arg='
+            '--stored-class-loader-context=PCL[]{'
+            'PCL[/system/x.jar]#'
+            'PCL[/product/y.jar]#'
+            'PCL[/system/a.jar]#'
+            'PCL[/product/b.jar]{'
+            'PCL[/product/b1.jar]#'
+            'PCL[/product/b2.jar]#'
+            'PCL[/product/b4.jar]{'
+            'PCL[/product/b41.jar]#'
+            'PCL[/product/b42.jar]'
+            '}'
+            '}'
+            '}')
+        self.assertEqual(actual, expected)
 
     def test_construct_context_29(self):
-        args = ['--target-sdk-version', '29'] + contexts
-        result = construct_contexts(args)
-        expect = (
-            'class_loader_context_arg=--class-loader-context=PCL[]{PCL[out/adir/a.jar]#PCL[out/bdir/b.jar]}'
+        actual = construct_context_args('29')
+        expected = (
+            'class_loader_context_arg='
+            '--class-loader-context=PCL[]{'
+            'PCL[out/adir/a.jar]#'
+            'PCL[out/bdir/b.jar]{'
+            'PCL[out/b1dir/b1.jar]#'
+            'PCL[out/b2dir/b2.jar]#'
+            'PCL[out/b4dir/b4.jar]{'
+            'PCL[out/b41dir/b41.jar]#'
+            'PCL[out/b42dir/b42.jar]'
+            '}'
+            '}'
+            '}'
             ' ; '
-            'stored_class_loader_context_arg=--stored-class-loader-context=PCL[]{PCL[/system/a.jar]#PCL[/product/b.jar]}')
-        self.assertEqual(result, expect)
+            'stored_class_loader_context_arg='
+            '--stored-class-loader-context=PCL[]{'
+            'PCL[/system/a.jar]#'
+            'PCL[/product/b.jar]{'
+            'PCL[/product/b1.jar]#'
+            'PCL[/product/b2.jar]#'
+            'PCL[/product/b4.jar]{'
+            'PCL[/product/b41.jar]#'
+            'PCL[/product/b42.jar]'
+            '}'
+            '}'
+            '}')
+        self.assertEqual(actual, expected)
 
     def test_construct_context_S(self):
-        args = ['--target-sdk-version', 'S'] + contexts
-        result = construct_contexts(args)
-        expect = (
-            'class_loader_context_arg=--class-loader-context=PCL[]{PCL[out/adir/a.jar]#PCL[out/bdir/b.jar]}'
+        actual = construct_context_args('S')
+        expected = (
+            'class_loader_context_arg='
+            '--class-loader-context=PCL[]{'
+            'PCL[out/adir/a.jar]#'
+            'PCL[out/bdir/b.jar]{'
+            'PCL[out/b1dir/b1.jar]#'
+            'PCL[out/b2dir/b2.jar]#'
+            'PCL[out/b4dir/b4.jar]{'
+            'PCL[out/b41dir/b41.jar]#'
+            'PCL[out/b42dir/b42.jar]'
+            '}'
+            '}'
+            '}'
             ' ; '
-            'stored_class_loader_context_arg=--stored-class-loader-context=PCL[]{PCL[/system/a.jar]#PCL[/product/b.jar]}')
-        self.assertEqual(result, expect)
-#pylint: enable=line-too-long
+            'stored_class_loader_context_arg='
+            '--stored-class-loader-context=PCL[]{'
+            'PCL[/system/a.jar]#'
+            'PCL[/product/b.jar]{'
+            'PCL[/product/b1.jar]#'
+            'PCL[/product/b2.jar]#'
+            'PCL[/product/b4.jar]{'
+            'PCL[/product/b41.jar]#'
+            'PCL[/product/b42.jar]'
+            '}'
+            '}'
+            '}')
+        self.assertEqual(actual, expected)
+
 
 if __name__ == '__main__':
     unittest.main(verbosity=2)
diff --git a/ui/build/config.go b/ui/build/config.go
index 8ec9680..bfbf85d 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -15,7 +15,6 @@
 package build
 
 import (
-	"context"
 	"encoding/json"
 	"fmt"
 	"io/ioutil"
@@ -39,9 +38,6 @@
 const (
 	envConfigDir = "vendor/google/tools/soong_config"
 	jsonSuffix   = "json"
-
-	configFetcher         = "vendor/google/tools/soong/expconfigfetcher"
-	envConfigFetchTimeout = 10 * time.Second
 )
 
 var (
@@ -170,87 +166,6 @@
 	}
 }
 
-// fetchEnvConfig optionally fetches a configuration file that can then subsequently be
-// loaded into Soong environment to control certain aspects of build behavior (e.g., enabling RBE).
-// If a configuration file already exists on disk, the fetch is run in the background
-// so as to NOT block the rest of the build execution.
-func fetchEnvConfig(ctx Context, config *configImpl, envConfigName string) error {
-	configName := envConfigName + "." + jsonSuffix
-	expConfigFetcher := &smpb.ExpConfigFetcher{Filename: &configName}
-	defer func() {
-		ctx.Metrics.ExpConfigFetcher(expConfigFetcher)
-	}()
-	if !config.GoogleProdCredsExist() {
-		status := smpb.ExpConfigFetcher_MISSING_GCERT
-		expConfigFetcher.Status = &status
-		return nil
-	}
-
-	s, err := os.Stat(configFetcher)
-	if err != nil {
-		if os.IsNotExist(err) {
-			return nil
-		}
-		return err
-	}
-	if s.Mode()&0111 == 0 {
-		status := smpb.ExpConfigFetcher_ERROR
-		expConfigFetcher.Status = &status
-		return fmt.Errorf("configuration fetcher binary %v is not executable: %v", configFetcher, s.Mode())
-	}
-
-	configExists := false
-	outConfigFilePath := filepath.Join(config.OutDir(), configName)
-	if _, err := os.Stat(outConfigFilePath); err == nil {
-		configExists = true
-	}
-
-	tCtx, cancel := context.WithTimeout(ctx, envConfigFetchTimeout)
-	fetchStart := time.Now()
-	cmd := exec.CommandContext(tCtx, configFetcher, "-output_config_dir", config.OutDir(),
-		"-output_config_name", configName)
-	if err := cmd.Start(); err != nil {
-		status := smpb.ExpConfigFetcher_ERROR
-		expConfigFetcher.Status = &status
-		return err
-	}
-
-	fetchCfg := func() error {
-		if err := cmd.Wait(); err != nil {
-			status := smpb.ExpConfigFetcher_ERROR
-			expConfigFetcher.Status = &status
-			return err
-		}
-		fetchEnd := time.Now()
-		expConfigFetcher.Micros = proto.Uint64(uint64(fetchEnd.Sub(fetchStart).Microseconds()))
-		expConfigFetcher.Filename = proto.String(outConfigFilePath)
-
-		if _, err := os.Stat(outConfigFilePath); err != nil {
-			status := smpb.ExpConfigFetcher_NO_CONFIG
-			expConfigFetcher.Status = &status
-			return err
-		}
-		status := smpb.ExpConfigFetcher_CONFIG
-		expConfigFetcher.Status = &status
-		return nil
-	}
-
-	// If a config file does not exist, wait for the config file to be fetched. Otherwise
-	// fetch the config file in the background and return immediately.
-	if !configExists {
-		defer cancel()
-		return fetchCfg()
-	}
-
-	go func() {
-		defer cancel()
-		if err := fetchCfg(); err != nil {
-			ctx.Verbosef("Failed to fetch config file %v: %v\n", configName, err)
-		}
-	}()
-	return nil
-}
-
 func loadEnvConfig(ctx Context, config *configImpl, bc string) error {
 	if bc == "" {
 		return nil
@@ -350,9 +265,6 @@
 	bc := os.Getenv("ANDROID_BUILD_ENVIRONMENT_CONFIG")
 
 	if bc != "" {
-		if err := fetchEnvConfig(ctx, ret, bc); err != nil {
-			ctx.Verbosef("Failed to fetch config file: %v\n", err)
-		}
 		if err := loadEnvConfig(ctx, ret, bc); err != nil {
 			ctx.Fatalln("Failed to parse env config files: %v", err)
 		}
@@ -1229,6 +1141,13 @@
 	panic("TARGET_PRODUCT is not defined")
 }
 
+func (c *configImpl) TargetProductOrErr() (string, error) {
+	if v, ok := c.environ.Get("TARGET_PRODUCT"); ok {
+		return v, nil
+	}
+	return "", fmt.Errorf("TARGET_PRODUCT is not defined")
+}
+
 func (c *configImpl) TargetDevice() string {
 	return c.targetDevice
 }
@@ -1486,7 +1405,7 @@
 	if googleProdCredsExistCache {
 		return googleProdCredsExistCache
 	}
-	if _, err := exec.Command("/usr/bin/prodcertstatus", "--simple_output", "--nocheck_loas").Output(); err != nil {
+	if _, err := exec.Command("/usr/bin/gcertstatus").Output(); err != nil {
 		return false
 	}
 	googleProdCredsExistCache = true
@@ -1554,11 +1473,21 @@
 }
 
 func (c *configImpl) SoongVarsFile() string {
-	return filepath.Join(c.SoongOutDir(), "soong.variables")
+	targetProduct, err := c.TargetProductOrErr()
+	if err != nil {
+		return filepath.Join(c.SoongOutDir(), "soong.variables")
+	} else {
+		return filepath.Join(c.SoongOutDir(), "soong."+targetProduct+".variables")
+	}
 }
 
 func (c *configImpl) SoongNinjaFile() string {
-	return filepath.Join(c.SoongOutDir(), "build.ninja")
+	targetProduct, err := c.TargetProductOrErr()
+	if err != nil {
+		return filepath.Join(c.SoongOutDir(), "build.ninja")
+	} else {
+		return filepath.Join(c.SoongOutDir(), "build."+targetProduct+".ninja")
+	}
 }
 
 func (c *configImpl) CombinedNinjaFile() string {
diff --git a/ui/build/soong.go b/ui/build/soong.go
index 18bf3b9..6de8d6f 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -235,7 +235,7 @@
 	} else if !exists {
 		// The tree is out of date for the current epoch, delete files used by bootstrap
 		// and force the primary builder to rerun.
-		os.Remove(filepath.Join(config.SoongOutDir(), "build.ninja"))
+		os.Remove(config.SoongNinjaFile())
 		for _, globFile := range bootstrapGlobFileList(config) {
 			os.Remove(globFile)
 		}
@@ -263,7 +263,9 @@
 	// Clean up some files for incremental builds across incompatible changes.
 	bootstrapEpochCleanup(ctx, config)
 
-	mainSoongBuildExtraArgs := []string{"-o", config.SoongNinjaFile()}
+	baseArgs := []string{"--soong_variables", config.SoongVarsFile()}
+
+	mainSoongBuildExtraArgs := append(baseArgs, "-o", config.SoongNinjaFile())
 	if config.EmptyNinjaFile() {
 		mainSoongBuildExtraArgs = append(mainSoongBuildExtraArgs, "--empty-ninja-file")
 	}
@@ -306,49 +308,59 @@
 			specificArgs: mainSoongBuildExtraArgs,
 		},
 		{
-			name:         bp2buildFilesTag,
-			description:  fmt.Sprintf("converting Android.bp files to BUILD files at %s/bp2build", config.SoongOutDir()),
-			config:       config,
-			output:       config.Bp2BuildFilesMarkerFile(),
-			specificArgs: []string{"--bp2build_marker", config.Bp2BuildFilesMarkerFile()},
+			name:        bp2buildFilesTag,
+			description: fmt.Sprintf("converting Android.bp files to BUILD files at %s/bp2build", config.SoongOutDir()),
+			config:      config,
+			output:      config.Bp2BuildFilesMarkerFile(),
+			specificArgs: append(baseArgs,
+				"--bp2build_marker", config.Bp2BuildFilesMarkerFile(),
+			),
 		},
 		{
-			name:         bp2buildWorkspaceTag,
-			description:  "Creating Bazel symlink forest",
-			config:       config,
-			output:       config.Bp2BuildWorkspaceMarkerFile(),
-			specificArgs: []string{"--symlink_forest_marker", config.Bp2BuildWorkspaceMarkerFile()},
+			name:        bp2buildWorkspaceTag,
+			description: "Creating Bazel symlink forest",
+			config:      config,
+			output:      config.Bp2BuildWorkspaceMarkerFile(),
+			specificArgs: append(baseArgs,
+				"--symlink_forest_marker", config.Bp2BuildWorkspaceMarkerFile(),
+			),
 		},
 		{
 			name:        jsonModuleGraphTag,
 			description: fmt.Sprintf("generating the Soong module graph at %s", config.ModuleGraphFile()),
 			config:      config,
 			output:      config.ModuleGraphFile(),
-			specificArgs: []string{
+			specificArgs: append(baseArgs,
 				"--module_graph_file", config.ModuleGraphFile(),
 				"--module_actions_file", config.ModuleActionsFile(),
-			},
+			),
 		},
 		{
-			name:         queryviewTag,
-			description:  fmt.Sprintf("generating the Soong module graph as a Bazel workspace at %s", queryviewDir),
-			config:       config,
-			output:       config.QueryviewMarkerFile(),
-			specificArgs: []string{"--bazel_queryview_dir", queryviewDir},
+			name:        queryviewTag,
+			description: fmt.Sprintf("generating the Soong module graph as a Bazel workspace at %s", queryviewDir),
+			config:      config,
+			output:      config.QueryviewMarkerFile(),
+			specificArgs: append(baseArgs,
+				"--bazel_queryview_dir", queryviewDir,
+			),
 		},
 		{
-			name:         apiBp2buildTag,
-			description:  fmt.Sprintf("generating BUILD files for API contributions at %s", apiBp2buildDir),
-			config:       config,
-			output:       config.ApiBp2buildMarkerFile(),
-			specificArgs: []string{"--bazel_api_bp2build_dir", apiBp2buildDir},
+			name:        apiBp2buildTag,
+			description: fmt.Sprintf("generating BUILD files for API contributions at %s", apiBp2buildDir),
+			config:      config,
+			output:      config.ApiBp2buildMarkerFile(),
+			specificArgs: append(baseArgs,
+				"--bazel_api_bp2build_dir", apiBp2buildDir,
+			),
 		},
 		{
-			name:         soongDocsTag,
-			description:  fmt.Sprintf("generating Soong docs at %s", config.SoongDocsHtml()),
-			config:       config,
-			output:       config.SoongDocsHtml(),
-			specificArgs: []string{"--soong_docs", config.SoongDocsHtml()},
+			name:        soongDocsTag,
+			description: fmt.Sprintf("generating Soong docs at %s", config.SoongDocsHtml()),
+			config:      config,
+			output:      config.SoongDocsHtml(),
+			specificArgs: append(baseArgs,
+				"--soong_docs", config.SoongDocsHtml(),
+			),
 		},
 	}