Merge changes I9512642d,I6548889c,I8db5198f

* changes:
  Add build_test.sh, split common parts of soong_ui.bash
  Allow specifying a build variant
  Improve multiproduct_kati output
diff --git a/android/arch.go b/android/arch.go
index 51fc444..effd5a6 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -882,10 +882,12 @@
 		{"arm", "armv7-a-neon", "cortex-a15", []string{"armeabi-v7a"}},
 		{"arm", "armv7-a-neon", "cortex-a53", []string{"armeabi-v7a"}},
 		{"arm", "armv7-a-neon", "cortex-a53.a57", []string{"armeabi-v7a"}},
+		{"arm", "armv7-a-neon", "cortex-a73", []string{"armeabi-v7a"}},
 		{"arm", "armv7-a-neon", "denver", []string{"armeabi-v7a"}},
 		{"arm", "armv7-a-neon", "krait", []string{"armeabi-v7a"}},
 		{"arm", "armv7-a-neon", "kryo", []string{"armeabi-v7a"}},
 		{"arm64", "armv8-a", "cortex-a53", []string{"arm64-v8a"}},
+		{"arm64", "armv8-a", "cortex-a73", []string{"arm64-v8a"}},
 		{"arm64", "armv8-a", "denver64", []string{"arm64-v8a"}},
 		{"arm64", "armv8-a", "kryo", []string{"arm64-v8a"}},
 		{"mips", "mips32-fp", "", []string{"mips"}},
diff --git a/android/config.go b/android/config.go
index 8be16cf..b83ffd4 100644
--- a/android/config.go
+++ b/android/config.go
@@ -249,6 +249,20 @@
 	return filepath.Join(c.buildDir, "host", c.PrebuiltOS(), "bin")
 }
 
+// HostSystemTool looks for non-hermetic tools from the system we're running on.
+// Generally shouldn't be used, but useful to find the XCode SDK, etc.
+func (c *config) HostSystemTool(name string) string {
+	for _, dir := range filepath.SplitList(c.Getenv("PATH")) {
+		path := filepath.Join(dir, name)
+		if s, err := os.Stat(path); err != nil {
+			continue
+		} else if m := s.Mode(); !s.IsDir() && m&0111 != 0 {
+			return path
+		}
+	}
+	return name
+}
+
 // PrebuiltOS returns the name of the host OS used in prebuilts directories
 func (c *config) PrebuiltOS() string {
 	switch runtime.GOOS {
@@ -289,7 +303,7 @@
 		if c.envFrozen {
 			panic("Cannot access new environment variables after envdeps are frozen")
 		}
-		val = os.Getenv(key)
+		val, _ = originalEnv[key]
 		c.envDeps[key] = val
 	}
 	return val
diff --git a/android/env.go b/android/env.go
index c7409e8..ec5794e 100644
--- a/android/env.go
+++ b/android/env.go
@@ -15,6 +15,9 @@
 package android
 
 import (
+	"os"
+	"strings"
+
 	"android/soong/env"
 
 	"github.com/google/blueprint"
@@ -27,6 +30,19 @@
 // compare the contents of the environment variables, rewriting the file if necessary to cause
 // a manifest regeneration.
 
+var originalEnv map[string]string
+
+func init() {
+	originalEnv = make(map[string]string)
+	for _, env := range os.Environ() {
+		idx := strings.IndexRune(env, '=')
+		if idx != -1 {
+			originalEnv[env[:idx]] = env[idx+1:]
+		}
+	}
+	os.Clearenv()
+}
+
 func EnvSingleton() blueprint.Singleton {
 	return &envSingleton{}
 }
diff --git a/android/package_ctx.go b/android/package_ctx.go
index d9bb109..5304403 100644
--- a/android/package_ctx.go
+++ b/android/package_ctx.go
@@ -120,16 +120,16 @@
 	})
 }
 
-// PrefixedPathsForOptionalSourceVariable returns a Variable whose value is the
+// PrefixedExistentPathsForSourcesVariable returns a Variable whose value is the
 // list of present source paths prefixed with the supplied prefix. It may only
 // be called during a Go package's initialization - either from the init()
 // function or as part of a package-scoped variable's initialization.
