Sandbox soong_build by changing to root directory

Store the current working directory and then change to the root
directory so that all file accesses must go through helpers in
the android package that properly track dependencies.

Fixes: 146437378
Test: m checkbuild
Change-Id: I12a0f907753fefd1997ab8b4ea2ac331234093cf
diff --git a/Android.bp b/Android.bp
index 9403b26..0382ee2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -69,6 +69,7 @@
         "android/proto.go",
         "android/register.go",
         "android/rule_builder.go",
+        "android/sandbox.go",
         "android/sdk.go",
         "android/sh_binary.go",
         "android/singleton.go",
diff --git a/android/androidmk.go b/android/androidmk.go
index f3c15e4..dbf3aa8 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -323,7 +323,7 @@
 		return
 	}
 
-	err := translateAndroidMk(ctx, transMk.String(), androidMkModulesList)
+	err := translateAndroidMk(ctx, absolutePath(transMk.String()), androidMkModulesList)
 	if err != nil {
 		ctx.Errorf(err.Error())
 	}
@@ -364,8 +364,8 @@
 	}
 
 	// Don't write to the file if it hasn't changed
-	if _, err := os.Stat(mkFile); !os.IsNotExist(err) {
-		if data, err := ioutil.ReadFile(mkFile); err == nil {
+	if _, err := os.Stat(absolutePath(mkFile)); !os.IsNotExist(err) {
+		if data, err := ioutil.ReadFile(absolutePath(mkFile)); err == nil {
 			matches := buf.Len() == len(data)
 
 			if matches {
@@ -383,7 +383,7 @@
 		}
 	}
 
-	return ioutil.WriteFile(mkFile, buf.Bytes(), 0666)
+	return ioutil.WriteFile(absolutePath(mkFile), buf.Bytes(), 0666)
 }
 
 func translateAndroidMkModule(ctx SingletonContext, w io.Writer, mod blueprint.Module) error {
diff --git a/android/config.go b/android/config.go
index 101f457..3c49c1a 100644
--- a/android/config.go
+++ b/android/config.go
@@ -135,12 +135,12 @@
 }
 
 func loadConfig(config *config) error {
-	err := loadFromConfigFile(&config.FileConfigurableOptions, config.ConfigFileName)
+	err := loadFromConfigFile(&config.FileConfigurableOptions, absolutePath(config.ConfigFileName))
 	if err != nil {
 		return err
 	}
 
-	return loadFromConfigFile(&config.productVariables, config.ProductVariablesFileName)
+	return loadFromConfigFile(&config.productVariables, absolutePath(config.ProductVariablesFileName))
 }
 
 // loads configuration options from a JSON file in the cwd.
@@ -204,6 +204,17 @@
 	return nil
 }
 
+// NullConfig returns a mostly empty Config for use by standalone tools like dexpreopt_gen that
+// use the android package.
+func NullConfig(buildDir string) Config {
+	return Config{
+		config: &config{
+			buildDir: buildDir,
+			fs:       pathtools.OsFs,
+		},
+	}
+}
+
 // TestConfig returns a Config object suitable for using for tests
 func TestConfig(buildDir string, env map[string]string, bp string, fs map[string][]byte) Config {
 	envCopy := make(map[string]string)
@@ -320,7 +331,7 @@
 		buildDir:          buildDir,
 		multilibConflicts: make(map[ArchType]bool),
 
-		fs: pathtools.OsFs,
+		fs: pathtools.NewOsFs(absSrcDir),
 	}
 
 	config.deviceConfig = &deviceConfig{
@@ -350,7 +361,7 @@
 	}
 
 	inMakeFile := filepath.Join(buildDir, ".soong.in_make")
-	if _, err := os.Stat(inMakeFile); err == nil {
+	if _, err := os.Stat(absolutePath(inMakeFile)); err == nil {
 		config.inMake = true
 	}
 
@@ -398,6 +409,8 @@
 	return Config{config}, nil
 }
 
+var TestConfigOsFs = map[string][]byte{}
+
 // mockFileSystem replaces all reads with accesses to the provided map of
 // filenames to contents stored as a byte slice.
 func (c *config) mockFileSystem(bp string, fs map[string][]byte) {
@@ -901,8 +914,13 @@
 	return c.productVariables.BootJars
 }
 
-func (c *config) DexpreoptGlobalConfig() string {
-	return String(c.productVariables.DexpreoptGlobalConfig)
+func (c *config) DexpreoptGlobalConfig(ctx PathContext) ([]byte, error) {
+	if c.productVariables.DexpreoptGlobalConfig == nil {
+		return nil, nil
+	}
+	path := absolutePath(*c.productVariables.DexpreoptGlobalConfig)
+	ctx.AddNinjaFileDeps(path)
+	return ioutil.ReadFile(path)
 }
 
 func (c *config) FrameworksBaseDirExists(ctx PathContext) bool {
diff --git a/android/env.go b/android/env.go
index d9f2db2..46bd3d6 100644
--- a/android/env.go
+++ b/android/env.go
@@ -52,6 +52,17 @@
 	os.Clearenv()
 }
 
+// getenv checks either os.Getenv or originalEnv so that it works before or after the init()
+// function above.  It doesn't add any dependencies on the environment variable, so it should
+// only be used for values that won't change.  For values that might change use ctx.Config().Getenv.
+func getenv(key string) string {
+	if originalEnv == nil {
+		return os.Getenv(key)
+	} else {
+		return originalEnv[key]
+	}
+}
+
 func EnvSingleton() Singleton {
 	return &envSingleton{}
 }
@@ -66,7 +77,12 @@
 		return
 	}
 
-	err := env.WriteEnvFile(envFile.String(), envDeps)
+	data, err := env.EnvFileContents(envDeps)
+	if err != nil {
+		ctx.Errorf(err.Error())
+	}
+
+	err = WriteFileToOutputDir(envFile, data, 0666)
 	if err != nil {
 		ctx.Errorf(err.Error())
 	}
diff --git a/android/makevars.go b/android/makevars.go
index 38a028c..aba4cce 100644
--- a/android/makevars.go
+++ b/android/makevars.go
@@ -23,7 +23,6 @@
 	"strings"
 
 	"github.com/google/blueprint"
-	"github.com/google/blueprint/pathtools"
 	"github.com/google/blueprint/proptools"
 )
 
