Merge "Revert "Build support for 32-bit armv8-a""
diff --git a/android/config.go b/android/config.go
index ee2f40f..c435e85 100644
--- a/android/config.go
+++ b/android/config.go
@@ -378,6 +378,10 @@
 	return strconv.Itoa(c.PlatformSdkVersionInt())
 }
 
+func (c *config) MinSupportedSdkVersion() int {
+	return 14
+}
+
 // Codenames that are active in the current lunch target.
 func (c *config) PlatformVersionActiveCodenames() []string {
 	return c.ProductVariables.Platform_version_active_codenames
diff --git a/android/makevars.go b/android/makevars.go
index 482fbde..024e015 100644
--- a/android/makevars.go
+++ b/android/makevars.go
@@ -19,11 +19,20 @@
 	"fmt"
 	"io/ioutil"
 	"os"
+	"strconv"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
 
+func init() {
+	RegisterMakeVarsProvider(pctx, androidMakeVarsProvider)
+}
+
+func androidMakeVarsProvider(ctx MakeVarsContext) {
+	ctx.Strict("MIN_SUPPORTED_SDK_VERSION", strconv.Itoa(ctx.Config().MinSupportedSdkVersion()))
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 // Interface for other packages to use to declare make variables
 type MakeVarsContext interface {
diff --git a/androidmk/cmd/androidmk/androidmk.go b/androidmk/cmd/androidmk/androidmk.go
index d26643a..5fad586 100644
--- a/androidmk/cmd/androidmk/androidmk.go
+++ b/androidmk/cmd/androidmk/androidmk.go
@@ -48,10 +48,11 @@
 	f.bpPos.Line++
 }
 
-func (f *bpFile) errorf(node mkparser.Node, s string, args ...interface{}) {
-	orig := node.Dump()
-	s = fmt.Sprintf(s, args...)
-	f.insertExtraComment(fmt.Sprintf("// ANDROIDMK TRANSLATION ERROR: %s", s))
+// records that the given node failed to be converted and includes an explanatory message
+func (f *bpFile) errorf(failedNode mkparser.Node, message string, args ...interface{}) {
+	orig := failedNode.Dump()
+	message = fmt.Sprintf(message, args...)
+	f.addErrorText(fmt.Sprintf("// ANDROIDMK TRANSLATION ERROR: %s", message))
 
 	lines := strings.Split(orig, "\n")
 	for _, l := range lines {
@@ -59,6 +60,17 @@
 	}
 }
 
+// records that something unexpected occurred
+func (f *bpFile) warnf(message string, args ...interface{}) {
+	message = fmt.Sprintf(message, args...)
+	f.addErrorText(fmt.Sprintf("// ANDROIDMK TRANSLATION WARNING: %s", message))
+}
+
+// adds the given error message as-is to the bottom of the (in-progress) file
+func (f *bpFile) addErrorText(message string) {
+	f.insertExtraComment(message)
+}
+
 func (f *bpFile) setMkPos(pos, end scanner.Position) {
 	if pos.Line < f.mkPos.Line {
 		panic(fmt.Errorf("out of order lines, %q after %q", pos, f.mkPos))
@@ -358,6 +370,10 @@
 			*oldValue = val
 		} else {
 			names := strings.Split(name, ".")
+			if file.module == nil {
+				file.warnf("No 'include $(CLEAR_VARS)' detected before first assignment; clearing vars now")
+				resetModule(file)
+			}
 			container := &file.module.Properties
 
 			for i, n := range names[:len(names)-1] {
diff --git a/androidmk/cmd/androidmk/androidmk_test.go b/androidmk/cmd/androidmk/androidmk_test.go
index 07d1c10..037ce26 100644
--- a/androidmk/cmd/androidmk/androidmk_test.go
+++ b/androidmk/cmd/androidmk/androidmk_test.go
@@ -393,6 +393,20 @@
 }
 `,
 	},
+	{
+
+		desc: "Don't fail on missing CLEAR_VARS",
+		in: `
+LOCAL_MODULE := iAmAModule
+include $(BUILD_SHARED_LIBRARY)`,
+
+		expected: `
+// ANDROIDMK TRANSLATION WARNING: No 'include $(CLEAR_VARS)' detected before first assignment; clearing vars now
+cc_library_shared {
+  name: "iAmAModule",
+
+}`,
+	},
 }
 
 func reformatBlueprint(input string) string {
diff --git a/bootstrap.bash b/bootstrap.bash
index ff1ac8a..769736f 100755
--- a/bootstrap.bash
+++ b/bootstrap.bash
@@ -1,61 +1,9 @@
 #!/bin/bash
 
-set -e
+echo '==== ERROR: bootstrap.bash & ./soong are obsolete ====' >&2
+echo 'Use `m --skip-make` with a standalone OUT_DIR instead.' >&2
+echo 'Without envsetup.sh, use:' >&2
+echo '  build/soong/soong_ui.bash --make-mode --skip-make' >&2
+echo '======================================================' >&2
+exit 1
 
-if [ -z "$NO_DEPRECATION_WARNING" ]; then
-    echo '== WARNING: bootstrap.bash & ./soong are deprecated ==' >&2
-    echo 'Use `m --skip-make` with a standalone OUT_DIR instead.' >&2
-    echo 'Without envsetup.sh, use:' >&2
-    echo '  build/soong/soong_ui.bash --make-mode --skip-make' >&2
-    echo '======================================================' >&2
-fi
-
-ORIG_SRCDIR=$(dirname "${BASH_SOURCE[0]}")
-if [[ "$ORIG_SRCDIR" != "." ]]; then
-  if [[ ! -z "$BUILDDIR" ]]; then
-    echo "error: To use BUILDDIR, run from the source directory"
-    exit 1
-  fi
-  export BUILDDIR=$("${ORIG_SRCDIR}/build/soong/scripts/reverse_path.py" "$ORIG_SRCDIR")
-  cd $ORIG_SRCDIR
-fi
-if [[ -z "$BUILDDIR" ]]; then
-  echo "error: Run ${BASH_SOURCE[0]} from the build output directory"
-  exit 1
-fi
-export SRCDIR="."
-export BOOTSTRAP="${SRCDIR}/bootstrap.bash"
-export BLUEPRINTDIR="${SRCDIR}/build/blueprint"
-
-export TOPNAME="Android.bp"
-export RUN_TESTS="-t"
-
-case $(uname) in
-    Linux)
-	export PREBUILTOS="linux-x86"
-	;;
-    Darwin)
-	export PREBUILTOS="darwin-x86"
-	;;
-    *) echo "unknown OS:" $(uname) && exit 1;;
-esac
-export GOROOT="${SRCDIR}/prebuilts/go/$PREBUILTOS"
-
-if [[ $# -eq 0 ]]; then
-    mkdir -p $BUILDDIR
-
-    if [[ $(find $BUILDDIR -maxdepth 1 -name Android.bp) ]]; then
-      echo "FAILED: The build directory must not be a source directory"
-      exit 1
-    fi
-
-    export SRCDIR_FROM_BUILDDIR=$(build/soong/scripts/reverse_path.py "$BUILDDIR")
-
-    sed -e "s|@@BuildDir@@|${BUILDDIR}|" \
-        -e "s|@@SrcDirFromBuildDir@@|${SRCDIR_FROM_BUILDDIR}|" \
-        -e "s|@@PrebuiltOS@@|${PREBUILTOS}|" \
-        "$SRCDIR/build/soong/soong.bootstrap.in" > $BUILDDIR/.soong.bootstrap
-    ln -sf "${SRCDIR_FROM_BUILDDIR}/build/soong/soong.bash" $BUILDDIR/soong
-fi
-
-"$SRCDIR/build/blueprint/bootstrap.bash" "$@"
diff --git a/cc/cc.go b/cc/cc.go
index ba06be2..cfe748b 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -638,7 +638,7 @@
 		feature.begin(ctx)
 	}
 	if ctx.sdk() {
-		version, err := normalizeNdkApiLevel(ctx.sdkVersion(), ctx.Arch())
+		version, err := normalizeNdkApiLevel(ctx, ctx.sdkVersion(), ctx.Arch())
 		if err != nil {
 			ctx.PropertyErrorf("sdk_version", err.Error())
 		}
diff --git a/cc/ndk_library.go b/cc/ndk_library.go
index 8fbffcf..fc7cd91 100644
--- a/cc/ndk_library.go
+++ b/cc/ndk_library.go
@@ -110,18 +110,20 @@
 	}
 }
 
-func normalizeNdkApiLevel(apiLevel string, arch android.Arch) (string, error) {
+func normalizeNdkApiLevel(ctx android.BaseContext, apiLevel string,
+	arch android.Arch) (string, error) {
+
 	if apiLevel == "current" {
 		return apiLevel, nil
 	}
 
-	minVersion := 9 // Minimum version supported by the NDK.
+	minVersion := ctx.AConfig().MinSupportedSdkVersion()
 	firstArchVersions := map[android.ArchType]int{
-		android.Arm:    9,
+		android.Arm:    minVersion,
 		android.Arm64:  21,
-		android.Mips:   9,
+		android.Mips:   minVersion,
 		android.Mips64: 21,
-		android.X86:    9,
+		android.X86:    minVersion,
 		android.X86_64: 21,
 	}
 
@@ -188,7 +190,7 @@
 func generateStubApiVariants(mctx android.BottomUpMutatorContext, c *stubDecorator) {
 	platformVersion := mctx.AConfig().PlatformSdkVersionInt()
 
-	firstSupportedVersion, err := normalizeNdkApiLevel(c.properties.First_version,
+	firstSupportedVersion, err := normalizeNdkApiLevel(mctx, c.properties.First_version,
 		mctx.Arch())
 	if err != nil {
 		mctx.PropertyErrorf("first_version", err.Error())
diff --git a/cc/vndk.go b/cc/vndk.go
index 2e6ac13..395069b 100644
--- a/cc/vndk.go
+++ b/cc/vndk.go
@@ -15,6 +15,7 @@
 package cc
 
 import (
+	"sort"
 	"strings"
 	"sync"
 
@@ -116,6 +117,7 @@
 			name := strings.TrimSuffix(m.Name(), llndkLibrarySuffix)
 			if !inList(name, llndkLibraries) {
 				llndkLibraries = append(llndkLibraries, name)
+				sort.Strings(llndkLibraries)
 			}
 		} else if lib, ok := m.linker.(*libraryDecorator); ok && lib.shared() {
 			if m.vndkdep.isVndk() {
@@ -124,10 +126,12 @@
 				if m.vndkdep.isVndkSp() {
 					if !inList(m.Name(), vndkSpLibraries) {
 						vndkSpLibraries = append(vndkSpLibraries, m.Name())
+						sort.Strings(vndkSpLibraries)
 					}
 				} else {
 					if !inList(m.Name(), vndkCoreLibraries) {
 						vndkCoreLibraries = append(vndkCoreLibraries, m.Name())
+						sort.Strings(vndkCoreLibraries)
 					}
 				}
 			}
diff --git a/cmd/multiproduct_kati/main.go b/cmd/multiproduct_kati/main.go
index fb1c890..e771c15 100644
--- a/cmd/multiproduct_kati/main.go
+++ b/cmd/multiproduct_kati/main.go
@@ -233,6 +233,9 @@
 	var wg sync.WaitGroup
 	productConfigs := make(chan Product, len(products))
 
+	finder := build.NewSourceFinder(buildCtx, config)
+	defer finder.Shutdown()
+
 	// Run the product config for every product in parallel
 	for _, product := range products {
 		wg.Add(1)
@@ -276,6 +279,7 @@
 
 			productConfig := build.NewConfig(productCtx)
 			productConfig.Environment().Set("OUT_DIR", productOutDir)
+			build.FindSources(productCtx, productConfig, finder)
 			productConfig.Lunch(productCtx, product, *buildVariant)
 
 			build.Build(productCtx, productConfig, build.BuildProductConfig)
diff --git a/cmd/soong_ui/main.go b/cmd/soong_ui/main.go
index 94d6d5c..8a26171 100644
--- a/cmd/soong_ui/main.go
+++ b/cmd/soong_ui/main.go
@@ -95,5 +95,9 @@
 		}
 	}
 
+	f := build.NewSourceFinder(buildCtx, config)
+	defer f.Shutdown()
+	build.FindSources(buildCtx, config, f)
+
 	build.Build(buildCtx, config, build.BuildAll)
 }
diff --git a/cmd/soong_zip/soong_zip.go b/cmd/soong_zip/soong_zip.go
index 8407788..d634dda 100644
--- a/cmd/soong_zip/soong_zip.go
+++ b/cmd/soong_zip/soong_zip.go
@@ -646,15 +646,24 @@
 }
 
 func (z *zipWriter) writeDirectory(dir string) error {
-	if dir != "" && !strings.HasSuffix(dir, "/") {
-		dir = dir + "/"
+	// clean the input
+	cleanDir := filepath.Clean(dir)
+
+	// discover any uncreated directories in the path
+	zipDirs := []string{}
+	for cleanDir != "" && cleanDir != "." && !z.createdDirs[cleanDir] {
+
+		z.createdDirs[cleanDir] = true
+		// parent directories precede their children
+		zipDirs = append([]string{cleanDir}, zipDirs...)
+
+		cleanDir = filepath.Dir(cleanDir)
 	}
 
-	for dir != "" && dir != "./" && !z.createdDirs[dir] {
-		z.createdDirs[dir] = true
-
+	// make a directory entry for each uncreated directory
+	for _, cleanDir := range zipDirs {
 		dirHeader := &zip.FileHeader{
-			Name: dir,
+			Name: cleanDir + "/",
 		}
 		dirHeader.SetMode(0700 | os.ModeDir)
 		dirHeader.SetModTime(z.time)
@@ -665,8 +674,6 @@
 		}
 		close(ze)
 		z.writeOps <- ze
-
-		dir, _ = filepath.Split(dir)
 	}
 
 	return nil
diff --git a/finder/finder.go b/finder/finder.go
index f15c8c1..8f9496d 100644
--- a/finder/finder.go
+++ b/finder/finder.go
@@ -148,10 +148,11 @@
 	filesystem          fs.FileSystem
 
 	// temporary state
-	threadPool *threadPool
-	mutex      sync.Mutex
-	fsErrs     []fsErr
-	errlock    sync.Mutex
+	threadPool        *threadPool
+	mutex             sync.Mutex
+	fsErrs            []fsErr
+	errlock           sync.Mutex
+	shutdownWaitgroup sync.WaitGroup
 
 	// non-temporary state
 	modifiedFlag int32
@@ -183,6 +184,8 @@
 
 		nodes:  *newPathMap("/"),
 		DbPath: dbPath,
+
+		shutdownWaitgroup: sync.WaitGroup{},
 	}
 
 	f.loadFromFilesystem()
@@ -195,9 +198,12 @@
 
 	// confirm that every path mentioned in the CacheConfig exists
 	for _, path := range cacheParams.RootDirs {
+		if !filepath.IsAbs(path) {
+			path = filepath.Join(f.cacheMetadata.Config.WorkingDirectory, path)
+		}
 		node := f.nodes.GetNode(filepath.Clean(path), false)
 		if node == nil || node.ModTime == 0 {
-			return nil, fmt.Errorf("%v does not exist\n", path)
+			return nil, fmt.Errorf("path %v was specified to be included in the cache but does not exist\n", path)
 		}
 	}
 
@@ -310,20 +316,32 @@
 	return results
 }
 
-// Shutdown saves the contents of the Finder to its database file
+// Shutdown declares that the finder is no longer needed and waits for its cleanup to complete
+// Currently, that only entails waiting for the database dump to complete.
 func (f *Finder) Shutdown() {
-	f.verbosef("Shutting down\n")
+	f.waitForDbDump()
+}
+
+// End of public api
+
+func (f *Finder) goDumpDb() {
 	if f.wasModified() {
-		err := f.dumpDb()
-		if err != nil {
-			f.verbosef("%v\n", err)
-		}
+		f.shutdownWaitgroup.Add(1)
+		go func() {
+			err := f.dumpDb()
+			if err != nil {
+				f.verbosef("%v\n", err)
+			}
+			f.shutdownWaitgroup.Done()
+		}()
 	} else {
 		f.verbosef("Skipping dumping unmodified db\n")
 	}
 }
 
-// End of public api
+func (f *Finder) waitForDbDump() {
+	f.shutdownWaitgroup.Wait()
+}
 
 // joinCleanPaths is like filepath.Join but is faster because
 // joinCleanPaths doesn't have to support paths ending in "/" or containing ".."
@@ -353,6 +371,8 @@
 		f.startWithoutExternalCache()
 	}
 
+	f.goDumpDb()
+
 	f.threadPool = nil
 }
 
diff --git a/finder/finder_test.go b/finder/finder_test.go
index 15c3728..8d1bbd7 100644
--- a/finder/finder_test.go
+++ b/finder/finder_test.go
@@ -466,12 +466,13 @@
 	create(t, "/cwd/hi.txt", filesystem)
 	create(t, "/cwd/a/hi.txt", filesystem)
 	create(t, "/cwd/a/a/hi.txt", filesystem)
+	create(t, "/rel/a/hi.txt", filesystem)
 
 	finder := newFinder(
 		t,
 		filesystem,
 		CacheParams{
-			RootDirs:     []string{"/cwd", "/tmp/include"},
+			RootDirs:     []string{"/cwd", "../rel", "/tmp/include"},
 			IncludeFiles: []string{"hi.txt"},
 		},
 	)
@@ -491,6 +492,10 @@
 			"a/hi.txt",
 			"a/a/hi.txt"})
 
+	foundPaths = finder.FindNamedAt("/rel", "hi.txt")
+	assertSameResponse(t, foundPaths,
+		[]string{"/rel/a/hi.txt"})
+
 	foundPaths = finder.FindNamedAt("/tmp/include", "hi.txt")
 	assertSameResponse(t, foundPaths, []string{"/tmp/include/hi.txt"})
 }
diff --git a/java/config/makevars.go b/java/config/makevars.go
index ec0d939..ac02782 100644
--- a/java/config/makevars.go
+++ b/java/config/makevars.go
@@ -34,6 +34,7 @@
 	ctx.Strict("JAVA", "${JavaCmd}")
 	ctx.Strict("JAVAC", "${JavacCmd}")
 	ctx.Strict("JAR", "${JarCmd}")
+	ctx.Strict("JAR_ARGS", "${JarArgsCmd}")
 	ctx.Strict("JAVADOC", "${JavadocCmd}")
 	ctx.Strict("COMMON_JDK_FLAGS", "${CommonJdkFlags}")
 }
diff --git a/scripts/reverse_path.py b/scripts/reverse_path.py
deleted file mode 100755
index 7b7d621..0000000
--- a/scripts/reverse_path.py
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/usr/bin/env python
-
-from __future__ import print_function
-
-import os
-import sys
-
-# Find the best reverse path to reference the current directory from another
-# directory. We use this to find relative paths to and from the source and build
-# directories.
-#
-# If the directory is given as an absolute path, return an absolute path to the
-# current directory.
-#
-# If there's a symlink involved, and the same relative path would not work if
-# the symlink was replace with a regular directory, then return an absolute
-# path. This handles paths like out -> /mnt/ssd/out
-#
-# For symlinks that can use the same relative path (out -> out.1), just return
-# the relative path. That way out.1 can be renamed as long as the symlink is
-# updated.
-#
-# For everything else, just return the relative path. That allows the source and
-# output directories to be moved as long as they stay in the same position
-# relative to each other.
-def reverse_path(path):
-    if path.startswith("/"):
-        return os.path.abspath('.')
-
-    realpath = os.path.relpath(os.path.realpath('.'), os.path.realpath(path))
-    relpath = os.path.relpath('.', path)
-
-    if realpath != relpath:
-        return os.path.abspath('.')
-
-    return relpath
-
-
-if __name__ == '__main__':
-    print(reverse_path(sys.argv[1]))
diff --git a/scripts/reverse_path_test.py b/scripts/reverse_path_test.py
deleted file mode 100755
index 5577693..0000000
--- a/scripts/reverse_path_test.py
+++ /dev/null
@@ -1,47 +0,0 @@
-#!/usr/bin/env python
-
-from __future__ import print_function
-
-import os
-import shutil
-import tempfile
-import unittest
-
-from reverse_path import reverse_path
-
-class TestReversePath(unittest.TestCase):
-    def setUp(self):
-        self.tmpdir = tempfile.mkdtemp()
-        os.chdir(self.tmpdir)
-
-    def tearDown(self):
-        shutil.rmtree(self.tmpdir)
-
-    def test_absolute(self):
-        self.assertEqual(self.tmpdir, reverse_path('/out'))
-
-    def test_relative(self):
-        os.mkdir('a')
-        os.mkdir('b')
-
-        self.assertEqual('..', reverse_path('a'))
-
-        os.chdir('a')
-        self.assertEqual('a', reverse_path('..'))
-        self.assertEqual('.', reverse_path('../a'))
-        self.assertEqual('../a', reverse_path('../b'))
-
-    def test_symlink(self):
-        os.mkdir('b')
-        os.symlink('b', 'a')
-        os.mkdir('b/d')
-        os.symlink('b/d', 'c')
-
-        self.assertEqual('..', reverse_path('a'))
-        self.assertEqual('..', reverse_path('b'))
-        self.assertEqual(self.tmpdir, reverse_path('c'))
-        self.assertEqual('../..', reverse_path('b/d'))
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/soong.bash b/soong.bash
index d832eb2..41608d5 100755
--- a/soong.bash
+++ b/soong.bash
@@ -1,48 +1,8 @@
 #!/bin/bash
 
-set -e
-
-# Switch to the build directory
-cd $(dirname "${BASH_SOURCE[0]}")
-
-if [ -z "$NO_DEPRECATION_WARNING" ]; then
-    echo '== WARNING: bootstrap.bash & ./soong are deprecated ==' >&2
-    echo 'Use `m --skip-make` with a standalone OUT_DIR instead.' >&2
-    echo 'Without envsetup.sh, use:' >&2
-    echo '  build/soong/soong_ui.bash --make-mode --skip-make' >&2
-    echo '======================================================' >&2
-fi
-
-# The source directory path and operating system will get written to
-# .soong.bootstrap by the bootstrap script.
-
-BOOTSTRAP=".soong.bootstrap"
-if [ ! -f "${BOOTSTRAP}" ]; then
-    echo "Error: soong script must be located in a directory created by bootstrap.bash"
-    exit 1
-fi
-
-source "${BOOTSTRAP}"
-
-# Now switch to the source directory so that all the relative paths from
-# $BOOTSTRAP are correct
-cd ${SRCDIR_FROM_BUILDDIR}
-
-# Ninja can't depend on environment variables, so do a manual comparison
-# of the relevant environment variables from the last build using the
-# soong_env tool and trigger a build manifest regeneration if necessary
-ENVFILE="${BUILDDIR}/.soong.environment"
-ENVTOOL="${BUILDDIR}/.bootstrap/bin/soong_env"
-if [ -f "${ENVFILE}" ]; then
-    if [ -x "${ENVTOOL}" ]; then
-        if ! "${ENVTOOL}" "${ENVFILE}"; then
-            echo "forcing build manifest regeneration"
-            rm -f "${ENVFILE}"
-        fi
-    else
-        echo "Missing soong_env tool, forcing build manifest regeneration"
-        rm -f "${ENVFILE}"
-    fi
-fi
-
-BUILDDIR="${BUILDDIR}" NINJA="prebuilts/build-tools/${PREBUILTOS}/bin/ninja" build/blueprint/blueprint.bash "$@"
+echo '==== ERROR: bootstrap.bash & ./soong are obsolete ====' >&2
+echo 'Use `m --skip-make` with a standalone OUT_DIR instead.' >&2
+echo 'Without envsetup.sh, use:' >&2
+echo '  build/soong/soong_ui.bash --make-mode --skip-make' >&2
+echo '======================================================' >&2
+exit 1
diff --git a/ui/build/Android.bp b/ui/build/Android.bp
index 548baee..34c21f7 100644
--- a/ui/build/Android.bp
+++ b/ui/build/Android.bp
@@ -19,6 +19,8 @@
         "soong-ui-logger",
         "soong-ui-tracer",
         "soong-shared",
+        "soong-finder",
+        "blueprint-microfactory",
     ],
     srcs: [
         "build.go",
@@ -27,6 +29,7 @@
         "context.go",
         "environment.go",
         "exec.go",
+        "finder.go",
         "kati.go",
         "make.go",
         "ninja.go",
diff --git a/ui/build/build.go b/ui/build/build.go
index 9650eaa..45d18e0 100644
--- a/ui/build/build.go
+++ b/ui/build/build.go
@@ -32,6 +32,7 @@
 	// The ninja_build file is used by our buildbots to understand that the output
 	// can be parsed as ninja output.
 	ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), "ninja_build"))
+	ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), ".out-dir"))
 }
 
 var combinedBuildNinjaTemplate = template.Must(template.New("combined").Parse(`
@@ -155,7 +156,6 @@
 
 	if what&BuildSoong != 0 {
 		// Run Soong
-		runSoongBootstrap(ctx, config)
 		runSoong(ctx, config)
 	}
 
diff --git a/ui/build/config.go b/ui/build/config.go
index 045f674..1c2f73b 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -280,6 +280,10 @@
 	return shared.TempDirForOutDir(c.SoongOutDir())
 }
 
+func (c *configImpl) FileListDir() string {
+	return filepath.Join(c.OutDir(), ".module_paths")
+}
+
 func (c *configImpl) KatiSuffix() string {
 	if c.katiSuffix != "" {
 		return c.katiSuffix
diff --git a/ui/build/finder.go b/ui/build/finder.go
new file mode 100644
index 0000000..05dec3a
--- /dev/null
+++ b/ui/build/finder.go
@@ -0,0 +1,102 @@
+// 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 (
+	"android/soong/finder"
+	"android/soong/fs"
+	"android/soong/ui/logger"
+	"bytes"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+)
+
+// This file provides an interface to the Finder for use in Soong UI
+// This file stores configuration information about which files to find
+
+// NewSourceFinder returns a new Finder configured to search for source files.
+// Callers of NewSourceFinder should call <f.Shutdown()> when done
+func NewSourceFinder(ctx Context, config Config) (f *finder.Finder) {
+	ctx.BeginTrace("find modules")
+	defer ctx.EndTrace()
+
+	dir, err := os.Getwd()
+	if err != nil {
+		ctx.Fatalf("No working directory for module-finder: %v", err.Error())
+	}
+	cacheParams := finder.CacheParams{
+		WorkingDirectory: dir,
+		RootDirs:         []string{"."},
+		ExcludeDirs:      []string{".git", ".repo"},
+		PruneFiles:       []string{".out-dir", ".find-ignore"},
+		IncludeFiles:     []string{"Android.mk", "Android.bp", "Blueprints", "CleanSpec.mk"},
+	}
+	dumpDir := config.FileListDir()
+	f, err = finder.New(cacheParams, fs.OsFs, logger.New(ioutil.Discard),
+		filepath.Join(dumpDir, "files.db"))
+	if err != nil {
+		ctx.Fatalf("Could not create module-finder: %v", err)
+	}
+	return f
+}
+
+// FindSources searches for source files known to <f> and writes them to the filesystem for
+// use later.
+func FindSources(ctx Context, config Config, f *finder.Finder) {
+	// note that dumpDir in FindSources may be different than dumpDir in NewSourceFinder
+	// if a caller such as multiproduct_kati wants to share one Finder among several builds
+	dumpDir := config.FileListDir()
+	os.MkdirAll(dumpDir, 0777)
+
+	androidMks := f.FindFirstNamedAt(".", "Android.mk")
+	err := dumpListToFile(androidMks, filepath.Join(dumpDir, "Android.mk.list"))
+	if err != nil {
+		ctx.Fatalf("Could not export module list: %v", err)
+	}
+
+	cleanSpecs := f.FindFirstNamedAt(".", "CleanSpec.mk")
+	dumpListToFile(cleanSpecs, filepath.Join(dumpDir, "CleanSpec.mk.list"))
+	if err != nil {
+		ctx.Fatalf("Could not export module list: %v", err)
+	}
+
+	isBlueprintFile := func(dir finder.DirEntries) (dirs []string, files []string) {
+		files = []string{}
+		for _, file := range dir.FileNames {
+			if file == "Android.bp" || file == "Blueprints" {
+				files = append(files, file)
+			}
+		}
+
+		return dir.DirNames, files
+	}
+	androidBps := f.FindMatching(".", isBlueprintFile)
+	err = dumpListToFile(androidBps, filepath.Join(dumpDir, "Android.bp.list"))
+	if err != nil {
+		ctx.Fatalf("Could not find modules: %v", err)
+	}
+}
+
+func dumpListToFile(list []string, filePath string) (err error) {
+	desiredText := strings.Join(list, "\n")
+	desiredBytes := []byte(desiredText)
+	actualBytes, readErr := ioutil.ReadFile(filePath)
+	if readErr != nil || !bytes.Equal(desiredBytes, actualBytes) {
+		err = ioutil.WriteFile(filePath, desiredBytes, 0777)
+	}
+	return err
+}
diff --git a/ui/build/soong.go b/ui/build/soong.go
index 2af3616..8220597 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -15,37 +15,101 @@
 package build
 
 import (
+	"os"
 	"path/filepath"
+	"strconv"
+	"time"
+
+	"github.com/google/blueprint/microfactory"
 )
 
-func runSoongBootstrap(ctx Context, config Config) {
-	ctx.BeginTrace("bootstrap soong")
-	defer ctx.EndTrace()
-
-	cmd := Command(ctx, config, "soong bootstrap", "./bootstrap.bash")
-	cmd.Environment.Set("BUILDDIR", config.SoongOutDir())
-	cmd.Environment.Set("NINJA_BUILDDIR", config.OutDir())
-	cmd.Environment.Set("NO_DEPRECATION_WARNING", "true")
-	cmd.Sandbox = soongSandbox
-	cmd.Stdout = ctx.Stdout()
-	cmd.Stderr = ctx.Stderr()
-	cmd.RunOrFatal()
-}
-
 func runSoong(ctx Context, config Config) {
 	ctx.BeginTrace("soong")
 	defer ctx.EndTrace()
 
-	cmd := Command(ctx, config, "soong",
-		filepath.Join(config.SoongOutDir(), "soong"), "-w", "dupbuild=err")
-	if config.IsVerbose() {
-		cmd.Args = append(cmd.Args, "-v")
+	func() {
+		ctx.BeginTrace("blueprint bootstrap")
+		defer ctx.EndTrace()
+
+		cmd := Command(ctx, config, "blueprint bootstrap", "build/blueprint/bootstrap.bash", "-t")
+		cmd.Environment.Set("BLUEPRINTDIR", "./build/blueprint")
+		cmd.Environment.Set("BOOTSTRAP", "./build/blueprint/bootstrap.bash")
+		cmd.Environment.Set("BUILDDIR", config.SoongOutDir())
+		cmd.Environment.Set("GOROOT", filepath.Join("./prebuilts/go", config.HostPrebuiltTag()))
+		cmd.Environment.Set("NINJA_BUILDDIR", config.OutDir())
+		cmd.Environment.Set("SRCDIR", ".")
+		cmd.Environment.Set("TOPNAME", "Android.bp")
+		cmd.Sandbox = soongSandbox
+		cmd.Stdout = ctx.Stdout()
+		cmd.Stderr = ctx.Stderr()
+		cmd.RunOrFatal()
+	}()
+
+	func() {
+		ctx.BeginTrace("environment check")
+		defer ctx.EndTrace()
+
+		envFile := filepath.Join(config.SoongOutDir(), ".soong.environment")
+		envTool := filepath.Join(config.SoongOutDir(), ".bootstrap/bin/soong_env")
+		if _, err := os.Stat(envFile); err == nil {
+			if _, err := os.Stat(envTool); err == nil {
+				cmd := Command(ctx, config, "soong_env", envTool, envFile)
+				cmd.Sandbox = soongSandbox
+				cmd.Stdout = ctx.Stdout()
+				cmd.Stderr = ctx.Stderr()
+				if err := cmd.Run(); err != nil {
+					ctx.Verboseln("soong_env failed, forcing manifest regeneration")
+					os.Remove(envFile)
+				}
+			} else {
+				ctx.Verboseln("Missing soong_env tool, forcing manifest regeneration")
+				os.Remove(envFile)
+			}
+		} else if !os.IsNotExist(err) {
+			ctx.Fatalf("Failed to stat %f: %v", envFile, err)
+		}
+	}()
+
+	func() {
+		ctx.BeginTrace("minibp")
+		defer ctx.EndTrace()
+
+		var cfg microfactory.Config
+		cfg.Map("github.com/google/blueprint", "build/blueprint")
+
+		if absPath, err := filepath.Abs("."); err == nil {
+			cfg.TrimPath = absPath
+		}
+
+		minibp := filepath.Join(config.SoongOutDir(), ".minibootstrap/minibp")
+		if _, err := microfactory.Build(&cfg, minibp, "github.com/google/blueprint/bootstrap/minibp"); err != nil {
+			ctx.Fatalln("Failed to build minibp:", err)
+		}
+	}()
+
+	ninja := func(name, file string) {
+		ctx.BeginTrace(name)
+		defer ctx.EndTrace()
+
+		cmd := Command(ctx, config, "soong "+name,
+			config.PrebuiltBuildTool("ninja"),
+			"-d", "keepdepfile",
+			"-w", "dupbuild=err",
+			"-j", strconv.Itoa(config.Parallel()),
+			"-f", filepath.Join(config.SoongOutDir(), file))
+		if config.IsVerbose() {
+			cmd.Args = append(cmd.Args, "-v")
+		}
+		cmd.Environment.Set("GOROOT", filepath.Join("./prebuilts/go", config.HostPrebuiltTag()))
+		cmd.Sandbox = soongSandbox
+		cmd.Stdin = ctx.Stdin()
+		cmd.Stdout = ctx.Stdout()
+		cmd.Stderr = ctx.Stderr()
+
+		defer ctx.ImportNinjaLog(filepath.Join(config.OutDir(), ".ninja_log"), time.Now())
+		cmd.RunOrFatal()
 	}
-	cmd.Environment.Set("SKIP_NINJA", "true")
-	cmd.Environment.Set("NO_DEPRECATION_WARNING", "true")
-	cmd.Sandbox = soongSandbox
-	cmd.Stdin = ctx.Stdin()
-	cmd.Stdout = ctx.Stdout()
-	cmd.Stderr = ctx.Stderr()
-	cmd.RunOrFatal()
+
+	ninja("minibootstrap", ".minibootstrap/build.ninja")
+	ninja("bootstrap", ".bootstrap/build.ninja")
 }