Merge "Add OWNERS in build/soong"
diff --git a/Android.bp b/Android.bp
index 9f508d5..c571e74 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1,17 +1,6 @@
-//
-// WARNING: Modifying this file will NOT automatically regenerate build.ninja.in!
-//
-// Before modifying this file make sure minibp is up to date:
-//    1) "repo sync build/soong" to make sure you have the latest build.ninja.in
-//    2) build minibp, which builds automicatically through the normal build steps. For example:
-//
-// After modifying this file regenerate build.ninja.in and build your changes:
-//    1) In your build directory, execute "../bootstrap.bash -r" to regenerate build.ninja.in
-//    2) Build again
-//
-
 subdirs = [
     "androidmk",
+    "bpfix",
     "cmd/*",
     "third_party/zip",
     "ui/*",
@@ -168,6 +157,7 @@
         "blueprint-pathtools",
         "soong",
         "soong-android",
+        "soong-shared",
     ],
     srcs: [
         "genrule/filegroup.go",
@@ -233,6 +223,14 @@
     pluginFor: ["soong_build"],
 }
 
+bootstrap_go_package {
+    name: "soong-shared",
+    pkgPath: "android/soong/shared",
+    srcs: [
+        "shared/paths.go",
+    ],
+}
+
 //
 // Defaults to enable various configurations of host bionic
 //
diff --git a/android/config.go b/android/config.go
index 869a5c4..c3beb08 100644
--- a/android/config.go
+++ b/android/config.go
@@ -52,6 +52,10 @@
 	*config
 }
 
+func (c Config) BuildDir() string {
+	return c.buildDir
+}
+
 // A DeviceConfig object represents the configuration for a particular device being built.  For
 // now there will only be one of these, but in the future there may be multiple devices being
 // built
diff --git a/android/paths.go b/android/paths.go
index 26b72d1..aa06127 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -672,15 +672,17 @@
 func PathForModuleInstall(ctx ModuleContext, pathComponents ...string) OutputPath {
 	var outPaths []string
 	if ctx.Device() {
-		partition := "system"
+		var partition string
 		if ctx.Vendor() {
 			partition = ctx.DeviceConfig().VendorPath()
+		} else if ctx.InstallInData() {
+			partition = "data"
+		} else {
+			partition = "system"
 		}
 
 		if ctx.InstallInSanitizerDir() {
 			partition = "data/asan/" + partition
-		} else if ctx.InstallInData() {
-			partition = "data"
 		}
 		outPaths = []string{"target", "product", ctx.AConfig().DeviceName(), partition}
 	} else {
diff --git a/androidmk/Android.bp b/androidmk/Android.bp
index 67baf0a..442452f 100644
--- a/androidmk/Android.bp
+++ b/androidmk/Android.bp
@@ -29,6 +29,7 @@
     deps: [
         "androidmk-parser",
         "blueprint-parser",
+        "bpfix-lib",
     ],
 }
 
diff --git a/androidmk/cmd/androidmk/androidmk.go b/androidmk/cmd/androidmk/androidmk.go
index 1d94b65..d26643a 100644
--- a/androidmk/cmd/androidmk/androidmk.go
+++ b/androidmk/cmd/androidmk/androidmk.go
@@ -8,6 +8,8 @@
 	"strings"
 	"text/scanner"
 
+	"android/soong/bpfix/bpfix"
+
 	mkparser "android/soong/androidmk/parser"
 
 	bpparser "github.com/google/blueprint/parser"
@@ -176,10 +178,18 @@
 		}
 	}
 
-	out, err := bpparser.Print(&bpparser.File{
+	tree := &bpparser.File{
 		Defs:     file.defs,
 		Comments: file.comments,
-	})
+	}
+
+	// check for common supported but undesirable structures and clean them up
+	fixed, err := bpfix.FixTree(tree, bpfix.NewFixRequest().AddAll())
+	if err != nil {
+		return "", []error{err}
+	}
+
+	out, err := bpparser.Print(fixed)
 	if err != nil {
 		return "", []error{err}
 	}