@@ -41,7 +40,6 @@
 	Config() Config
 	DeviceConfig() DeviceConfig
 	AddNinjaFileDeps(deps ...string)
-	Fs() pathtools.FileSystem
 
 	ModuleName(module blueprint.Module) string
 	ModuleDir(module blueprint.Module) string
@@ -151,7 +149,8 @@
 		return
 	}
 
-	outFile := PathForOutput(ctx, "make_vars"+proptools.String(ctx.Config().productVariables.Make_suffix)+".mk").String()
+	outFile := absolutePath(PathForOutput(ctx,
+		"make_vars"+proptools.String(ctx.Config().productVariables.Make_suffix)+".mk").String())
 
 	if ctx.Failed() {
 		return
@@ -175,15 +174,15 @@
 
 	outBytes := s.writeVars(vars)
 
-	if _, err := os.Stat(outFile); err == nil {
-		if data, err := ioutil.ReadFile(outFile); err == nil {
+	if _, err := os.Stat(absolutePath(outFile)); err == nil {
+		if data, err := ioutil.ReadFile(absolutePath(outFile)); err == nil {
 			if bytes.Equal(data, outBytes) {
 				return
 			}
 		}
 	}
 
-	if err := ioutil.WriteFile(outFile, outBytes, 0666); err != nil {
+	if err := ioutil.WriteFile(absolutePath(outFile), outBytes, 0666); err != nil {
 		ctx.Errorf(err.Error())
 	}
 }
diff --git a/android/module.go b/android/module.go
index c998007..67d1f12 100644
--- a/android/module.go
+++ b/android/module.go
@@ -16,13 +16,13 @@
 
 import (
 	"fmt"
+	"os"
 	"path"
 	"path/filepath"
 	"strings"
 	"text/scanner"
 
 	"github.com/google/blueprint"
-	"github.com/google/blueprint/pathtools"
 	"github.com/google/blueprint/proptools"
 )
 
@@ -91,7 +91,8 @@
 
 	Glob(globPattern string, excludes []string) Paths
 	GlobFiles(globPattern string, excludes []string) Paths
-	Fs() pathtools.FileSystem
+	IsSymlink(path Path) bool
+	Readlink(path Path) string
 }
 
 // BaseModuleContext is the same as blueprint.BaseModuleContext except that Config() returns
@@ -1172,6 +1173,22 @@
 	return pathsForModuleSrcFromFullPath(e, ret, false)
 }
 
+func (b *earlyModuleContext) IsSymlink(path Path) bool {
+	fileInfo, err := b.config.fs.Lstat(path.String())
+	if err != nil {
+		b.ModuleErrorf("os.Lstat(%q) failed: %s", path.String(), err)
+	}
+	return fileInfo.Mode()&os.ModeSymlink == os.ModeSymlink
+}
+
+func (b *earlyModuleContext) Readlink(path Path) string {
+	dest, err := b.config.fs.Readlink(path.String())
+	if err != nil {
+		b.ModuleErrorf("os.Readlink(%q) failed: %s", path.String(), err)
+	}
+	return dest
+}
+
 func (e *earlyModuleContext) Module() Module {
 	module, _ := e.EarlyModuleContext.Module().(Module)
 	return module
diff --git a/android/package_ctx.go b/android/package_ctx.go
index d3527fa..a228910 100644
--- a/android/package_ctx.go
+++ b/android/package_ctx.go
@@ -19,7 +19,6 @@
 	"strings"
 
 	"github.com/google/blueprint"
-	"github.com/google/blueprint/pathtools"
 )
 
 // PackageContext is a wrapper for blueprint.PackageContext that adds
@@ -60,10 +59,6 @@
 	e.pctx.AddNinjaFileDeps(deps...)
 }
 