-func (p AndroidPackageContext) PrefixedPathsForOptionalSourceVariable(
+func (p AndroidPackageContext) PrefixedExistentPathsForSourcesVariable(
 	name, prefix string, paths []string) blueprint.Variable {
 
 	return p.VariableFunc(name, func(config interface{}) (string, error) {
 		ctx := &configErrorWrapper{p, config.(Config), []error{}}
-		paths := PathsForOptionalSource(ctx, "", paths)
+		paths := ExistentPathsForSources(ctx, "", paths)
 		if len(ctx.errors) > 0 {
 			return "", ctx.errors[0]
 		}
diff --git a/android/paths.go b/android/paths.go
index 0d26dc0..a23dd74 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -97,6 +97,8 @@
 type WritablePath interface {
 	Path
 
+	// the writablePath method doesn't directly do anything,
+	// but it allows a struct to distinguish between whether or not it implements the WritablePath interface
 	writablePath()
 }
 
@@ -137,7 +139,7 @@
 	if path, ok := p.(resPathProvider); ok {
 		return path.resPathWithName(ctx, name)
 	}
-	reportPathError(ctx, "Tried to create object file from unsupported path: %s (%s)", reflect.TypeOf(p).Name(), p)
+	reportPathError(ctx, "Tried to create res file from unsupported path: %s (%s)", reflect.TypeOf(p).Name(), p)
 	return PathForModuleRes(ctx)
 }
 
@@ -188,7 +190,7 @@
 			ret := make(Paths, 0, len(paths))
 			intermediates := filepath.Join(modCtx.ModuleDir(), modCtx.ModuleName(), modCtx.ModuleSubDir(), "missing")
 			for _, path := range paths {
-				p := OptionalPathForSource(ctx, intermediates, path)
+				p := ExistentPathForSource(ctx, intermediates, path)
 				if p.Valid() {
 					ret = append(ret, p.Path())
 				} else {
@@ -205,13 +207,13 @@
 	return ret
 }
 
-// PathsForOptionalSource returns a list of Paths rooted from SrcDir that are
+// ExistentPathsForSources returns a list of Paths rooted from SrcDir that are
 // found in the tree. If any are not found, they are omitted from the list,
 // and dependencies are added so that we're re-run when they are added.
-func PathsForOptionalSource(ctx PathContext, intermediates string, paths []string) Paths {
+func ExistentPathsForSources(ctx PathContext, intermediates string, paths []string) Paths {
 	ret := make(Paths, 0, len(paths))
 	for _, path := range paths {
-		p := OptionalPathForSource(ctx, intermediates, path)
+		p := ExistentPathForSource(ctx, intermediates, path)
 		if p.Valid() {
 			ret = append(ret, p.Path())
 		}
@@ -337,12 +339,11 @@
 	return ret
 }
 
-// PathForSource returns a SourcePath for the provided paths... (which are
-// joined together with filepath.Join). This also validates that the path
-// doesn't escape the source dir, or is contained in the build dir. On error, it
-// will return a usable, but invalid SourcePath, and report a ModuleError.
-func PathForSource(ctx PathContext, paths ...string) SourcePath {
-	p := validatePath(ctx, paths...)
+// PathForSource joins the provided path components and validates that the result
+// neither escapes the source dir nor is in the out dir.
+// On error, it will return a usable, but invalid SourcePath, and report a ModuleError.
+func PathForSource(ctx PathContext, pathComponents ...string) SourcePath {
+	p := validatePath(ctx, pathComponents...)
 	ret := SourcePath{basePath{p, pathConfig(ctx), ""}}
 
 	abs, err := filepath.Abs(ret.String())
@@ -368,16 +369,16 @@
 	return ret
 }
 
-// OptionalPathForSource returns an OptionalPath with the SourcePath if the
+// ExistentPathForSource returns an OptionalPath with the SourcePath if the
 // path exists, or an empty OptionalPath if it doesn't exist. Dependencies are added
 // so that the ninja file will be regenerated if the state of the path changes.
-func OptionalPathForSource(ctx PathContext, intermediates string, paths ...string) OptionalPath {
-	if len(paths) == 0 {
+func ExistentPathForSource(ctx PathContext, intermediates string, pathComponents ...string) OptionalPath {
+	if len(pathComponents) == 0 {
 		// For when someone forgets the 'intermediates' argument
 		panic("Missing path(s)")
 	}
 
-	p := validatePath(ctx, paths...)
+	p := validatePath(ctx, pathComponents...)
 	path := SourcePath{basePath{p, pathConfig(ctx), ""}}
 
 	abs, err := filepath.Abs(path.String())
@@ -483,12 +484,11 @@
 
 var _ Path = OutputPath{}
 
-// PathForOutput returns an OutputPath for the provided paths... (which are
-// joined together with filepath.Join). This also validates that the path
-// does not escape the build dir. On error, it will return a usable, but invalid
-// OutputPath, and report a ModuleError.
-func PathForOutput(ctx PathContext, paths ...string) OutputPath {
-	path := validatePath(ctx, paths...)
+// PathForOutput joins the provided paths and returns an OutputPath that is
+// validated to not escape the build dir.
+// On error, it will return a usable, but invalid OutputPath, and report a ModuleError.
+func PathForOutput(ctx PathContext, pathComponents ...string) OutputPath {
+	path := validatePath(ctx, pathComponents...)
 	return OutputPath{basePath{path, pathConfig(ctx), ""}}
 }
 
@@ -597,7 +597,7 @@
 	}
 	refDumpFileStr := "prebuilts/abi-dumps/" + vndkOrNdkDir + "/" + version + "/" +
 		archName + "/" + sourceOrBinaryDir + "/" + fileName + ext
-	return OptionalPathForSource(ctx, "", refDumpFileStr)
+	return ExistentPathForSource(ctx, "", refDumpFileStr)
 }
 
 // PathForModuleOut returns a Path representing the paths... under the module's
@@ -647,8 +647,8 @@
 
 // PathForModuleObj returns a Path representing the paths... under the module's
 // 'obj' directory.
-func PathForModuleObj(ctx ModuleContext, paths ...string) ModuleObjPath {
-	p := validatePath(ctx, paths...)
+func PathForModuleObj(ctx ModuleContext, pathComponents ...string) ModuleObjPath {
+	p := validatePath(ctx, pathComponents...)
 	return ModuleObjPath{PathForModuleOut(ctx, "obj", p)}
 }
 
@@ -662,14 +662,14 @@
 
 // PathForModuleRes returns a Path representing the paths... under the module's
 // 'res' directory.
-func PathForModuleRes(ctx ModuleContext, paths ...string) ModuleResPath {
-	p := validatePath(ctx, paths...)
+func PathForModuleRes(ctx ModuleContext, pathComponents ...string) ModuleResPath {
+	p := validatePath(ctx, pathComponents...)
 	return ModuleResPath{PathForModuleOut(ctx, "res", p)}
 }
 
 // PathForModuleInstall returns a Path representing the install path for the
 // module appended with paths...
-func PathForModuleInstall(ctx ModuleContext, paths ...string) OutputPath {
+func PathForModuleInstall(ctx ModuleContext, pathComponents ...string) OutputPath {
 	var outPaths []string
 	if ctx.Device() {
 		partition := "system"
@@ -689,14 +689,14 @@
 	if ctx.Debug() {
 		outPaths = append([]string{"debug"}, outPaths...)
 	}
-	outPaths = append(outPaths, paths...)
+	outPaths = append(outPaths, pathComponents...)
 	return PathForOutput(ctx, outPaths...)
 }
 
 // validateSafePath validates a path that we trust (may contain ninja variables).
 // Ensures that each path component does not attempt to leave its component.
-func validateSafePath(ctx PathContext, paths ...string) string {
-	for _, path := range paths {
+func validateSafePath(ctx PathContext, pathComponents ...string) string {
+	for _, path := range pathComponents {
 		path := filepath.Clean(path)
 		if path == ".." || strings.HasPrefix(path, "../") || strings.HasPrefix(path, "/") {
 			reportPathError(ctx, "Path is outside directory: %s", path)
@@ -706,20 +706,20 @@
 	// TODO: filepath.Join isn't necessarily correct with embedded ninja
 	// variables. '..' may remove the entire ninja variable, even if it
 	// will be expanded to multiple nested directories.
-	return filepath.Join(paths...)
+	return filepath.Join(pathComponents...)
 }
 
 // validatePath validates that a path does not include ninja variables, and that
 // each path component does not attempt to leave its component. Returns a joined
 // version of each path component.
-func validatePath(ctx PathContext, paths ...string) string {
-	for _, path := range paths {
+func validatePath(ctx PathContext, pathComponents ...string) string {
+	for _, path := range pathComponents {
 		if strings.Contains(path, "$") {
 			reportPathError(ctx, "Path contains invalid character($): %s", path)
 			return ""
 		}
 	}
-	return validateSafePath(ctx, paths...)
+	return validateSafePath(ctx, pathComponents...)
 }
 
 type testPath struct {
diff --git a/androidmk/cmd/androidmk/android.go b/androidmk/cmd/androidmk/android.go
index 8801b34..78395c3 100644
--- a/androidmk/cmd/androidmk/android.go
+++ b/androidmk/cmd/androidmk/android.go
@@ -107,7 +107,7 @@
 			"LOCAL_DX_FLAGS":              "dxflags",
 			"LOCAL_JAVA_LIBRARIES":        "java_libs",
 			"LOCAL_STATIC_JAVA_LIBRARIES": "java_static_libs",
-			"LOCAL_AIDL_INCLUDES":         "aidl_includes",
+			"LOCAL_AIDL_INCLUDES":         "aidl.include_dirs",
 			"LOCAL_AAPT_FLAGS":            "aaptflags",
 			"LOCAL_PACKAGE_SPLITS":        "package_splits",
 			"LOCAL_COMPATIBILITY_SUITE":   "test_suites",
diff --git a/cc/config/arm64_device.go b/cc/config/arm64_device.go
index f387ddf..a371cf6 100644
--- a/cc/config/arm64_device.go
+++ b/cc/config/arm64_device.go
@@ -93,7 +93,8 @@
 func init() {
 	android.RegisterArchVariants(android.Arm64,
 		"armv8_a",
-		"cortex_a53",
+		"cortex-a53",
+		"cortex-a73",
 		"kryo",
 		"denver64")
 
@@ -129,12 +130,14 @@
 	arm64CpuVariantCflagsVar = map[string]string{
 		"":           "",
 		"cortex-a53": "${config.Arm64CortexA53Cflags}",
+		"cortex-a73": "${config.Arm64CortexA53Cflags}",
 		"kryo":       "${config.Arm64KryoCflags}",
 	}
 
 	arm64ClangCpuVariantCflagsVar = map[string]string{
 		"":           "",
 		"cortex-a53": "${config.Arm64ClangCortexA53Cflags}",
+		"cortex-a73": "${config.Arm64ClangCortexA53Cflags}",
 		"kryo":       "${config.Arm64ClangKryoCflags}",
 	}
 )
diff --git a/cc/config/arm_device.go b/cc/config/arm_device.go
index 94627e7..ee9e042 100644
--- a/cc/config/arm_device.go
+++ b/cc/config/arm_device.go
@@ -127,6 +127,15 @@
 			// better solution comes around. See Bug 27340895
 			"-D__ARM_FEATURE_LPAE=1",
 		},
+		"cortex-a53": []string{
+			"-mcpu=cortex-a53",
+			"-mfpu=neon-fp-armv8",
+			// Fake an ARM compiler flag as these processors support LPAE which GCC/clang
+			// don't advertise.
+			// TODO This is a hack and we need to add it for each processor that supports LPAE until some
+			// better solution comes around. See Bug 27340895
+			"-D__ARM_FEATURE_LPAE=1",
+		},
 		"krait": []string{
 			"-mcpu=cortex-a15",
 			"-mfpu=neon-vfpv4",
@@ -169,6 +178,7 @@
 		"cortex-a15",
 		"cortex-a53",
 		"cortex-a53-a57",
+		"cortex-a73",
 		"krait",
 		"kryo",
 		"denver")
@@ -207,6 +217,7 @@
 	pctx.StaticVariable("ArmCortexA7Cflags", strings.Join(armCpuVariantCflags["cortex-a7"], " "))
 	pctx.StaticVariable("ArmCortexA8Cflags", strings.Join(armCpuVariantCflags["cortex-a8"], " "))
 	pctx.StaticVariable("ArmCortexA15Cflags", strings.Join(armCpuVariantCflags["cortex-a15"], " "))
+	pctx.StaticVariable("ArmCortexA53Cflags", strings.Join(armCpuVariantCflags["cortex-a53"], " "))
 	pctx.StaticVariable("ArmKraitCflags", strings.Join(armCpuVariantCflags["krait"], " "))
 	pctx.StaticVariable("ArmKryoCflags", strings.Join(armCpuVariantCflags["kryo"], " "))
 
@@ -237,6 +248,8 @@
 		strings.Join(armClangCpuVariantCflags["cortex-a8"], " "))
 	pctx.StaticVariable("ArmClangCortexA15Cflags",
 		strings.Join(armClangCpuVariantCflags["cortex-a15"], " "))
+	pctx.StaticVariable("ArmClangCortexA53Cflags",
+		strings.Join(armClangCpuVariantCflags["cortex-a53"], " "))
 	pctx.StaticVariable("ArmClangKraitCflags",
 		strings.Join(armClangCpuVariantCflags["krait"], " "))
 	pctx.StaticVariable("ArmClangKryoCflags",
@@ -255,8 +268,9 @@
 		"cortex-a7":      "${config.ArmCortexA7Cflags}",
 		"cortex-a8":      "${config.ArmCortexA8Cflags}",
 		"cortex-a15":     "${config.ArmCortexA15Cflags}",
-		"cortex-a53":     "${config.ArmCortexA7Cflags}",
-		"cortex-a53.a57": "${config.ArmCortexA7Cflags}",
+		"cortex-a53":     "${config.ArmCortexA53Cflags}",
+		"cortex-a53.a57": "${config.ArmCortexA53Cflags}",
+		"cortex-a73":     "${config.ArmCortexA53Cflags}",
 		"krait":          "${config.ArmKraitCflags}",
 		"kryo":           "${config.ArmKryoCflags}",
 		"denver":         "${config.ArmCortexA15Cflags}",
@@ -273,8 +287,9 @@
 		"cortex-a7":      "${config.ArmClangCortexA7Cflags}",
 		"cortex-a8":      "${config.ArmClangCortexA8Cflags}",
 		"cortex-a15":     "${config.ArmClangCortexA15Cflags}",
-		"cortex-a53":     "${config.ArmClangCortexA7Cflags}",
-		"cortex-a53.a57": "${config.ArmClangCortexA7Cflags}",
+		"cortex-a53":     "${config.ArmClangCortexA53Cflags}",
+		"cortex-a53.a57": "${config.ArmClangCortexA53Cflags}",
+		"cortex-a73":     "${config.ArmClangCortexA53Cflags}",
 		"krait":          "${config.ArmClangKraitCflags}",
 		"kryo":           "${config.ArmClangKryoCflags}",
 		"denver":         "${config.ArmClangCortexA15Cflags}",
diff --git a/cc/config/global.go b/cc/config/global.go
index a29afec..997256e 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -104,7 +104,7 @@
 
 	// Everything in these lists is a crime against abstraction and dependency tracking.
 	// Do not add anything to this list.
-	pctx.PrefixedPathsForOptionalSourceVariable("CommonGlobalIncludes", "-I",
+	pctx.PrefixedExistentPathsForSourcesVariable("CommonGlobalIncludes", "-I",
 		[]string{
 			"system/core/include",
 			"system/media/audio/include",
@@ -118,7 +118,7 @@
 		})
 	// This is used by non-NDK modules to get jni.h. export_include_dirs doesn't help
 	// with this, since there is no associated library.
-	pctx.PrefixedPathsForOptionalSourceVariable("CommonNativehelperInclude", "-I",
+	pctx.PrefixedExistentPathsForSourcesVariable("CommonNativehelperInclude", "-I",
 		[]string{"libnativehelper/include/nativehelper"})
 
 	pctx.SourcePathVariable("ClangDefaultBase", "prebuilts/clang/host")
@@ -153,7 +153,7 @@
 	pctx.StaticVariable("RSLLVMPrebuiltsPath", "${RSClangBase}/${HostPrebuiltTag}/${RSClangVersion}/bin")
 	pctx.StaticVariable("RSIncludePath", "${RSLLVMPrebuiltsPath}/../lib64/clang/${RSReleaseVersion}/include")
 
-	pctx.PrefixedPathsForOptionalSourceVariable("RsGlobalIncludes", "-I",
+	pctx.PrefixedExistentPathsForSourcesVariable("RsGlobalIncludes", "-I",
 		[]string{
 			"external/clang/lib/Headers",
 			"frameworks/rs/script_api/include",
diff --git a/cc/config/x86_darwin_host.go b/cc/config/x86_darwin_host.go
index b6b08fe..65fa1ed 100644
--- a/cc/config/x86_darwin_host.go
+++ b/cc/config/x86_darwin_host.go
@@ -115,7 +115,8 @@
 
 func init() {
 	pctx.VariableFunc("macSdkPath", func(config interface{}) (string, error) {
-		bytes, err := exec.Command("xcode-select", "--print-path").Output()
+		xcodeselect := config.(android.Config).HostSystemTool("xcode-select")
+		bytes, err := exec.Command(xcodeselect, "--print-path").Output()
 		return strings.TrimSpace(string(bytes)), err
 	})
 	pctx.VariableFunc("macSdkRoot", func(config interface{}) (string, error) {
@@ -123,18 +124,16 @@
 	})
 	pctx.StaticVariable("macMinVersion", "10.8")
 	pctx.VariableFunc("MacArPath", func(config interface{}) (string, error) {
-		bytes, err := exec.Command("xcrun", "--find", "ar").Output()
-		return strings.TrimSpace(string(bytes)), err
+		return xcrun(config.(android.Config), "--find", "ar")
 	})
 
 	pctx.VariableFunc("MacStripPath", func(config interface{}) (string, error) {
-		bytes, err := exec.Command("xcrun", "--find", "strip").Output()
-		return strings.TrimSpace(string(bytes)), err
+		return xcrun(config.(android.Config), "--find", "strip")
 	})
 
 	pctx.VariableFunc("MacToolPath", func(config interface{}) (string, error) {
-		bytes, err := exec.Command("xcrun", "--find", "ld").Output()
-		return filepath.Dir(strings.TrimSpace(string(bytes))), err
+		path, err := xcrun(config.(android.Config), "--find", "ld")
+		return filepath.Dir(path), err
 	})
 
 	pctx.StaticVariable("DarwinGccVersion", darwinGccVersion)
@@ -162,13 +161,20 @@
 	pctx.StaticVariable("DarwinX8664ClangLdflags", strings.Join(darwinX8664ClangLdflags, " "))
 }
 
+func xcrun(config android.Config, args ...string) (string, error) {
+	xcrun := config.HostSystemTool("xcrun")
+	bytes, err := exec.Command(xcrun, args...).Output()
+	return strings.TrimSpace(string(bytes)), err
+}
+
 func xcrunSdk(config android.Config, arg string) (string, error) {
+	xcrun := config.HostSystemTool("xcrun")
 	if selected := config.Getenv("MAC_SDK_VERSION"); selected != "" {
 		if !inList(selected, darwinSupportedSdkVersions) {
 			return "", fmt.Errorf("MAC_SDK_VERSION %s isn't supported: %q", selected, darwinSupportedSdkVersions)
 		}
 
-		bytes, err := exec.Command("xcrun", "--sdk", "macosx"+selected, arg).Output()
+		bytes, err := exec.Command(xcrun, "--sdk", "macosx"+selected, arg).Output()
 		if err == nil {
 			return strings.TrimSpace(string(bytes)), err
 		}
@@ -176,7 +182,7 @@
 	}
 
 	for _, sdk := range darwinSupportedSdkVersions {
-		bytes, err := exec.Command("xcrun", "--sdk", "macosx"+sdk, arg).Output()
+		bytes, err := exec.Command(xcrun, "--sdk", "macosx"+sdk, arg).Output()
 		if err == nil {
 			return strings.TrimSpace(string(bytes)), err
 		}
diff --git a/ui/build/Android.bp b/ui/build/Android.bp
index d44c112..7a83684 100644
--- a/ui/build/Android.bp
+++ b/ui/build/Android.bp
@@ -25,6 +25,7 @@
         "context.go",
         "environment.go",
         "exec.go",
+        "java.go",
         "kati.go",
         "make.go",
         "ninja.go",
diff --git a/ui/build/build.go b/ui/build/build.go
index d6059c0..598e342 100644
--- a/ui/build/build.go
+++ b/ui/build/build.go
@@ -15,8 +15,10 @@
 package build
 
 import (
+	"io/ioutil"
 	"os"
 	"path/filepath"
+	"strings"
 	"text/template"
 )
 
@@ -59,6 +61,93 @@
 	BuildAll           = BuildProductConfig | BuildSoong | BuildKati | BuildNinja
 )
 
+func checkCaseSensitivity(ctx Context, config Config) {
+	outDir := config.OutDir()
+	lowerCase := filepath.Join(outDir, "casecheck.txt")
+	upperCase := filepath.Join(outDir, "CaseCheck.txt")
+	lowerData := "a"
+	upperData := "B"
+
+	err := ioutil.WriteFile(lowerCase, []byte(lowerData), 0777)
+	if err != nil {
+		ctx.Fatalln("Failed to check case sensitivity:", err)
+	}
+
+	err = ioutil.WriteFile(upperCase, []byte(upperData), 0777)
+	if err != nil {
+		ctx.Fatalln("Failed to check case sensitivity:", err)
+	}
+
+	res, err := ioutil.ReadFile(lowerCase)
+	if err != nil {
+		ctx.Fatalln("Failed to check case sensitivity:", err)
+	}
+
+	if string(res) != lowerData {
+		ctx.Println("************************************************************")
+		ctx.Println("You are building on a case-insensitive filesystem.")
+		ctx.Println("Please move your source tree to a case-sensitive filesystem.")
+		ctx.Println("************************************************************")
+		ctx.Fatalln("Case-insensitive filesystems not supported")
+	}
+}
+
+// Since products and build variants (unfortunately) shared the same
+// PRODUCT_OUT staging directory, things can get out of sync if different
+// build configurations are built in the same tree. This function will
+// notice when the configuration has changed and call installclean to
+// remove the files necessary to keep things consistent.
+func installcleanIfNecessary(ctx Context, config Config) {
+	if inList("installclean", config.Arguments()) {
+		return
+	}
+
+	configFile := config.DevicePreviousProductConfig()
+	prefix := "PREVIOUS_BUILD_CONFIG := "
+	suffix := "\n"
+	currentProduct := prefix + config.TargetProduct() + "-" + config.TargetBuildVariant() + suffix
+
+	writeConfig := func() {
+		err := ioutil.WriteFile(configFile, []byte(currentProduct), 0777)
+		if err != nil {
+			ctx.Fatalln("Failed to write product config:", err)
+		}
+	}
+
+	prev, err := ioutil.ReadFile(configFile)
+	if err != nil {
+		if os.IsNotExist(err) {
+			writeConfig()
+			return
+		} else {
+			ctx.Fatalln("Failed to read previous product config:", err)
+		}
+	} else if string(prev) == currentProduct {
+		return
+	}
+
+	if disable, _ := config.Environment().Get("DISABLE_AUTO_INSTALLCLEAN"); disable == "true" {
+		ctx.Println("DISABLE_AUTO_INSTALLCLEAN is set; skipping auto-clean. Your tree may be in an inconsistent state.")
+		return
+	}
+
+	ctx.BeginTrace("installclean")
+	defer ctx.EndTrace()
+
+	prevConfig := strings.TrimPrefix(strings.TrimSuffix(string(prev), suffix), prefix)
+	currentConfig := strings.TrimPrefix(strings.TrimSuffix(currentProduct, suffix), prefix)
+
+	ctx.Printf("Build configuration changed: %q -> %q, forcing installclean\n", prevConfig, currentConfig)
+
+	cleanConfig := CopyConfig(ctx, config, "installclean")
+	cleanConfig.SetKatiArgs([]string{"installclean"})
+	cleanConfig.SetNinjaArgs([]string{"installclean"})
+
+	Build(ctx, cleanConfig, BuildKati|BuildNinja)
+
+	writeConfig()
+}
+
 // Build the tree. The 'what' argument can be used to chose which components of
 // the build to run.
 func Build(ctx Context, config Config, what int) {
@@ -73,10 +162,26 @@
 		cmd.Stderr = ctx.Stderr()
 		cmd.RunOrFatal()
 		return
+	} else if inList("clean", config.Arguments()) || inList("clobber", config.Arguments()) {
+		// We can't use os.RemoveAll, since we don't want to remove the
+		// output directory itself, in case it's a symlink. So just do
+		// exactly what make used to do.
+		cmd := Command(ctx, config, "rm -rf $OUT_DIR/*",
+			"/bin/bash", "-c", "rm -rf "+config.OutDir()+"/*")
+		cmd.Stdout = ctx.Stdout()
+		cmd.Stderr = ctx.Stderr()
+		cmd.RunOrFatal()
+		ctx.Println("Entire build directory removed.")
+		return
 	}
 
+	// Start getting java version as early as possible
+	getJavaVersions(ctx, config)
+
 	SetupOutDir(ctx, config)
 
+	checkCaseSensitivity(ctx, config)
+
 	if what&BuildProductConfig != 0 {
 		// Run make for product config
 		runMakeProductConfig(ctx, config)
@@ -88,12 +193,17 @@
 		runSoong(ctx, config)
 	}
 
+	// Check the java versions we read earlier
+	checkJavaVersion(ctx, config)
+
 	if what&BuildKati != 0 {
 		// Run ckati
 		runKati(ctx, config)
 	}
 
 	if what&BuildNinja != 0 {
+		installcleanIfNecessary(ctx, config)
+
 		// Write combined ninja file
 		createCombinedBuildNinjaFile(ctx, config)
 
diff --git a/ui/build/config.go b/ui/build/config.go
index e677d93..51cff50 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -38,9 +38,10 @@
 	dist      bool
 
 	// From the product config
-	katiArgs   []string
-	ninjaArgs  []string
-	katiSuffix string
+	katiArgs     []string
+	ninjaArgs    []string
+	katiSuffix   string
+	targetDevice string
 }
 
 const srcDirFileCheck = "build/soong/root.bp"
@@ -106,6 +107,32 @@
 		log.Fatalln("Error verifying tree state:", err)
 	}
 
+	if srcDir, err := filepath.Abs("."); err == nil {
+		if strings.ContainsRune(srcDir, ' ') {
+			log.Println("You are building in a directory whose absolute path contains a space character:")
+			log.Println()
+			log.Printf("%q\n", srcDir)
+			log.Println()
+			log.Fatalln("Directory names containing spaces are not supported")
+		}
+	}
+
+	if outDir := ret.OutDir(); strings.ContainsRune(outDir, ' ') {
+		log.Println("The absolute path of your output directory ($OUT_DIR) contains a space character:")
+		log.Println()
+		log.Printf("%q\n", outDir)
+		log.Println()
+		log.Fatalln("Directory names containing spaces are not supported")
+	}
+
+	if distDir := ret.DistDir(); strings.ContainsRune(distDir, ' ') {
+		log.Println("The absolute path of your dist directory ($DIST_DIR) contains a space character:")
+		log.Println()
+		log.Printf("%q\n", distDir)
+		log.Println()
+		log.Fatalln("Directory names containing spaces are not supported")
+	}
+
 	for _, arg := range args {
 		arg = strings.TrimSpace(arg)
 		if arg == "--make-mode" {
@@ -140,6 +167,22 @@
 	return Config{ret}
 }
 
+// CopyConfig copies the configuration from an existing configuration, but replaces
+// the Arguments() list with a new set. Useful if you need to run a different build
+// with the same state as an existing build config.
+func CopyConfig(ctx Context, config Config, args ...string) Config {
+	return Config{&configImpl{
+		arguments: args,
+		goma:      config.goma,
+		environ:   config.environ.Copy(),
+
+		parallel:  config.parallel,
+		keepGoing: config.keepGoing,
+		verbose:   config.verbose,
+		dist:      config.dist,
+	}}
+}
+
 // Lunch configures the environment for a specific product similarly to the
 // `lunch` bash function.
 func (c *configImpl) Lunch(ctx Context, product, variant string) {
@@ -245,6 +288,21 @@
 	panic("TARGET_PRODUCT is not defined")
 }
 
+func (c *configImpl) TargetDevice() string {
+	return c.targetDevice
+}
+
+func (c *configImpl) SetTargetDevice(device string) {
+	c.targetDevice = device
+}
+
+func (c *configImpl) TargetBuildVariant() string {
+	if v, ok := c.environ.Get("TARGET_BUILD_VARIANT"); ok {
+		return v
+	}
+	panic("TARGET_BUILD_VARIANT is not defined")
+}
+
 func (c *configImpl) KatiArgs() []string {
 	return c.katiArgs
 }
@@ -311,6 +369,10 @@
 	return filepath.Join(c.SoongOutDir(), "make_vars-"+c.TargetProduct()+".mk")
 }
 
+func (c *configImpl) DevicePreviousProductConfig() string {
+	return filepath.Join(c.OutDir(), "target", "product", c.TargetDevice(), "previous_build_config.mk")
+}
+
 func (c *configImpl) HostPrebuiltTag() string {
 	if runtime.GOOS == "linux" {
 		return "linux-x86"
diff --git a/ui/build/exec.go b/ui/build/exec.go
index 4c45c50..c8c5c9a 100644
--- a/ui/build/exec.go
+++ b/ui/build/exec.go
@@ -84,24 +84,38 @@
 	}
 }
 
+func (c *Cmd) reportError(err error) {
+	if err == nil {
+		return
+	}
+	if e, ok := err.(*exec.ExitError); ok {
+		c.ctx.Fatalf("%s failed with: %v", c.name, e.ProcessState.String())
+	} else {
+		c.ctx.Fatalf("Failed to run %s: %v", c.name, err)
+	}
+}
+
 // RunOrFatal is equivalent to Run, but handles the error with a call to ctx.Fatal
 func (c *Cmd) RunOrFatal() {
-	if err := c.Run(); err != nil {
-		if e, ok := err.(*exec.ExitError); ok {
-			c.ctx.Fatalf("%s failed with: %v", c.name, e.ProcessState.String())
-		} else {
-			c.ctx.Fatalf("Failed to run %s: %v", c.name, err)
-		}
-	}
+	c.reportError(c.Run())
 }
 
 // WaitOrFatal is equivalent to Wait, but handles the error with a call to ctx.Fatal
 func (c *Cmd) WaitOrFatal() {
-	if err := c.Wait(); err != nil {
-		if e, ok := err.(*exec.ExitError); ok {
-			c.ctx.Fatalf("%s failed with: %v", c.name, e.ProcessState.String())
-		} else {
-			c.ctx.Fatalf("Failed to run %s: %v", c.name, err)
-		}
-	}
+	c.reportError(c.Wait())
+}
+
+// OutputOrFatal is equivalent to Output, but handles the error with a call to ctx.Fatal
+func (c *Cmd) OutputOrFatal() []byte {
+	ret, err := c.Output()
+	c.reportError(err)
+	return ret
+}
+
+// CombinedOutputOrFatal is equivalent to CombinedOutput, but handles the error with
+// a call to ctx.Fatal
+func (c *Cmd) CombinedOutputOrFatal() []byte {
+	ret, err := c.CombinedOutput()
+	c.reportError(err)
+	return ret
 }
diff --git a/ui/build/java.go b/ui/build/java.go
new file mode 100644
index 0000000..5a09b1a
--- /dev/null
+++ b/ui/build/java.go
@@ -0,0 +1,156 @@
+// Copyright 2017 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 build
+
+import (
+	"regexp"
+	"runtime"
+	"strings"
+	"sync"
+)
+
+const incompatibleJavacStr = "google"
+
+var javaVersionInfo = struct {
+	once      sync.Once
+	startOnce sync.Once
+
+	java_version_output  string
+	javac_version_output string
+}{}
+
+func getJavaVersions(ctx Context, config Config) {
+	javaVersionInfo.startOnce.Do(func() {
+		go func() {
+			if ctx.Tracer != nil {
+				thread := ctx.Tracer.NewThread("java_version")
+				ctx.Tracer.Begin("get version", thread)
+				defer ctx.Tracer.End(thread)
+			}
+
+			getJavaVersionsImpl(ctx, config)
+		}()
+	})
+}
+
+func getJavaVersionsImpl(ctx Context, config Config) {
+	javaVersionInfo.once.Do(func() {
+		cmd := Command(ctx, config, "java", "java", "-version")
+		cmd.Environment.Unset("_JAVA_OPTIONS")
+		javaVersionInfo.java_version_output = string(cmd.CombinedOutputOrFatal())
+
+		cmd = Command(ctx, config, "javac", "javac", "-version")
+		cmd.Environment.Unset("_JAVA_OPTIONS")
+		javaVersionInfo.javac_version_output = string(cmd.CombinedOutputOrFatal())
+	})
+}
+
+func checkJavaVersion(ctx Context, config Config) {
+	ctx.BeginTrace("java_version_check")
+	defer ctx.EndTrace()
+
+	getJavaVersionsImpl(ctx, config)
+
+	var required_java_version string
+	var java_version_regexp *regexp.Regexp
+	var javac_version_regexp *regexp.Regexp
+	if legacy, _ := config.Environment().Get("LEGACY_USE_JAVA7"); legacy != "" {
+		required_java_version = "1.7"
+		java_version_regexp = regexp.MustCompile(`^java .*[ "]1\.7[\. "$]`)
+		javac_version_regexp = regexp.MustCompile(`[ "]1\.7[\. "$]`)
+	} else {
+		required_java_version = "1.8"
+		java_version_regexp = regexp.MustCompile(`[ "]1\.8[\. "$]`)
+		javac_version_regexp = java_version_regexp
+	}
+
+	java_version := javaVersionInfo.java_version_output
+	javac_version := javaVersionInfo.javac_version_output
+
+	found := false
+	for _, l := range strings.Split(java_version, "\n") {
+		if java_version_regexp.MatchString(l) {
+			java_version = l
+			found = true
+			break
+		}
+	}
+	if !found {
+		ctx.Println("***************************************************************")
+		ctx.Println("You are attempting to build with the incorrect version of java.")
+		ctx.Println()
+		ctx.Println("Your version is:", java_version)
+		ctx.Println("The required version is:", required_java_version+".x")
+		ctx.Println()
+		ctx.Println("Please follow the machine setup instructions at:")
+		ctx.Println("    https://source.android.com/source/initializing.html")
+		ctx.Println("***************************************************************")
+		ctx.Fatalln("stop")
+	}
+
+	if runtime.GOOS == "linux" {
+		if !strings.Contains(java_version, "openjdk") {
+			ctx.Println("*******************************************************")
+			ctx.Println("You are attempting to build with an unsupported JDK.")
+			ctx.Println()
+			ctx.Println("Only an OpenJDK based JDK is supported.")
+			ctx.Println()
+			ctx.Println("Please follow the machine setup instructions at:")
+			ctx.Println("    https://source.android.com/source/initializing.html")
+			ctx.Println("*******************************************************")
+			ctx.Fatalln("stop")
+		}
+	} else { // darwin
+		if strings.Contains(java_version, "openjdk") {
+			ctx.Println("*******************************************************")
+			ctx.Println("You are attempting to build with an unsupported JDK.")
+			ctx.Println()
+			ctx.Println("You use OpenJDK, but only Sun/Oracle JDK is supported.")
+			ctx.Println()
+			ctx.Println("Please follow the machine setup instructions at:")
+			ctx.Println("    https://source.android.com/source/initializing.html")
+			ctx.Println("*******************************************************")
+			ctx.Fatalln("stop")
+		}
+	}
+
+	incompatible_javac := strings.Contains(javac_version, incompatibleJavacStr)
+
+	found = false
+	for _, l := range strings.Split(javac_version, "\n") {
+		if javac_version_regexp.MatchString(l) {
+			javac_version = l
+			found = true
+			break
+		}
+	}
+	if !found || incompatible_javac {
+		ctx.Println("****************************************************************")
+		ctx.Println("You are attempting to build with the incorrect version of javac.")
+		ctx.Println()
+		ctx.Println("Your version is:", javac_version)
+		if incompatible_javac {
+			ctx.Println("The '" + incompatibleJavacStr + "' version is not supported for Android platform builds.")
+			ctx.Println("Use a publically available JDK and make sure you have run envsetup.sh / lunch.")
+		} else {
+			ctx.Println("The required version is:", required_java_version)
+		}
+		ctx.Println()
+		ctx.Println("Please follow the machine setup instructions at:")
+		ctx.Println("    https://source.android.com/source/initializing.html")
+		ctx.Println("****************************************************************")
+		ctx.Fatalln("stop")
+	}
+}
diff --git a/ui/build/make.go b/ui/build/make.go
index 32dc17b..2b39926 100644
--- a/ui/build/make.go
+++ b/ui/build/make.go
@@ -83,6 +83,7 @@
 		// So that we can use the correct TARGET_PRODUCT if it's been
 		// modified by PRODUCT-* arguments
 		"TARGET_PRODUCT",
+		"TARGET_BUILD_VARIANT",
 
 		// compiler wrappers set up by make
 		"CC_WRAPPER",
@@ -129,6 +130,9 @@
 		// Used to execute Kati and Ninja
 		"NINJA_GOALS",
 		"KATI_GOALS",
+
+		// To find target/product/<DEVICE>
+		"TARGET_DEVICE",
 	}, exportEnvVars...), bannerVars...)
 
 	make_vars, err := DumpMakeVars(ctx, config, config.Arguments(), []string{
@@ -159,4 +163,5 @@
 
 	config.SetKatiArgs(strings.Fields(make_vars["KATI_GOALS"]))
 	config.SetNinjaArgs(strings.Fields(make_vars["NINJA_GOALS"]))
+	config.SetTargetDevice(make_vars["TARGET_DEVICE"])
 }
diff --git a/ui/tracer/tracer.go b/ui/tracer/tracer.go
index b372885..f19ac18 100644
--- a/ui/tracer/tracer.go
+++ b/ui/tracer/tracer.go
@@ -46,6 +46,8 @@
 	Complete(name string, thread Thread, begin, end uint64)
 
 	ImportNinjaLog(thread Thread, filename string, startOffset time.Time)
+
+	NewThread(name string) Thread
 }
 
 type tracerImpl struct {