diff --git a/bpfix/Android.bp b/bpfix/Android.bp
new file mode 100644
index 0000000..90a453d
--- /dev/null
+++ b/bpfix/Android.bp
@@ -0,0 +1,43 @@
+// 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.
+
+//
+// androidmk Android.mk to Blueprints translator
+//
+
+blueprint_go_binary {
+    name: "bpfix",
+    srcs: [
+        "cmd/bpfix.go",
+    ],
+    deps: [
+        "bpfix-lib",
+    ],
+}
+
+bootstrap_go_package {
+    name: "bpfix-lib",
+    pkgPath: "android/soong/bpfix/bpfix",
+    srcs: [
+        "bpfix/bpfix.go",
+    ],
+    testSrcs: [
+      "bpfix/bpfix_test.go",
+    ],
+    deps: [
+        "blueprint-parser",
+    ],
+}
+
+
diff --git a/bpfix/bpfix/bpfix.go b/bpfix/bpfix/bpfix.go
new file mode 100644
index 0000000..1249494
--- /dev/null
+++ b/bpfix/bpfix/bpfix.go
@@ -0,0 +1,185 @@
+// 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.
+
+// This file implements the logic of bpfix and also provides a programmatic interface
+
+package bpfix
+
+import (
+	"bytes"
+	"fmt"
+	"github.com/google/blueprint/parser"
+)
+
+// A FixRequest specifies the details of which fixes to apply to an individual file
+// A FixRequest doesn't specify whether to do a dry run or where to write the results; that's in cmd/bpfix.go
+type FixRequest struct {
+	simplifyKnownRedundantVariables bool
+	removeEmptyLists                bool
+}
+
+func NewFixRequest() FixRequest {
+	return FixRequest{}
+}
+
+func (r FixRequest) AddAll() (result FixRequest) {
+	result = r
+	result.simplifyKnownRedundantVariables = true
+	result.removeEmptyLists = true
+	return result
+}
+
+// FixTree repeatedly applies the fixes listed in the given FixRequest to the given File
+// until there is no fix that affects the tree
+func FixTree(tree *parser.File, config FixRequest) (fixed *parser.File, err error) {
+	prevIdentifier, err := fingerprint(tree)
+	if err != nil {
+		return nil, err
+	}
+
+	fixed = tree
+	maxNumIterations := 20
+	i := 0
+	for {
+		fixed, err = fixTreeOnce(fixed, config)
+		newIdentifier, err := fingerprint(tree)
+		if err != nil {
+			return nil, err
+		}
+		if bytes.Equal(newIdentifier, prevIdentifier) {
+			break
+		}
+		prevIdentifier = newIdentifier
+		// any errors from a previous iteration generally get thrown away and overwritten by errors on the next iteration
+
+		// detect infinite loop
+		i++
+		if i >= maxNumIterations {
+			return nil, fmt.Errorf("Applied fixes %s times and yet the tree continued to change. Is there an infinite loop?", i)
+			break
+		}
+	}
+	return fixed, err
+}
+
+// returns a unique identifier for the given tree that can be used to determine whether the tree changed
+func fingerprint(tree *parser.File) (fingerprint []byte, err error) {
+	bytes, err := parser.Print(tree)
+	if err != nil {
+		return nil, err
+	}
+	return bytes, nil
+}
+
+func fixTreeOnce(tree *parser.File, config FixRequest) (fixed *parser.File, err error) {
+	if config.simplifyKnownRedundantVariables {
+		tree, err = simplifyKnownPropertiesDuplicatingEachOther(tree)
+		if err != nil {
+			return nil, err
+		}
+	}
+	if config.removeEmptyLists {
+		tree, err = removePropertiesHavingTheirDefaultValues(tree)
+		if err != nil {
+			return nil, err
+		}
+	}
+	return tree, err
+}
+
+func simplifyKnownPropertiesDuplicatingEachOther(tree *parser.File) (fixed *parser.File, err error) {
+	// remove from local_include_dirs anything in export_include_dirs
+	fixed, err = removeMatchingModuleListProperties(tree, "export_include_dirs", "local_include_dirs")
+	return fixed, err
+}
+
+// removes from <items> every item present in <removals>
+func filterExpressionList(items *parser.List, removals *parser.List) {
+	writeIndex := 0
+	for _, item := range items.Values {
+		included := true
+		for _, removal := range removals.Values {
+			equal, err := parser.ExpressionsAreSame(item, removal)
+			if err != nil {
+				continue
+			}
+			if equal {
+				included = false
+				break
+			}
+		}
+		if included {
+			items.Values[writeIndex] = item
+			writeIndex++
+		}
+	}
+	items.Values = items.Values[:writeIndex]
+}
+
+// Remove each modules[i].Properties[<legacyName>][j] that matches a modules[i].Properties[<canonicalName>][k]
+func removeMatchingModuleListProperties(tree *parser.File, canonicalName string, legacyName string) (fixed *parser.File, err error) {
+	for _, def := range tree.Defs {
+		mod, ok := def.(*parser.Module)
+		if !ok {
+			continue
+		}
+		legacy, ok := mod.GetProperty(legacyName)
+		if !ok {
+			continue
+		}
+		legacyList, ok := legacy.Value.(*parser.List)
+		if !ok {
+			continue
+		}
+		canonical, ok := mod.GetProperty(canonicalName)
+		if !ok {
+			continue
+		}
+		canonicalList, ok := canonical.Value.(*parser.List)
+		if !ok {
+			continue
+		}
+		filterExpressionList(legacyList, canonicalList)
+	}
+	return tree, nil
+}
+
+func removePropertiesHavingTheirDefaultValues(tree *parser.File) (fixed *parser.File, err error) {
+	for _, def := range tree.Defs {
+		mod, ok := def.(*parser.Module)
+		if !ok {
+			continue
+		}
+		writeIndex := 0
+		for _, prop := range mod.Properties {
+			val := prop.Value
+			keep := true
+			switch val := val.(type) {
+			case *parser.List:
+				if len(val.Values) == 0 {
+					keep = false
+				}
+				break
+			default:
+				keep = true
+			}
+			if keep {
+				mod.Properties[writeIndex] = prop
+				writeIndex++
+			}
+		}
+		mod.Properties = mod.Properties[:writeIndex]
+	}
+	return tree, nil
+}
diff --git a/bpfix/bpfix/bpfix_test.go b/bpfix/bpfix/bpfix_test.go
new file mode 100644
index 0000000..06ae139
--- /dev/null
+++ b/bpfix/bpfix/bpfix_test.go
@@ -0,0 +1,114 @@
+// 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.
+
+// This file implements the logic of bpfix and also provides a programmatic interface
+
+package bpfix
+
+import (
+	"fmt"
+	"strings"
+	"testing"
+
+	"github.com/google/blueprint/parser"
+	"reflect"
+)
+
+// TODO(jeffrygaston) remove this when position is removed from ParseNode (in b/38325146) and we can directly do reflect.DeepEqual
+func printListOfStrings(items []string) (text string) {
+	if len(items) == 0 {
+		return "[]"
+	}
+	return fmt.Sprintf("[\"%s\"]", strings.Join(items, "\", \""))
+
+}
+
+func buildTree(local_include_dirs []string, export_include_dirs []string) (file *parser.File, errs []error) {
+	// TODO(jeffrygaston) use the builder class when b/38325146 is done
+	input := fmt.Sprintf(`cc_library_shared {
+	    name: "iAmAModule",
+	    local_include_dirs: %s,
+	    export_include_dirs: %s,
+	}
+	`,
+		printListOfStrings(local_include_dirs), printListOfStrings(export_include_dirs))
+	tree, errs := parser.Parse("", strings.NewReader(input), parser.NewScope(nil))
+	if len(errs) > 0 {
+		errs = append([]error{fmt.Errorf("failed to parse:\n%s", input)}, errs...)
+	}
+	return tree, errs
+}
+
+func implFilterListTest(t *testing.T, local_include_dirs []string, export_include_dirs []string, expectedResult []string) {
+	// build tree
+	tree, errs := buildTree(local_include_dirs, export_include_dirs)
+	if len(errs) > 0 {
+		t.Error("failed to build tree")
+		for _, err := range errs {
+			t.Error(err)
+		}
+		t.Fatalf("%d parse errors", len(errs))
+	}
+
+	// apply simplifications
+	tree, err := simplifyKnownPropertiesDuplicatingEachOther(tree)
+	if len(errs) > 0 {
+		t.Fatal(err)
+	}
+
+	// lookup legacy property
+	mod := tree.Defs[0].(*parser.Module)
+	_, found := mod.GetProperty("local_include_dirs")
+	if !found {
+		t.Fatalf("failed to include key local_include_dirs in parse tree")
+	}
+
+	// check that the value for the legacy property was updated to the correct value
+	errorHeader := fmt.Sprintf("\nFailed to correctly simplify key 'local_include_dirs' in the presence of 'export_include_dirs.'\n"+
+		"original local_include_dirs: %q\n"+
+		"original export_include_dirs: %q\n"+
+		"expected result: %q\n"+
+		"actual result: ",
+		local_include_dirs, export_include_dirs, expectedResult)
+	result, ok := mod.GetProperty("local_include_dirs")
+	if !ok {
+		t.Fatal(errorHeader + "property not found")
+	}
+
+	listResult, ok := result.Value.(*parser.List)
+	if !ok {
+		t.Fatalf("%sproperty is not a list: %v", errorHeader, listResult)
+	}
+
+	actualExpressions := listResult.Values
+	actualValues := make([]string, 0)
+	for _, expr := range actualExpressions {
+		str := expr.(*parser.String)
+		actualValues = append(actualValues, str.Value)
+	}
+
+	if !reflect.DeepEqual(actualValues, expectedResult) {
+		t.Fatalf("%s%q\nlists are different", errorHeader, actualValues)
+	}
+}
+
+func TestSimplifyKnownVariablesDuplicatingEachOther(t *testing.T) {
+	// TODO use []Expression{} once buildTree above can support it (which is after b/38325146 is done)
+	implFilterListTest(t, []string{"include"}, []string{"include"}, []string{})
+	implFilterListTest(t, []string{"include1"}, []string{"include2"}, []string{"include1"})
+	implFilterListTest(t, []string{"include1", "include2", "include3", "include4"}, []string{"include2"},
+		[]string{"include1", "include3", "include4"})
+	implFilterListTest(t, []string{}, []string{"include"}, []string{})
+	implFilterListTest(t, []string{}, []string{}, []string{})
+}
diff --git a/bpfix/cmd/bpfix.go b/bpfix/cmd/bpfix.go
new file mode 100644
index 0000000..f51c6f7
--- /dev/null
+++ b/bpfix/cmd/bpfix.go
@@ -0,0 +1,188 @@
+// 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.
+
+// This file provides a command-line interface to bpfix
+
+// TODO(jeffrygaston) should this file be consolidated with bpfmt.go?
+
+package main
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+
+	"github.com/google/blueprint/parser"
+
+	"android/soong/bpfix/bpfix"
+)
+
+var (
+	// main operation modes
+	list   = flag.Bool("l", false, "list files whose formatting differs from bpfmt's")
+	write  = flag.Bool("w", false, "write result to (source) file instead of stdout")
+	doDiff = flag.Bool("d", false, "display diffs instead of rewriting files")
+)
+
+var (
+	exitCode = 0
+)
+
+func report(err error) {
+	fmt.Fprintln(os.Stderr, err)
+	exitCode = 2
+}
+
+func openAndProcess(filename string, out io.Writer, fixRequest bpfix.FixRequest) error {
+	f, err := os.Open(filename)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	return processFile(filename, f, out, fixRequest)
+}
+
+// If in == nil, the source is the contents of the file with the given filename.
+func processFile(filename string, in io.Reader, out io.Writer, fixRequest bpfix.FixRequest) error {
+	// load the input file
+	src, err := ioutil.ReadAll(in)
+	if err != nil {
+		return err
+	}
+	r := bytes.NewBuffer(src)
+	file, errs := parser.Parse(filename, r, parser.NewScope(nil))
+	if len(errs) > 0 {
+		for _, err := range errs {
+			fmt.Fprintln(os.Stderr, err)
+		}
+		return fmt.Errorf("%d parsing errors", len(errs))
+	}
+
+	// compute and apply any requested fixes
+	fixed, err := bpfix.FixTree(file, fixRequest)
+	if err != nil {
+		return err
+	}
+
+	// output the results
+	res, err := parser.Print(fixed)
+	if err != nil {
+		return err
+	}
+	if !bytes.Equal(src, res) {
+		// contents have changed
+		if *list {
+			fmt.Fprintln(out, filename)
+		}
+		if *write {
+			err = ioutil.WriteFile(filename, res, 0644)
+			if err != nil {
+				return err
+			}
+		}
+		if *doDiff {
+			data, err := diff(src, res)
+			if err != nil {
+				return fmt.Errorf("computing diff: %s", err)
+			}
+			fmt.Printf("diff %s bpfix/%s\n", filename, filename)
+			out.Write(data)
+		}
+	}
+	if !*list && !*write && !*doDiff {
+		_, err = out.Write(res)
+	}
+	return err
+}
+
+func makeFileVisitor(fixRequest bpfix.FixRequest) func(string, os.FileInfo, error) error {
+	return func(path string, f os.FileInfo, err error) error {
+		if err == nil && (f.Name() == "Blueprints" || f.Name() == "Android.bp") {
+			err = openAndProcess(path, os.Stdout, fixRequest)
+		}
+		if err != nil {
+			report(err)
+		}
+		return nil
+	}
+}
+
+func walkDir(path string, fixRequest bpfix.FixRequest) {
+	filepath.Walk(path, makeFileVisitor(fixRequest))
+}
+
+func main() {
+	flag.Parse()
+
+	fixRequest := bpfix.NewFixRequest().AddAll()
+
+	if flag.NArg() == 0 {
+		if *write {
+			fmt.Fprintln(os.Stderr, "error: cannot use -w with standard input")
+			exitCode = 2
+			return
+		}
+		if err := processFile("<standard input>", os.Stdin, os.Stdout, fixRequest); err != nil {
+			report(err)
+		}
+		return
+	}
+
+	for i := 0; i < flag.NArg(); i++ {
+		path := flag.Arg(i)
+		switch dir, err := os.Stat(path); {
+		case err != nil:
+			report(err)
+		case dir.IsDir():
+			walkDir(path, fixRequest)
+		default:
+			if err := openAndProcess(path, os.Stdout, fixRequest); err != nil {
+				report(err)
+			}
+		}
+	}
+}
+
+func diff(b1, b2 []byte) (data []byte, err error) {
+	f1, err := ioutil.TempFile("", "bpfix")
+	if err != nil {
+		return
+	}
+	defer os.Remove(f1.Name())
+	defer f1.Close()
+
+	f2, err := ioutil.TempFile("", "bpfix")
+	if err != nil {
+		return
+	}
+	defer os.Remove(f2.Name())
+	defer f2.Close()
+
+	f1.Write(b1)
+	f2.Write(b2)
+
+	data, err = exec.Command("diff", "-u", f1.Name(), f2.Name()).CombinedOutput()
+	if len(data) > 0 {
+		// diff exits with a non-zero status when the files don't match.
+		// Ignore that failure as long as we get output.
+		err = nil
+	}
+	return
+
+}
diff --git a/build_test.bash b/build_test.bash
index f833366..ab841cb 100755
--- a/build_test.bash
+++ b/build_test.bash
@@ -30,5 +30,11 @@
 export TOP=$(cd $(dirname ${BASH_SOURCE[0]})/../..; PWD= /bin/pwd)
 source "${TOP}/build/soong/cmd/microfactory/microfactory.bash"
 