-func (e *configErrorWrapper) Fs() pathtools.FileSystem {
-	return nil
-}
-
 type PackageVarContext interface {
 	PathContext
 	errorfContext
diff --git a/android/paths.go b/android/paths.go
index a03fe17..02f56d0 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -16,6 +16,8 @@
 
 import (
 	"fmt"
+	"io/ioutil"
+	"os"
 	"path/filepath"
 	"reflect"
 	"sort"
@@ -25,10 +27,11 @@
 	"github.com/google/blueprint/pathtools"
 )
 
+var absSrcDir string
+
 // PathContext is the subset of a (Module|Singleton)Context required by the
 // Path methods.
 type PathContext interface {
-	Fs() pathtools.FileSystem
 	Config() Config
 	AddNinjaFileDeps(deps ...string)
 }
@@ -390,7 +393,7 @@
 		return PathsWithModuleSrcSubDir(ctx, paths, ""), nil
 	} else {
 		p := pathForModuleSrc(ctx, s)
-		if exists, _, err := ctx.Fs().Exists(p.String()); err != nil {
+		if exists, _, err := ctx.Config().fs.Exists(p.String()); err != nil {
 			reportPathErrorf(ctx, "%s: %s", p, err.Error())
 		} else if !exists {
 			reportPathErrorf(ctx, "module source path %q does not exist", p)
@@ -720,7 +723,7 @@
 		var deps []string
 		// We cannot add build statements in this context, so we fall back to
 		// AddNinjaFileDeps
-		files, deps, err = pathtools.Glob(path.String(), nil, pathtools.FollowSymlinks)
+		files, deps, err = ctx.Config().fs.Glob(path.String(), nil, pathtools.FollowSymlinks)
 		ctx.AddNinjaFileDeps(deps...)
 	}
 
@@ -752,7 +755,7 @@
 		if !exists {
 			modCtx.AddMissingDependencies([]string{path.String()})
 		}
-	} else if exists, _, err := ctx.Fs().Exists(path.String()); err != nil {
+	} else if exists, _, err := ctx.Config().fs.Exists(path.String()); err != nil {
 		reportPathErrorf(ctx, "%s: %s", path, err.Error())
 	} else if !exists {
 		reportPathErrorf(ctx, "source path %q does not exist", path)
@@ -1356,7 +1359,6 @@
 	config Config
 }
 
-func (x *testPathContext) Fs() pathtools.FileSystem   { return x.config.fs }
 func (x *testPathContext) Config() Config             { return x.config }
 func (x *testPathContext) AddNinjaFileDeps(...string) {}
 
@@ -1402,3 +1404,16 @@
 	}
 	return rel, true, nil
 }
