Make RuleBuilder methods take Paths

There are no more Make paths being used in Soong now that
dexpreopting and hiddenapi are in Soong. Use the Path types
in the inputs to RuleBuilder, and fix all users of RuleBuilder.

Test: all soong tests
Test: m checkbuild
Change-Id: I886f803d9a3419a43b2cae412537645f94c5dfbf
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index ee3cc8d..6f8ea3a 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -17,6 +17,7 @@
 import (
 	"encoding/json"
 	"io/ioutil"
+	"strings"
 
 	"android/soong/android"
 )
@@ -74,12 +75,13 @@
 	InstructionSetFeatures map[android.ArchType]string // instruction set for each architecture
 
 	// Only used for boot image
-	DirtyImageObjects string   // path to a dirty-image-objects file
-	PreloadedClasses  string   // path to a preloaded-classes file
-	BootImageProfiles []string // path to a boot-image-profile.txt file
-	BootFlags         string   // extra flags to pass to dex2oat for the boot image
-	Dex2oatImageXmx   string   // max heap size for dex2oat for the boot image
-	Dex2oatImageXms   string   // initial heap size for dex2oat for the boot image
+	DirtyImageObjects      android.OptionalPath // path to a dirty-image-objects file
+	PreloadedClasses       android.OptionalPath // path to a preloaded-classes file
+	BootImageProfiles      android.Paths        // path to a boot-image-profile.txt file
+	UseProfileForBootImage bool                 // whether a profile should be used to compile the boot image
+	BootFlags              string               // extra flags to pass to dex2oat for the boot image
+	Dex2oatImageXmx        string               // max heap size for dex2oat for the boot image
+	Dex2oatImageXms        string               // initial heap size for dex2oat for the boot image
 
 	Tools Tools // paths to tools possibly used by the generated commands
 }
@@ -87,38 +89,38 @@
 // Tools contains paths to tools possibly used by the generated commands.  If you add a new tool here you MUST add it
 // to the order-only dependency list in DEXPREOPT_GEN_DEPS.
 type Tools struct {
-	Profman  string
-	Dex2oat  string
-	Aapt     string
-	SoongZip string
-	Zip2zip  string
+	Profman  android.Path
+	Dex2oat  android.Path
+	Aapt     android.Path
+	SoongZip android.Path
+	Zip2zip  android.Path
 
-	VerifyUsesLibraries string
-	ConstructContext    string
+	VerifyUsesLibraries android.Path
+	ConstructContext    android.Path
 }
 
 type ModuleConfig struct {
 	Name            string
 	DexLocation     string // dex location on device
-	BuildPath       string
-	DexPath         string
+	BuildPath       android.OutputPath
+	DexPath         android.Path
 	UncompressedDex bool
 	HasApkLibraries bool
 	PreoptFlags     []string
 
-	ProfileClassListing  string
+	ProfileClassListing  android.OptionalPath
 	ProfileIsTextListing bool
 
 	EnforceUsesLibraries  bool
 	OptionalUsesLibraries []string
 	UsesLibraries         []string
-	LibraryPaths          map[string]string
+	LibraryPaths          map[string]android.Path
 
 	Archs           []android.ArchType
-	DexPreoptImages []string
+	DexPreoptImages []android.Path
 
-	PreoptBootClassPathDexFiles     []string // file paths of boot class path files
-	PreoptBootClassPathDexLocations []string // virtual locations of boot class path files
+	PreoptBootClassPathDexFiles     android.Paths // file paths of boot class path files
+	PreoptBootClassPathDexLocations []string      // virtual locations of boot class path files
 
 	PreoptExtractedApk bool // Overrides OnlyPreoptModules
 
@@ -128,24 +130,137 @@
 	PresignedPrebuilt bool
 
 	NoStripping     bool
-	StripInputPath  string
-	StripOutputPath string
+	StripInputPath  android.Path
+	StripOutputPath android.WritablePath
 }
 