+case $(uname) in
+  Linux)
+    export LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so
+    ;;
+esac
+
 build_go multiproduct_kati android/soong/cmd/multiproduct_kati
 exec "$(getoutdir)/multiproduct_kati" "$@"
diff --git a/cc/builder.go b/cc/builder.go
index 51c4ce9..61c0572 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -206,26 +206,27 @@
 }
 
 type builderFlags struct {
-	globalFlags string
-	arFlags     string
-	asFlags     string
-	cFlags      string
-	conlyFlags  string
-	cppFlags    string
-	ldFlags     string
-	libFlags    string
-	yaccFlags   string
-	protoFlags  string
-	tidyFlags   string
-	sAbiFlags   string
-	yasmFlags   string
-	aidlFlags   string
-	rsFlags     string
-	toolchain   config.Toolchain
-	clang       bool
-	tidy        bool
-	coverage    bool
-	sAbiDump    bool
+	globalFlags   string
+	arFlags       string
+	asFlags       string
+	cFlags        string
+	toolingCFlags string // Seperate set of Cflags for clang LibTooling tools
+	conlyFlags    string
+	cppFlags      string
+	ldFlags       string
+	libFlags      string
+	yaccFlags     string
+	protoFlags    string
+	tidyFlags     string
+	sAbiFlags     string
+	yasmFlags     string
+	aidlFlags     string
+	rsFlags       string
+	toolchain     config.Toolchain
+	clang         bool
+	tidy          bool
+	coverage      bool
+	sAbiDump      bool
 
 	systemIncludeFlags string
 
@@ -275,25 +276,40 @@
 		coverageFiles = make(android.Paths, 0, len(srcFiles))
 	}
 
-	cflags := strings.Join([]string{
+	commonFlags := strings.Join([]string{
 		flags.globalFlags,
 		flags.systemIncludeFlags,
+	}, " ")
+
+	toolingCflags := strings.Join([]string{
+		commonFlags,
+		flags.toolingCFlags,
+		flags.conlyFlags,
+	}, " ")
+
+	cflags := strings.Join([]string{
+		commonFlags,
 		flags.cFlags,
 		flags.conlyFlags,
 	}, " ")
 
+	toolingCppflags := strings.Join([]string{
+		commonFlags,
+		flags.toolingCFlags,
+		flags.cppFlags,
+	}, " ")
+
 	cppflags := strings.Join([]string{
-		flags.globalFlags,
-		flags.systemIncludeFlags,
+		commonFlags,
 		flags.cFlags,
 		flags.cppFlags,
 	}, " ")
 
 	asflags := strings.Join([]string{
-		flags.globalFlags,
-		flags.systemIncludeFlags,
+		commonFlags,
 		flags.asFlags,
 	}, " ")
+
 	var sAbiDumpFiles android.Paths
 	if flags.sAbiDump && flags.clang {
 		sAbiDumpFiles = make(android.Paths, 0, len(srcFiles))
@@ -301,7 +317,9 @@
 
 	if flags.clang {
 		cflags += " ${config.NoOverrideClangGlobalCflags}"
+		toolingCflags += " ${config.NoOverrideClangGlobalCflags}"
 		cppflags += " ${config.NoOverrideClangGlobalCflags}"
+		toolingCppflags += " ${config.NoOverrideClangGlobalCflags}"
 	} else {
 		cflags += " ${config.NoOverrideGlobalCflags}"
 		cppflags += " ${config.NoOverrideGlobalCflags}"
@@ -327,6 +345,7 @@
 		}
 
 		var moduleCflags string
+		var moduleToolingCflags string
 		var ccCmd string
 		tidy := flags.tidy && flags.clang
 		coverage := flags.coverage
@@ -342,9 +361,11 @@
 		case ".c":
 			ccCmd = "gcc"
 			moduleCflags = cflags
+			moduleToolingCflags = toolingCflags
 		case ".cpp", ".cc", ".mm":
 			ccCmd = "g++"
 			moduleCflags = cppflags
+			moduleToolingCflags = toolingCppflags
 		default:
 			ctx.ModuleErrorf("File %s has unknown extension", srcFile)
 			continue
@@ -402,7 +423,7 @@
 				// support exporting dependencies.
 				Implicit: objFile,
 				Args: map[string]string{
-					"cFlags":    moduleCflags,
+					"cFlags":    moduleToolingCflags,
 					"tidyFlags": flags.tidyFlags,
 				},
 			})
@@ -419,7 +440,7 @@
 				Input:       srcFile,
 				Implicit:    objFile,
 				Args: map[string]string{
-					"cFlags":     moduleCflags,
+					"cFlags":     moduleToolingCflags,
 					"exportDirs": flags.sAbiFlags,
 				},
 			})
diff --git a/cc/cc.go b/cc/cc.go
index 43825ca..867b196 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -97,21 +97,23 @@
 }
 
 type Flags struct {
-	GlobalFlags []string // Flags that apply to C, C++, and assembly source files
-	ArFlags     []string // Flags that apply to ar
-	AsFlags     []string // Flags that apply to assembly source files
-	CFlags      []string // Flags that apply to C and C++ source files
-	ConlyFlags  []string // Flags that apply to C source files
-	CppFlags    []string // Flags that apply to C++ source files
-	YaccFlags   []string // Flags that apply to Yacc source files
-	protoFlags  []string // Flags that apply to proto source files
-	aidlFlags   []string // Flags that apply to aidl source files
-	rsFlags     []string // Flags that apply to renderscript source files
-	LdFlags     []string // Flags that apply to linker command lines
-	libFlags    []string // Flags to add libraries early to the link order
-	TidyFlags   []string // Flags that apply to clang-tidy
-	SAbiFlags   []string // Flags that apply to header-abi-dumper
-	YasmFlags   []string // Flags that apply to yasm assembly source files
+	GlobalFlags     []string // Flags that apply to C, C++, and assembly source files
+	ArFlags         []string // Flags that apply to ar
+	AsFlags         []string // Flags that apply to assembly source files
+	CFlags          []string // Flags that apply to C and C++ source files
+	ToolingCFlags   []string // Flags that apply to C and C++ source files parsed by clang LibTooling tools
+	ConlyFlags      []string // Flags that apply to C source files
+	CppFlags        []string // Flags that apply to C++ source files
+	ToolingCppFlags []string // Flags that apply to C++ source files parsed by clang LibTooling tools
+	YaccFlags       []string // Flags that apply to Yacc source files
+	protoFlags      []string // Flags that apply to proto source files
+	aidlFlags       []string // Flags that apply to aidl source files
+	rsFlags         []string // Flags that apply to renderscript source files
+	LdFlags         []string // Flags that apply to linker command lines
+	libFlags        []string // Flags to add libraries early to the link order
+	TidyFlags       []string // Flags that apply to clang-tidy
+	SAbiFlags       []string // Flags that apply to header-abi-dumper
+	YasmFlags       []string // Flags that apply to yasm assembly source files
 
 	// Global include flags that apply to C, C++, and assembly source files
 	// These must be after any module include flags, which will be in GlobalFlags.
@@ -507,9 +509,6 @@
 	if c.coverage != nil {
 		flags = c.coverage.flags(ctx, flags)
 	}
-	if c.sabi != nil {
-		flags = c.sabi.flags(ctx, flags)
-	}
 	for _, feature := range c.features {
 		flags = feature.flags(ctx, flags)
 	}
@@ -527,7 +526,10 @@
 	}
 	flags.GlobalFlags = append(flags.GlobalFlags, deps.Flags...)
 	c.flags = flags
-
+	// We need access to all the flags seen by a source file.
+	if c.sabi != nil {
+		flags = c.sabi.flags(ctx, flags)
+	}
 	// Optimization to reduce size of build.ninja
 	// Replace the long list of flags for each file with a module-local variable
 	ctx.Variable(pctx, "cflags", strings.Join(flags.CFlags, " "))