+
+// Writes a file to the output directory.  Attempting to write directly to the output directory
+// will fail due to the sandbox of the soong_build process.
+func WriteFileToOutputDir(path WritablePath, data []byte, perm os.FileMode) error {
+	return ioutil.WriteFile(absolutePath(path.String()), data, perm)
+}
+
+func absolutePath(path string) string {
+	if filepath.IsAbs(path) {
+		return path
+	}
+	return filepath.Join(absSrcDir, path)
+}
diff --git a/android/paths_test.go b/android/paths_test.go
index ec5e598..46e3e1f 100644
--- a/android/paths_test.go
+++ b/android/paths_test.go
@@ -21,7 +21,6 @@
 	"strings"
 	"testing"
 
-	"github.com/google/blueprint/pathtools"
 	"github.com/google/blueprint/proptools"
 )
 
@@ -207,10 +206,6 @@
 	inRoot         bool
 }
 
-func (moduleInstallPathContextImpl) Fs() pathtools.FileSystem {
-	return pathtools.MockFs(nil)
-}
-
 func (m moduleInstallPathContextImpl) Config() Config {
 	return m.baseModuleContext.config
 }
diff --git a/android/register.go b/android/register.go
index b5defec..b48d3d1 100644
--- a/android/register.go
+++ b/android/register.go
@@ -84,7 +84,9 @@
 }
 
 func NewContext() *Context {
-	return &Context{blueprint.NewContext()}
+	ctx := &Context{blueprint.NewContext()}
+	ctx.SetSrcDir(absSrcDir)
+	return ctx
 }
 
 func (ctx *Context) Register() {
diff --git a/android/sandbox.go b/android/sandbox.go
new file mode 100644
index 0000000..ed022fb
--- /dev/null
+++ b/android/sandbox.go
@@ -0,0 +1,47 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import (
+	"fmt"
+	"os"
+)
+
+func init() {
+	// Stash the working directory in a private variable and then change the working directory
+	// to "/", which will prevent untracked accesses to files by Go Soong plugins. The
+	// SOONG_SANDBOX_SOONG_BUILD environment variable is set by soong_ui, and is not
+	// overrideable on the command line.
+
+	orig, err := os.Getwd()
+	if err != nil {
+		panic(fmt.Errorf("failed to get working directory: %s", err))
+	}
+	absSrcDir = orig
+
+	if getenv("SOONG_SANDBOX_SOONG_BUILD") == "true" {
+		err = os.Chdir("/")
+		if err != nil {
+			panic(fmt.Errorf("failed to change working directory to '/': %s", err))
+		}
+	}
+}
+
+// DO NOT USE THIS FUNCTION IN NEW CODE.
+// Deprecated: This function will be removed as soon as the existing use cases that use it have been
+// replaced.
+func AbsSrcDirForExistingUseCases() string {
+	return absSrcDir
+}
diff --git a/android/singleton.go b/android/singleton.go
index 5519ca0..91268ad 100644
--- a/android/singleton.go
+++ b/android/singleton.go
@@ -16,7 +16,6 @@
 
 import (
 	"github.com/google/blueprint"
-	"github.com/google/blueprint/pathtools"
 )
 
 // SingletonContext
@@ -74,8 +73,6 @@
 	// builder whenever a file matching the pattern as added or removed, without rerunning if a
 	// file that does not match the pattern is added to a searched directory.
 	GlobWithDeps(pattern string, excludes []string) ([]string, error)
-
-	Fs() pathtools.FileSystem
 }
 
 type singletonAdaptor struct {
diff --git a/androidmk/Android.bp b/androidmk/Android.bp
index 4110073..70fc1f7 100644
--- a/androidmk/Android.bp
+++ b/androidmk/Android.bp
@@ -41,7 +41,6 @@
         "androidmk-parser",
         "blueprint-parser",
         "bpfix-lib",
-        "soong-android",
     ],
 }
 
diff --git a/androidmk/androidmk/android.go b/androidmk/androidmk/android.go
index 0082d8b..8860984 100644
--- a/androidmk/androidmk/android.go
+++ b/androidmk/androidmk/android.go
@@ -15,9 +15,9 @@
 package androidmk
 
 import (
-	"android/soong/android"
 	mkparser "android/soong/androidmk/parser"
 	"fmt"
+	"sort"
 	"strings"
 
 	bpparser "github.com/google/blueprint/parser"
@@ -350,7 +350,13 @@
 		return err
 	}
 