-func LoadGlobalConfig(path string) (GlobalConfig, error) {
-	config := GlobalConfig{}
-	err := loadConfig(path, &config)
-	return config, err
+func constructPath(ctx android.PathContext, path string) android.Path {
+	buildDirPrefix := ctx.Config().BuildDir() + "/"
+	if path == "" {
+		return nil
+	} else if strings.HasPrefix(path, buildDirPrefix) {
+		return android.PathForOutput(ctx, strings.TrimPrefix(path, buildDirPrefix))
+	} else {
+		return android.PathForSource(ctx, path)
+	}
 }
 
-func LoadModuleConfig(path string) (ModuleConfig, error) {
-	config := ModuleConfig{}
-	err := loadConfig(path, &config)
-	return config, err
+func constructPaths(ctx android.PathContext, paths []string) android.Paths {
+	var ret android.Paths
+	for _, path := range paths {
+		ret = append(ret, constructPath(ctx, path))
+	}
+	return ret
 }
 
-func loadConfig(path string, config interface{}) error {
-	data, err := ioutil.ReadFile(path)
+func constructPathMap(ctx android.PathContext, paths map[string]string) map[string]android.Path {
+	ret := map[string]android.Path{}
+	for key, path := range paths {
+		ret[key] = constructPath(ctx, path)
+	}
+	return ret
+}
+
+func constructWritablePath(ctx android.PathContext, path string) android.WritablePath {
+	if path == "" {
+		return nil
+	}
+	return constructPath(ctx, path).(android.WritablePath)
+}
+
+// LoadGlobalConfig reads the global dexpreopt.config file into a GlobalConfig struct.  It is used directly in Soong
+// and in dexpreopt_gen called from Make to read the $OUT/dexpreopt.config written by Make.
+func LoadGlobalConfig(ctx android.PathContext, path string) (GlobalConfig, error) {
+	type GlobalJSONConfig struct {
+		GlobalConfig
+
+		// Copies of entries in GlobalConfig that are not constructable without extra parameters.  They will be
+		// used to construct the real value manually below.
+		DirtyImageObjects string
+		PreloadedClasses  string
+		BootImageProfiles []string
+
+		Tools struct {
+			Profman  string
+			Dex2oat  string
+			Aapt     string
+			SoongZip string
+			Zip2zip  string
+
+			VerifyUsesLibraries string
+			ConstructContext    string
+		}
+	}
+
+	config := GlobalJSONConfig{}
+	err := loadConfig(ctx, path, &config)
+	if err != nil {
+		return config.GlobalConfig, err
+	}
+
+	// Construct paths that require a PathContext.
+	config.GlobalConfig.DirtyImageObjects = android.OptionalPathForPath(constructPath(ctx, config.DirtyImageObjects))
+	config.GlobalConfig.PreloadedClasses = android.OptionalPathForPath(constructPath(ctx, config.PreloadedClasses))
+	config.GlobalConfig.BootImageProfiles = constructPaths(ctx, config.BootImageProfiles)
+
+	config.GlobalConfig.Tools.Profman = constructPath(ctx, config.Tools.Profman)
+	config.GlobalConfig.Tools.Dex2oat = constructPath(ctx, config.Tools.Dex2oat)
+	config.GlobalConfig.Tools.Aapt = constructPath(ctx, config.Tools.Aapt)
+	config.GlobalConfig.Tools.SoongZip = constructPath(ctx, config.Tools.SoongZip)
+	config.GlobalConfig.Tools.Zip2zip = constructPath(ctx, config.Tools.Zip2zip)
+	config.GlobalConfig.Tools.VerifyUsesLibraries = constructPath(ctx, config.Tools.VerifyUsesLibraries)
+	config.GlobalConfig.Tools.ConstructContext = constructPath(ctx, config.Tools.ConstructContext)
+
+	return config.GlobalConfig, nil
+}
+
+// LoadModuleConfig reads a per-module dexpreopt.config file into a ModuleConfig struct.  It is not used in Soong, which
+// receives a ModuleConfig struct directly from java/dexpreopt.go.  It is used in dexpreopt_gen called from oMake to
+// read the module dexpreopt.config written by Make.
+func LoadModuleConfig(ctx android.PathContext, path string) (ModuleConfig, error) {
+	type ModuleJSONConfig struct {
+		ModuleConfig
+
+		// Copies of entries in ModuleConfig that are not constructable without extra parameters.  They will be
+		// used to construct the real value manually below.
+		BuildPath                   string
+		DexPath                     string
+		ProfileClassListing         string
+		LibraryPaths                map[string]string
+		DexPreoptImages             []string
+		PreoptBootClassPathDexFiles []string
+		StripInputPath              string
+		StripOutputPath             string
+	}
+
+	config := ModuleJSONConfig{}
+
+	err := loadConfig(ctx, path, &config)
+	if err != nil {
+		return config.ModuleConfig, err
+	}
+
+	// Construct paths that require a PathContext.
+	config.ModuleConfig.BuildPath = constructPath(ctx, config.BuildPath).(android.OutputPath)
+	config.ModuleConfig.DexPath = constructPath(ctx, config.DexPath)
+	config.ModuleConfig.ProfileClassListing = android.OptionalPathForPath(constructPath(ctx, config.ProfileClassListing))
+	config.ModuleConfig.LibraryPaths = constructPathMap(ctx, config.LibraryPaths)
+	config.ModuleConfig.DexPreoptImages = constructPaths(ctx, config.DexPreoptImages)
+	config.ModuleConfig.PreoptBootClassPathDexFiles = constructPaths(ctx, config.PreoptBootClassPathDexFiles)
+	config.ModuleConfig.StripInputPath = constructPath(ctx, config.StripInputPath)
+	config.ModuleConfig.StripOutputPath = constructWritablePath(ctx, config.StripOutputPath)
+
+	return config.ModuleConfig, nil
+}
+
+func loadConfig(ctx android.PathContext, path string, config interface{}) error {
+	r, err := ctx.Fs().Open(path)
+	if err != nil {
+		return err
+	}
+	defer r.Close()
+
+	data, err := ioutil.ReadAll(r)
 	if err != nil {
 		return err
 	}
@@ -157,3 +272,56 @@
 
 	return nil
 }
+
+func GlobalConfigForTests(ctx android.PathContext) GlobalConfig {
+	return GlobalConfig{
+		DefaultNoStripping:                 false,
+		DisablePreoptModules:               nil,
+		OnlyPreoptBootImageAndSystemServer: false,
+		HasSystemOther:                     false,
+		PatternsOnSystemOther:              nil,
+		DisableGenerateProfile:             false,
+		BootJars:                           nil,
+		RuntimeApexJars:                    nil,
+		ProductUpdatableBootModules:        nil,
+		ProductUpdatableBootLocations:      nil,
+		SystemServerJars:                   nil,
+		SystemServerApps:                   nil,
+		SpeedApps:                          nil,
+		PreoptFlags:                        nil,
+		DefaultCompilerFilter:              "",
+		SystemServerCompilerFilter:         "",
+		GenerateDMFiles:                    false,
+		NeverAllowStripping:                false,
+		NoDebugInfo:                        false,
+		AlwaysSystemServerDebugInfo:        false,
+		NeverSystemServerDebugInfo:         false,
+		AlwaysOtherDebugInfo:               false,
+		NeverOtherDebugInfo:                false,
+		MissingUsesLibraries:               nil,
+		IsEng:                              false,
+		SanitizeLite:                       false,
+		DefaultAppImages:                   false,
+		Dex2oatXmx:                         "",
+		Dex2oatXms:                         "",
+		EmptyDirectory:                     "empty_dir",
+		CpuVariant:                         nil,
+		InstructionSetFeatures:             nil,
+		DirtyImageObjects:                  android.OptionalPath{},
+		PreloadedClasses:                   android.OptionalPath{},
+		BootImageProfiles:                  nil,
+		UseProfileForBootImage:             false,
+		BootFlags:                          "",
+		Dex2oatImageXmx:                    "",
+		Dex2oatImageXms:                    "",
+		Tools: Tools{
+			Profman:             android.PathForTesting("profman"),
+			Dex2oat:             android.PathForTesting("dex2oat"),
+			Aapt:                android.PathForTesting("aapt"),
+			SoongZip:            android.PathForTesting("soong_zip"),
+			Zip2zip:             android.PathForTesting("zip2zip"),
+			VerifyUsesLibraries: android.PathForTesting("verify_uses_libraries.sh"),
+			ConstructContext:    android.PathForTesting("construct_context.sh"),
+		},
+	}
+}
diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go
index 7fdfb49..9e333c1 100644
--- a/dexpreopt/dexpreopt.go
+++ b/dexpreopt/dexpreopt.go
@@ -37,6 +37,7 @@
 import (
 	"fmt"
 	"path/filepath"
+	"runtime"
 	"strings"
 
 	"android/soong/android"
@@ -52,7 +53,9 @@
 func GenerateStripRule(global GlobalConfig, module ModuleConfig) (rule *android.RuleBuilder, err error) {
 	defer func() {
 		if r := recover(); r != nil {
-			if e, ok := r.(error); ok {
+			if _, ok := r.(runtime.Error); ok {
+				panic(r)
+			} else if e, ok := r.(error); ok {
 				err = e
 				rule = nil
 			} else {
@@ -86,10 +89,14 @@
 
 // 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(global GlobalConfig, module ModuleConfig) (rule *android.RuleBuilder, err error) {
+func GenerateDexpreoptRule(ctx android.PathContext,
+	global GlobalConfig, module ModuleConfig) (rule *android.RuleBuilder, err error) {
+
 	defer func() {
 		if r := recover(); r != nil {
-			if e, ok := r.(error); ok {
+			if _, ok := r.(runtime.Error); ok {
+				panic(r)
+			} else if e, ok := r.(error); ok {
 				err = e
 				rule = nil
 			} else {
@@ -100,11 +107,11 @@
 
 	rule = android.NewRuleBuilder()
 
-	generateProfile := module.ProfileClassListing != "" && !global.DisableGenerateProfile
+	generateProfile := module.ProfileClassListing.Valid() && !global.DisableGenerateProfile
 
-	var profile string
+	var profile android.WritablePath
 	if generateProfile {
-		profile = profileCommand(global, module, rule)
+		profile = profileCommand(ctx, global, module, rule)
 	}
 
 	if !dexpreoptDisabled(global, module) {
@@ -118,7 +125,7 @@
 
 			for i, arch := range module.Archs {
 				image := module.DexPreoptImages[i]
-				dexpreoptCommand(global, module, rule, arch, profile, image, appImage, generateDM)
+				dexpreoptCommand(ctx, global, module, rule, arch, profile, image, appImage, generateDM)
 			}
 		}
 	}
@@ -143,8 +150,10 @@
 	return false
 }
 
-func profileCommand(global GlobalConfig, module ModuleConfig, rule *android.RuleBuilder) string {
-	profilePath := filepath.Join(filepath.Dir(module.BuildPath), "profile.prof")
+func profileCommand(ctx android.PathContext, global GlobalConfig, module ModuleConfig,
+	rule *android.RuleBuilder) android.WritablePath {
+
+	profilePath := module.BuildPath.InSameDir(ctx, "profile.prof")
 	profileInstalledPath := module.DexLocation + ".prof"
 
 	if !module.ProfileIsTextListing {
@@ -158,13 +167,13 @@
 	if module.ProfileIsTextListing {
 		// The profile is a test listing of classes (used for framework jars).
 		// We need to generate the actual binary profile before being able to compile.
-		cmd.FlagWithInput("--create-profile-from=", module.ProfileClassListing)
+		cmd.FlagWithInput("--create-profile-from=", module.ProfileClassListing.Path())
 	} else {
 		// The profile is binary profile (used for apps). Run it through profman to
 		// ensure the profile keys match the apk.
 		cmd.
 			Flag("--copy-and-update-profile-key").
-			FlagWithInput("--profile-file=", module.ProfileClassListing)
+			FlagWithInput("--profile-file=", module.ProfileClassListing.Path())
 	}
 
 	cmd.
@@ -180,8 +189,8 @@
 	return profilePath
 }
 
-func dexpreoptCommand(global GlobalConfig, module ModuleConfig, rule *android.RuleBuilder,
-	arch android.ArchType, profile, bootImage string, appImage, generateDM bool) {
+func dexpreoptCommand(ctx android.PathContext, global GlobalConfig, module ModuleConfig, rule *android.RuleBuilder,
+	arch android.ArchType, profile, bootImage android.Path, appImage, generateDM bool) {
 
 	// HACK: make soname in Soong-generated .odex files match Make.
 	base := filepath.Base(module.DexLocation)
@@ -199,21 +208,21 @@
 			pathtools.ReplaceExtension(filepath.Base(path), "odex"))
 	}
 
-	odexPath := toOdexPath(filepath.Join(filepath.Dir(module.BuildPath), base))
+	odexPath := module.BuildPath.InSameDir(ctx, "oat", arch.String(), pathtools.ReplaceExtension(base, "odex"))
 	odexInstallPath := toOdexPath(module.DexLocation)
 	if odexOnSystemOther(module, global) {
 		odexInstallPath = strings.Replace(odexInstallPath, SystemPartition, SystemOtherPartition, 1)
 	}
 
-	vdexPath := pathtools.ReplaceExtension(odexPath, "vdex")
+	vdexPath := odexPath.ReplaceExtension(ctx, "vdex")
 	vdexInstallPath := pathtools.ReplaceExtension(odexInstallPath, "vdex")
 
-	invocationPath := pathtools.ReplaceExtension(odexPath, "invocation")
+	invocationPath := odexPath.ReplaceExtension(ctx, "invocation")
 
 	// bootImage is .../dex_bootjars/system/framework/arm64/boot.art, but dex2oat wants
 	// .../dex_bootjars/system/framework/boot.art on the command line
 	var bootImageLocation string
-	if bootImage != "" {
+	if bootImage != nil {
 		bootImageLocation = PathToLocation(bootImage, arch)
 	}
 
@@ -227,19 +236,21 @@
 	var filteredOptionalUsesLibs []string
 
 	// The class loader context using paths in the build
-	var classLoaderContextHost []string
+	var classLoaderContextHost android.Paths
 
 	// The class loader context using paths as they will be on the device
 	var classLoaderContextTarget []string
 
 	// Extra paths that will be appended to the class loader if the APK manifest has targetSdkVersion < 28
-	var conditionalClassLoaderContextHost28 []string
+	var conditionalClassLoaderContextHost28 android.Paths
 	var conditionalClassLoaderContextTarget28 []string
 
 	// Extra paths that will be appended to the class loader if the APK manifest has targetSdkVersion < 29
-	var conditionalClassLoaderContextHost29 []string
+	var conditionalClassLoaderContextHost29 android.Paths
 	var conditionalClassLoaderContextTarget29 []string
 
+	var classLoaderContextHostString string
+
 	if module.EnforceUsesLibraries {
 		verifyUsesLibs = copyOf(module.UsesLibraries)
 		verifyOptionalUsesLibs = copyOf(module.OptionalUsesLibraries)
@@ -281,31 +292,41 @@
 			pathForLibrary(module, hidlBase))
 		conditionalClassLoaderContextTarget29 = append(conditionalClassLoaderContextTarget29,
 			filepath.Join("/system/framework", hidlBase+".jar"))
+
+		classLoaderContextHostString = strings.Join(classLoaderContextHost.Strings(), ":")
 	} else {
 		// Pass special class loader context to skip the classpath and collision check.
 		// This will get removed once LOCAL_USES_LIBRARIES is enforced.
 		// Right now LOCAL_USES_LIBRARIES is opt in, for the case where it's not specified we still default
 		// to the &.
-		classLoaderContextHost = []string{`\&`}
+		classLoaderContextHostString = `\&`
 	}
 
-	rule.Command().FlagWithArg("mkdir -p ", filepath.Dir(odexPath))
+	rule.Command().FlagWithArg("mkdir -p ", filepath.Dir(odexPath.String()))
 	rule.Command().FlagWithOutput("rm -f ", odexPath)
 	// Set values in the environment of the rule.  These may be modified by construct_context.sh.
-	rule.Command().FlagWithArg("class_loader_context_arg=--class-loader-context=",
-		strings.Join(classLoaderContextHost, ":"))
+	rule.Command().FlagWithArg("class_loader_context_arg=--class-loader-context=", classLoaderContextHostString)
 	rule.Command().Text(`stored_class_loader_context_arg=""`)
 
 	if module.EnforceUsesLibraries {
 		rule.Command().Textf(`uses_library_names="%s"`, strings.Join(verifyUsesLibs, " "))
 		rule.Command().Textf(`optional_uses_library_names="%s"`, strings.Join(verifyOptionalUsesLibs, " "))
 		rule.Command().Textf(`aapt_binary="%s"`, global.Tools.Aapt)
-		rule.Command().Textf(`dex_preopt_host_libraries="%s"`, strings.Join(classLoaderContextHost, " "))
-		rule.Command().Textf(`dex_preopt_target_libraries="%s"`, strings.Join(classLoaderContextTarget, " "))
-		rule.Command().Textf(`conditional_host_libs_28="%s"`, strings.Join(conditionalClassLoaderContextHost28, " "))
-		rule.Command().Textf(`conditional_target_libs_28="%s"`, strings.Join(conditionalClassLoaderContextTarget28, " "))
-		rule.Command().Textf(`conditional_host_libs_29="%s"`, strings.Join(conditionalClassLoaderContextHost29, " "))
-		rule.Command().Textf(`conditional_target_libs_29="%s"`, strings.Join(conditionalClassLoaderContextTarget29, " "))
+		rule.Command().Textf(`dex_preopt_host_libraries="%s"`,
+			strings.Join(classLoaderContextHost.Strings(), " ")).
+			Implicits(classLoaderContextHost)
+		rule.Command().Textf(`dex_preopt_target_libraries="%s"`,
+			strings.Join(classLoaderContextTarget, " "))
+		rule.Command().Textf(`conditional_host_libs_28="%s"`,
+			strings.Join(conditionalClassLoaderContextHost28.Strings(), " ")).
+			Implicits(conditionalClassLoaderContextHost28)
+		rule.Command().Textf(`conditional_target_libs_28="%s"`,
+			strings.Join(conditionalClassLoaderContextTarget28, " "))
+		rule.Command().Textf(`conditional_host_libs_29="%s"`,
+			strings.Join(conditionalClassLoaderContextHost29.Strings(), " ")).
+			Implicits(conditionalClassLoaderContextHost29)
+		rule.Command().Textf(`conditional_target_libs_29="%s"`,
+			strings.Join(conditionalClassLoaderContextTarget29, " "))
 		rule.Command().Text("source").Tool(global.Tools.VerifyUsesLibraries).Input(module.DexPath)
 		rule.Command().Text("source").Tool(global.Tools.ConstructContext)
 	}
@@ -364,7 +385,7 @@
 			// Apps loaded into system server, and apps the product default to being compiled with the
 			// 'speed' compiler filter.
 			compilerFilter = "speed"
-		} else if profile != "" {
+		} else if profile != nil {
 			// For non system server jars, use speed-profile when we have a profile.
 			compilerFilter = "speed-profile"
 		} else if global.DefaultCompilerFilter != "" {
@@ -377,9 +398,9 @@
 
 	if generateDM {
 		cmd.FlagWithArg("--copy-dex-files=", "false")
-		dmPath := filepath.Join(filepath.Dir(module.BuildPath), "generated.dm")
+		dmPath := module.BuildPath.InSameDir(ctx, "generated.dm")
 		dmInstalledPath := pathtools.ReplaceExtension(module.DexLocation, "dm")
-		tmpPath := filepath.Join(filepath.Dir(module.BuildPath), "primary.vdex")
+		tmpPath := module.BuildPath.InSameDir(ctx, "primary.vdex")
 		rule.Command().Text("cp -f").Input(vdexPath).Output(tmpPath)
 		rule.Command().Tool(global.Tools.SoongZip).
 			FlagWithArg("-L", "9").
@@ -428,15 +449,15 @@
 	cmd.FlagWithArg("--compilation-reason=", "prebuilt")
 
 	if appImage {
-		appImagePath := pathtools.ReplaceExtension(odexPath, "art")
+		appImagePath := odexPath.ReplaceExtension(ctx, "art")
 		appImageInstallPath := pathtools.ReplaceExtension(odexInstallPath, "art")
 		cmd.FlagWithOutput("--app-image-file=", appImagePath).
 			FlagWithArg("--image-format=", "lz4")
 		rule.Install(appImagePath, appImageInstallPath)
 	}
 
-	if profile != "" {
-		cmd.FlagWithArg("--profile-file=", profile)
+	if profile != nil {
+		cmd.FlagWithInput("--profile-file=", profile)
 	}
 
 	rule.Install(odexPath, odexInstallPath)
@@ -522,17 +543,17 @@
 }
 
 // PathToLocation converts .../system/framework/arm64/boot.art to .../system/framework/boot.art
-func PathToLocation(path string, arch android.ArchType) string {
-	pathArch := filepath.Base(filepath.Dir(path))
+func PathToLocation(path android.Path, arch android.ArchType) string {
+	pathArch := filepath.Base(filepath.Dir(path.String()))
 	if pathArch != arch.String() {
 		panic(fmt.Errorf("last directory in %q must be %q", path, arch.String()))
 	}
-	return filepath.Join(filepath.Dir(filepath.Dir(path)), filepath.Base(path))
+	return filepath.Join(filepath.Dir(filepath.Dir(path.String())), filepath.Base(path.String()))
 }
 
-func pathForLibrary(module ModuleConfig, lib string) string {
-	path := module.LibraryPaths[lib]
-	if path == "" {
+func pathForLibrary(module ModuleConfig, lib string) android.Path {
+	path, ok := module.LibraryPaths[lib]
+	if !ok {
 		panic(fmt.Errorf("unknown library path for %q", lib))
 	}
 	return path
diff --git a/dexpreopt/dexpreopt_gen/dexpreopt_gen.go b/dexpreopt/dexpreopt_gen/dexpreopt_gen.go
index cc3c1f1..c72f684 100644
--- a/dexpreopt/dexpreopt_gen/dexpreopt_gen.go
+++ b/dexpreopt/dexpreopt_gen/dexpreopt_gen.go
@@ -21,6 +21,7 @@
 	"os"
 	"path/filepath"
 	"runtime"
+	"strings"
 
 	"android/soong/android"
 	"android/soong/dexpreopt"
@@ -33,8 +34,17 @@
 	stripScriptPath     = flag.String("strip_script", "", "path to output strip script")
 	globalConfigPath    = flag.String("global", "", "path to global configuration file")
 	moduleConfigPath    = flag.String("module", "", "path to module configuration file")
+	outDir              = flag.String("out_dir", "", "path to output directory")
 )
 
+type pathContext struct {
+	config android.Config
+}
+
+func (x *pathContext) Fs() pathtools.FileSystem   { return pathtools.OsFs }
+func (x *pathContext) Config() android.Config     { return x.config }
+func (x *pathContext) AddNinjaFileDeps(...string) {}
+
 func main() {
 	flag.Parse()
 
@@ -66,18 +76,26 @@
 		usage("path to module configuration file is required")
 	}
 
-	globalConfig, err := dexpreopt.LoadGlobalConfig(*globalConfigPath)
+	ctx := &pathContext{android.TestConfig(*outDir, nil)}
+
+	globalConfig, err := dexpreopt.LoadGlobalConfig(ctx, *globalConfigPath)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "error loading global config %q: %s\n", *globalConfigPath, err)
 		os.Exit(2)
 	}
 
-	moduleConfig, err := dexpreopt.LoadModuleConfig(*moduleConfigPath)
+	moduleConfig, err := dexpreopt.LoadModuleConfig(ctx, *moduleConfigPath)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "error loading module config %q: %s\n", *moduleConfigPath, err)
 		os.Exit(2)
 	}
 
+	// This shouldn't be using *PathForTesting, but it's outside of soong_build so its OK for now.
+	moduleConfig.StripInputPath = android.PathForTesting("$1")
+	moduleConfig.StripOutputPath = android.WritablePathForTesting("$2")
+
+	moduleConfig.DexPath = android.PathForTesting("$1")
+
 	defer func() {
 		if r := recover(); r != nil {
 			switch x := r.(type) {
@@ -92,30 +110,30 @@
 		}
 	}()
 
-	writeScripts(globalConfig, moduleConfig, *dexpreoptScriptPath, *stripScriptPath)
+	writeScripts(ctx, globalConfig, moduleConfig, *dexpreoptScriptPath, *stripScriptPath)
 }
 
-func writeScripts(global dexpreopt.GlobalConfig, module dexpreopt.ModuleConfig,
+func writeScripts(ctx android.PathContext, global dexpreopt.GlobalConfig, module dexpreopt.ModuleConfig,
 	dexpreoptScriptPath, stripScriptPath string) {
-	dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(global, module)
+	dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(ctx, global, module)
 	if err != nil {
 		panic(err)
 	}
 
-	installDir := filepath.Join(filepath.Dir(module.BuildPath), "dexpreopt_install")
+	installDir := module.BuildPath.InSameDir(ctx, "dexpreopt_install")
 
-	dexpreoptRule.Command().FlagWithArg("rm -rf ", installDir)
-	dexpreoptRule.Command().FlagWithArg("mkdir -p ", installDir)
+	dexpreoptRule.Command().FlagWithArg("rm -rf ", installDir.String())
+	dexpreoptRule.Command().FlagWithArg("mkdir -p ", installDir.String())
 
 	for _, install := range dexpreoptRule.Installs() {
-		installPath := filepath.Join(installDir, install.To)
-		dexpreoptRule.Command().Text("mkdir -p").Flag(filepath.Dir(installPath))
+		installPath := installDir.Join(ctx, strings.TrimPrefix(install.To, "/"))
+		dexpreoptRule.Command().Text("mkdir -p").Flag(filepath.Dir(installPath.String()))
 		dexpreoptRule.Command().Text("cp -f").Input(install.From).Output(installPath)
 	}
 	dexpreoptRule.Command().Tool(global.Tools.SoongZip).
-		FlagWithOutput("-o ", "$2").
-		FlagWithArg("-C ", installDir).
-		FlagWithArg("-D ", installDir)
+		FlagWithArg("-o ", "$2").
+		FlagWithArg("-C ", installDir.String()).
+		FlagWithArg("-D ", installDir.String())
 
 	stripRule, err := dexpreopt.GenerateStripRule(global, module)
 	if err != nil {
@@ -139,7 +157,7 @@
 		for _, input := range rule.Inputs() {
 			// Assume the rule that ran the script already has a dependency on the input file passed on the
 			// command line.
-			if input != "$1" {
+			if input.String() != "$1" {
 				fmt.Fprintf(depFile, `    %s \`+"\n", input)
 			}
 		}
@@ -159,13 +177,13 @@
 	}
 
 	// The written scripts will assume the input is $1 and the output is $2
-	if module.DexPath != "$1" {
+	if module.DexPath.String() != "$1" {
 		panic(fmt.Errorf("module.DexPath must be '$1', was %q", module.DexPath))
 	}
-	if module.StripInputPath != "$1" {
+	if module.StripInputPath.String() != "$1" {
 		panic(fmt.Errorf("module.StripInputPath must be '$1', was %q", module.StripInputPath))
 	}
-	if module.StripOutputPath != "$2" {
+	if module.StripOutputPath.String() != "$2" {
 		panic(fmt.Errorf("module.StripOutputPath must be '$2', was %q", module.StripOutputPath))
 	}
 
diff --git a/dexpreopt/dexpreopt_test.go b/dexpreopt/dexpreopt_test.go
index 949f91f..2a58ab9 100644
--- a/dexpreopt/dexpreopt_test.go
+++ b/dexpreopt/dexpreopt_test.go
@@ -21,98 +21,47 @@
 	"testing"
 )
 
-var testGlobalConfig = GlobalConfig{
-	DefaultNoStripping:                 false,
-	DisablePreoptModules:               nil,
-	OnlyPreoptBootImageAndSystemServer: false,
-	HasSystemOther:                     false,
-	PatternsOnSystemOther:              nil,
-	DisableGenerateProfile:             false,
-	BootJars:                           nil,
-	RuntimeApexJars:                    nil,
-	ProductUpdatableBootModules:        nil,
-	ProductUpdatableBootLocations:      nil,
-	SystemServerJars:                   nil,
-	SystemServerApps:                   nil,
-	SpeedApps:                          nil,
-	PreoptFlags:                        nil,
-	DefaultCompilerFilter:              "",
-	SystemServerCompilerFilter:         "",
-	GenerateDMFiles:                    false,
-	NeverAllowStripping:                false,
-	NoDebugInfo:                        false,
-	AlwaysSystemServerDebugInfo:        false,
-	NeverSystemServerDebugInfo:         false,
-	AlwaysOtherDebugInfo:               false,
-	NeverOtherDebugInfo:                false,
-	MissingUsesLibraries:               nil,
-	IsEng:                              false,
-	SanitizeLite:                       false,
-	DefaultAppImages:                   false,
-	Dex2oatXmx:                         "",
-	Dex2oatXms:                         "",
-	EmptyDirectory:                     "",
-	CpuVariant:                         nil,
-	InstructionSetFeatures:             nil,
-	DirtyImageObjects:                  "",
-	PreloadedClasses:                   "",
-	BootImageProfiles:                  nil,
-	BootFlags:                          "",
-	Dex2oatImageXmx:                    "",
-	Dex2oatImageXms:                    "",
-	Tools: Tools{
-		Profman:             "profman",
-		Dex2oat:             "dex2oat",
-		Aapt:                "aapt",
-		SoongZip:            "soong_zip",
-		Zip2zip:             "zip2zip",
-		VerifyUsesLibraries: "verify_uses_libraries.sh",
-		ConstructContext:    "construct_context.sh",
-	},
-}
-
-var testModuleConfig = ModuleConfig{
-	Name:                            "",
-	DexLocation:                     "",
-	BuildPath:                       "",
-	DexPath:                         "",
-	UncompressedDex:                 false,
-	HasApkLibraries:                 false,
-	PreoptFlags:                     nil,
-	ProfileClassListing:             "",
-	ProfileIsTextListing:            false,
-	EnforceUsesLibraries:            false,
-	OptionalUsesLibraries:           nil,
-	UsesLibraries:                   nil,
-	LibraryPaths:                    nil,
-	Archs:                           []android.ArchType{android.Arm},
-	DexPreoptImages:                 []string{"system/framework/arm/boot.art"},
-	PreoptBootClassPathDexFiles:     nil,
-	PreoptBootClassPathDexLocations: nil,
-	PreoptExtractedApk:              false,
-	NoCreateAppImage:                false,
-	ForceCreateAppImage:             false,
-	PresignedPrebuilt:               false,
-	NoStripping:                     false,
-	StripInputPath:                  "",
-	StripOutputPath:                 "",
+func testModuleConfig(ctx android.PathContext) ModuleConfig {
+	return ModuleConfig{
+		Name:                            "test",
+		DexLocation:                     "/system/app/test/test.apk",
+		BuildPath:                       android.PathForOutput(ctx, "test/test.apk"),
+		DexPath:                         android.PathForOutput(ctx, "test/dex/test.jar"),
+		UncompressedDex:                 false,
+		HasApkLibraries:                 false,
+		PreoptFlags:                     nil,
+		ProfileClassListing:             android.OptionalPath{},
+		ProfileIsTextListing:            false,
+		EnforceUsesLibraries:            false,
+		OptionalUsesLibraries:           nil,
+		UsesLibraries:                   nil,
+		LibraryPaths:                    nil,
+		Archs:                           []android.ArchType{android.Arm},
+		DexPreoptImages:                 android.Paths{android.PathForTesting("system/framework/arm/boot.art")},
+		PreoptBootClassPathDexFiles:     nil,
+		PreoptBootClassPathDexLocations: nil,
+		PreoptExtractedApk:              false,
+		NoCreateAppImage:                false,
+		ForceCreateAppImage:             false,
+		PresignedPrebuilt:               false,
+		NoStripping:                     false,
+		StripInputPath:                  android.PathForOutput(ctx, "unstripped/test.apk"),
+		StripOutputPath:                 android.PathForOutput(ctx, "stripped/test.apk"),
+	}
 }
 
 func TestDexPreopt(t *testing.T) {
-	global, module := testGlobalConfig, testModuleConfig
+	ctx := android.PathContextForTesting(android.TestConfig("out", nil), nil)
+	global, module := GlobalConfigForTests(ctx), testModuleConfig(ctx)
 
-	module.Name = "test"
-	module.DexLocation = "/system/app/test/test.apk"
-	module.BuildPath = "out/test/test.apk"
-
-	rule, err := GenerateDexpreoptRule(global, module)
+	rule, err := GenerateDexpreoptRule(ctx, global, module)
 	if err != nil {
-		t.Error(err)
+		t.Fatal(err)
 	}
 
 	wantInstalls := android.RuleBuilderInstalls{
-		{"out/test/oat/arm/package.odex", "/system/app/test/oat/arm/test.odex"},
-		{"out/test/oat/arm/package.vdex", "/system/app/test/oat/arm/test.vdex"},
+		{android.PathForOutput(ctx, "test/oat/arm/package.odex"), "/system/app/test/oat/arm/test.odex"},
+		{android.PathForOutput(ctx, "test/oat/arm/package.vdex"), "/system/app/test/oat/arm/test.vdex"},
 	}
 
 	if !reflect.DeepEqual(rule.Installs(), wantInstalls) {
@@ -122,13 +71,11 @@
 
 func TestDexPreoptStrip(t *testing.T) {
 	// Test that we panic if we strip in a configuration where stripping is not allowed.
-	global, module := testGlobalConfig, testModuleConfig
+	ctx := android.PathContextForTesting(android.TestConfig("out", nil), nil)
+	global, module := GlobalConfigForTests(ctx), testModuleConfig(ctx)
 
 	global.NeverAllowStripping = true
 	module.NoStripping = false
-	module.Name = "test"
-	module.DexLocation = "/system/app/test/test.apk"
-	module.BuildPath = "out/test/test.apk"
 
 	_, err := GenerateStripRule(global, module)
 	if err == nil {
@@ -137,23 +84,20 @@
 }
 
 func TestDexPreoptSystemOther(t *testing.T) {
-	global, module := testGlobalConfig, testModuleConfig
+	ctx := android.PathContextForTesting(android.TestConfig("out", nil), nil)
+	global, module := GlobalConfigForTests(ctx), testModuleConfig(ctx)
 
 	global.HasSystemOther = true
 	global.PatternsOnSystemOther = []string{"app/%"}
 
-	module.Name = "test"
-	module.DexLocation = "/system/app/test/test.apk"
-	module.BuildPath = "out/test/test.apk"
-
-	rule, err := GenerateDexpreoptRule(global, module)
+	rule, err := GenerateDexpreoptRule(ctx, global, module)
 	if err != nil {
-		t.Error(err)
+		t.Fatal(err)
 	}
 
 	wantInstalls := android.RuleBuilderInstalls{
-		{"out/test/oat/arm/package.odex", "/system_other/app/test/oat/arm/test.odex"},
-		{"out/test/oat/arm/package.vdex", "/system_other/app/test/oat/arm/test.vdex"},
+		{android.PathForOutput(ctx, "test/oat/arm/package.odex"), "/system_other/app/test/oat/arm/test.odex"},
+		{android.PathForOutput(ctx, "test/oat/arm/package.vdex"), "/system_other/app/test/oat/arm/test.vdex"},
 	}
 
 	if !reflect.DeepEqual(rule.Installs(), wantInstalls) {
@@ -162,23 +106,21 @@
 }
 
 func TestDexPreoptProfile(t *testing.T) {
-	global, module := testGlobalConfig, testModuleConfig
+	ctx := android.PathContextForTesting(android.TestConfig("out", nil), nil)
+	global, module := GlobalConfigForTests(ctx), testModuleConfig(ctx)
 
-	module.Name = "test"
-	module.DexLocation = "/system/app/test/test.apk"
-	module.BuildPath = "out/test/test.apk"
-	module.ProfileClassListing = "profile"
+	module.ProfileClassListing = android.OptionalPathForPath(android.PathForTesting("profile"))
 
-	rule, err := GenerateDexpreoptRule(global, module)
+	rule, err := GenerateDexpreoptRule(ctx, global, module)
 	if err != nil {
-		t.Error(err)
+		t.Fatal(err)
 	}
 
 	wantInstalls := android.RuleBuilderInstalls{
-		{"out/test/profile.prof", "/system/app/test/test.apk.prof"},
-		{"out/test/oat/arm/package.art", "/system/app/test/oat/arm/test.art"},
-		{"out/test/oat/arm/package.odex", "/system/app/test/oat/arm/test.odex"},
-		{"out/test/oat/arm/package.vdex", "/system/app/test/oat/arm/test.vdex"},
+		{android.PathForOutput(ctx, "test/profile.prof"), "/system/app/test/test.apk.prof"},
+		{android.PathForOutput(ctx, "test/oat/arm/package.art"), "/system/app/test/oat/arm/test.art"},
+		{android.PathForOutput(ctx, "test/oat/arm/package.odex"), "/system/app/test/oat/arm/test.odex"},
+		{android.PathForOutput(ctx, "test/oat/arm/package.vdex"), "/system/app/test/oat/arm/test.vdex"},
 	}
 
 	if !reflect.DeepEqual(rule.Installs(), wantInstalls) {
@@ -212,29 +154,24 @@
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
 
-			global, module := testGlobalConfig, testModuleConfig
-
-			module.Name = "test"
-			module.DexLocation = "/system/app/test/test.apk"
-			module.BuildPath = "out/test/test.apk"
-			module.StripInputPath = "$1"
-			module.StripOutputPath = "$2"
+			ctx := android.PathContextForTesting(android.TestConfig("out", nil), nil)
+			global, module := GlobalConfigForTests(ctx), testModuleConfig(ctx)
 
 			test.setup(&global, &module)
 
 			rule, err := GenerateStripRule(global, module)
 			if err != nil {
-				t.Error(err)
+				t.Fatal(err)
 			}
 
 			if test.strip {
-				want := `zip2zip -i $1 -o $2 -x "classes*.dex"`
+				want := `zip2zip -i out/unstripped/test.apk -o out/stripped/test.apk -x "classes*.dex"`
 				if len(rule.Commands()) < 1 || !strings.Contains(rule.Commands()[0], want) {
 					t.Errorf("\nwant commands[0] to have:\n   %v\ngot:\n   %v", want, rule.Commands()[0])
 				}
 			} else {
 				wantCommands := []string{
-					"cp -f $1 $2",
+					"cp -f out/unstripped/test.apk out/stripped/test.apk",
 				}
 				if !reflect.DeepEqual(rule.Commands(), wantCommands) {
 					t.Errorf("\nwant commands:\n   %v\ngot:\n   %v", wantCommands, rule.Commands())