diff --git a/cc/config/clang.go b/cc/config/clang.go
index 3ff6b1b..977afe1 100644
--- a/cc/config/clang.go
+++ b/cc/config/clang.go
@@ -65,6 +65,11 @@
 	"-mbionic",
 })
 
+var ClangLibToolingUnknownCflags = []string{
+	"-flto",
+	"-fsanitize*",
+}
+
 func init() {
 	pctx.StaticVariable("ClangExtraCflags", strings.Join([]string{
 		"-D__compiler_offsetof=__builtin_offsetof",
diff --git a/cc/linker.go b/cc/linker.go
index 5a3b478..2c39132 100644
--- a/cc/linker.go
+++ b/cc/linker.go
@@ -87,6 +87,14 @@
 	// group static libraries.  This can resolve missing symbols issues with interdependencies
 	// between static libraries, but it is generally better to order them correctly instead.
 	Group_static_libs *bool `android:"arch_variant"`
+
+	Target struct {
+		Vendor struct {
+			// list of shared libs that should not be used to build
+			// the vendor variant of the C/C++ module.
+			Exclude_shared_libs []string
+		}
+	}
 }
 
 func NewBaseLinker() *baseLinker {
@@ -123,6 +131,10 @@
 	deps.StaticLibs = append(deps.StaticLibs, linker.Properties.Static_libs...)
 	deps.SharedLibs = append(deps.SharedLibs, linker.Properties.Shared_libs...)
 
+	if ctx.vndk() {
+		deps.SharedLibs = removeListFromList(deps.SharedLibs, linker.Properties.Target.Vendor.Exclude_shared_libs)
+	}
+
 	deps.ReexportHeaderLibHeaders = append(deps.ReexportHeaderLibHeaders, linker.Properties.Export_header_lib_headers...)
 	deps.ReexportStaticLibHeaders = append(deps.ReexportStaticLibHeaders, linker.Properties.Export_static_lib_headers...)
 	deps.ReexportSharedLibHeaders = append(deps.ReexportSharedLibHeaders, linker.Properties.Export_shared_lib_headers...)
diff --git a/cc/makevars.go b/cc/makevars.go
index a1e97a5..2a5c813 100644
--- a/cc/makevars.go
+++ b/cc/makevars.go
@@ -59,12 +59,12 @@
 		ctx.Strict("BOARD_VNDK_VERSION", "")
 	}
 
-	ctx.Strict("ADDRESS_SANITIZER_CONFIG_EXTRA_CFLAGS", asanCflags)
-	ctx.Strict("ADDRESS_SANITIZER_CONFIG_EXTRA_LDFLAGS", asanLdflags)
-	ctx.Strict("ADDRESS_SANITIZER_CONFIG_EXTRA_STATIC_LIBRARIES", asanLibs)
+	ctx.Strict("ADDRESS_SANITIZER_CONFIG_EXTRA_CFLAGS", strings.Join(asanCflags, " "))
+	ctx.Strict("ADDRESS_SANITIZER_CONFIG_EXTRA_LDFLAGS", strings.Join(asanLdflags, " "))
+	ctx.Strict("ADDRESS_SANITIZER_CONFIG_EXTRA_STATIC_LIBRARIES", strings.Join(asanLibs, " "))
 
-	ctx.Strict("CFI_EXTRA_CFLAGS", cfiCflags)
-	ctx.Strict("CFI_EXTRA_LDFLAGS", cfiLdflags)
+	ctx.Strict("CFI_EXTRA_CFLAGS", strings.Join(cfiCflags, " "))
+	ctx.Strict("CFI_EXTRA_LDFLAGS", strings.Join(cfiLdflags, " "))
 
 	ctx.Strict("DEFAULT_C_STD_VERSION", config.CStdVersion)
 	ctx.Strict("DEFAULT_CPP_STD_VERSION", config.CppStdVersion)
diff --git a/cc/ndk_library.go b/cc/ndk_library.go
index ba3ec7d..d801775 100644
--- a/cc/ndk_library.go
+++ b/cc/ndk_library.go
@@ -281,6 +281,10 @@
 }
 
 func (c *stubDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) Objects {
+	if !strings.HasSuffix(c.properties.Symbol_file, ".map.txt") {
+		ctx.PropertyErrorf("symbol_file", "must end with .map.txt")
+	}
+
 	objs, versionScript := compileStubLibrary(ctx, flags, c.properties.Symbol_file, c.properties.ApiLevel, "")
 	c.versionScriptPath = versionScript
 	return objs
diff --git a/cc/sabi.go b/cc/sabi.go
index 01ef737..92fc7cf 100644
--- a/cc/sabi.go
+++ b/cc/sabi.go
@@ -15,6 +15,8 @@
 package cc
 
 import (
+	"strings"
+
 	"android/soong/android"
 	"github.com/google/blueprint"
 
@@ -40,7 +42,31 @@
 	return deps
 }
 
+func inListWithPrefixSearch(flag string, filter []string) bool {
+	// Assuming the filter is small enough.
+	// If the suffix of a filter element is *, try matching prefixes as well.
+	for _, f := range filter {
+		if (f == flag) || (strings.HasSuffix(f, "*") && strings.HasPrefix(flag, strings.TrimSuffix(f, "*"))) {
+			return true
+		}
+	}
+	return false
+}
+
+func filterOutWithPrefix(list []string, filter []string) (remainder []string) {
+	// Go through the filter, matching and optionally doing a prefix search for list elements.
+	for _, l := range list {
+		if !inListWithPrefixSearch(l, filter) {
+			remainder = append(remainder, l)
+		}
+	}
+	return
+}
+
 func (sabimod *sabi) flags(ctx ModuleContext, flags Flags) Flags {
+	// Assuming that the cflags which clang LibTooling tools cannot
+	// understand have not been converted to ninja variables yet.
+	flags.ToolingCFlags = filterOutWithPrefix(flags.CFlags, config.ClangLibToolingUnknownCflags)
 	return flags
 }
 
diff --git a/cc/sanitize.go b/cc/sanitize.go
index 8900ee8..b847c6f 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -25,17 +25,21 @@
 	"android/soong/cc/config"
 )
 
-const (
-	asanCflags  = "-fno-omit-frame-pointer"
-	asanLdflags = "-Wl,-u,__asan_preinit"
-	asanLibs    = "libasan"
+var (
+	// Any C flags added by sanitizer which libTooling tools may not
+	// understand also need to be added to ClangLibToolingUnknownCflags in
+	// cc/config/clang.go
 
-	cfiCflags = "-flto -fsanitize-cfi-cross-dso -fvisibility=default " +
-		"-fsanitize-blacklist=external/compiler-rt/lib/cfi/cfi_blacklist.txt"
+	asanCflags  = []string{"-fno-omit-frame-pointer"}
+	asanLdflags = []string{"-Wl,-u,__asan_preinit"}
+	asanLibs    = []string{"libasan"}
+
+	cfiCflags = []string{"-flto", "-fsanitize-cfi-cross-dso", "-fvisibility=default",
+		"-fsanitize-blacklist=external/compiler-rt/lib/cfi/cfi_blacklist.txt"}
 	// FIXME: revert the __cfi_check flag when clang is updated to r280031.
-	cfiLdflags = "-flto -fsanitize-cfi-cross-dso -fsanitize=cfi " +
-		"-Wl,-plugin-opt,O1 -Wl,-export-dynamic-symbol=__cfi_check"
-	cfiArflags = "--plugin ${config.ClangBin}/../lib64/LLVMgold.so"
+	cfiLdflags = []string{"-flto", "-fsanitize-cfi-cross-dso", "-fsanitize=cfi",
+		"-Wl,-plugin-opt,O1 -Wl,-export-dynamic-symbol=__cfi_check"}
+	cfiArflags = []string{"--plugin ${config.ClangBin}/../lib64/LLVMgold.so"}
 )
 
 type sanitizerType int
@@ -85,8 +89,9 @@
 		// Replaces abort() on error with a human-readable error message.
 		// Address and Thread sanitizers always run in diagnostic mode.
 		Diag struct {
-			Undefined *bool `android:"arch_variant"`
-			Cfi       *bool `android:"arch_variant"`
+			Undefined      *bool    `android:"arch_variant"`
+			Cfi            *bool    `android:"arch_variant"`
+			Misc_undefined []string `android:"arch_variant"`
 		}
 
 		// value to pass to -fsanitize-recover=
@@ -212,8 +217,8 @@
 		// TODO(ccross): error for compile_multilib = "32"?
 	}
 
-	if Bool(s.All_undefined) || Bool(s.Undefined) || Bool(s.Address) ||
-		Bool(s.Thread) || Bool(s.Coverage) || Bool(s.Safestack) || Bool(s.Cfi) {
+	if ctx.Os() != android.Windows && (Bool(s.All_undefined) || Bool(s.Undefined) || Bool(s.Address) || Bool(s.Thread) ||
+		Bool(s.Coverage) || Bool(s.Safestack) || Bool(s.Cfi) || len(s.Misc_undefined) > 0) {
 		sanitize.Properties.SanitizerEnabled = true
 	}
 
@@ -231,7 +236,7 @@
 
 	if ctx.Device() {
 		if Bool(sanitize.Properties.Sanitize.Address) {
-			deps.StaticLibs = append(deps.StaticLibs, asanLibs)
+			deps.StaticLibs = append(deps.StaticLibs, asanLibs...)
 		}
 		if Bool(sanitize.Properties.Sanitize.Address) || Bool(sanitize.Properties.Sanitize.Thread) {
 			deps.SharedLibs = append(deps.SharedLibs, "libdl")
@@ -287,30 +292,26 @@
 		sanitizers = append(sanitizers, sanitize.Properties.Sanitize.Misc_undefined...)
 	}
 
-	if Bool(sanitize.Properties.Sanitize.Diag.Undefined) &&
-		(Bool(sanitize.Properties.Sanitize.All_undefined) ||
-			Bool(sanitize.Properties.Sanitize.Undefined) ||
-			len(sanitize.Properties.Sanitize.Misc_undefined) > 0) {
+	if Bool(sanitize.Properties.Sanitize.Diag.Undefined) {
 		diagSanitizers = append(diagSanitizers, "undefined")
 	}
 
+	diagSanitizers = append(diagSanitizers, sanitize.Properties.Sanitize.Diag.Misc_undefined...)
+
 	if Bool(sanitize.Properties.Sanitize.Address) {
 		if ctx.Arch().ArchType == android.Arm {
 			// Frame pointer based unwinder in ASan requires ARM frame setup.
 			// TODO: put in flags?
 			flags.RequiredInstructionSet = "arm"
 		}
-		flags.CFlags = append(flags.CFlags, asanCflags)
-		flags.LdFlags = append(flags.LdFlags, asanLdflags)
+		flags.CFlags = append(flags.CFlags, asanCflags...)
+		flags.LdFlags = append(flags.LdFlags, asanLdflags...)
 
 		if ctx.Host() {
 			// -nodefaultlibs (provided with libc++) prevents the driver from linking
 			// libraries needed with -fsanitize=address. http://b/18650275 (WAI)
 			flags.LdFlags = append(flags.LdFlags, "-lm", "-lpthread")
 			flags.LdFlags = append(flags.LdFlags, "-Wl,--no-as-needed")
-			// Host ASAN only links symbols in the final executable, so
-			// there will always be undefined symbols in intermediate libraries.
-			_, flags.LdFlags = removeFromList("-Wl,--no-undefined", flags.LdFlags)
 		} else {
 			flags.CFlags = append(flags.CFlags, "-mllvm", "-asan-globals=0")
 			flags.DynamicLinker = "/system/bin/linker_asan"
@@ -340,9 +341,9 @@
 			flags.LdFlags = append(flags.LdFlags, "-march=armv7-a")
 		}
 		sanitizers = append(sanitizers, "cfi")
-		flags.CFlags = append(flags.CFlags, cfiCflags)
-		flags.LdFlags = append(flags.LdFlags, cfiLdflags)
-		flags.ArFlags = append(flags.ArFlags, cfiArflags)
+		flags.CFlags = append(flags.CFlags, cfiCflags...)
+		flags.LdFlags = append(flags.LdFlags, cfiLdflags...)
+		flags.ArFlags = append(flags.ArFlags, cfiArflags...)
 		if Bool(sanitize.Properties.Sanitize.Diag.Cfi) {
 			diagSanitizers = append(diagSanitizers, "cfi")
 		}
@@ -354,7 +355,13 @@
 		if ctx.Host() {
 			flags.CFlags = append(flags.CFlags, "-fno-sanitize-recover=all")
 			flags.LdFlags = append(flags.LdFlags, sanitizeArg)
-			flags.LdFlags = append(flags.LdFlags, "-lrt", "-ldl")
+			if ctx.Os() == android.Linux {
+				flags.LdFlags = append(flags.LdFlags, "-lrt")
+			}
+			flags.LdFlags = append(flags.LdFlags, "-ldl")
+			// Host sanitizers only link symbols in the final executable, so
+			// there will always be undefined symbols in intermediate libraries.
+			_, flags.LdFlags = removeFromList("-Wl,--no-undefined", flags.LdFlags)
 		} else {
 			flags.CFlags = append(flags.CFlags, "-fsanitize-trap=all", "-ftrap-function=abort")
 		}
diff --git a/cc/util.go b/cc/util.go
index 2febb57..eeb64eb 100644
--- a/cc/util.go
+++ b/cc/util.go
@@ -66,6 +66,16 @@
 	return
 }
 
+func removeListFromList(list []string, filter_out []string) (result []string) {
+	result = make([]string, 0, len(list))
+	for _, l := range list {
+		if !inList(l, filter_out) {
+			result = append(result, l)
+		}
+	}
+	return
+}
+
 func removeFromList(s string, list []string) (bool, []string) {
 	i := indexList(s, list)
 	if i != -1 {
@@ -87,26 +97,27 @@
 
 func flagsToBuilderFlags(in Flags) builderFlags {
 	return builderFlags{
-		globalFlags: strings.Join(in.GlobalFlags, " "),
-		arFlags:     strings.Join(in.ArFlags, " "),
-		asFlags:     strings.Join(in.AsFlags, " "),
-		cFlags:      strings.Join(in.CFlags, " "),
-		conlyFlags:  strings.Join(in.ConlyFlags, " "),
-		cppFlags:    strings.Join(in.CppFlags, " "),
-		yaccFlags:   strings.Join(in.YaccFlags, " "),
-		protoFlags:  strings.Join(in.protoFlags, " "),
-		aidlFlags:   strings.Join(in.aidlFlags, " "),
-		rsFlags:     strings.Join(in.rsFlags, " "),
-		ldFlags:     strings.Join(in.LdFlags, " "),
-		libFlags:    strings.Join(in.libFlags, " "),
-		tidyFlags:   strings.Join(in.TidyFlags, " "),
-		sAbiFlags:   strings.Join(in.SAbiFlags, " "),
-		yasmFlags:   strings.Join(in.YasmFlags, " "),
-		toolchain:   in.Toolchain,
-		clang:       in.Clang,
-		coverage:    in.Coverage,
-		tidy:        in.Tidy,
-		sAbiDump:    in.SAbiDump,
+		globalFlags:   strings.Join(in.GlobalFlags, " "),
+		arFlags:       strings.Join(in.ArFlags, " "),
+		asFlags:       strings.Join(in.AsFlags, " "),
+		cFlags:        strings.Join(in.CFlags, " "),
+		toolingCFlags: strings.Join(in.ToolingCFlags, " "),
+		conlyFlags:    strings.Join(in.ConlyFlags, " "),
+		cppFlags:      strings.Join(in.CppFlags, " "),
+		yaccFlags:     strings.Join(in.YaccFlags, " "),
+		protoFlags:    strings.Join(in.protoFlags, " "),
+		aidlFlags:     strings.Join(in.aidlFlags, " "),
+		rsFlags:       strings.Join(in.rsFlags, " "),
+		ldFlags:       strings.Join(in.LdFlags, " "),
+		libFlags:      strings.Join(in.libFlags, " "),
+		tidyFlags:     strings.Join(in.TidyFlags, " "),
+		sAbiFlags:     strings.Join(in.SAbiFlags, " "),
+		yasmFlags:     strings.Join(in.YasmFlags, " "),
+		toolchain:     in.Toolchain,
+		clang:         in.Clang,
+		coverage:      in.Coverage,
+		tidy:          in.Tidy,
+		sAbiDump:      in.SAbiDump,
 
 		systemIncludeFlags: strings.Join(in.SystemIncludeFlags, " "),
 
diff --git a/cmd/pom2mk/Android.bp b/cmd/pom2mk/Android.bp
new file mode 100644
index 0000000..54422b1
--- /dev/null
+++ b/cmd/pom2mk/Android.bp
@@ -0,0 +1,19 @@
+// 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.
+
+blueprint_go_binary {
+    name: "pom2mk",
+    deps: ["blueprint-proptools"],
+    srcs: ["pom2mk.go"],
+}
diff --git a/cmd/pom2mk/pom2mk.go b/cmd/pom2mk/pom2mk.go
new file mode 100644
index 0000000..e6144a5
--- /dev/null
+++ b/cmd/pom2mk/pom2mk.go
@@ -0,0 +1,254 @@
+// 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 main
+
+import (
+	"encoding/xml"
+	"flag"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"regexp"
+	"sort"
+	"strings"
+	"text/template"
+
+	"github.com/google/blueprint/proptools"
+)
+
+type RewriteNames []RewriteName
+type RewriteName struct {
+	regexp *regexp.Regexp
+	repl   string
+}
+
+func (r *RewriteNames) String() string {
+	return ""
+}
+
+func (r *RewriteNames) Set(v string) error {
+	split := strings.SplitN(v, "=", 2)
+	if len(split) != 2 {
+		return fmt.Errorf("Must be in the form of <regex>=<replace>")
+	}
+	regex, err := regexp.Compile(split[0])
+	if err != nil {
+		return nil
+	}
+	*r = append(*r, RewriteName{
+		regexp: regex,
+		repl:   split[1],
+	})
+	return nil
+}
+
+func (r *RewriteNames) Rewrite(name string) string {
+	for _, r := range *r {
+		if r.regexp.MatchString(name) {
+			return r.regexp.ReplaceAllString(name, r.repl)
+		}
+	}
+	return name
+}
+
+var rewriteNames = RewriteNames{}
+
+type ExtraDeps map[string][]string
+
+func (d ExtraDeps) String() string {
+	return ""
+}
+
+func (d ExtraDeps) Set(v string) error {
+	split := strings.SplitN(v, "=", 2)
+	if len(split) != 2 {
+		return fmt.Errorf("Must be in the form of <module>=<module>[,<module>]")
+	}
+	d[split[0]] = strings.Split(split[1], ",")
+	return nil
+}
+
+var extraDeps = make(ExtraDeps)
+
+type Dependency struct {
+	XMLName xml.Name `xml:"dependency"`
+
+	GroupId    string `xml:"groupId"`
+	ArtifactId string `xml:"artifactId"`
+	Version    string `xml:"version"`
+	Type       string `xml:"type"`
+
+	Scope string `xml:"scope"`
+}
+
+type Pom struct {
+	XMLName xml.Name `xml:"http://maven.apache.org/POM/4.0.0 project"`
+
+	ArtifactFile string `xml:"-"`
+
+	GroupId    string `xml:"groupId"`
+	ArtifactId string `xml:"artifactId"`
+	Version    string `xml:"version"`
+	Packaging  string `xml:"packaging"`
+
+	Dependencies []Dependency `xml:"dependencies>dependency"`
+}
+
+func (p Pom) MkName() string {
+	return rewriteNames.Rewrite(p.ArtifactId)
+}
+
+func (p Pom) MkDeps() []string {
+	var ret []string
+	for _, d := range p.Dependencies {
+		if d.Type != "aar" {
+			continue
+		}
+		name := rewriteNames.Rewrite(d.ArtifactId)
+		ret = append(ret, name)
+		ret = append(ret, extraDeps[name]...)
+	}
+	return ret
+}
+
+var mkTemplate = template.Must(template.New("mk").Parse(`
+include $(CLEAR_VARS)
+LOCAL_MODULE := {{.MkName}}
+LOCAL_MODULE_CLASS := JAVA_LIBRARIES
+LOCAL_UNINSTALLABLE_MODULE := true
+LOCAL_SRC_FILES := {{.ArtifactFile}}
+LOCAL_BUILT_MODULE_STEM := javalib.jar
+LOCAL_MODULE_SUFFIX := .{{.Packaging}}
+LOCAL_USE_AAPT2 := true
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+{{range .MkDeps}}  {{.}} \
+{{end}}
+include $(BUILD_PREBUILT)
+`))
+
+func convert(filename string, out io.Writer) error {
+	data, err := ioutil.ReadFile(filename)
+	if err != nil {
+		return err
+	}
+
+	var pom Pom
+	err = xml.Unmarshal(data, &pom)
+	if err != nil {
+		return err
+	}
+
+	if pom.Packaging == "" {
+		pom.Packaging = "jar"
+	}
+
+	pom.ArtifactFile = strings.TrimSuffix(filename, ".pom") + "." + pom.Packaging
+
+	return mkTemplate.Execute(out, pom)
+}
+
+func main() {
+	flag.Usage = func() {
+		fmt.Fprintf(os.Stderr, `pom2mk, a tool to create Android.mk files from maven repos
+
+The tool will extract the necessary information from *.pom files to create an Android.mk whose
+aar libraries can be linked against when using AAPT2.
+
+Usage: %s [--rewrite <regex>=<replace>] [--extra-deps <module>=<module>[,<module>]] <dir>
+
+  -rewrite <regex>=<replace>
+     rewrite can be used to specify mappings between the artifactId in the pom files and module
+     names in the Android.mk files. This can be specified multiple times, the first matching
+     regex will be used.
+  -extra-deps <module>=<module>[,<module>]
+     Some Android.mk modules have transitive dependencies that must be specified when they are
+     depended upon (like android-support-v7-mediarouter requires android-support-v7-appcompat).
+     This may be specified multiple times to declare these dependencies.
+  <dir>
+     The directory to search for *.pom files under.
+
+The makefile is written to stdout, to be put in the current directory (often as Android.mk)
+`, os.Args[0])
+	}
+
+	flag.Var(&extraDeps, "extra-deps", "Extra dependencies needed when depending on a module")
+	flag.Var(&rewriteNames, "rewrite", "Regex(es) to rewrite artifact names")
+	flag.Parse()
+
+	if flag.NArg() != 1 {
+		flag.Usage()
+		os.Exit(1)
+	}
+
+	dir := flag.Arg(0)
+	absDir, err := filepath.Abs(dir)
+	if err != nil {
+		fmt.Println(os.Stderr, "Failed to get absolute directory:", err)
+		os.Exit(1)
+	}
+
+	var filenames []string
+	err = filepath.Walk(absDir, func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+
+		name := info.Name()
+		if info.IsDir() {
+			if strings.HasPrefix(name, ".") {
+				return filepath.SkipDir
+			}
+			return nil
+		}
+
+		if strings.HasPrefix(name, ".") {
+			return nil
+		}
+
+		if strings.HasSuffix(name, ".pom") {
+			path, err = filepath.Rel(absDir, path)
+			if err != nil {
+				return err
+			}
+			filenames = append(filenames, filepath.Join(dir, path))
+		}
+		return nil
+	})
+	if err != nil {
+		fmt.Fprintln(os.Stderr, "Error walking files:", err)
+		os.Exit(1)
+	}
+
+	if len(filenames) == 0 {
+		fmt.Fprintln(os.Stderr, "Error: no *.pom files found under", dir)
+		os.Exit(1)
+	}
+
+	sort.Strings(filenames)
+
+	fmt.Println("# Automatically generated with:")
+	fmt.Println("# pom2mk", strings.Join(proptools.ShellEscape(os.Args[1:]), " "))
+	fmt.Println("LOCAL_PATH := $(call my-dir)")
+
+	for _, filename := range filenames {
+		err := convert(filename, os.Stdout)
+		if err != nil {
+			fmt.Fprintln(os.Stderr, "Error converting", filename, err)
+			os.Exit(1)
+		}
+	}
+}
diff --git a/cmd/sbox/Android.bp b/cmd/sbox/Android.bp
new file mode 100644
index 0000000..fe4c7bb
--- /dev/null
+++ b/cmd/sbox/Android.bp
@@ -0,0 +1,21 @@
+// 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.
+
+blueprint_go_binary {
+    name: "sbox",
+    srcs: [
+        "sbox.go",
+    ],
+}
+
diff --git a/cmd/sbox/sbox.go b/cmd/sbox/sbox.go
new file mode 100644
index 0000000..5064626
--- /dev/null
+++ b/cmd/sbox/sbox.go
@@ -0,0 +1,185 @@
+// 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 main
+
+import (
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path"
+	"path/filepath"
+	"strings"
+)
+
+func main() {
+	error := run()
+	if error != nil {
+		fmt.Fprintln(os.Stderr, error)
+		os.Exit(1)
+	}
+}
+
+var usage = "Usage: sbox -c <commandToRun> --sandbox-path <sandboxPath> --output-root <outputRoot> <outputFile> [<outputFile>...]\n" +
+	"\n" +
+	"Runs <commandToRun> and moves each <outputFile> out of <sandboxPath>\n" +
+	"If any file in <outputFiles> is specified by absolute path, then <outputRoot> must be specified as well,\n" +
+	"to enable sbox to compute the relative path within the sandbox of the specified output files"
+
+func usageError(violation string) error {
+	return fmt.Errorf("Usage error: %s.\n\n%s", violation, usage)
+}
+
+func run() error {
+	var outFiles []string
+	args := os.Args[1:]
+
+	var rawCommand string
+	var sandboxesRoot string
+	removeTempDir := true
+	var outputRoot string
+
+	for i := 0; i < len(args); i++ {
+		arg := args[i]
+		if arg == "--sandbox-path" {
+			sandboxesRoot = args[i+1]
+			i++
+		} else if arg == "-c" {
+			rawCommand = args[i+1]
+			i++
+		} else if arg == "--output-root" {
+			outputRoot = args[i+1]
+			i++
+		} else if arg == "--keep-out-dir" {
+			removeTempDir = false
+		} else {
+			outFiles = append(outFiles, arg)
+		}
+	}
+	if len(rawCommand) == 0 {
+		return usageError("-c <commandToRun> is required and must be non-empty")
+	}
+	if outFiles == nil {
+		return usageError("at least one output file must be given")
+	}
+	if len(sandboxesRoot) == 0 {
+		// In practice, the value of sandboxesRoot will mostly likely be at a fixed location relative to OUT_DIR,
+		// and the sbox executable will most likely be at a fixed location relative to OUT_DIR too, so
+		// the value of sandboxesRoot will most likely be at a fixed location relative to the sbox executable
+		// However, Soong also needs to be able to separately remove the sandbox directory on startup (if it has anything left in it)
+		// and by passing it as a parameter we don't need to duplicate its value
+		return usageError("--sandbox-path <sandboxPath> is required and must be non-empty")
+	}
+
+	// Rewrite output file paths to be relative to output root
+	// This facilitates matching them up against the corresponding paths in the temporary directory in case they're absolute
+	for i, filePath := range outFiles {
+		if path.IsAbs(filePath) {
+			if len(outputRoot) == 0 {
+				return fmt.Errorf("Absolute path %s requires nonempty value for --output-root", filePath)
+			}
+		}
+		relativePath, err := filepath.Rel(outputRoot, filePath)
+		if err != nil {
+			return err
+		}
+		outFiles[i] = relativePath
+	}
+
+	os.MkdirAll(sandboxesRoot, 0777)
+
+	tempDir, err := ioutil.TempDir(sandboxesRoot, "sbox")
+	if err != nil {
+		return fmt.Errorf("Failed to create temp dir: %s", err)
+	}
+
+	// In the common case, the following line of code is what removes the sandbox
+	// If a fatal error occurs (such as if our Go process is killed unexpectedly),
+	// then at the beginning of the next build, Soong will retry the cleanup
+	defer func() {
+		// in some cases we decline to remove the temp dir, to facilitate debugging
+		if removeTempDir {
+			os.RemoveAll(tempDir)
+		}
+	}()
+
+	if strings.Contains(rawCommand, "__SBOX_OUT_DIR__") {
+		rawCommand = strings.Replace(rawCommand, "__SBOX_OUT_DIR__", tempDir, -1)
+	}
+
+	if strings.Contains(rawCommand, "__SBOX_OUT_FILES__") {
+		// expands into a space-separated list of output files to be generated into the sandbox directory
+		tempOutPaths := []string{}
+		for _, outputPath := range outFiles {
+			tempOutPath := path.Join(tempDir, outputPath)
+			tempOutPaths = append(tempOutPaths, tempOutPath)
+		}
+		pathsText := strings.Join(tempOutPaths, " ")
+		rawCommand = strings.Replace(rawCommand, "__SBOX_OUT_FILES__", pathsText, -1)
+	}
+
+	for _, filePath := range outFiles {
+		os.MkdirAll(path.Join(tempDir, filepath.Dir(filePath)), 0777)
+	}
+
+	commandDescription := rawCommand
+
+	cmd := exec.Command("bash", "-c", rawCommand)
+	cmd.Stdin = os.Stdin
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+	err = cmd.Run()
+
+	if exit, ok := err.(*exec.ExitError); ok && !exit.Success() {
+		return fmt.Errorf("sbox command (%s) failed with err %#v\n", commandDescription, err.Error())
+	} else if err != nil {
+		return err
+	}
+
+	// validate that all files are created properly
+	var outputErrors []error
+	for _, filePath := range outFiles {
+		tempPath := filepath.Join(tempDir, filePath)
+		fileInfo, err := os.Stat(tempPath)
+		if err != nil {
+			outputErrors = append(outputErrors, fmt.Errorf("failed to create expected output file: %s\n", tempPath))
+			continue
+		}
+		if fileInfo.IsDir() {
+			outputErrors = append(outputErrors, fmt.Errorf("Output path %s refers to a directory, not a file. This is not permitted because it prevents robust up-to-date checks\n", filePath))
+		}
+	}
+	if len(outputErrors) > 0 {
+		// Keep the temporary output directory around in case a user wants to inspect it for debugging purposes.
+		// Soong will delete it later anyway.
+		removeTempDir = false
+		return fmt.Errorf("mismatch between declared and actual outputs in sbox command (%s):\n%v", commandDescription, outputErrors)
+	}
+	// the created files match the declared files; now move them
+	for _, filePath := range outFiles {
+		tempPath := filepath.Join(tempDir, filePath)
+		destPath := filePath
+		if len(outputRoot) != 0 {
+			destPath = filepath.Join(outputRoot, filePath)
+		}
+		err := os.Rename(tempPath, destPath)
+		if err != nil {
+			return err
+		}
+	}
+
+	// TODO(jeffrygaston) if a process creates more output files than it declares, should there be a warning?
+	return nil
+}
diff --git a/genrule/genrule.go b/genrule/genrule.go
index 2ff018f..dc4e968 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -16,11 +16,14 @@
 
 import (
 	"fmt"
+	"path"
 	"strings"
 
 	"github.com/google/blueprint"
 
 	"android/soong/android"
+	"android/soong/shared"
+	"path/filepath"
 )
 
 func init() {
@@ -32,6 +35,10 @@
 	pctx = android.NewPackageContext("android/soong/genrule")
 )
 
+func init() {
+	pctx.HostBinToolVariable("sboxCmd", "sbox")
+}
+
 type SourceFileGenerator interface {
 	GeneratedSourceFiles() android.Paths
 	GeneratedHeaderDirs() android.Paths
@@ -42,7 +49,11 @@
 }
 
 type generatorProperties struct {
-	// command to run on one or more input files.  Available variables for substitution:
+	// The command to run on one or more input files. Cmd supports substitution of a few variables
+	// (the actual substitution is implemented in GenerateAndroidBuildActions below)
+	//
+	// Available variables for substitution:
+	//
 	// $(location): the path to the first entry in tools or tool_files
 	// $(location <label>): the path to the tool or tool_file with name <label>
 	// $(in): one or more input files
@@ -51,9 +62,8 @@
 	// $(genDir): the sandbox directory for this tool; contains $(out)
 	// $$: a literal $
 	//
-	// DO NOT directly reference paths to files in the source tree, or the
-	// command will be missing proper dependencies to re-run if the files
-	// change.
+	// All files used must be declared as inputs (to ensure proper up-to-date checks).
+	// Use "$(in)" directly in Cmd to ensure that all inputs used are declared.
 	Cmd string
 
 	// Enable reading a file containing dependencies in gcc format after the command completes
@@ -164,7 +174,7 @@
 		}
 	}
 
-	cmd, err := android.Expand(g.properties.Cmd, func(name string) (string, error) {
+	rawCommand, err := android.Expand(g.properties.Cmd, func(name string) (string, error) {
 		switch name {
 		case "location":
 			if len(g.properties.Tools) > 0 {
@@ -175,14 +185,22 @@
 		case "in":
 			return "${in}", nil
 		case "out":
-			return "${out}", nil
+			return "__SBOX_OUT_FILES__", nil
 		case "depfile":
 			if !g.properties.Depfile {
 				return "", fmt.Errorf("$(depfile) used without depfile property")
 			}
 			return "${depfile}", nil
 		case "genDir":
-			return android.PathForModuleGen(ctx, "").String(), nil
+			genPath := android.PathForModuleGen(ctx, "").String()
+			var relativePath string
+			var err error
+			outputPath := android.PathForOutput(ctx).String()
+			relativePath, err = filepath.Rel(outputPath, genPath)
+			if err != nil {
+				panic(err)
+			}
+			return path.Join("__SBOX_OUT_DIR__", relativePath), nil
 		default:
 			if strings.HasPrefix(name, "location ") {
 				label := strings.TrimSpace(strings.TrimPrefix(name, "location "))
@@ -201,8 +219,17 @@
 		return
 	}
 
+	// tell the sbox command which directory to use as its sandbox root
+	buildDir := android.PathForOutput(ctx).String()
+	sandboxPath := shared.TempDirForOutDir(buildDir)
+
+	// recall that Sprintf replaces percent sign expressions, whereas dollar signs expressions remain as written,
+	// to be replaced later by ninja_strings.go
+	sandboxCommand := fmt.Sprintf("$sboxCmd --sandbox-path %s --output-root %s -c %q $out", sandboxPath, buildDir, rawCommand)
+
 	ruleParams := blueprint.RuleParams{
-		Command: cmd,
+		Command:     sandboxCommand,
+		CommandDeps: []string{"$sboxCmd"},
 	}
 	var args []string
 	if g.properties.Depfile {
diff --git a/java/builder.go b/java/builder.go
index 895a999..041c303 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -37,10 +37,11 @@
 	// this, all java rules write into separate directories and then a post-processing step lists
 	// the files in the the directory into a list file that later rules depend on (and sometimes
 	// read from directly using @<listfile>)
-	javac = pctx.AndroidStaticRule("javac",
+	javac = pctx.AndroidGomaStaticRule("javac",
 		blueprint.RuleParams{
 			Command: `rm -rf "$outDir" && mkdir -p "$outDir" && ` +
-				`$javacCmd -encoding UTF-8 $javacFlags $bootClasspath $classpath ` +
+				`${config.JavacWrapper}$javacCmd ` +
+				`-encoding UTF-8 $javacFlags $bootClasspath $classpath ` +
 				`-extdirs "" -d $outDir @$out.rsp || ( rm -rf "$outDir"; exit 41 ) && ` +
 				`find $outDir -name "*.class" > $out`,
 			Rspfile:        "$out.rsp",
@@ -88,6 +89,13 @@
 	pctx.StaticVariable("jarCmd", filepath.Join("${bootstrap.ToolDir}", "soong_zip"))
 	pctx.HostBinToolVariable("dxCmd", "dx")
 	pctx.HostJavaToolVariable("jarjarCmd", "jarjar.jar")
+
+	pctx.VariableFunc("JavacWrapper", func(config interface{}) (string, error) {
+		if override := config.(android.Config).Getenv("JAVAC_WRAPPER"); override != "" {
+			return override + " ", nil
+		}
+		return "", nil
+	})
 }
 
 type javaBuilderFlags struct {
diff --git a/python/python_test.go b/python/python_test.go
index 3f719f8..bb407e4 100644
--- a/python/python_test.go
+++ b/python/python_test.go
@@ -305,7 +305,7 @@
 
 func TestPythonModule(t *testing.T) {
 	config, buildDir := setupBuildEnv(t)
-	defer tearDownBuildEnv()
+	defer tearDownBuildEnv(buildDir)
 	android.TestPreDepsMutators(func(ctx android.RegisterMutatorsContext) {
 		ctx.BottomUp("version_split", versionSplitMutator()).Parallel()
 	})
@@ -432,8 +432,8 @@
 	return
 }
 
-func tearDownBuildEnv() {
-	os.RemoveAll(buildNamePrefix)
+func tearDownBuildEnv(buildDir string) {
+	os.RemoveAll(buildDir)
 }
 
 func findModule(ctx *blueprint.Context, name, variant string) blueprint.Module {
diff --git a/shared/paths.go b/shared/paths.go
new file mode 100644
index 0000000..f5dc5ac
--- /dev/null
+++ b/shared/paths.go
@@ -0,0 +1,26 @@
+// 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 shared
+
+// This file exists to share path-related logic between both soong_ui and soong
+
+import (
+	"path/filepath"
+)
+
+// Given the out directory, returns the root of the temp directory (to be cleared at the start of each execution of Soong)
+func TempDirForOutDir(outDir string) (tempPath string) {
+	return filepath.Join(outDir, ".temp")
+}
diff --git a/ui/build/Android.bp b/ui/build/Android.bp
index 25520da..489c06d 100644
--- a/ui/build/Android.bp
+++ b/ui/build/Android.bp
@@ -18,6 +18,7 @@
     deps: [
         "soong-ui-logger",
         "soong-ui-tracer",
+        "soong-shared",
     ],
     srcs: [
         "build.go",
diff --git a/ui/build/build.go b/ui/build/build.go
index 83dbcb6..1400c48 100644
--- a/ui/build/build.go
+++ b/ui/build/build.go
@@ -125,6 +125,8 @@
 
 	checkCaseSensitivity(ctx, config)
 
+	ensureEmptyDirectoriesExist(ctx, config.TempDir())
+
 	if what&BuildProductConfig != 0 {
 		// Run make for product config
 		runMakeProductConfig(ctx, config)
diff --git a/ui/build/config.go b/ui/build/config.go
index 7e8091b..16826f2 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -21,6 +21,8 @@
 	"runtime"
 	"strconv"
 	"strings"
+
+	"android/soong/shared"
 )
 
 type Config struct{ *configImpl }
@@ -250,6 +252,10 @@
 	return filepath.Join(c.OutDir(), "soong")
 }
 
+func (c *configImpl) TempDir() string {
+	return shared.TempDirForOutDir(c.SoongOutDir())
+}
+
 func (c *configImpl) KatiSuffix() string {
 	if c.katiSuffix != "" {
 		return c.katiSuffix
@@ -306,7 +312,7 @@
 }
 
 // RemoteParallel controls how many remote jobs (i.e., commands which contain
-// gomacc) are run in parallel.  Note the paralleism of all other jobs is
+// gomacc) are run in parallel.  Note the parallelism of all other jobs is
 // still limited by Parallel()
 func (c *configImpl) RemoteParallel() int {
 	if v, ok := c.environ.Get("NINJA_REMOTE_NUM_JOBS"); ok {
diff --git a/ui/build/exec.go b/ui/build/exec.go
index c8c5c9a..90fb19d 100644
--- a/ui/build/exec.go
+++ b/ui/build/exec.go
@@ -64,17 +64,20 @@
 
 func (c *Cmd) Run() error {
 	c.prepare()
-	return c.Cmd.Run()
+	err := c.Cmd.Run()
+	return err
 }
 
 func (c *Cmd) Output() ([]byte, error) {
 	c.prepare()
-	return c.Cmd.Output()
+	bytes, err := c.Cmd.Output()
+	return bytes, err
 }
 
 func (c *Cmd) CombinedOutput() ([]byte, error) {
 	c.prepare()
-	return c.Cmd.CombinedOutput()
+	bytes, err := c.Cmd.CombinedOutput()
+	return bytes, err
 }
 
 // StartOrFatal is equivalent to Start, but handles the error with a call to ctx.Fatal
diff --git a/ui/build/kati.go b/ui/build/kati.go
index 972b559..48c38d4 100644
--- a/ui/build/kati.go
+++ b/ui/build/kati.go
@@ -74,6 +74,7 @@
 		"--detect_android_echo",
 		"--color_warnings",
 		"--gen_all_targets",
+		"--werror_find_emulator",
 		"-f", "build/core/main.mk",
 	}
 
diff --git a/ui/build/ninja.go b/ui/build/ninja.go
index 5787a00..78d1170 100644
--- a/ui/build/ninja.go
+++ b/ui/build/ninja.go
@@ -15,6 +15,8 @@
 package build
 
 import (
+	"fmt"
+	"os"
 	"path/filepath"
 	"strconv"
 	"strings"
@@ -69,7 +71,69 @@
 	cmd.Stdin = ctx.Stdin()
 	cmd.Stdout = ctx.Stdout()
 	cmd.Stderr = ctx.Stderr()
+	logPath := filepath.Join(config.OutDir(), ".ninja_log")
+	ninjaHeartbeatDuration := time.Minute * 5
+	if overrideText, ok := cmd.Environment.Get("NINJA_HEARTBEAT_INTERVAL"); ok {
+		// For example, "1m"
+		overrideDuration, err := time.ParseDuration(overrideText)
+		if err == nil && overrideDuration.Seconds() > 0 {
+			ninjaHeartbeatDuration = overrideDuration
+		}
+	}
+	// Poll the ninja log for updates; if it isn't updated enough, then we want to show some diagnostics
+	done := make(chan struct{})
+	defer close(done)
+	ticker := time.NewTicker(ninjaHeartbeatDuration)
+	defer ticker.Stop()
+	checker := &statusChecker{}
+	go func() {
+		for {
+			select {
+			case <-ticker.C:
+				checker.check(ctx, config, logPath)
+			case <-done:
+				return
+			}
+		}
+	}()
+
 	startTime := time.Now()
-	defer ctx.ImportNinjaLog(filepath.Join(config.OutDir(), ".ninja_log"), startTime)
+	defer ctx.ImportNinjaLog(logPath, startTime)
+
 	cmd.RunOrFatal()
 }
+
+type statusChecker struct {
+	prevTime time.Time
+}
+
+func (c *statusChecker) check(ctx Context, config Config, pathToCheck string) {
+	info, err := os.Stat(pathToCheck)
+	var newTime time.Time
+	if err == nil {
+		newTime = info.ModTime()
+	}
+	if newTime == c.prevTime {
+		// ninja may be stuck
+		dumpStucknessDiagnostics(ctx, config, pathToCheck, newTime)
+	}
+	c.prevTime = newTime
+}
+
+// dumpStucknessDiagnostics gets called when it is suspected that Ninja is stuck and we want to output some diagnostics
+func dumpStucknessDiagnostics(ctx Context, config Config, statusPath string, lastUpdated time.Time) {
+
+	ctx.Verbosef("ninja may be stuck; last update to %v was %v. dumping process tree...", statusPath, lastUpdated)
+
+	// The "pstree" command doesn't exist on Mac, but "pstree" on Linux gives more convenient output than "ps"
+	// So, we try pstree first, and ps second
+	pstreeCommandText := fmt.Sprintf("pstree -pal %v", os.Getpid())
+	psCommandText := "ps -ef"
+	commandText := pstreeCommandText + " || " + psCommandText
+
+	cmd := Command(ctx, config, "dump process tree", "bash", "-c", commandText)
+	output := cmd.CombinedOutputOrFatal()
+	ctx.Verbose(string(output))
+
+	ctx.Verbosef("done\n")
+}
diff --git a/ui/build/util.go b/ui/build/util.go
index 37ac6b9..2555e8a 100644
--- a/ui/build/util.go
+++ b/ui/build/util.go
@@ -50,6 +50,19 @@
 	}
 }
 
+// ensureEmptyDirectoriesExist ensures that the given directories exist and are empty
+func ensureEmptyDirectoriesExist(ctx Context, dirs ...string) {
+	// remove all the directories
+	for _, dir := range dirs {
+		err := os.RemoveAll(dir)
+		if err != nil {
+			ctx.Fatalf("Error removing %s: %q\n", dir, err)
+		}
+	}
+	// recreate all the directories
+	ensureDirectoriesExist(ctx, dirs...)
+}
+
 // ensureEmptyFileExists ensures that the containing directory exists, and the
 // specified file exists. If it doesn't exist, it will write an empty file.
 func ensureEmptyFileExists(ctx Context, file string) {