-	for _, nameClassification := range android.SortedStringKeys(namesByClassification) {
+	var classifications []string
+	for classification := range namesByClassification {
+		classifications = append(classifications, classification)
+	}
+	sort.Strings(classifications)
+
+	for _, nameClassification := range classifications {
 		name := namesByClassification[nameClassification]
 		if component, ok := lists[nameClassification]; ok && !emptyList(component) {
 			err = setVariable(ctx.file, ctx.append, ctx.prefix, name, component, true)
diff --git a/cc/cmakelists.go b/cc/cmakelists.go
index 97d21f4..f7d9081 100644
--- a/cc/cmakelists.go
+++ b/cc/cmakelists.go
@@ -76,7 +76,7 @@
 	// Link all handmade CMakeLists.txt aggregate from
 	//     BASE/development/ide/clion to
 	// BASE/out/development/ide/clion.
-	dir := filepath.Join(getAndroidSrcRootDirectory(ctx), cLionAggregateProjectsDirectory)
+	dir := filepath.Join(android.AbsSrcDirForExistingUseCases(), cLionAggregateProjectsDirectory)
 	filepath.Walk(dir, linkAggregateCMakeListsFiles)
 
 	return
@@ -147,7 +147,7 @@
 	f.WriteString("# Tools > CMake > Change Project Root  \n\n")
 	f.WriteString(fmt.Sprintf("cmake_minimum_required(VERSION %s)\n", minimumCMakeVersionSupported))
 	f.WriteString(fmt.Sprintf("project(%s)\n", ccModule.ModuleBase.Name()))
-	f.WriteString(fmt.Sprintf("set(ANDROID_ROOT %s)\n\n", getAndroidSrcRootDirectory(ctx)))
+	f.WriteString(fmt.Sprintf("set(ANDROID_ROOT %s)\n\n", android.AbsSrcDirForExistingUseCases()))
 
 	pathToCC, _ := evalVariable(ctx, "${config.ClangBin}/")
 	f.WriteString(fmt.Sprintf("set(CMAKE_C_COMPILER \"%s%s\")\n", buildCMakePath(pathToCC), "clang"))
@@ -465,7 +465,7 @@
 }
 
 func getCMakeListsForModule(module *Module, ctx android.SingletonContext) string {
-	return filepath.Join(getAndroidSrcRootDirectory(ctx),
+	return filepath.Join(android.AbsSrcDirForExistingUseCases(),
 		cLionOutputProjectsDirectory,
 		path.Dir(ctx.BlueprintFile(module)),
 		module.ModuleBase.Name()+"-"+
@@ -473,8 +473,3 @@
 			module.ModuleBase.Os().Name,
 		cMakeListsFilename)
 }
-
-func getAndroidSrcRootDirectory(ctx android.SingletonContext) string {
-	srcPath, _ := filepath.Abs(android.PathForSource(ctx).String())
-	return srcPath
-}
diff --git a/cc/compdb.go b/cc/compdb.go
index dff14db..ea12443 100644
--- a/cc/compdb.go
+++ b/cc/compdb.go
@@ -79,9 +79,9 @@
 
 	// Create the output file.
 	dir := android.PathForOutput(ctx, compdbOutputProjectsDirectory)
-	os.MkdirAll(dir.String(), 0777)
+	os.MkdirAll(filepath.Join(android.AbsSrcDirForExistingUseCases(), dir.String()), 0777)
 	compDBFile := dir.Join(ctx, compdbFilename)
-	f, err := os.Create(compDBFile.String())
+	f, err := os.Create(filepath.Join(android.AbsSrcDirForExistingUseCases(), compDBFile.String()))
 	if err != nil {
 		log.Fatalf("Could not create file %s: %s", compDBFile, err)
 	}
@@ -103,8 +103,8 @@
 	}
 	f.Write(dat)
 
-	finalLinkPath := filepath.Join(ctx.Config().Getenv(envVariableCompdbLink), compdbFilename)
-	if finalLinkPath != "" {
+	if finalLinkDir := ctx.Config().Getenv(envVariableCompdbLink); finalLinkDir != "" {
+		finalLinkPath := filepath.Join(finalLinkDir, compdbFilename)
 		os.Remove(finalLinkPath)
 		if err := os.Symlink(compDBFile.String(), finalLinkPath); err != nil {
 			log.Fatalf("Unable to symlink %s to %s: %s", compDBFile, finalLinkPath, err)
@@ -174,18 +174,17 @@
 		return
 	}
 
-	rootDir := getCompdbAndroidSrcRootDirectory(ctx)
-	pathToCC, err := ctx.Eval(pctx, rootDir+"/${config.ClangBin}/")
+	pathToCC, err := ctx.Eval(pctx, "${config.ClangBin}")
 	ccPath := "/bin/false"
 	cxxPath := "/bin/false"
 	if err == nil {
-		ccPath = pathToCC + "clang"
-		cxxPath = pathToCC + "clang++"
+		ccPath = filepath.Join(pathToCC, "clang")
+		cxxPath = filepath.Join(pathToCC, "clang++")
 	}
 	for _, src := range srcs {
 		if _, ok := builds[src.String()]; !ok {
 			builds[src.String()] = compDbEntry{
-				Directory: rootDir,
+				Directory: android.AbsSrcDirForExistingUseCases(),
 				Arguments: getArguments(src, ctx, ccModule, ccPath, cxxPath),
 				File:      src.String(),
 			}
@@ -200,8 +199,3 @@
 	}
 	return []string{""}, err
 }
-
-func getCompdbAndroidSrcRootDirectory(ctx android.SingletonContext) string {
-	srcPath, _ := filepath.Abs(android.PathForSource(ctx).String())
-	return srcPath
-}
diff --git a/cc/ndk_headers.go b/cc/ndk_headers.go
index b8423be..5744bb2 100644
--- a/cc/ndk_headers.go
+++ b/cc/ndk_headers.go
@@ -16,7 +16,6 @@
 
 import (
 	"fmt"
-	"os"
 	"path/filepath"
 	"strings"
 
@@ -255,16 +254,8 @@
 	depsPath := android.PathForSource(ctx, "bionic/libc/versioner-dependencies")
 	depsGlob := ctx.Glob(filepath.Join(depsPath.String(), "**/*"), nil)
 	for i, path := range depsGlob {
-		fileInfo, err := os.Lstat(path.String())
-		if err != nil {
-			ctx.ModuleErrorf("os.Lstat(%q) failed: %s", path.String, err)
-		}
-		if fileInfo.Mode()&os.ModeSymlink == os.ModeSymlink {
-			dest, err := os.Readlink(path.String())
-			if err != nil {
-				ctx.ModuleErrorf("os.Readlink(%q) failed: %s",
-					path.String, err)
-			}
+		if ctx.IsSymlink(path) {
+			dest := ctx.Readlink(path)
 			// Additional .. to account for the symlink itself.
 			depsGlob[i] = android.PathForSource(
 				ctx, filepath.Clean(filepath.Join(path.String(), "..", dest)))
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index 0c79ccc..2a929c5 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -16,7 +16,6 @@
 
 import (
 	"encoding/json"
-	"io/ioutil"
 	"strings"
 
 	"android/soong/android"
@@ -185,7 +184,7 @@
 // soongConfig argument. LoadGlobalConfig 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, soongConfig GlobalSoongConfig) (GlobalConfig, []byte, error) {
+func LoadGlobalConfig(ctx android.PathContext, data []byte, soongConfig GlobalSoongConfig) (GlobalConfig, error) {
 	type GlobalJSONConfig struct {
 		GlobalConfig
 
@@ -196,9 +195,9 @@
 	}
 
 	config := GlobalJSONConfig{}
-	data, err := loadConfig(ctx, path, &config)
+	err := json.Unmarshal(data, &config)
 	if err != nil {
-		return config.GlobalConfig, nil, err
+		return config.GlobalConfig, err
 	}
 
 	// Construct paths that require a PathContext.
@@ -209,13 +208,13 @@
 	// either CreateGlobalSoongConfig or LoadGlobalSoongConfig).
 	config.GlobalConfig.SoongConfig = soongConfig
 
-	return config.GlobalConfig, data, nil
+	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) {
+func LoadModuleConfig(ctx android.PathContext, data []byte) (ModuleConfig, error) {
 	type ModuleJSONConfig struct {
 		ModuleConfig
 
@@ -233,7 +232,7 @@
 
 	config := ModuleJSONConfig{}
 
-	_, err := loadConfig(ctx, path, &config)
+	err := json.Unmarshal(data, &config)
 	if err != nil {
 		return config.ModuleConfig, err
 	}
@@ -289,10 +288,10 @@
 
 // LoadGlobalSoongConfig reads the dexpreopt_soong.config file into a
 // GlobalSoongConfig struct. It is only used in dexpreopt_gen.
-func LoadGlobalSoongConfig(ctx android.PathContext, path string) (GlobalSoongConfig, error) {
+func LoadGlobalSoongConfig(ctx android.PathContext, data []byte) (GlobalSoongConfig, error) {
 	var jc globalJsonSoongConfig
 
-	_, err := loadConfig(ctx, path, &jc)
+	err := json.Unmarshal(data, &jc)
 	if err != nil {
 		return GlobalSoongConfig{}, err
 	}
@@ -352,26 +351,6 @@
 	}, " "))
 }
 
-func loadConfig(ctx android.PathContext, path string, config interface{}) ([]byte, error) {
-	r, err := ctx.Fs().Open(path)
-	if err != nil {
-		return nil, err
-	}
-	defer r.Close()
-
-	data, err := ioutil.ReadAll(r)
-	if err != nil {
-		return nil, err
-	}
-
-	err = json.Unmarshal(data, config)
-	if err != nil {
-		return nil, err
-	}
-
-	return data, nil
-}
-
 func GlobalConfigForTests(ctx android.PathContext) GlobalConfig {
 	return GlobalConfig{
 		DisablePreopt:                      false,
diff --git a/dexpreopt/dexpreopt_gen/dexpreopt_gen.go b/dexpreopt/dexpreopt_gen/dexpreopt_gen.go
index d2faa00..e2818bb 100644
--- a/dexpreopt/dexpreopt_gen/dexpreopt_gen.go
+++ b/dexpreopt/dexpreopt_gen/dexpreopt_gen.go
@@ -18,6 +18,7 @@
 	"bytes"
 	"flag"
 	"fmt"
+	"io/ioutil"
 	"os"
 	"path/filepath"
 	"runtime"
@@ -41,7 +42,6 @@
 	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) {}
 
@@ -76,21 +76,39 @@
 		usage("--module configuration file is required")
 	}
 
-	ctx := &pathContext{android.TestConfig(*outDir, nil, "", nil)}
+	ctx := &pathContext{android.NullConfig(*outDir)}
 
-	globalSoongConfig, err := dexpreopt.LoadGlobalSoongConfig(ctx, *globalSoongConfigPath)
+	globalSoongConfigData, err := ioutil.ReadFile(*globalSoongConfigPath)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "error reading global config %q: %s\n", *globalSoongConfigPath, err)
+		os.Exit(2)
+	}
+
+	globalSoongConfig, err := dexpreopt.LoadGlobalSoongConfig(ctx, globalSoongConfigData)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "error loading global config %q: %s\n", *globalSoongConfigPath, err)
 		os.Exit(2)
 	}
 
-	globalConfig, _, err := dexpreopt.LoadGlobalConfig(ctx, *globalConfigPath, globalSoongConfig)
+	globalConfigData, err := ioutil.ReadFile(*globalConfigPath)
 	if err != nil {
-		fmt.Fprintf(os.Stderr, "error loading global config %q: %s\n", *globalConfigPath, err)
+		fmt.Fprintf(os.Stderr, "error reading global config %q: %s\n", *globalConfigPath, err)
 		os.Exit(2)
 	}
 
-	moduleConfig, err := dexpreopt.LoadModuleConfig(ctx, *moduleConfigPath)
+	globalConfig, err := dexpreopt.LoadGlobalConfig(ctx, globalConfigData, globalSoongConfig)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "error parse global config %q: %s\n", *globalConfigPath, err)
+		os.Exit(2)
+	}
+
+	moduleConfigData, err := ioutil.ReadFile(*moduleConfigPath)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "error reading module config %q: %s\n", *moduleConfigPath, err)
+		os.Exit(2)
+	}
+
+	moduleConfig, err := dexpreopt.LoadModuleConfig(ctx, moduleConfigData)
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "error loading module config %q: %s\n", *moduleConfigPath, err)
 		os.Exit(2)
diff --git a/env/env.go b/env/env.go
index bf58a99..a98e1f6 100644
--- a/env/env.go
+++ b/env/env.go
@@ -27,7 +27,7 @@
 type envFileEntry struct{ Key, Value string }
 type envFileData []envFileEntry
 
-func WriteEnvFile(filename string, envDeps map[string]string) error {
+func EnvFileContents(envDeps map[string]string) ([]byte, error) {
 	contents := make(envFileData, 0, len(envDeps))
 	for key, value := range envDeps {
 		contents = append(contents, envFileEntry{key, value})
@@ -37,17 +37,12 @@
 
 	data, err := json.MarshalIndent(contents, "", "    ")
 	if err != nil {
-		return err
+		return nil, err
 	}
 
 	data = append(data, '\n')
 
-	err = ioutil.WriteFile(filename, data, 0664)
-	if err != nil {
-		return err
-	}
-
-	return nil
+	return data, nil
 }
 
 func StaleEnvFile(filename string) (bool, error) {
diff --git a/java/dexpreopt_config.go b/java/dexpreopt_config.go
index 35748b8..f3191e7 100644
--- a/java/dexpreopt_config.go
+++ b/java/dexpreopt_config.go
@@ -36,10 +36,11 @@
 
 func dexpreoptGlobalConfigRaw(ctx android.PathContext) globalConfigAndRaw {
 	return ctx.Config().Once(dexpreoptGlobalConfigKey, func() interface{} {
-		if f := ctx.Config().DexpreoptGlobalConfig(); f != "" {
+		if data, err := ctx.Config().DexpreoptGlobalConfig(ctx); err != nil {
+			panic(err)
+		} else if data != nil {
 			soongConfig := dexpreopt.CreateGlobalSoongConfig(ctx)
-			ctx.AddNinjaFileDeps(f)
-			globalConfig, data, err := dexpreopt.LoadGlobalConfig(ctx, f, soongConfig)
+			globalConfig, err := dexpreopt.LoadGlobalConfig(ctx, data, soongConfig)
 			if err != nil {
 				panic(err)
 			}
diff --git a/java/jdeps.go b/java/jdeps.go
index fccc40f..49e3de3 100644
--- a/java/jdeps.go
+++ b/java/jdeps.go
@@ -17,7 +17,6 @@
 import (
 	"encoding/json"
 	"fmt"
-	"os"
 
 	"android/soong/android"
 )
@@ -92,23 +91,21 @@
 		moduleInfos[name] = dpInfo
 	})
 
-	jfpath := android.PathForOutput(ctx, jdepsJsonFileName).String()
+	jfpath := android.PathForOutput(ctx, jdepsJsonFileName)
 	err := createJsonFile(moduleInfos, jfpath)
 	if err != nil {
 		ctx.Errorf(err.Error())
 	}
 }
 
-func createJsonFile(moduleInfos map[string]android.IdeInfo, jfpath string) error {
-	file, err := os.Create(jfpath)
-	if err != nil {
-		return fmt.Errorf("Failed to create file: %s, relative: %v", jdepsJsonFileName, err)
-	}
-	defer file.Close()
+func createJsonFile(moduleInfos map[string]android.IdeInfo, jfpath android.WritablePath) error {
 	buf, err := json.MarshalIndent(moduleInfos, "", "\t")
 	if err != nil {
-		return fmt.Errorf("Write file failed: %s, relative: %v", jdepsJsonFileName, err)
+		return fmt.Errorf("JSON marshal of java deps failed: %s", err)
 	}
-	fmt.Fprintf(file, string(buf))
+	err = android.WriteFileToOutputDir(jfpath, buf, 0666)
+	if err != nil {
+		return fmt.Errorf("Writing java deps to %s failed: %s", jfpath.String(), err)
+	}
 	return nil
 }
diff --git a/ui/build/soong.go b/ui/build/soong.go
index 3388417..afbc073 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -119,6 +119,7 @@
 			"-j", strconv.Itoa(config.Parallel()),
 			"--frontend_file", fifo,
 			"-f", filepath.Join(config.SoongOutDir(), file))
+		cmd.Environment.Set("SOONG_SANDBOX_SOONG_BUILD", "true")
 		cmd.Sandbox = soongSandbox
 		cmd.RunAndStreamOrFatal()
 	}