Snap for 8426163 from e59efdaf6be220d41a4092c4e87dc273a6e10776 to mainline-tzdata2-release

Change-Id: I87a3dadf645e1f326d82c3f101a9ef69022d6122
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
deleted file mode 100644
index deb261d..0000000
--- a/.github/workflows/build.yml
+++ /dev/null
@@ -1,47 +0,0 @@
-name: build
-
-on:
-  push:
-    branches: [ master ]
-  pull_request:
-    branches: [ master ]
-
-jobs:
-
-  build:
-    runs-on: ubuntu-latest
-    strategy:
-      matrix:
-        go: [ '1.14', '1.13' ]
-    name: Build and test on go ${{ matrix.go }}
-    steps:
-
-    - name: Set up Go ${{ matrix.go }}
-      uses: actions/setup-go@v2
-      with:
-        go-version: ${{ matrix.go }}
-      id: go
-
-    - name: Check out code
-      uses: actions/checkout@v2
-
-    - name: Install ninja
-      run: |
-        mkdir -p ${GITHUB_WORKSPACE}/ninja-bin; cd ${GITHUB_WORKSPACE}/ninja-bin
-        wget https://github.com/ninja-build/ninja/releases/download/v1.7.2/ninja-linux.zip
-        unzip ninja-linux.zip
-        rm ninja-linux.zip
-        echo "${GITHUB_WORKSPACE}/ninja-bin" >> $GITHUB_PATH
-
-    - name: Run gofmt
-      run: ./.gofmt.sh
-
-    - name: Test
-      run: go test ./...
-
-    - name: Test with race detector
-      run: go test -race -short ./...
-
-    - run: ./tests/test.sh
-    - run: ./tests/test_tree_tests.sh
-    - run: ./tests/test_tree_tests.sh -t
diff --git a/.travis.fix-fork.sh b/.travis.fix-fork.sh
new file mode 100755
index 0000000..af26716
--- /dev/null
+++ b/.travis.fix-fork.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+if echo $TRAVIS_BUILD_DIR | grep -vq "github.com/google/blueprint$" ; then
+  cd ../..
+  mkdir -p google
+  mv $TRAVIS_BUILD_DIR google/blueprint
+  cd google/blueprint
+  export TRAVIS_BUILD_DIR=$PWD
+fi
diff --git a/.gofmt.sh b/.travis.gofmt.sh
similarity index 100%
rename from .gofmt.sh
rename to .travis.gofmt.sh
diff --git a/.travis.install-ninja.sh b/.travis.install-ninja.sh
new file mode 100755
index 0000000..5309945
--- /dev/null
+++ b/.travis.install-ninja.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+# Version of ninja to build -- can be any git revision
+VERSION="v1.7.1"
+
+set -ev
+
+SCRIPT_HASH=$(sha1sum ${BASH_SOURCE[0]} | awk '{print $1}')
+
+cd ~
+if [[ -d ninjabin && "$SCRIPT_HASH" == "$(cat ninjabin/script_hash)" ]]; then
+  exit 0
+fi
+
+git clone https://github.com/martine/ninja
+cd ninja
+./configure.py --bootstrap
+
+mkdir -p ../ninjabin
+rm -f ../ninjabin/ninja
+echo -n $SCRIPT_HASH >../ninjabin/script_hash
+mv ninja ../ninjabin/
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..706e469
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,27 @@
+language: go
+
+go:
+    - "1.10"
+    - "1.11"
+    - "1.12"
+    - "1.13"
+
+cache:
+    directories:
+        - $HOME/ninjabin
+
+install:
+    - ./.travis.install-ninja.sh
+    - export PATH=$PATH:~/ninjabin
+
+before_script:
+    - source .travis.fix-fork.sh
+
+script:
+    - export GOROOT=$(go env GOROOT)
+    - ./.travis.gofmt.sh
+    - go test ./...
+    - go test -race -short ./...
+    - ./tests/test.sh
+    - ./tests/test_tree_tests.sh
+    - ./tests/test_tree_tests.sh -t
diff --git a/Blueprints b/Blueprints
index ab9fd3c..ecc0792 100644
--- a/Blueprints
+++ b/Blueprints
@@ -17,7 +17,6 @@
         "ninja_strings.go",
         "ninja_writer.go",
         "package_ctx.go",
-        "provider.go",
         "scope.go",
         "singleton_ctx.go",
     ],
@@ -27,7 +26,6 @@
         "module_ctx_test.go",
         "ninja_strings_test.go",
         "ninja_writer_test.go",
-        "provider_test.go",
         "splice_modules_test.go",
         "visit_test.go",
     ],
@@ -47,7 +45,6 @@
         "parser/modify_test.go",
         "parser/parser_test.go",
         "parser/printer_test.go",
-	"parser/sort_test.go",
     ],
 }
 
@@ -71,7 +68,6 @@
     testSrcs: [
         "pathtools/fs_test.go",
         "pathtools/glob_test.go",
-        "pathtools/lists_test.go",
     ],
 }
 
diff --git a/CODEOWNERS b/CODEOWNERS
deleted file mode 100644
index 8cf6944..0000000
--- a/CODEOWNERS
+++ /dev/null
@@ -1 +0,0 @@
-* @google/blueprint
diff --git a/OWNERS b/OWNERS
index 1ee860c..5dca797 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,2 +1,4 @@
-include platform/build/soong:/OWNERS
-
+asmundak@google.com
+ccross@android.com
+dwillemsen@google.com
+jungjw@google.com
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
deleted file mode 100644
index 317f5c4..0000000
--- a/PREUPLOAD.cfg
+++ /dev/null
@@ -1,6 +0,0 @@
-[Builtin Hooks]
-gofmt = true
-bpfmt = true
-
-[Hook Scripts]
-do_not_use_DO_NOT_MERGE = ${REPO_ROOT}/build/soong/scripts/check_do_not_merge.sh ${PREUPLOAD_COMMIT}
diff --git a/README.md b/README.md
index 961bc64..5370c05 100644
--- a/README.md
+++ b/README.md
@@ -1,25 +1,14 @@
 Blueprint Build System
 ======================
+[![Build Status](https://travis-ci.org/google/blueprint.svg?branch=master)](https://travis-ci.org/google/blueprint) 
 
-Blueprint is being archived on 2021 May 3.
-
-On 2021 May 3, we will be archiving the Blueprint project. This means it will
-not be possible to file new issues or open new pull requests for this GitHub
-project. As the project is being archived, patches -- including security
-patches -- will not be applied after May 3. The source tree will remain
-available, but changes to Blueprint in AOSP will not be merged here and
-Blueprint's source tree in AOSP will eventually stop being usable outside of
-Android.
-
-Whereas there are no meta-build systems one can use as a drop-in replacement for
-Blueprint, there are a number of build systems that can be used:
-
-* [Bazel](https://bazel.build), Google's multi-language build tool to build and
-  test software of any size, quickly and reliably
-* [Soong](https://source.android.com/setup/build), for building the Android
-  operating system itself
-* [CMake](https://cmake.org), an open-source, cross-platform family of tools
-  designed to build, test and package software
-* [Buck](https://buck.build), a fast build system that encourages the creation
-  of small, reusable modules over a variety of platforms and languages
-* The venerable [GNU Make](https://www.gnu.org/software/make/)
+Blueprint is a meta-build system that reads in Blueprints files that describe
+modules that need to be built, and produces a
+[Ninja](https://ninja-build.org/) manifest describing the commands that
+need to be run and their dependencies.  Where most build systems use built-in
+rules or a domain-specific language to describe the logic for converting module
+descriptions to build rules, Blueprint delegates this to per-project build
+logic written in Go.  For large, heterogenous projects this allows the inherent
+complexity of the build logic to be maintained in a high-level language, while
+still allowing simple changes to individual modules by modifying easy to
+understand Blueprints files.
diff --git a/bootstrap.bash b/bootstrap.bash
index b08bf1e..08b85b5 100755
--- a/bootstrap.bash
+++ b/bootstrap.bash
@@ -67,14 +67,12 @@
     echo "  -h: print a help message and exit"
     echo "  -b <builddir>: set the build directory"
     echo "  -t: run tests"
-    echo "  -n: use validations to depend on tests"
 }
 
 # Parse the command line flags.
-while getopts ":b:hnt" opt; do
+while getopts ":b:ht" opt; do
     case $opt in
         b) BUILDDIR="$OPTARG";;
-        n) USE_VALIDATIONS=true;;
         t) RUN_TESTS=true;;
         h)
             usage
@@ -95,9 +93,6 @@
 # If RUN_TESTS is set, behave like -t was passed in as an option.
 [ ! -z "$RUN_TESTS" ] && EXTRA_ARGS="${EXTRA_ARGS} -t"
 
-# If $USE_VALIDATIONS is set, pass --use-validations.
-[ ! -z "$USE_VALIDATIONS" ] && EXTRA_ARGS="${EXTRA_ARGS} --use-validations"
-
 # If EMPTY_NINJA_FILE is set, have the primary build write out a 0-byte ninja
 # file instead of a full length one. Useful if you don't plan on executing the
 # build, but want to verify the primary builder execution.
diff --git a/bootstrap/bootstrap.go b/bootstrap/bootstrap.go
index d5befd9..79e5c8e 100644
--- a/bootstrap/bootstrap.go
+++ b/bootstrap/bootstrap.go
@@ -17,6 +17,8 @@
 import (
 	"fmt"
 	"go/build"
+	"io/ioutil"
+	"os"
 	"path/filepath"
 	"runtime"
 	"strings"
@@ -57,7 +59,7 @@
 	compile = pctx.StaticRule("compile",
 		blueprint.RuleParams{
 			Command: "GOROOT='$goRoot' $compileCmd $parallelCompile -o $out.tmp " +
-				"$debugFlags -p $pkgPath -complete $incFlags -pack $in && " +
+				"-p $pkgPath -complete $incFlags -pack $in && " +
 				"if cmp --quiet $out.tmp $out; then rm $out.tmp; else mv -f $out.tmp $out; fi",
 			CommandDeps: []string{"$compileCmd"},
 			Description: "compile $out",
@@ -123,27 +125,14 @@
 
 	generateBuildNinja = pctx.StaticRule("build.ninja",
 		blueprint.RuleParams{
-			// TODO: it's kinda ugly that some parameters are computed from
-			// environment variables and some from Ninja parameters, but it's probably
-			// better to not to touch that while Blueprint and Soong are separate
-			// NOTE: The spaces at EOL are important because otherwise Ninja would
-			// omit all spaces between the different options.
-			Command: `cd "$$(dirname "$builder")" && ` +
-				`BUILDER="$$PWD/$$(basename "$builder")" && ` +
-				`cd / && ` +
-				`env -i "$$BUILDER" ` +
-				`    --top "$$TOP" ` +
-				`    --out "$buildDir" ` +
-				`    -n "$ninjaBuildDir" ` +
-				`    -d "$out.d" ` +
-				`    $extra`,
+			Command:     "$builder $extra -b $buildDir -n $ninjaBuildDir -d $out.d -globFile $globFile -o $out $in",
 			CommandDeps: []string{"$builder"},
 			Description: "$builder $out",
 			Deps:        blueprint.DepsGCC,
 			Depfile:     "$out.d",
 			Restat:      true,
 		},
-		"builder", "extra")
+		"builder", "extra", "generator", "globFile")
 
 	// Work around a Ninja issue.  See https://github.com/martine/ninja/pull/634
 	phony = pctx.StaticRule("phony",
@@ -155,7 +144,7 @@
 		"depfile")
 
 	_ = pctx.VariableFunc("BinDir", func(config interface{}) (string, error) {
-		return bootstrapBinDir(config), nil
+		return bootstrapBinDir(), nil
 	})
 
 	_ = pctx.VariableFunc("ToolDir", func(config interface{}) (string, error) {
@@ -178,23 +167,21 @@
 	isGoBinary()
 }
 
-func bootstrapBinDir(config interface{}) string {
-	return filepath.Join(config.(BootstrapConfig).BuildDir(), bootstrapSubDir, "bin")
+func bootstrapBinDir() string {
+	return filepath.Join(BuildDir, bootstrapSubDir, "bin")
 }
 
 func toolDir(config interface{}) string {
 	if c, ok := config.(ConfigBlueprintToolLocation); ok {
 		return filepath.Join(c.BlueprintToolLocation())
 	}
-	return filepath.Join(config.(BootstrapConfig).BuildDir(), "bin")
+	return filepath.Join(BuildDir, "bin")
 }
 
 func pluginDeps(ctx blueprint.BottomUpMutatorContext) {
 	if pkg, ok := ctx.Module().(*goPackage); ok {
-		if ctx.PrimaryModule() == ctx.Module() {
-			for _, plugin := range pkg.properties.PluginFor {
-				ctx.AddReverseDependency(ctx.Module(), nil, plugin)
-			}
+		for _, plugin := range pkg.properties.PluginFor {
+			ctx.AddReverseDependency(ctx.Module(), nil, plugin)
 		}
 	}
 }
@@ -224,7 +211,7 @@
 	}
 }
 
-func IsBootstrapModule(module blueprint.Module) bool {
+func isBootstrapModule(module blueprint.Module) bool {
 	_, isPackage := module.(*goPackage)
 	_, isBinary := module.(*goBinary)
 	return isPackage || isBinary
@@ -281,9 +268,6 @@
 }
 
 func (g *goPackage) DynamicDependencies(ctx blueprint.DynamicDependerModuleContext) []string {
-	if ctx.Module() != ctx.PrimaryModule() {
-		return nil
-	}
 	return g.properties.Deps
 }
 
@@ -313,16 +297,6 @@
 }
 
 func (g *goPackage) GenerateBuildActions(ctx blueprint.ModuleContext) {
-	// Allow the primary builder to create multiple variants.  Any variants after the first
-	// will copy outputs from the first.
-	if ctx.Module() != ctx.PrimaryModule() {
-		primary := ctx.PrimaryModule().(*goPackage)
-		g.pkgRoot = primary.pkgRoot
-		g.archiveFile = primary.archiveFile
-		g.testResultFile = primary.testResultFile
-		return
-	}
-
 	var (
 		name       = ctx.ModuleName()
 		hasPlugins = false
@@ -359,13 +333,11 @@
 		testSrcs = append(g.properties.TestSrcs, g.properties.Linux.TestSrcs...)
 	}
 
-	if g.config.runGoTests {
-		testArchiveFile := filepath.Join(testRoot(ctx, g.config),
-			filepath.FromSlash(g.properties.PkgPath)+".a")
-		g.testResultFile = buildGoTest(ctx, testRoot(ctx, g.config), testArchiveFile,
-			g.properties.PkgPath, srcs, genSrcs,
-			testSrcs, g.config.useValidations)
-	}
+	testArchiveFile := filepath.Join(testRoot(ctx, g.config),
+		filepath.FromSlash(g.properties.PkgPath)+".a")
+	g.testResultFile = buildGoTest(ctx, testRoot(ctx, g.config), testArchiveFile,
+		g.properties.PkgPath, srcs, genSrcs,
+		testSrcs)
 
 	buildGoPackage(ctx, g.pkgRoot, g.properties.PkgPath, g.archiveFile,
 		srcs, genSrcs)
@@ -412,9 +384,6 @@
 }
 
 func (g *goBinary) DynamicDependencies(ctx blueprint.DynamicDependerModuleContext) []string {
-	if ctx.Module() != ctx.PrimaryModule() {
-		return nil
-	}
 	return g.properties.Deps
 }
 
@@ -424,14 +393,6 @@
 }
 
 func (g *goBinary) GenerateBuildActions(ctx blueprint.ModuleContext) {
-	// Allow the primary builder to create multiple variants.  Any variants after the first
-	// will copy outputs from the first.
-	if ctx.Module() != ctx.PrimaryModule() {
-		primary := ctx.PrimaryModule().(*goBinary)
-		g.installPath = primary.installPath
-		return
-	}
-
 	var (
 		name            = ctx.ModuleName()
 		objDir          = moduleObjDir(ctx, g.config)
@@ -445,8 +406,10 @@
 
 	if g.properties.Tool_dir {
 		g.installPath = filepath.Join(toolDir(ctx.Config()), name)
+	} else if g.config.stage == StageMain {
+		g.installPath = filepath.Join(mainDir, "bin", name)
 	} else {
-		g.installPath = filepath.Join(stageDir(g.config), "bin", name)
+		g.installPath = filepath.Join(bootstrapDir, "bin", name)
 	}
 
 	ctx.VisitDepsDepthFirstIf(isGoPluginFor(name),
@@ -456,7 +419,7 @@
 		genSrcs = append(genSrcs, pluginSrc)
 	}
 
-	var testDeps []string
+	var deps []string
 
 	if hasPlugins && !buildGoPluginLoader(ctx, "main", pluginSrc) {
 		return
@@ -471,9 +434,10 @@
 		testSrcs = append(g.properties.TestSrcs, g.properties.Linux.TestSrcs...)
 	}
 
+	testDeps := buildGoTest(ctx, testRoot(ctx, g.config), testArchiveFile,
+		name, srcs, genSrcs, testSrcs)
 	if g.config.runGoTests {
-		testDeps = buildGoTest(ctx, testRoot(ctx, g.config), testArchiveFile,
-			name, srcs, genSrcs, testSrcs, g.config.useValidations)
+		deps = append(deps, testDeps...)
 	}
 
 	buildGoPackage(ctx, objDir, "main", archiveFile, srcs, genSrcs)
@@ -486,7 +450,9 @@
 			linkDeps = append(linkDeps, dep.GoPackageTarget())
 			libDir := dep.GoPkgRoot()
 			libDirFlags = append(libDirFlags, "-L "+libDir)
-			testDeps = append(testDeps, dep.GoTestTargets()...)
+			if g.config.runGoTests {
+				deps = append(deps, dep.GoTestTargets()...)
+			}
 		})
 
 	linkArgs := map[string]string{}
@@ -503,20 +469,12 @@
 		Optional:  true,
 	})
 
-	var orderOnlyDeps, validationDeps []string
-	if g.config.useValidations {
-		validationDeps = testDeps
-	} else {
-		orderOnlyDeps = testDeps
-	}
-
 	ctx.Build(pctx, blueprint.BuildParams{
-		Rule:        cp,
-		Outputs:     []string{g.installPath},
-		Inputs:      []string{aoutFile},
-		OrderOnly:   orderOnlyDeps,
-		Validations: validationDeps,
-		Optional:    !g.properties.Default,
+		Rule:      cp,
+		Outputs:   []string{g.installPath},
+		Inputs:    []string{aoutFile},
+		OrderOnly: deps,
+		Optional:  !g.properties.Default,
 	})
 }
 
@@ -581,7 +539,7 @@
 }
 
 func buildGoTest(ctx blueprint.ModuleContext, testRoot, testPkgArchive,
-	pkgPath string, srcs, genSrcs, testSrcs []string, useValidations bool) []string {
+	pkgPath string, srcs, genSrcs, testSrcs []string) []string {
 
 	if len(testSrcs) == 0 {
 		return nil
@@ -643,19 +601,11 @@
 		Optional: true,
 	})
 
-	var orderOnlyDeps, validationDeps []string
-	if useValidations {
-		validationDeps = testDeps
-	} else {
-		orderOnlyDeps = testDeps
-	}
-
 	ctx.Build(pctx, blueprint.BuildParams{
-		Rule:        test,
-		Outputs:     []string{testPassed},
-		Inputs:      []string{testFile},
-		OrderOnly:   orderOnlyDeps,
-		Validations: validationDeps,
+		Rule:      test,
+		Outputs:   []string{testPassed},
+		Inputs:    []string{testFile},
+		OrderOnly: testDeps,
 		Args: map[string]string{
 			"pkg":       pkgPath,
 			"pkgSrcDir": filepath.Dir(testFiles[0]),
@@ -686,62 +636,89 @@
 	var primaryBuilders []*goBinary
 	// blueprintTools contains blueprint go binaries that will be built in StageMain
 	var blueprintTools []string
-	ctx.VisitAllModulesIf(isBootstrapBinaryModule,
-		func(module blueprint.Module) {
-			if ctx.PrimaryModule(module) == module {
-				binaryModule := module.(*goBinary)
-
-				if binaryModule.properties.Tool_dir {
-					blueprintTools = append(blueprintTools, binaryModule.InstallPath())
-				}
-				if binaryModule.properties.PrimaryBuilder {
-					primaryBuilders = append(primaryBuilders, binaryModule)
-				}
+	// blueprintTests contains the result files from the tests
+	var blueprintTests []string
+	ctx.VisitAllModules(func(module blueprint.Module) {
+		if binaryModule, ok := module.(*goBinary); ok {
+			if binaryModule.properties.Tool_dir {
+				blueprintTools = append(blueprintTools, binaryModule.InstallPath())
 			}
-		})
+			if binaryModule.properties.PrimaryBuilder {
+				primaryBuilders = append(primaryBuilders, binaryModule)
+			}
+		}
 
-	var primaryBuilderCmdlinePrefix []string
-	var primaryBuilderName string
+		if packageModule, ok := module.(goPackageProducer); ok {
+			blueprintTests = append(blueprintTests, packageModule.GoTestTargets()...)
+		}
+	})
 
-	if len(primaryBuilders) == 0 {
+	var extraSharedFlagArray []string
+	if s.config.runGoTests {
+		extraSharedFlagArray = append(extraSharedFlagArray, "-t")
+	}
+	if s.config.moduleListFile != "" {
+		extraSharedFlagArray = append(extraSharedFlagArray, "-l", s.config.moduleListFile)
+	}
+	if s.config.emptyNinjaFile {
+		extraSharedFlagArray = append(extraSharedFlagArray, "--empty-ninja-file")
+	}
+	extraSharedFlagString := strings.Join(extraSharedFlagArray, " ")
+
+	var primaryBuilderName, primaryBuilderExtraFlags string
+	switch len(primaryBuilders) {
+	case 0:
 		// If there's no primary builder module then that means we'll use minibp
 		// as the primary builder.  We can trigger its primary builder mode with
 		// the -p flag.
 		primaryBuilderName = "minibp"
-		primaryBuilderCmdlinePrefix = append(primaryBuilderCmdlinePrefix, "-p")
-	} else if len(primaryBuilders) > 1 {
+		primaryBuilderExtraFlags = "-p " + extraSharedFlagString
+
+	case 1:
+		primaryBuilderName = ctx.ModuleName(primaryBuilders[0])
+		primaryBuilderExtraFlags = extraSharedFlagString
+
+	default:
 		ctx.Errorf("multiple primary builder modules present:")
 		for _, primaryBuilder := range primaryBuilders {
 			ctx.ModuleErrorf(primaryBuilder, "<-- module %s",
 				ctx.ModuleName(primaryBuilder))
 		}
 		return
-	} else {
-		primaryBuilderName = ctx.ModuleName(primaryBuilders[0])
 	}
 
 	primaryBuilderFile := filepath.Join("$BinDir", primaryBuilderName)
+
+	// Get the filename of the top-level Blueprints file to pass to minibp.
+	topLevelBlueprints := filepath.Join("$srcDir",
+		filepath.Base(s.config.topLevelBlueprintsFile))
+
 	ctx.SetNinjaBuildDir(pctx, "${ninjaBuildDir}")
 
 	if s.config.stage == StagePrimary {
-		ctx.AddSubninja(s.config.globFile)
+		mainNinjaFile := filepath.Join("$buildDir", "build.ninja")
+		primaryBuilderNinjaGlobFile := absolutePath(filepath.Join(BuildDir, bootstrapSubDir, "build-globs.ninja"))
 
-		for _, i := range s.config.primaryBuilderInvocations {
-			flags := make([]string, 0)
-			flags = append(flags, primaryBuilderCmdlinePrefix...)
-			flags = append(flags, i.Args...)
-
-			// Build the main build.ninja
-			ctx.Build(pctx, blueprint.BuildParams{
-				Rule:    generateBuildNinja,
-				Outputs: i.Outputs,
-				Inputs:  i.Inputs,
-				Args: map[string]string{
-					"builder": primaryBuilderFile,
-					"extra":   strings.Join(flags, " "),
-				},
-			})
+		if _, err := os.Stat(primaryBuilderNinjaGlobFile); os.IsNotExist(err) {
+			err = ioutil.WriteFile(primaryBuilderNinjaGlobFile, nil, 0666)
+			if err != nil {
+				ctx.Errorf("Failed to create empty ninja file: %s", err)
+			}
 		}
+
+		ctx.AddSubninja(primaryBuilderNinjaGlobFile)
+
+		// Build the main build.ninja
+		ctx.Build(pctx, blueprint.BuildParams{
+			Rule:    generateBuildNinja,
+			Outputs: []string{mainNinjaFile},
+			Inputs:  []string{topLevelBlueprints},
+			Args: map[string]string{
+				"builder":  primaryBuilderFile,
+				"extra":    primaryBuilderExtraFlags,
+				"globFile": primaryBuilderNinjaGlobFile,
+			},
+		})
 	}
 
 	if s.config.stage == StageMain {
@@ -764,8 +741,8 @@
 		docsFile := filepath.Join(docsDir, primaryBuilderName+".html")
 		bigbpDocs := ctx.Rule(pctx, "bigbpDocs",
 			blueprint.RuleParams{
-				Command: fmt.Sprintf("%s -b $buildDir --docs $out %s", primaryBuilderFile,
-					s.config.topLevelBlueprintsFile),
+				Command: fmt.Sprintf("%s %s -b $buildDir --docs $out %s", primaryBuilderFile,
+					primaryBuilderExtraFlags, topLevelBlueprints),
 				CommandDeps: []string{primaryBuilderFile},
 				Description: fmt.Sprintf("%s docs $out", primaryBuilderName),
 			})
@@ -788,6 +765,14 @@
 			Outputs: []string{"blueprint_tools"},
 			Inputs:  blueprintTools,
 		})
+
+		// Add a phony target for running all of the tests
+		ctx.Build(pctx, blueprint.BuildParams{
+			Rule:    blueprint.Phony,
+			Outputs: []string{"blueprint_tests"},
+			Inputs:  blueprintTests,
+		})
+
 	}
 }
 
diff --git a/bootstrap/bpdoc/bpdoc.go b/bootstrap/bpdoc/bpdoc.go
index 8ed02c2..4abf2e7 100644
--- a/bootstrap/bpdoc/bpdoc.go
+++ b/bootstrap/bpdoc/bpdoc.go
@@ -5,7 +5,6 @@
 	"html/template"
 	"reflect"
 	"sort"
-	"strings"
 
 	"github.com/google/blueprint/proptools"
 )
@@ -59,7 +58,6 @@
 	OtherTexts []template.HTML
 	Properties []Property
 	Default    string
-	Anonymous  bool
 }
 
 func AllPackages(pkgFiles map[string][]string, moduleTypeNameFactories map[string]reflect.Value,
@@ -77,7 +75,6 @@
 			return nil, err
 		}
 		// Some pruning work
-		removeAnonymousProperties(mtInfo)
 		removeEmptyPropertyStructs(mtInfo)
 		collapseDuplicatePropertyStructs(mtInfo)
 		collapseNestedPropertyStructs(mtInfo)
@@ -131,9 +128,7 @@
 		}
 		ps.ExcludeByTag("blueprint", "mutated")
 
-		for _, nestedProperty := range nestedPropertyStructs(v) {
-			nestedName := nestedProperty.nestPoint
-			nestedValue := nestedProperty.value
+		for nestedName, nestedValue := range nestedPropertyStructs(v) {
 			nestedType := nestedValue.Type()
 
 			// Ignore property structs with unexported or unnamed types
@@ -145,28 +140,12 @@
 				return nil, err
 			}
 			nested.ExcludeByTag("blueprint", "mutated")
-			if nestedName == "" {
-				ps.Nest(nested)
-			} else {
-				nestPoint := ps.GetByName(nestedName)
-				if nestPoint == nil {
-					return nil, fmt.Errorf("nesting point %q not found", nestedName)
-				}
-				nestPoint.Nest(nested)
+			nestPoint := ps.GetByName(nestedName)
+			if nestPoint == nil {
+				return nil, fmt.Errorf("nesting point %q not found", nestedName)
 			}
 
-			if nestedProperty.anonymous {
-				if nestedName != "" {
-					nestedName += "."
-				}
-				nestedName += proptools.PropertyNameForField(nested.Name)
-				nestedProp := ps.GetByName(nestedName)
-				// Anonymous properties may have already been omitted, no need to ensure they are filtered later
-				if nestedProp != nil {
-					// Set property to anonymous to allow future filtering
-					nestedProp.SetAnonymous()
-				}
-			}
+			nestPoint.Nest(nested)
 		}
 		mt.PropertyStructs = append(mt.PropertyStructs, ps)
 	}
@@ -174,31 +153,10 @@
 	return mt, nil
 }
 
-type nestedProperty struct {
-	nestPoint string
-	value     reflect.Value
-	anonymous bool
-}
-
-func nestedPropertyStructs(s reflect.Value) []nestedProperty {
-	ret := make([]nestedProperty, 0)
+func nestedPropertyStructs(s reflect.Value) map[string]reflect.Value {
+	ret := make(map[string]reflect.Value)
 	var walk func(structValue reflect.Value, prefix string)
 	walk = func(structValue reflect.Value, prefix string) {
-		var nestStruct func(field reflect.StructField, value reflect.Value, fieldName string)
-		nestStruct = func(field reflect.StructField, value reflect.Value, fieldName string) {
-			nestPoint := prefix
-			if field.Anonymous {
-				nestPoint = strings.TrimSuffix(nestPoint, ".")
-			} else {
-				nestPoint = nestPoint + proptools.PropertyNameForField(fieldName)
-			}
-			ret = append(ret, nestedProperty{nestPoint: nestPoint, value: value, anonymous: field.Anonymous})
-			if nestPoint != "" {
-				nestPoint += "."
-			}
-			walk(value, nestPoint)
-		}
-
 		typ := structValue.Type()
 		for i := 0; i < structValue.NumField(); i++ {
 			field := typ.Field(i)
@@ -216,9 +174,8 @@
 			case reflect.Bool, reflect.String, reflect.Slice, reflect.Int, reflect.Uint:
 				// Nothing
 			case reflect.Struct:
-				nestStruct(field, fieldValue, field.Name)
+				walk(fieldValue, prefix+proptools.PropertyNameForField(field.Name)+".")
 			case reflect.Ptr, reflect.Interface:
-
 				if !fieldValue.IsNil() {
 					// We leave the pointer intact and zero out the struct that's
 					// pointed to.
@@ -231,7 +188,9 @@
 						elem = elem.Elem()
 					}
 					if elem.Kind() == reflect.Struct {
-						nestStruct(field, elem, field.Name)
+						nestPoint := prefix + proptools.PropertyNameForField(field.Name)
+						ret[nestPoint] = elem
+						walk(elem, nestPoint+".")
 					}
 				}
 			default:
@@ -255,27 +214,6 @@
 	}
 }
 
-// Remove any property structs that are anonymous
-func removeAnonymousProperties(mt *ModuleType) {
-	var removeAnonymousProps func(props []Property) []Property
-	removeAnonymousProps = func(props []Property) []Property {
-		newProps := make([]Property, 0, len(props))
-		for _, p := range props {
-			if p.Anonymous {
-				continue
-			}
-			if len(p.Properties) > 0 {
-				p.Properties = removeAnonymousProps(p.Properties)
-			}
-			newProps = append(newProps, p)
-		}
-		return newProps
-	}
-	for _, ps := range mt.PropertyStructs {
-		ps.Properties = removeAnonymousProps(ps.Properties)
-	}
-}
-
 // Squashes duplicates of the same property struct into single entries
 func collapseDuplicatePropertyStructs(mt *ModuleType) {
 	var collapsed []*PropertyStruct
diff --git a/bootstrap/bpdoc/bpdoc_test.go b/bootstrap/bpdoc/bpdoc_test.go
index 67ad783..687d97b 100644
--- a/bootstrap/bpdoc/bpdoc_test.go
+++ b/bootstrap/bpdoc/bpdoc_test.go
@@ -1,16 +1,10 @@
 package bpdoc
 
 import (
-	"fmt"
 	"reflect"
 	"testing"
 )
 
-type propInfo struct {
-	name string
-	typ  string
-}
-
 type parentProps struct {
 	A string
 
@@ -41,133 +35,12 @@
 	// mutated shouldn't be found because it's a mutated property.
 	expected := []string{"child", "child.child"}
 	if len(allStructs) != len(expected) {
-		t.Fatalf("expected %d structs, got %d, all entries: %v",
+		t.Errorf("expected %d structs, got %d, all entries: %q",
 			len(expected), len(allStructs), allStructs)
 	}
-	got := []string{}
-	for _, s := range allStructs {
-		got = append(got, s.nestPoint)
-	}
-
-	if !reflect.DeepEqual(got, expected) {
-		t.Errorf("Expected nested properties:\n\t %q,\n but got\n\t %q", expected, got)
-	}
-}
-
-func TestAllPackages(t *testing.T) {
-	packages, err := AllPackages(pkgFiles, moduleTypeNameFactories, moduleTypeNamePropertyStructs)
-	if err != nil {
-		t.Fatalf("expected nil error for AllPackages(%v, %v, %v), got %s", pkgFiles, moduleTypeNameFactories, moduleTypeNamePropertyStructs, err)
-	}
-
-	if numPackages := len(packages); numPackages != 1 {
-		t.Errorf("Expected %d package, got %d packages %v instead", len(pkgFiles), numPackages, packages)
-	}
-
-	pkg := packages[0]
-
-	expectedProps := map[string][]propInfo{
-		"bar": []propInfo{
-			propInfo{
-				name: "a",
-				typ:  "string",
-			},
-			propInfo{
-				name: "nested",
-				typ:  "",
-			},
-			propInfo{
-				name: "nested.c",
-				typ:  "string",
-			},
-			propInfo{
-				name: "nested_struct",
-				typ:  "structToNest",
-			},
-			propInfo{
-				name: "nested_struct.e",
-				typ:  "string",
-			},
-			propInfo{
-				name: "struct_has_embed",
-				typ:  "StructWithEmbedded",
-			},
-			propInfo{
-				name: "struct_has_embed.nested_in_embedded",
-				typ:  "structToNest",
-			},
-			propInfo{
-				name: "struct_has_embed.nested_in_embedded.e",
-				typ:  "string",
-			},
-			propInfo{
-				name: "struct_has_embed.f",
-				typ:  "string",
-			},
-			propInfo{
-				name: "list_of_ints",
-				typ:  "list of int",
-			},
-			propInfo{
-				name: "list_of_nested",
-				typ:  "list of structToNest",
-			},
-			propInfo{
-				name: "nested_in_other_embedded",
-				typ:  "otherStructToNest",
-			},
-			propInfo{
-				name: "nested_in_other_embedded.g",
-				typ:  "string",
-			},
-			propInfo{
-				name: "h",
-				typ:  "string",
-			},
-		},
-		"foo": []propInfo{
-			propInfo{
-				name: "a",
-				typ:  "string",
-			},
-		},
-	}
-
-	for _, m := range pkg.ModuleTypes {
-		foundProps := []propInfo{}
-
-		for _, p := range m.PropertyStructs {
-			nestedProps, errs := findAllProperties("", p.Properties)
-			foundProps = append(foundProps, nestedProps...)
-			for _, err := range errs {
-				t.Errorf("%s", err)
-			}
-		}
-		if wanted, ok := expectedProps[m.Name]; ok {
-			if !reflect.DeepEqual(foundProps, wanted) {
-				t.Errorf("For %s, expected\n\t %q,\nbut got\n\t %q", m.Name, wanted, foundProps)
-			}
+	for _, e := range expected {
+		if _, ok := allStructs[e]; !ok {
+			t.Errorf("missing entry %q, all entries: %q", e, allStructs)
 		}
 	}
 }
-
-func findAllProperties(prefix string, properties []Property) ([]propInfo, []error) {
-	foundProps := []propInfo{}
-	errs := []error{}
-	for _, p := range properties {
-		prop := propInfo{
-			name: prefix + p.Name,
-			typ:  p.Type,
-		}
-		foundProps = append(foundProps, prop)
-		if hasTag(p.Tag, "blueprint", "mutated") {
-			err := fmt.Errorf("Property %s has `blueprint:\"mutated\" tag but should have been excluded.", p.Name)
-			errs = append(errs, err)
-		}
-
-		nestedProps, nestedErrs := findAllProperties(prefix+p.Name+".", p.Properties)
-		foundProps = append(foundProps, nestedProps...)
-		errs = append(errs, nestedErrs...)
-	}
-	return foundProps, errs
-}
diff --git a/bootstrap/bpdoc/properties.go b/bootstrap/bpdoc/properties.go
index 2ca8e65..9256d8e 100644
--- a/bootstrap/bpdoc/properties.go
+++ b/bootstrap/bpdoc/properties.go
@@ -142,10 +142,6 @@
 	return getByName(name, "", &ps.Properties)
 }
 
-func (ps *PropertyStruct) Nest(nested *PropertyStruct) {
-	ps.Properties = append(ps.Properties, nested.Properties...)
-}
-
 func getByName(name string, prefix string, props *[]Property) *Property {
 	for i := range *props {
 		if prefix+(*props)[i].Name == name {
@@ -161,10 +157,6 @@
 	p.Properties = append(p.Properties, nested.Properties...)
 }
 
-func (p *Property) SetAnonymous() {
-	p.Anonymous = true
-}
-
 func newPropertyStruct(t *doc.Type) (*PropertyStruct, error) {
 	typeSpec := t.Decl.Specs[0].(*ast.TypeSpec)
 	ps := PropertyStruct{
@@ -197,7 +189,8 @@
 			}
 		}
 		for _, n := range names {
-			var name, tag, text string
+			var name, typ, tag, text string
+			var innerProps []Property
 			if n != nil {
 				name = proptools.PropertyNameForField(n.Name)
 			}
@@ -210,9 +203,25 @@
 					return nil, err
 				}
 			}
-			typ, innerProps, err := getType(f.Type)
-			if err != nil {
-				return nil, err
+
+			t := f.Type
+			if star, ok := t.(*ast.StarExpr); ok {
+				t = star.X
+			}
+			switch a := t.(type) {
+			case *ast.ArrayType:
+				typ = "list of strings"
+			case *ast.InterfaceType:
+				typ = "interface"
+			case *ast.Ident:
+				typ = a.Name
+			case *ast.StructType:
+				innerProps, err = structProperties(a)
+				if err != nil {
+					return nil, err
+				}
+			default:
+				typ = fmt.Sprintf("%T", f.Type)
 			}
 
 			props = append(props, Property{
@@ -228,37 +237,6 @@
 	return props, nil
 }
 
-func getType(expr ast.Expr) (typ string, innerProps []Property, err error) {
-	var t ast.Expr
-	if star, ok := expr.(*ast.StarExpr); ok {
-		t = star.X
-	} else {
-		t = expr
-	}
-	switch a := t.(type) {
-	case *ast.ArrayType:
-		var elt string
-		elt, innerProps, err = getType(a.Elt)
-		if err != nil {
-			return "", nil, err
-		}
-		typ = "list of " + elt
-	case *ast.InterfaceType:
-		typ = "interface"
-	case *ast.Ident:
-		typ = a.Name
-	case *ast.StructType:
-		innerProps, err = structProperties(a)
-		if err != nil {
-			return "", nil, err
-		}
-	default:
-		typ = fmt.Sprintf("%T", expr)
-	}
-
-	return typ, innerProps, nil
-}
-
 func (ps *PropertyStruct) ExcludeByTag(key, value string) {
 	filterPropsByTag(&ps.Properties, key, value, true)
 }
@@ -273,7 +251,6 @@
 	filtered := (*props)[:0]
 	for _, x := range *props {
 		if hasTag(x.Tag, key, value) == !exclude {
-			filterPropsByTag(&x.Properties, key, value, exclude)
 			filtered = append(filtered, x)
 		}
 	}
diff --git a/bootstrap/bpdoc/properties_test.go b/bootstrap/bpdoc/properties_test.go
index 085bcdf..4045cb1 100644
--- a/bootstrap/bpdoc/properties_test.go
+++ b/bootstrap/bpdoc/properties_test.go
@@ -28,8 +28,11 @@
 
 	ps.ExcludeByTag("tag1", "a")
 
-	expected := []string{"c", "d", "g"}
-	actual := actualProperties(t, ps.Properties)
+	expected := []string{"c"}
+	actual := []string{}
+	for _, p := range ps.Properties {
+		actual = append(actual, p.Name)
+	}
 	if !reflect.DeepEqual(expected, actual) {
 		t.Errorf("unexpected ExcludeByTag result, expected: %q, actual: %q", expected, actual)
 	}
@@ -44,20 +47,12 @@
 
 	ps.IncludeByTag("tag1", "c")
 
-	expected := []string{"b", "c", "d", "f", "g"}
-	actual := actualProperties(t, ps.Properties)
+	expected := []string{"b", "c"}
+	actual := []string{}
+	for _, p := range ps.Properties {
+		actual = append(actual, p.Name)
+	}
 	if !reflect.DeepEqual(expected, actual) {
 		t.Errorf("unexpected IncludeByTag result, expected: %q, actual: %q", expected, actual)
 	}
 }
-
-func actualProperties(t *testing.T, props []Property) []string {
-	t.Helper()
-
-	actual := []string{}
-	for _, p := range props {
-		actual = append(actual, p.Name)
-		actual = append(actual, actualProperties(t, p.Properties)...)
-	}
-	return actual
-}
diff --git a/bootstrap/bpdoc/reader_test.go b/bootstrap/bpdoc/reader_test.go
index bf324bf..0d608b3 100644
--- a/bootstrap/bpdoc/reader_test.go
+++ b/bootstrap/bpdoc/reader_test.go
@@ -16,7 +16,6 @@
 package bpdoc
 
 import (
-	"html/template"
 	"reflect"
 	"runtime"
 	"testing"
@@ -24,65 +23,11 @@
 	"github.com/google/blueprint"
 )
 
-type factoryFn func() (blueprint.Module, []interface{})
-
 // foo docs.
 func fooFactory() (blueprint.Module, []interface{}) {
 	return nil, []interface{}{&props{}}
 }
 
-// bar docs.
-func barFactory() (blueprint.Module, []interface{}) {
-	return nil, []interface{}{&complexProps{}}
-}
-
-type structToNest struct {
-	E string
-}
-
-type StructToEmbed struct {
-	Nested_in_embedded structToNest
-
-	// F string
-	F string
-}
-
-type otherStructToNest struct {
-	G string
-}
-
-type OtherStructToEmbed struct {
-	Nested_in_other_embedded otherStructToNest
-
-	// F string
-	H string
-}
-
-type StructWithEmbedded struct {
-	StructToEmbed
-}
-
-// for bpdoc_test.go
-type complexProps struct {
-	A         string
-	B_mutated string `blueprint:"mutated"`
-
-	Nested struct {
-		C         string
-		D_mutated string `blueprint:"mutated"`
-	}
-
-	Nested_struct structToNest
-
-	Struct_has_embed StructWithEmbedded
-
-	OtherStructToEmbed
-
-	List_of_ints []int
-
-	List_of_nested []structToNest
-}
-
 // props docs.
 type props struct {
 	// A docs.
@@ -94,18 +39,10 @@
 	A string `tag1:"a,b" tag2:"c"`
 	B string `tag1:"a,c"`
 	C string `tag1:"b,c"`
-
-	D struct {
-		E string `tag1:"a,b" tag2:"c"`
-		F string `tag1:"a,c"`
-		G string `tag1:"b,c"`
-	} `tag1:"b,c"`
 }
 
 var pkgPath string
 var pkgFiles map[string][]string
-var moduleTypeNameFactories map[string]reflect.Value
-var moduleTypeNamePropertyStructs map[string][]interface{}
 
 func init() {
 	pc, filename, _, _ := runtime.Caller(0)
@@ -120,34 +57,21 @@
 	pkgFiles = map[string][]string{
 		pkgPath: {filename},
 	}
-
-	factories := map[string]factoryFn{"foo": fooFactory, "bar": barFactory}
-
-	moduleTypeNameFactories = make(map[string]reflect.Value, len(factories))
-	moduleTypeNamePropertyStructs = make(map[string][]interface{}, len(factories))
-	for name, factory := range factories {
-		moduleTypeNameFactories[name] = reflect.ValueOf(factory)
-		_, structs := factory()
-		moduleTypeNamePropertyStructs[name] = structs
-	}
 }
 
 func TestModuleTypeDocs(t *testing.T) {
 	r := NewReader(pkgFiles)
-	for m := range moduleTypeNameFactories {
-		mt, err := r.ModuleType(m+"_module", moduleTypeNameFactories[m])
-		if err != nil {
-			t.Fatal(err)
-		}
+	mt, err := r.ModuleType("foo_module", reflect.ValueOf(fooFactory))
+	if err != nil {
+		t.Fatal(err)
+	}
 
-		expectedText := template.HTML(m + " docs.\n\n")
-		if mt.Text != expectedText {
-			t.Errorf("unexpected docs %q", mt.Text)
-		}
+	if mt.Text != "foo docs.\n\n" {
+		t.Errorf("unexpected docs %q", mt.Text)
+	}
 
-		if mt.PkgPath != pkgPath {
-			t.Errorf("expected pkgpath %q, got %q", pkgPath, mt.PkgPath)
-		}
+	if mt.PkgPath != pkgPath {
+		t.Errorf("expected pkgpath %q, got %q", pkgPath, mt.PkgPath)
 	}
 }
 
diff --git a/bootstrap/bpglob/bpglob.go b/bootstrap/bpglob/bpglob.go
index 81c0dd0..fe47b6f 100644
--- a/bootstrap/bpglob/bpglob.go
+++ b/bootstrap/bpglob/bpglob.go
@@ -19,206 +19,69 @@
 package main
 
 import (
-	"bytes"
-	"errors"
 	"flag"
 	"fmt"
 	"io/ioutil"
 	"os"
-	"strconv"
 	"time"
 
-	"github.com/google/blueprint/deptools"
 	"github.com/google/blueprint/pathtools"
 )
 
 var (
-	// flagSet is a flag.FlagSet with flag.ContinueOnError so that we can handle the versionMismatchError
-	// error from versionArg.
-	flagSet = flag.NewFlagSet("bpglob", flag.ContinueOnError)
+	out = flag.String("o", "", "file to write list of files that match glob")
 
-	out = flagSet.String("o", "", "file to write list of files that match glob")
-
-	versionMatch versionArg
-	globs        []globArg
+	excludes multiArg
 )
 
 func init() {
-	flagSet.Var(&versionMatch, "v", "version number the command line was generated for")
-	flagSet.Var((*patternsArgs)(&globs), "p", "pattern to include in results")
-	flagSet.Var((*excludeArgs)(&globs), "e", "pattern to exclude from results from the most recent pattern")
+	flag.Var(&excludes, "e", "pattern to exclude from results")
 }
 
-// bpglob is executed through the rules in build-globs.ninja to determine whether soong_build
-// needs to rerun.  That means when the arguments accepted by bpglob change it will be called
-// with the old arguments, then soong_build will rerun and update build-globs.ninja with the new
-// arguments.
-//
-// To avoid having to maintain backwards compatibility with old arguments across the transition,
-// a version argument is used to detect the transition in order to stop parsing arguments, touch the
-// output file and exit immediately.  Aborting parsing arguments is necessary to handle parsing
-// errors that would be fatal, for example the removal of a flag.  The version number in
-// pathtools.BPGlobArgumentVersion should be manually incremented when the bpglob argument format
-// changes.
-//
-// If the version argument is not passed then a version mismatch is assumed.
+type multiArg []string
 
-// versionArg checks the argument against pathtools.BPGlobArgumentVersion, returning a
-// versionMismatchError error if it does not match.
-type versionArg bool
+func (m *multiArg) String() string {
+	return `""`
+}
 
-var versionMismatchError = errors.New("version mismatch")
-
-func (v *versionArg) String() string { return "" }
-
-func (v *versionArg) Set(s string) error {
-	vers, err := strconv.Atoi(s)
-	if err != nil {
-		return fmt.Errorf("error parsing version argument: %w", err)
-	}
-
-	// Force the -o argument to come before the -v argument so that the output file can be
-	// updated on error.
-	if *out == "" {
-		return fmt.Errorf("-o argument must be passed before -v")
-	}
-
-	if vers != pathtools.BPGlobArgumentVersion {
-		return versionMismatchError
-	}
-
-	*v = true
-
+func (m *multiArg) Set(s string) error {
+	*m = append(*m, s)
 	return nil
 }
 
-// A glob arg holds a single -p argument with zero or more following -e arguments.
-type globArg struct {
-	pattern  string
-	excludes []string
-}
-
-// patternsArgs implements flag.Value to handle -p arguments by adding a new globArg to the list.
-type patternsArgs []globArg
-
-func (p *patternsArgs) String() string { return `""` }
-
-func (p *patternsArgs) Set(s string) error {
-	globs = append(globs, globArg{
-		pattern: s,
-	})
-	return nil
-}
-
-// excludeArgs implements flag.Value to handle -e arguments by adding to the last globArg in the
-// list.
-type excludeArgs []globArg
-
-func (e *excludeArgs) String() string { return `""` }
-
-func (e *excludeArgs) Set(s string) error {
-	if len(*e) == 0 {
-		return fmt.Errorf("-p argument is required before the first -e argument")
-	}
-
-	glob := &(*e)[len(*e)-1]
-	glob.excludes = append(glob.excludes, s)
-	return nil
+func (m *multiArg) Get() interface{} {
+	return m
 }
 
 func usage() {
-	fmt.Fprintln(os.Stderr, "usage: bpglob -o out -v version -p glob [-e excludes ...] [-p glob ...]")
-	flagSet.PrintDefaults()
+	fmt.Fprintln(os.Stderr, "usage: bpglob -o out glob")
+	flag.PrintDefaults()
 	os.Exit(2)
 }
 
 func main() {
-	// Save the command line flag error output to a buffer, the flag package unconditionally
-	// writes an error message to the output on error, and we want to hide the error for the
-	// version mismatch case.
-	flagErrorBuffer := &bytes.Buffer{}
-	flagSet.SetOutput(flagErrorBuffer)
-
-	err := flagSet.Parse(os.Args[1:])
-
-	if !versionMatch {
-		// A version mismatch error occurs when the arguments written into build-globs.ninja
-		// don't match the format expected by the bpglob binary.  This happens during the
-		// first incremental build after bpglob is changed.  Handle this case by aborting
-		// argument parsing and updating the output file with something that will always cause
-		// the primary builder to rerun.
-		// This can happen when there is no -v argument or if the -v argument doesn't match
-		// pathtools.BPGlobArgumentVersion.
-		writeErrorOutput(*out, versionMismatchError)
-		os.Exit(0)
-	}
-
-	if err != nil {
-		os.Stderr.Write(flagErrorBuffer.Bytes())
-		fmt.Fprintln(os.Stderr, "error:", err.Error())
-		usage()
-	}
+	flag.Parse()
 
 	if *out == "" {
 		fmt.Fprintln(os.Stderr, "error: -o is required")
 		usage()
 	}
 
-	if flagSet.NArg() > 0 {
+	if flag.NArg() != 1 {
 		usage()
 	}
 
-	err = globsWithDepFile(*out, *out+".d", globs)
+	_, err := pathtools.GlobWithDepFile(flag.Arg(0), *out, *out+".d", excludes)
 	if err != nil {
 		// Globs here were already run in the primary builder without error.  The only errors here should be if the glob
 		// pattern was made invalid by a change in the pathtools glob implementation, in which case the primary builder
 		// needs to be rerun anyways.  Update the output file with something that will always cause the primary builder
 		// to rerun.
-		writeErrorOutput(*out, err)
-	}
-}
-
-// writeErrorOutput writes an error to the output file with a timestamp to ensure that it is
-// considered dirty by ninja.
-func writeErrorOutput(path string, globErr error) {
-	s := fmt.Sprintf("%s: error: %s\n", time.Now().Format(time.StampNano), globErr.Error())
-	err := ioutil.WriteFile(path, []byte(s), 0666)
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "error: %s\n", err.Error())
-		os.Exit(1)
-	}
-}
-
-// globsWithDepFile finds all files and directories that match glob.  Directories
-// will have a trailing '/'.  It compares the list of matches against the
-// contents of fileListFile, and rewrites fileListFile if it has changed.  It
-// also writes all of the directories it traversed as dependencies on fileListFile
-// to depFile.
-//
-// The format of glob is either path/*.ext for a single directory glob, or
-// path/**/*.ext for a recursive glob.
-func globsWithDepFile(fileListFile, depFile string, globs []globArg) error {
-	var results pathtools.MultipleGlobResults
-	for _, glob := range globs {
-		result, err := pathtools.Glob(glob.pattern, glob.excludes, pathtools.FollowSymlinks)
+		s := fmt.Sprintf("%s: error: %s\n", time.Now().Format(time.StampNano), err.Error())
+		err := ioutil.WriteFile(*out, []byte(s), 0666)
 		if err != nil {
-			return err
+			fmt.Fprintf(os.Stderr, "error: %s\n", err.Error())
+			os.Exit(1)
 		}
-		results = append(results, result)
 	}
-
-	// Only write the output file if it has changed.
-	err := pathtools.WriteFileIfChanged(fileListFile, results.FileList(), 0666)
-	if err != nil {
-		return fmt.Errorf("failed to write file list to %q: %w", fileListFile, err)
-	}
-
-	// The depfile can be written unconditionally as its timestamp doesn't affect ninja's restat
-	// feature.
-	err = deptools.WriteDepFile(depFile, fileListFile, results.Deps())
-	if err != nil {
-		return fmt.Errorf("failed to write dep file to %q: %w", depFile, err)
-	}
-
-	return nil
 }
diff --git a/bootstrap/cleanup.go b/bootstrap/cleanup.go
index 9dbea2a..6444081 100644
--- a/bootstrap/cleanup.go
+++ b/bootstrap/cleanup.go
@@ -31,8 +31,8 @@
 // removeAbandonedFilesUnder removes any files that appear in the Ninja log, and
 // are prefixed with one of the `under` entries, but that are not currently
 // build targets, or in `exempt`
-func removeAbandonedFilesUnder(ctx *blueprint.Context,
-	srcDir, buildDir string, under, exempt []string) error {
+func removeAbandonedFilesUnder(ctx *blueprint.Context, config *Config,
+	srcDir string, under, exempt []string) error {
 
 	if len(under) == 0 {
 		return nil
@@ -50,7 +50,7 @@
 
 	replacer := strings.NewReplacer(
 		"@@SrcDir@@", srcDir,
-		"@@BuildDir@@", buildDir)
+		"@@BuildDir@@", BuildDir)
 	ninjaBuildDir = replacer.Replace(ninjaBuildDir)
 	targets := make(map[string]bool)
 	for target := range targetRules {
diff --git a/bootstrap/command.go b/bootstrap/command.go
index 4a938db..cbbd32d 100644
--- a/bootstrap/command.go
+++ b/bootstrap/command.go
@@ -31,123 +31,57 @@
 	"github.com/google/blueprint/deptools"
 )
 
-type Args struct {
-	OutFile                  string
-	GlobFile                 string
-	DepFile                  string
-	DocFile                  string
-	Cpuprofile               string
-	Memprofile               string
-	DelveListen              string
-	DelvePath                string
-	TraceFile                string
-	RunGoTests               bool
-	UseValidations           bool
-	NoGC                     bool
-	EmptyNinjaFile           bool
-	BuildDir                 string
-	ModuleListFile           string
-	NinjaBuildDir            string
-	TopFile                  string
-	GeneratingPrimaryBuilder bool
-
-	PrimaryBuilderInvocations []PrimaryBuilderInvocation
-}
-
 var (
-	CmdlineArgs Args
-	absSrcDir   string
+	outFile        string
+	globFile       string
+	depFile        string
+	docFile        string
+	cpuprofile     string
+	memprofile     string
+	traceFile      string
+	runGoTests     bool
+	noGC           bool
+	moduleListFile string
+	emptyNinjaFile bool
+
+	BuildDir      string
+	NinjaBuildDir string
+	SrcDir        string
+
+	absSrcDir string
 )
 
 func init() {
-	flag.StringVar(&CmdlineArgs.OutFile, "o", "build.ninja", "the Ninja file to output")
-	flag.StringVar(&CmdlineArgs.GlobFile, "globFile", "build-globs.ninja", "the Ninja file of globs to output")
-	flag.StringVar(&CmdlineArgs.BuildDir, "b", ".", "the build output directory")
-	flag.StringVar(&CmdlineArgs.NinjaBuildDir, "n", "", "the ninja builddir directory")
-	flag.StringVar(&CmdlineArgs.DepFile, "d", "", "the dependency file to output")
-	flag.StringVar(&CmdlineArgs.DocFile, "docs", "", "build documentation file to output")
-	flag.StringVar(&CmdlineArgs.Cpuprofile, "cpuprofile", "", "write cpu profile to file")
-	flag.StringVar(&CmdlineArgs.TraceFile, "trace", "", "write trace to file")
-	flag.StringVar(&CmdlineArgs.Memprofile, "memprofile", "", "write memory profile to file")
-	flag.BoolVar(&CmdlineArgs.NoGC, "nogc", false, "turn off GC for debugging")
-	flag.BoolVar(&CmdlineArgs.RunGoTests, "t", false, "build and run go tests during bootstrap")
-	flag.BoolVar(&CmdlineArgs.UseValidations, "use-validations", false, "use validations to depend on go tests")
-	flag.StringVar(&CmdlineArgs.ModuleListFile, "l", "", "file that lists filepaths to parse")
-	flag.BoolVar(&CmdlineArgs.EmptyNinjaFile, "empty-ninja-file", false, "write out a 0-byte ninja file")
+	flag.StringVar(&outFile, "o", "build.ninja", "the Ninja file to output")
+	flag.StringVar(&globFile, "globFile", "build-globs.ninja", "the Ninja file of globs to output")
+	flag.StringVar(&BuildDir, "b", ".", "the build output directory")
+	flag.StringVar(&NinjaBuildDir, "n", "", "the ninja builddir directory")
+	flag.StringVar(&depFile, "d", "", "the dependency file to output")
+	flag.StringVar(&docFile, "docs", "", "build documentation file to output")
+	flag.StringVar(&cpuprofile, "cpuprofile", "", "write cpu profile to file")
+	flag.StringVar(&traceFile, "trace", "", "write trace to file")
+	flag.StringVar(&memprofile, "memprofile", "", "write memory profile to file")
+	flag.BoolVar(&noGC, "nogc", false, "turn off GC for debugging")
+	flag.BoolVar(&runGoTests, "t", false, "build and run go tests during bootstrap")
+	flag.StringVar(&moduleListFile, "l", "", "file that lists filepaths to parse")
+	flag.BoolVar(&emptyNinjaFile, "empty-ninja-file", false, "write out a 0-byte ninja file")
 }
 
-func Main(ctx *blueprint.Context, config interface{}, generatingPrimaryBuilder bool) {
+func Main(ctx *blueprint.Context, config interface{}, extraNinjaFileDeps ...string) {
 	if !flag.Parsed() {
 		flag.Parse()
 	}
 
-	if flag.NArg() != 1 {
-		fatalf("no Blueprints file specified")
-	}
-
-	CmdlineArgs.TopFile = flag.Arg(0)
-	CmdlineArgs.GeneratingPrimaryBuilder = generatingPrimaryBuilder
-	ninjaDeps := RunBlueprint(CmdlineArgs, ctx, config)
-	err := deptools.WriteDepFile(CmdlineArgs.DepFile, CmdlineArgs.OutFile, ninjaDeps)
-	if err != nil {
-		fatalf("Cannot write depfile '%s': %s", CmdlineArgs.DepFile, err)
-	}
-}
-
-func PrimaryBuilderExtraFlags(args Args, globFile, mainNinjaFile string) []string {
-	result := make([]string, 0)
-
-	if args.RunGoTests {
-		result = append(result, "-t")
-	}
-
-	result = append(result, "-l", args.ModuleListFile)
-	result = append(result, "-globFile", globFile)
-	result = append(result, "-o", mainNinjaFile)
-
-	if args.EmptyNinjaFile {
-		result = append(result, "--empty-ninja-file")
-	}
-
-	if args.DelveListen != "" {
-		result = append(result, "--delve_listen", args.DelveListen)
-	}
-
-	if args.DelvePath != "" {
-		result = append(result, "--delve_path", args.DelvePath)
-	}
-
-	return result
-}
-
-func writeEmptyGlobFile(path string) {
-	err := os.MkdirAll(filepath.Dir(path), 0777)
-	if err != nil {
-		fatalf("Failed to create parent directories of empty ninja glob file '%s': %s", path, err)
-	}
-
-	if _, err := os.Stat(path); os.IsNotExist(err) {
-		err = ioutil.WriteFile(path, nil, 0666)
-		if err != nil {
-			fatalf("Failed to create empty ninja glob file '%s': %s", path, err)
-		}
-	}
-}
-
-// Returns the list of dependencies the emitted Ninja files has. These can be
-// written to the .d file for the output so that it is correctly rebuilt when
-// needed in case Blueprint is itself invoked from Ninja
-func RunBlueprint(args Args, ctx *blueprint.Context, config interface{}) []string {
 	runtime.GOMAXPROCS(runtime.NumCPU())
 
-	if args.NoGC {
+	if noGC {
 		debug.SetGCPercent(-1)
 	}
 
 	absSrcDir = ctx.SrcDir()
 
-	if args.Cpuprofile != "" {
-		f, err := os.Create(absolutePath(args.Cpuprofile))
+	if cpuprofile != "" {
+		f, err := os.Create(absolutePath(cpuprofile))
 		if err != nil {
 			fatalf("error opening cpuprofile: %s", err)
 		}
@@ -156,8 +90,8 @@
 		defer pprof.StopCPUProfile()
 	}
 
-	if args.TraceFile != "" {
-		f, err := os.Create(absolutePath(args.TraceFile))
+	if traceFile != "" {
+		f, err := os.Create(absolutePath(traceFile))
 		if err != nil {
 			fatalf("error opening trace: %s", err)
 		}
@@ -166,56 +100,40 @@
 		defer trace.Stop()
 	}
 
-	srcDir := filepath.Dir(args.TopFile)
+	if flag.NArg() != 1 {
+		fatalf("no Blueprints file specified")
+	}
 
-	ninjaDeps := make([]string, 0)
-
-	if args.ModuleListFile != "" {
-		ctx.SetModuleListFile(args.ModuleListFile)
-		ninjaDeps = append(ninjaDeps, args.ModuleListFile)
+	SrcDir = filepath.Dir(flag.Arg(0))
+	if moduleListFile != "" {
+		ctx.SetModuleListFile(moduleListFile)
+		extraNinjaFileDeps = append(extraNinjaFileDeps, moduleListFile)
 	} else {
 		fatalf("-l <moduleListFile> is required and must be nonempty")
 	}
-	filesToParse, err := ctx.ListModulePaths(srcDir)
+	filesToParse, err := ctx.ListModulePaths(SrcDir)
 	if err != nil {
 		fatalf("could not enumerate files: %v\n", err.Error())
 	}
 
-	buildDir := config.(BootstrapConfig).BuildDir()
-
-	stage := StageMain
-	if args.GeneratingPrimaryBuilder {
-		stage = StagePrimary
+	if NinjaBuildDir == "" {
+		NinjaBuildDir = BuildDir
 	}
 
-	primaryBuilderNinjaGlobFile := absolutePath(filepath.Join(args.BuildDir, bootstrapSubDir, "build-globs.ninja"))
-	mainNinjaFile := filepath.Join("$buildDir", "build.ninja")
-
-	writeEmptyGlobFile(primaryBuilderNinjaGlobFile)
-
-	var invocations []PrimaryBuilderInvocation
-
-	if args.PrimaryBuilderInvocations != nil {
-		invocations = args.PrimaryBuilderInvocations
-	} else {
-		primaryBuilderArgs := PrimaryBuilderExtraFlags(args, primaryBuilderNinjaGlobFile, mainNinjaFile)
-		primaryBuilderArgs = append(primaryBuilderArgs, args.TopFile)
-
-		invocations = []PrimaryBuilderInvocation{{
-			Inputs:  []string{args.TopFile},
-			Outputs: []string{mainNinjaFile},
-			Args:    primaryBuilderArgs,
-		}}
+	stage := StageMain
+	if c, ok := config.(ConfigInterface); ok {
+		if c.GeneratingPrimaryBuilder() {
+			stage = StagePrimary
+		}
 	}
 
 	bootstrapConfig := &Config{
 		stage: stage,
 
-		topLevelBlueprintsFile:    args.TopFile,
-		globFile:                  primaryBuilderNinjaGlobFile,
-		runGoTests:                args.RunGoTests,
-		useValidations:            args.UseValidations,
-		primaryBuilderInvocations: invocations,
+		topLevelBlueprintsFile: flag.Arg(0),
+		emptyNinjaFile:         emptyNinjaFile,
+		runGoTests:             runGoTests,
+		moduleListFile:         moduleListFile,
 	}
 
 	ctx.RegisterBottomUpMutator("bootstrap_plugin_deps", pluginDeps)
@@ -224,33 +142,33 @@
 	ctx.RegisterModuleType("blueprint_go_binary", newGoBinaryModuleFactory(bootstrapConfig, true))
 	ctx.RegisterSingletonType("bootstrap", newSingletonFactory(bootstrapConfig))
 
-	ctx.RegisterSingletonType("glob", globSingletonFactory(bootstrapConfig, ctx))
+	ctx.RegisterSingletonType("glob", globSingletonFactory(ctx))
 
-	blueprintFiles, errs := ctx.ParseFileList(filepath.Dir(args.TopFile), filesToParse, config)
+	deps, errs := ctx.ParseFileList(filepath.Dir(bootstrapConfig.topLevelBlueprintsFile), filesToParse, config)
 	if len(errs) > 0 {
 		fatalErrors(errs)
 	}
 
 	// Add extra ninja file dependencies
-	ninjaDeps = append(ninjaDeps, blueprintFiles...)
+	deps = append(deps, extraNinjaFileDeps...)
 
 	extraDeps, errs := ctx.ResolveDependencies(config)
 	if len(errs) > 0 {
 		fatalErrors(errs)
 	}
-	ninjaDeps = append(ninjaDeps, extraDeps...)
+	deps = append(deps, extraDeps...)
 
-	if args.DocFile != "" {
-		err := writeDocs(ctx, config, absolutePath(args.DocFile))
+	if docFile != "" {
+		err := writeDocs(ctx, absolutePath(docFile))
 		if err != nil {
 			fatalErrors([]error{err})
 		}
-		return nil
+		return
 	}
 
 	if c, ok := config.(ConfigStopBefore); ok {
 		if c.StopBefore() == StopBeforePrepareBuildActions {
-			return ninjaDeps
+			return
 		}
 	}
 
@@ -258,45 +176,46 @@
 	if len(errs) > 0 {
 		fatalErrors(errs)
 	}
-	ninjaDeps = append(ninjaDeps, extraDeps...)
-
-	if c, ok := config.(ConfigStopBefore); ok {
-		if c.StopBefore() == StopBeforeWriteNinja {
-			return ninjaDeps
-		}
-	}
+	deps = append(deps, extraDeps...)
 
 	const outFilePermissions = 0666
-	var out io.StringWriter
+	var out io.Writer
 	var f *os.File
 	var buf *bufio.Writer
 
-	if args.EmptyNinjaFile {
-		if err := ioutil.WriteFile(absolutePath(args.OutFile), []byte(nil), outFilePermissions); err != nil {
+	if emptyNinjaFile {
+		if err := ioutil.WriteFile(absolutePath(outFile), []byte(nil), outFilePermissions); err != nil {
 			fatalf("error writing empty Ninja file: %s", err)
 		}
 	}
 
-	if stage != StageMain || !args.EmptyNinjaFile {
-		f, err = os.OpenFile(absolutePath(args.OutFile), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, outFilePermissions)
+	if stage != StageMain || !emptyNinjaFile {
+		f, err = os.OpenFile(absolutePath(outFile), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, outFilePermissions)
 		if err != nil {
 			fatalf("error opening Ninja file: %s", err)
 		}
-		buf = bufio.NewWriterSize(f, 16*1024*1024)
+		buf = bufio.NewWriter(f)
 		out = buf
 	} else {
-		out = ioutil.Discard.(io.StringWriter)
+		out = ioutil.Discard
 	}
 
-	if args.GlobFile != "" {
-		buffer, errs := generateGlobNinjaFile(bootstrapConfig, config, ctx.Globs)
+	if globFile != "" {
+		buffer, errs := generateGlobNinjaFile(ctx.Globs)
 		if len(errs) > 0 {
 			fatalErrors(errs)
 		}
 
-		err = ioutil.WriteFile(absolutePath(args.GlobFile), buffer, outFilePermissions)
+		err = ioutil.WriteFile(absolutePath(globFile), buffer, outFilePermissions)
 		if err != nil {
-			fatalf("error writing %s: %s", args.GlobFile, err)
+			fatalf("error writing %s: %s", globFile, err)
+		}
+	}
+
+	if depFile != "" {
+		err := deptools.WriteDepFile(absolutePath(depFile), outFile, deps)
+		if err != nil {
+			fatalf("error writing depfile: %s", err)
 		}
 	}
 
@@ -320,23 +239,21 @@
 	}
 
 	if c, ok := config.(ConfigRemoveAbandonedFilesUnder); ok {
-		under, except := c.RemoveAbandonedFilesUnder(buildDir)
-		err := removeAbandonedFilesUnder(ctx, srcDir, buildDir, under, except)
+		under, except := c.RemoveAbandonedFilesUnder()
+		err := removeAbandonedFilesUnder(ctx, bootstrapConfig, SrcDir, under, except)
 		if err != nil {
 			fatalf("error removing abandoned files: %s", err)
 		}
 	}
 
-	if args.Memprofile != "" {
-		f, err := os.Create(absolutePath(args.Memprofile))
+	if memprofile != "" {
+		f, err := os.Create(absolutePath(memprofile))
 		if err != nil {
 			fatalf("error opening memprofile: %s", err)
 		}
 		defer f.Close()
 		pprof.WriteHeapProfile(f)
 	}
-
-	return ninjaDeps
 }
 
 func fatalf(format string, args ...interface{}) {
diff --git a/bootstrap/config.go b/bootstrap/config.go
index a29ba76..9499aeb 100644
--- a/bootstrap/config.go
+++ b/bootstrap/config.go
@@ -15,7 +15,6 @@
 package bootstrap
 
 import (
-	"fmt"
 	"os"
 	"path/filepath"
 	"runtime"
@@ -24,30 +23,25 @@
 	"github.com/google/blueprint"
 )
 
-func bootstrapVariable(name string, value func(BootstrapConfig) string) blueprint.Variable {
+func bootstrapVariable(name string, value func() string) blueprint.Variable {
 	return pctx.VariableFunc(name, func(config interface{}) (string, error) {
-		c, ok := config.(BootstrapConfig)
-		if !ok {
-			panic(fmt.Sprintf("Bootstrap rules were passed a configuration that does not include theirs, config=%q",
-				config))
-		}
-		return value(c), nil
+		return value(), nil
 	})
 }
 
 var (
 	// These variables are the only configuration needed by the bootstrap
 	// modules.
-	srcDirVariable = bootstrapVariable("srcDir", func(c BootstrapConfig) string {
-		return c.SrcDir()
+	srcDir = bootstrapVariable("srcDir", func() string {
+		return SrcDir
 	})
-	buildDirVariable = bootstrapVariable("buildDir", func(c BootstrapConfig) string {
-		return c.BuildDir()
+	buildDir = bootstrapVariable("buildDir", func() string {
+		return BuildDir
 	})
-	ninjaBuildDirVariable = bootstrapVariable("ninjaBuildDir", func(c BootstrapConfig) string {
-		return c.NinjaBuildDir()
+	ninjaBuildDir = bootstrapVariable("ninjaBuildDir", func() string {
+		return NinjaBuildDir
 	})
-	goRootVariable = bootstrapVariable("goRoot", func(c BootstrapConfig) string {
+	goRoot = bootstrapVariable("goRoot", func() string {
 		goroot := runtime.GOROOT()
 		// Prefer to omit absolute paths from the ninja file
 		if cwd, err := os.Getwd(); err == nil {
@@ -59,35 +53,19 @@
 		}
 		return goroot
 	})
-	compileCmdVariable = bootstrapVariable("compileCmd", func(c BootstrapConfig) string {
+	compileCmd = bootstrapVariable("compileCmd", func() string {
 		return "$goRoot/pkg/tool/" + runtime.GOOS + "_" + runtime.GOARCH + "/compile"
 	})
-	linkCmdVariable = bootstrapVariable("linkCmd", func(c BootstrapConfig) string {
+	linkCmd = bootstrapVariable("linkCmd", func() string {
 		return "$goRoot/pkg/tool/" + runtime.GOOS + "_" + runtime.GOARCH + "/link"
 	})
-	debugFlagsVariable = bootstrapVariable("debugFlags", func(c BootstrapConfig) string {
-		if c.DebugCompilation() {
-			// -N: disable optimizations, -l: disable inlining
-			return "-N -l"
-		} else {
-			return ""
-		}
-	})
 )
 
-type BootstrapConfig interface {
-	// The top-level directory of the source tree
-	SrcDir() string
-
-	// The directory where files emitted during bootstrapping are located.
-	// Usually NinjaBuildDir() + "/soong".
-	BuildDir() string
-
-	// The output directory for the build.
-	NinjaBuildDir() string
-
-	// Whether to compile Go code in such a way that it can be debugged
-	DebugCompilation() bool
+type ConfigInterface interface {
+	// GeneratingPrimaryBuilder should return true if this build invocation is
+	// creating a .bootstrap/build.ninja file to be used to build the
+	// primary builder
+	GeneratingPrimaryBuilder() bool
 }
 
 type ConfigRemoveAbandonedFilesUnder interface {
@@ -95,7 +73,7 @@
 	// - a slice of path prefixes that will be cleaned of files that are no
 	//   longer active targets, but are listed in the .ninja_log.
 	// - a slice of paths that are exempt from cleaning
-	RemoveAbandonedFilesUnder(buildDir string) (under, except []string)
+	RemoveAbandonedFilesUnder() (under, except []string)
 }
 
 type ConfigBlueprintToolLocation interface {
@@ -109,7 +87,6 @@
 
 const (
 	StopBeforePrepareBuildActions StopBefore = 1
-	StopBeforeWriteNinja          StopBefore = 2
 )
 
 type ConfigStopBefore interface {
@@ -123,20 +100,12 @@
 	StageMain
 )
 
-type PrimaryBuilderInvocation struct {
-	Inputs  []string
-	Outputs []string
-	Args    []string
-}
-
 type Config struct {
 	stage Stage
 
 	topLevelBlueprintsFile string
-	globFile               string
 
+	emptyNinjaFile bool
 	runGoTests     bool
-	useValidations bool
-
-	primaryBuilderInvocations []PrimaryBuilderInvocation
+	moduleListFile string
 }
diff --git a/bootstrap/glob.go b/bootstrap/glob.go
index 39c662b..52dbf2f 100644
--- a/bootstrap/glob.go
+++ b/bootstrap/glob.go
@@ -17,13 +17,11 @@
 import (
 	"bytes"
 	"fmt"
-	"hash/fnv"
-	"io"
 	"path/filepath"
-	"strconv"
 	"strings"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/deptools"
 	"github.com/google/blueprint/pathtools"
 )
 
@@ -49,71 +47,36 @@
 	// and writes it to $out if it has changed, and writes the directories to $out.d
 	GlobRule = pctx.StaticRule("GlobRule",
 		blueprint.RuleParams{
-			Command: fmt.Sprintf(`%s -o $out -v %d $args`,
-				globCmd, pathtools.BPGlobArgumentVersion),
+			Command:     fmt.Sprintf(`%s -o $out $excludes "$glob"`, globCmd),
 			CommandDeps: []string{globCmd},
-			Description: "glob",
+			Description: "glob $glob",
 
 			Restat:  true,
 			Deps:    blueprint.DepsGCC,
 			Depfile: "$out.d",
 		},
-		"args")
+		"glob", "excludes")
 )
 
 // GlobFileContext is the subset of ModuleContext and SingletonContext needed by GlobFile
 type GlobFileContext interface {
-	Config() interface{}
 	Build(pctx blueprint.PackageContext, params blueprint.BuildParams)
 }
 
 // GlobFile creates a rule to write to fileListFile a list of the files that match the specified
 // pattern but do not match any of the patterns specified in excludes.  The file will include
-// appropriate dependencies to regenerate the file if and only if the list of matching files has
-// changed.
-func GlobFile(ctx GlobFileContext, pattern string, excludes []string, fileListFile string) {
-	args := `-p "` + pattern + `"`
-	if len(excludes) > 0 {
-		args += " " + joinWithPrefixAndQuote(excludes, "-e ")
-	}
-	ctx.Build(pctx, blueprint.BuildParams{
-		Rule:    GlobRule,
-		Outputs: []string{fileListFile},
-		Args: map[string]string{
-			"args": args,
-		},
-		Description: "glob " + pattern,
-	})
-}
-
-// multipleGlobFilesRule creates a rule to write to fileListFile a list of the files that match the specified
-// pattern but do not match any of the patterns specified in excludes.  The file will include
-// appropriate dependencies to regenerate the file if and only if the list of matching files has
-// changed.
-func multipleGlobFilesRule(ctx GlobFileContext, fileListFile string, shard int, globs pathtools.MultipleGlobResults) {
-	args := strings.Builder{}
-
-	for i, glob := range globs {
-		if i != 0 {
-			args.WriteString(" ")
-		}
-		args.WriteString(`-p "`)
-		args.WriteString(glob.Pattern)
-		args.WriteString(`"`)
-		for _, exclude := range glob.Excludes {
-			args.WriteString(` -e "`)
-			args.WriteString(exclude)
-			args.WriteString(`"`)
-		}
-	}
+// appropriate dependencies written to depFile to regenerate the file if and only if the list of
+// matching files has changed.
+func GlobFile(ctx GlobFileContext, pattern string, excludes []string,
+	fileListFile, depFile string) {
 
 	ctx.Build(pctx, blueprint.BuildParams{
 		Rule:    GlobRule,
 		Outputs: []string{fileListFile},
 		Args: map[string]string{
-			"args": args.String(),
+			"glob":     pattern,
+			"excludes": joinWithPrefixAndQuote(excludes, "-e "),
 		},
-		Description: fmt.Sprintf("regenerate globs shard %d of %d", shard, numGlobBuckets),
 	})
 }
 
@@ -148,76 +111,53 @@
 // re-evaluate them whenever the contents of the searched directories change, and retrigger the
 // primary builder if the results change.
 type globSingleton struct {
-	config     *Config
-	globLister func() pathtools.MultipleGlobResults
+	globLister func() []blueprint.GlobPath
 	writeRule  bool
 }
 
-func globSingletonFactory(config *Config, ctx *blueprint.Context) func() blueprint.Singleton {
+func globSingletonFactory(ctx *blueprint.Context) func() blueprint.Singleton {
 	return func() blueprint.Singleton {
 		return &globSingleton{
-			config:     config,
 			globLister: ctx.Globs,
 		}
 	}
 }
 
 func (s *globSingleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
-	// Sort the list of globs into buckets.  A hash function is used instead of sharding so that
-	// adding a new glob doesn't force rerunning all the buckets by shifting them all by 1.
-	globBuckets := make([]pathtools.MultipleGlobResults, numGlobBuckets)
 	for _, g := range s.globLister() {
-		bucket := globToBucket(g)
-		globBuckets[bucket] = append(globBuckets[bucket], g)
-	}
-
-	// The directory for the intermediates needs to be different for bootstrap and the primary
-	// builder.
-	globsDir := globsDir(ctx.Config().(BootstrapConfig), s.config.stage)
-
-	for i, globs := range globBuckets {
-		fileListFile := filepath.Join(globsDir, strconv.Itoa(i))
+		fileListFile := filepath.Join(BuildDir, ".glob", g.Name)
 
 		if s.writeRule {
-			// Called from generateGlobNinjaFile.  Write out the file list to disk, and add a ninja
-			// rule to run bpglob if any of the dependencies (usually directories that contain
-			// globbed files) have changed.  The file list produced by bpglob should match exactly
-			// with the file written here so that restat can prevent rerunning the primary builder.
-			//
-			// We need to write the file list here so that it has an older modified date
-			// than the build.ninja (otherwise we'd run the primary builder twice on
-			// every new glob)
-			//
-			// We don't need to write the depfile because we're guaranteed that ninja
-			// will run the command at least once (to record it into the ninja_log), so
-			// the depfile will be loaded from that execution.
-			err := pathtools.WriteFileIfChanged(absolutePath(fileListFile), globs.FileList(), 0666)
+			depFile := fileListFile + ".d"
+
+			fileList := strings.Join(g.Files, "\n") + "\n"
+			err := pathtools.WriteFileIfChanged(absolutePath(fileListFile), []byte(fileList), 0666)
 			if err != nil {
 				panic(fmt.Errorf("error writing %s: %s", fileListFile, err))
 			}
+			err = deptools.WriteDepFile(absolutePath(depFile), fileListFile, g.Deps)
+			if err != nil {
+				panic(fmt.Errorf("error writing %s: %s", depFile, err))
+			}
 
-			// Write out the ninja rule to run bpglob.
-			multipleGlobFilesRule(ctx, fileListFile, i, globs)
+			GlobFile(ctx, g.Pattern, g.Excludes, fileListFile, depFile)
 		} else {
-			// Called from the main Context, make build.ninja depend on the fileListFile.
+			// Make build.ninja depend on the fileListFile
 			ctx.AddNinjaFileDeps(fileListFile)
 		}
 	}
 }
 
-func generateGlobNinjaFile(bootstrapConfig *Config, config interface{},
-	globLister func() pathtools.MultipleGlobResults) ([]byte, []error) {
-
+func generateGlobNinjaFile(globLister func() []blueprint.GlobPath) ([]byte, []error) {
 	ctx := blueprint.NewContext()
 	ctx.RegisterSingletonType("glob", func() blueprint.Singleton {
 		return &globSingleton{
-			config:     bootstrapConfig,
 			globLister: globLister,
 			writeRule:  true,
 		}
 	})
 
-	extraDeps, errs := ctx.ResolveDependencies(config)
+	extraDeps, errs := ctx.ResolveDependencies(nil)
 	if len(extraDeps) > 0 {
 		return nil, []error{fmt.Errorf("shouldn't have extra deps")}
 	}
@@ -225,7 +165,7 @@
 		return nil, errs
 	}
 
-	extraDeps, errs = ctx.PrepareBuildActions(config)
+	extraDeps, errs = ctx.PrepareBuildActions(nil)
 	if len(extraDeps) > 0 {
 		return nil, []error{fmt.Errorf("shouldn't have extra deps")}
 	}
@@ -241,37 +181,3 @@
 
 	return buf.Bytes(), nil
 }
-
-// globsDir returns a different directory to store glob intermediates for the bootstrap and
-// primary builder executions.
-func globsDir(config BootstrapConfig, stage Stage) string {
-	buildDir := config.BuildDir()
-	if stage == StageMain {
-		return filepath.Join(buildDir, mainSubDir, "globs")
-	} else {
-		return filepath.Join(buildDir, bootstrapSubDir, "globs")
-	}
-}
-
-// GlobFileListFiles returns the list of sharded glob file list files for the main stage.
-func GlobFileListFiles(config BootstrapConfig) []string {
-	globsDir := globsDir(config, StageMain)
-	var fileListFiles []string
-	for i := 0; i < numGlobBuckets; i++ {
-		fileListFiles = append(fileListFiles, filepath.Join(globsDir, strconv.Itoa(i)))
-	}
-	return fileListFiles
-}
-
-const numGlobBuckets = 1024
-
-// globToBucket converts a pathtools.GlobResult into a hashed bucket number in the range
-// [0, numGlobBuckets).
-func globToBucket(g pathtools.GlobResult) int {
-	hash := fnv.New32a()
-	io.WriteString(hash, g.Pattern)
-	for _, e := range g.Excludes {
-		io.WriteString(hash, e)
-	}
-	return int(hash.Sum32() % numGlobBuckets)
-}
diff --git a/bootstrap/minibp/main.go b/bootstrap/minibp/main.go
index 165f058..1714739 100644
--- a/bootstrap/minibp/main.go
+++ b/bootstrap/minibp/main.go
@@ -23,22 +23,24 @@
 )
 
 var runAsPrimaryBuilder bool
+var buildPrimaryBuilder bool
 
 func init() {
 	flag.BoolVar(&runAsPrimaryBuilder, "p", false, "run as a primary builder")
 }
 
 type Config struct {
+	generatingPrimaryBuilder bool
 }
 
-func (c Config) SrcDir() string {
-	return bootstrap.CmdlineArgs.BuildDir
+func (c Config) GeneratingPrimaryBuilder() bool {
+	return c.generatingPrimaryBuilder
 }
 
-func (c Config) RemoveAbandonedFilesUnder(buildDir string) (under, exempt []string) {
-	if !runAsPrimaryBuilder {
-		under = []string{filepath.Join(buildDir, ".bootstrap")}
-		exempt = []string{filepath.Join(buildDir, ".bootstrap", "build.ninja")}
+func (c Config) RemoveAbandonedFilesUnder() (under, exempt []string) {
+	if c.generatingPrimaryBuilder {
+		under = []string{filepath.Join(bootstrap.BuildDir, ".bootstrap")}
+		exempt = []string{filepath.Join(bootstrap.BuildDir, ".bootstrap", "build.ninja")}
 	}
 	return
 }
@@ -51,6 +53,9 @@
 		ctx.SetIgnoreUnknownModuleTypes(true)
 	}
 
-	config := Config{}
-	bootstrap.Main(ctx, config, !runAsPrimaryBuilder)
+	config := Config{
+		generatingPrimaryBuilder: !runAsPrimaryBuilder,
+	}
+
+	bootstrap.Main(ctx, config)
 }
diff --git a/bootstrap/writedocs.go b/bootstrap/writedocs.go
index 99df32f..4edbcab 100644
--- a/bootstrap/writedocs.go
+++ b/bootstrap/writedocs.go
@@ -15,7 +15,7 @@
 
 // ModuleTypeDocs returns a list of bpdoc.ModuleType objects that contain information relevant
 // to generating documentation for module types supported by the primary builder.
-func ModuleTypeDocs(ctx *blueprint.Context, config interface{}, factories map[string]reflect.Value) ([]*bpdoc.Package, error) {
+func ModuleTypeDocs(ctx *blueprint.Context, factories map[string]reflect.Value) ([]*bpdoc.Package, error) {
 	// Find the module that's marked as the "primary builder", which means it's
 	// creating the binary that we'll use to generate the non-bootstrap
 	// build.ninja file.
@@ -55,7 +55,7 @@
 		switch m := module.(type) {
 		case (*goPackage):
 			pkgFiles[m.properties.PkgPath] = pathtools.PrefixPaths(m.properties.Srcs,
-				filepath.Join(config.(BootstrapConfig).SrcDir(), ctx.ModuleDir(m)))
+				filepath.Join(SrcDir, ctx.ModuleDir(m)))
 		default:
 			panic(fmt.Errorf("unknown dependency type %T", module))
 		}
@@ -75,8 +75,8 @@
 	return bpdoc.AllPackages(pkgFiles, mergedFactories, ctx.ModuleTypePropertyStructs())
 }
 
-func writeDocs(ctx *blueprint.Context, config interface{}, filename string) error {
-	moduleTypeList, err := ModuleTypeDocs(ctx, config, nil)
+func writeDocs(ctx *blueprint.Context, filename string) error {
+	moduleTypeList, err := ModuleTypeDocs(ctx, nil)
 	if err != nil {
 		return err
 	}
diff --git a/bpfmt/bpfmt.go b/bpfmt/bpfmt.go
index 4e6bd1e..c287ea2 100644
--- a/bpfmt/bpfmt.go
+++ b/bpfmt/bpfmt.go
@@ -141,7 +141,7 @@
 		if err := processReader("<standard input>", os.Stdin, os.Stdout); err != nil {
 			report(err)
 		}
-		os.Exit(exitCode)
+		return
 	}
 
 	for i := 0; i < flag.NArg(); i++ {
diff --git a/bpmodify/bpmodify.go b/bpmodify/bpmodify.go
index 29d28f0..29e97d1 100644
--- a/bpmodify/bpmodify.go
+++ b/bpmodify/bpmodify.go
@@ -22,25 +22,20 @@
 
 var (
 	// main operation modes
-	list             = flag.Bool("l", false, "list files that would be modified by bpmodify")
-	write            = flag.Bool("w", false, "write result to (source) file instead of stdout")
-	doDiff           = flag.Bool("d", false, "display diffs instead of rewriting files")
-	sortLists        = flag.Bool("s", false, "sort touched lists, even if they were unsorted")
-	targetedModules  = new(identSet)
-	targetedProperty = new(qualifiedProperty)
-	addIdents        = new(identSet)
-	removeIdents     = new(identSet)
-
-	setString *string
+	list            = flag.Bool("l", false, "list files that would be modified by bpmodify")
+	write           = flag.Bool("w", false, "write result to (source) file instead of stdout")
+	doDiff          = flag.Bool("d", false, "display diffs instead of rewriting files")
+	sortLists       = flag.Bool("s", false, "sort touched lists, even if they were unsorted")
+	parameter       = flag.String("parameter", "deps", "name of parameter to modify on each module")
+	targetedModules = new(identSet)
+	addIdents       = new(identSet)
+	removeIdents    = new(identSet)
 )
 
 func init() {
 	flag.Var(targetedModules, "m", "comma or whitespace separated list of modules on which to operate")
-	flag.Var(targetedProperty, "parameter", "alias to -property=`name`")
-	flag.Var(targetedProperty, "property", "fully qualified `name` of property to modify (default \"deps\")")
 	flag.Var(addIdents, "a", "comma or whitespace separated list of identifiers to add")
 	flag.Var(removeIdents, "r", "comma or whitespace separated list of identifiers to remove")
-	flag.Var(stringPtrFlag{&setString}, "str", "set a string property")
 	flag.Usage = usage
 }
 
@@ -145,80 +140,24 @@
 
 func processModule(module *parser.Module, moduleName string,
 	file *parser.File) (modified bool, errs []error) {
-	prop, err := getRecursiveProperty(module, targetedProperty.name(), targetedProperty.prefixes())
-	if err != nil {
-		return false, []error{err}
-	}
-	if prop == nil {
-		if len(addIdents.idents) > 0 {
-			// We are adding something to a non-existing list prop, so we need to create it first.
-			prop, modified, err = createRecursiveProperty(module, targetedProperty.name(), targetedProperty.prefixes(), &parser.List{})
-		} else if setString != nil {
-			// We setting a non-existent string property, so we need to create it first.
-			prop, modified, err = createRecursiveProperty(module, targetedProperty.name(), targetedProperty.prefixes(), &parser.String{})
-		} else {
-			// We cannot find an existing prop, and we aren't adding anything to the prop,
-			// which means we must be removing something from a non-existing prop,
-			// which means this is a noop.
-			return false, nil
-		}
-		if err != nil {
-			// Here should be unreachable, but still handle it for completeness.
-			return false, []error{err}
+
+	for _, prop := range module.Properties {
+		if prop.Name == *parameter {
+			modified, errs = processParameter(prop.Value, *parameter, moduleName, file)
+			return
 		}
 	}
-	m, errs := processParameter(prop.Value, targetedProperty.String(), moduleName, file)
-	modified = modified || m
+
+	prop := parser.Property{Name: *parameter, Value: &parser.List{}}
+	modified, errs = processParameter(prop.Value, *parameter, moduleName, file)
+
+	if modified {
+		module.Properties = append(module.Properties, &prop)
+	}
+
 	return modified, errs
 }
 
-func getRecursiveProperty(module *parser.Module, name string, prefixes []string) (prop *parser.Property, err error) {
-	prop, _, err = getOrCreateRecursiveProperty(module, name, prefixes, nil)
-	return prop, err
-}
-
-func createRecursiveProperty(module *parser.Module, name string, prefixes []string,
-	empty parser.Expression) (prop *parser.Property, modified bool, err error) {
-
-	return getOrCreateRecursiveProperty(module, name, prefixes, empty)
-}
-
-func getOrCreateRecursiveProperty(module *parser.Module, name string, prefixes []string,
-	empty parser.Expression) (prop *parser.Property, modified bool, err error) {
-	m := &module.Map
-	for i, prefix := range prefixes {
-		if prop, found := m.GetProperty(prefix); found {
-			if mm, ok := prop.Value.Eval().(*parser.Map); ok {
-				m = mm
-			} else {
-				// We've found a property in the AST and such property is not of type
-				// *parser.Map, which must mean we didn't modify the AST.
-				return nil, false, fmt.Errorf("Expected property %q to be a map, found %s",
-					strings.Join(prefixes[:i+1], "."), prop.Value.Type())
-			}
-		} else if empty != nil {
-			mm := &parser.Map{}
-			m.Properties = append(m.Properties, &parser.Property{Name: prefix, Value: mm})
-			m = mm
-			// We've created a new node in the AST. This means the m.GetProperty(name)
-			// check after this for loop must fail, because the node we inserted is an
-			// empty parser.Map, thus this function will return |modified| is true.
-		} else {
-			return nil, false, nil
-		}
-	}
-	if prop, found := m.GetProperty(name); found {
-		// We've found a property in the AST, which must mean we didn't modify the AST.
-		return prop, false, nil
-	} else if empty != nil {
-		prop = &parser.Property{Name: name, Value: empty}
-		m.Properties = append(m.Properties, prop)
-		return prop, true, nil
-	} else {
-		return nil, false, nil
-	}
-}
-
 func processParameter(value parser.Expression, paramName, moduleName string,
 	file *parser.File) (modified bool, errs []error) {
 	if _, ok := value.(*parser.Variable); ok {
@@ -231,37 +170,26 @@
 			paramName, moduleName)}
 	}
 
-	if len(addIdents.idents) > 0 || len(removeIdents.idents) > 0 {
-		list, ok := value.(*parser.List)
-		if !ok {
-			return false, []error{fmt.Errorf("expected parameter %s in module %s to be list, found %s",
-				paramName, moduleName, value.Type().String())}
-		}
+	list, ok := value.(*parser.List)
+	if !ok {
+		return false, []error{fmt.Errorf("expected parameter %s in module %s to be list, found %s",
+			paramName, moduleName, value.Type().String())}
+	}
 
-		wasSorted := parser.ListIsSorted(list)
+	wasSorted := parser.ListIsSorted(list)
 
-		for _, a := range addIdents.idents {
-			m := parser.AddStringToList(list, a)
-			modified = modified || m
-		}
+	for _, a := range addIdents.idents {
+		m := parser.AddStringToList(list, a)
+		modified = modified || m
+	}
 
-		for _, r := range removeIdents.idents {
-			m := parser.RemoveStringFromList(list, r)
-			modified = modified || m
-		}
+	for _, r := range removeIdents.idents {
+		m := parser.RemoveStringFromList(list, r)
+		modified = modified || m
+	}
 
-		if (wasSorted || *sortLists) && modified {
-			parser.SortList(file, list)
-		}
-	} else if setString != nil {
-		str, ok := value.(*parser.String)
-		if !ok {
-			return false, []error{fmt.Errorf("expected parameter %s in module %s to be string, found %s",
-				paramName, moduleName, value.Type().String())}
-		}
-
-		str.Value = *setString
-		modified = true
+	if (wasSorted || *sortLists) && modified {
+		parser.SortList(file, list)
 	}
 
 	return modified, nil
@@ -304,10 +232,6 @@
 
 	flag.Parse()
 
-	if len(targetedProperty.parts) == 0 {
-		targetedProperty.Set("deps")
-	}
-
 	if flag.NArg() == 0 {
 		if *write {
 			report(fmt.Errorf("error: cannot use -w with standard input"))
@@ -324,8 +248,8 @@
 		return
 	}
 
-	if len(addIdents.idents) == 0 && len(removeIdents.idents) == 0 && setString == nil {
-		report(fmt.Errorf("-a, -r or -str parameter is required"))
+	if len(addIdents.idents) == 0 && len(removeIdents.idents) == 0 {
+		report(fmt.Errorf("-a or -r parameter is required"))
 		return
 	}
 
@@ -372,22 +296,6 @@
 
 }
 
-type stringPtrFlag struct {
-	s **string
-}
-
-func (f stringPtrFlag) Set(s string) error {
-	*f.s = &s
-	return nil
-}
-
-func (f stringPtrFlag) String() string {
-	if f.s == nil || *f.s == nil {
-		return ""
-	}
-	return **f.s
-}
-
 type identSet struct {
 	idents []string
 	all    bool
@@ -410,38 +318,3 @@
 func (m *identSet) Get() interface{} {
 	return m.idents
 }
-
-type qualifiedProperty struct {
-	parts []string
-}
-
-var _ flag.Getter = (*qualifiedProperty)(nil)
-
-func (p *qualifiedProperty) name() string {
-	return p.parts[len(p.parts)-1]
-}
-
-func (p *qualifiedProperty) prefixes() []string {
-	return p.parts[:len(p.parts)-1]
-}
-
-func (p *qualifiedProperty) String() string {
-	return strings.Join(p.parts, ".")
-}
-
-func (p *qualifiedProperty) Set(s string) error {
-	p.parts = strings.Split(s, ".")
-	if len(p.parts) == 0 {
-		return fmt.Errorf("%q is not a valid property name", s)
-	}
-	for _, part := range p.parts {
-		if part == "" {
-			return fmt.Errorf("%q is not a valid property name", s)
-		}
-	}
-	return nil
-}
-
-func (p *qualifiedProperty) Get() interface{} {
-	return p.parts
-}
diff --git a/bpmodify/bpmodify_test.go b/bpmodify/bpmodify_test.go
deleted file mode 100644
index a92d439..0000000
--- a/bpmodify/bpmodify_test.go
+++ /dev/null
@@ -1,338 +0,0 @@
-// Copyright 2020 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package main
-
-import (
-	"strings"
-	"testing"
-
-	"github.com/google/blueprint/parser"
-	"github.com/google/blueprint/proptools"
-)
-
-var testCases = []struct {
-	name      string
-	input     string
-	output    string
-	property  string
-	addSet    string
-	removeSet string
-	setString *string
-}{
-	{
-		name: "add",
-		input: `
-			cc_foo {
-				name: "foo",
-			}
-		`,
-		output: `
-			cc_foo {
-				name: "foo",
-				deps: ["bar"],
-			}
-		`,
-		property: "deps",
-		addSet:   "bar",
-	},
-	{
-		name: "remove",
-		input: `
-			cc_foo {
-				name: "foo",
-				deps: ["bar"],
-			}
-		`,
-		output: `
-			cc_foo {
-				name: "foo",
-				deps: [],
-			}
-		`,
-		property:  "deps",
-		removeSet: "bar",
-	},
-	{
-		name: "nested add",
-		input: `
-			cc_foo {
-				name: "foo",
-			}
-		`,
-		output: `
-			cc_foo {
-				name: "foo",
-				arch: {
-					arm: {
-						deps: [
-							"dep2",
-							"nested_dep",],
-					},
-				},
-			}
-		`,
-		property: "arch.arm.deps",
-		addSet:   "nested_dep,dep2",
-	},
-	{
-		name: "nested remove",
-		input: `
-			cc_foo {
-				name: "foo",
-				arch: {
-					arm: {
-						deps: [
-							"dep2",
-							"nested_dep",
-						],
-					},
-				},
-			}
-		`,
-		output: `
-			cc_foo {
-				name: "foo",
-				arch: {
-					arm: {
-						deps: [
-						],
-					},
-				},
-			}
-		`,
-		property:  "arch.arm.deps",
-		removeSet: "nested_dep,dep2",
-	},
-	{
-		name: "add existing",
-		input: `
-			cc_foo {
-				name: "foo",
-				arch: {
-					arm: {
-						deps: [
-							"nested_dep",
-							"dep2",
-						],
-					},
-				},
-			}
-		`,
-		output: `
-			cc_foo {
-				name: "foo",
-				arch: {
-					arm: {
-						deps: [
-							"nested_dep",
-							"dep2",
-						],
-					},
-				},
-			}
-		`,
-		property: "arch.arm.deps",
-		addSet:   "dep2,dep2",
-	},
-	{
-		name: "remove missing",
-		input: `
-			cc_foo {
-				name: "foo",
-				arch: {
-					arm: {
-						deps: [
-							"nested_dep",
-							"dep2",
-						],
-					},
-				},
-			}
-		`,
-		output: `
-			cc_foo {
-				name: "foo",
-				arch: {
-					arm: {
-						deps: [
-							"nested_dep",
-							"dep2",
-						],
-					},
-				},
-			}
-		`,
-		property:  "arch.arm.deps",
-		removeSet: "dep3,dep4",
-	},
-	{
-		name: "remove non existent",
-		input: `
-			cc_foo {
-				name: "foo",
-			}
-		`,
-		output: `
-			cc_foo {
-				name: "foo",
-			}
-		`,
-		property:  "deps",
-		removeSet: "bar",
-	},
-	{
-		name: "remove non existent nested",
-		input: `
-			cc_foo {
-				name: "foo",
-				arch: {},
-			}
-		`,
-		output: `
-			cc_foo {
-				name: "foo",
-				arch: {},
-			}
-		`,
-		property:  "arch.arm.deps",
-		removeSet: "dep3,dep4",
-	},
-	{
-		name: "add numeric sorted",
-		input: `
-			cc_foo {
-				name: "foo",
-				versions: ["1", "2"],
-			}
-		`,
-		output: `
-			cc_foo {
-				name: "foo",
-				versions: [
-					"1",
-					"2",
-					"10",
-				],
-			}
-		`,
-		property: "versions",
-		addSet:   "10",
-	},
-	{
-		name: "add mixed sorted",
-		input: `
-			cc_foo {
-				name: "foo",
-				deps: ["bar-v1-bar", "bar-v2-bar"],
-			}
-		`,
-		output: `
-			cc_foo {
-				name: "foo",
-				deps: [
-					"bar-v1-bar",
-					"bar-v2-bar",
-					"bar-v10-bar",
-				],
-			}
-		`,
-		property: "deps",
-		addSet:   "bar-v10-bar",
-	},
-	{
-		name: "set string",
-		input: `
-			cc_foo {
-				name: "foo",
-			}
-		`,
-		output: `
-			cc_foo {
-				name: "foo",
-				foo: "bar",
-			}
-		`,
-		property:  "foo",
-		setString: proptools.StringPtr("bar"),
-	},
-	{
-		name: "set existing string",
-		input: `
-			cc_foo {
-				name: "foo",
-				foo: "baz",
-			}
-		`,
-		output: `
-			cc_foo {
-				name: "foo",
-				foo: "bar",
-			}
-		`,
-		property:  "foo",
-		setString: proptools.StringPtr("bar"),
-	},
-}
-
-func simplifyModuleDefinition(def string) string {
-	var result string
-	for _, line := range strings.Split(def, "\n") {
-		result += strings.TrimSpace(line)
-	}
-	return result
-}
-
-func TestProcessModule(t *testing.T) {
-	for i, testCase := range testCases {
-		t.Run(testCase.name, func(t *testing.T) {
-			targetedProperty.Set(testCase.property)
-			addIdents.Set(testCase.addSet)
-			removeIdents.Set(testCase.removeSet)
-			setString = testCase.setString
-
-			inAst, errs := parser.ParseAndEval("", strings.NewReader(testCase.input), parser.NewScope(nil))
-			if len(errs) > 0 {
-				for _, err := range errs {
-					t.Errorf("  %s", err)
-				}
-				t.Errorf("failed to parse:")
-				t.Errorf("%+v", testCase)
-				t.FailNow()
-			}
-
-			if inModule, ok := inAst.Defs[0].(*parser.Module); !ok {
-				t.Fatalf("  input must only contain a single module definition: %s", testCase.input)
-			} else {
-				_, errs := processModule(inModule, "", inAst)
-				if len(errs) > 0 {
-					t.Errorf("test case %d:", i)
-					for _, err := range errs {
-						t.Errorf("  %s", err)
-					}
-				}
-				inModuleText, _ := parser.Print(inAst)
-				inModuleString := string(inModuleText)
-				if simplifyModuleDefinition(inModuleString) != simplifyModuleDefinition(testCase.output) {
-					t.Errorf("test case %d:", i)
-					t.Errorf("expected module definition:")
-					t.Errorf("  %s", testCase.output)
-					t.Errorf("actual module definition:")
-					t.Errorf("  %s", inModuleString)
-				}
-			}
-		})
-	}
-
-}
diff --git a/context.go b/context.go
index e891c23..3018209 100644
--- a/context.go
+++ b/context.go
@@ -17,7 +17,6 @@
 import (
 	"bytes"
 	"context"
-	"encoding/json"
 	"errors"
 	"fmt"
 	"io"
@@ -111,28 +110,13 @@
 
 	// set lazily by sortedModuleGroups
 	cachedSortedModuleGroups []*moduleGroup
-	// cache deps modified to determine whether cachedSortedModuleGroups needs to be recalculated
-	cachedDepsModified bool
 
-	globs    map[globKey]pathtools.GlobResult
+	globs    map[string]GlobPath
 	globLock sync.Mutex
 
 	srcDir         string
 	fs             pathtools.FileSystem
 	moduleListFile string
-
-	// Mutators indexed by the ID of the provider associated with them.  Not all mutators will
-	// have providers, and not all providers will have a mutator, or if they do the mutator may
-	// not be registered in this Context.
-	providerMutators []*mutatorInfo
-
-	// The currently running mutator
-	startedMutator *mutatorInfo
-	// True for any mutators that have already run over all modules
-	finishedMutators map[*mutatorInfo]bool
-
-	// Can be set by tests to avoid invalidating Module values after mutators.
-	skipCloneModulesAfterMutators bool
 }
 
 // An Error describes a problem that was encountered that is related to a
@@ -175,69 +159,22 @@
 }
 
 type moduleAlias struct {
-	variant variant
-	target  *moduleInfo
-}
-
-func (m *moduleAlias) alias() *moduleAlias              { return m }
-func (m *moduleAlias) module() *moduleInfo              { return nil }
-func (m *moduleAlias) moduleOrAliasTarget() *moduleInfo { return m.target }
-func (m *moduleAlias) moduleOrAliasVariant() variant    { return m.variant }
-
-func (m *moduleInfo) alias() *moduleAlias              { return nil }
-func (m *moduleInfo) module() *moduleInfo              { return m }
-func (m *moduleInfo) moduleOrAliasTarget() *moduleInfo { return m }
-func (m *moduleInfo) moduleOrAliasVariant() variant    { return m.variant }
-
-type moduleOrAlias interface {
-	alias() *moduleAlias
-	module() *moduleInfo
-	moduleOrAliasTarget() *moduleInfo
-	moduleOrAliasVariant() variant
-}
-
-type modulesOrAliases []moduleOrAlias
-
-func (l modulesOrAliases) firstModule() *moduleInfo {
-	for _, moduleOrAlias := range l {
-		if m := moduleOrAlias.module(); m != nil {
-			return m
-		}
-	}
-	panic(fmt.Errorf("no first module!"))
-}
-
-func (l modulesOrAliases) lastModule() *moduleInfo {
-	for i := range l {
-		if m := l[len(l)-1-i].module(); m != nil {
-			return m
-		}
-	}
-	panic(fmt.Errorf("no last module!"))
+	variantName       string
+	variant           variationMap
+	dependencyVariant variationMap
+	target            *moduleInfo
 }
 
 type moduleGroup struct {
 	name      string
 	ninjaName string
 
-	modules modulesOrAliases
+	modules []*moduleInfo
+	aliases []*moduleAlias
 
 	namespace Namespace
 }
 
-func (group *moduleGroup) moduleOrAliasByVariantName(name string) moduleOrAlias {
-	for _, module := range group.modules {
-		if module.moduleOrAliasVariant().name == name {
-			return module
-		}
-	}
-	return nil
-}
-
-func (group *moduleGroup) moduleByVariantName(name string) *moduleInfo {
-	return group.moduleOrAliasByVariantName(name).module()
-}
-
 type moduleInfo struct {
 	// set during Parse
 	typeName          string
@@ -247,7 +184,9 @@
 	propertyPos       map[string]scanner.Position
 	createdBy         *moduleInfo
 
-	variant variant
+	variantName       string
+	variant           variationMap
+	dependencyVariant variationMap
 
 	logicModule Module
 	group       *moduleGroup
@@ -262,28 +201,15 @@
 	forwardDeps []*moduleInfo
 	directDeps  []depInfo
 
-	// used by parallelVisit
+	// used by parallelVisitAllBottomUp
 	waitingCount int
 
 	// set during each runMutator
-	splitModules modulesOrAliases
+	splitModules []*moduleInfo
+	aliasTarget  *moduleInfo
 
 	// set during PrepareBuildActions
 	actionDefs localBuildActions
-
-	providers []interface{}
-
-	startedMutator  *mutatorInfo
-	finishedMutator *mutatorInfo
-
-	startedGenerateBuildActions  bool
-	finishedGenerateBuildActions bool
-}
-
-type variant struct {
-	name                 string
-	variations           variationMap
-	dependencyVariations variationMap
 }
 
 type depInfo struct {
@@ -306,8 +232,8 @@
 
 func (module *moduleInfo) String() string {
 	s := fmt.Sprintf("module %q", module.Name())
-	if module.variant.name != "" {
-		s += fmt.Sprintf(" variant %q", module.variant.name)
+	if module.variantName != "" {
+		s += fmt.Sprintf(" variant %q", module.variantName)
 	}
 	if module.createdBy != nil {
 		s += fmt.Sprintf(" (created by %s)", module.createdBy)
@@ -347,10 +273,10 @@
 }
 
 // Compare this variationMap to another one.  Returns true if the every entry in this map
-// exists and has the same value in the other map.
-func (vm variationMap) subsetOf(other variationMap) bool {
+// is either the same in the other map or doesn't exist in the other map.
+func (vm variationMap) subset(other variationMap) bool {
 	for k, v1 := range vm {
-		if v2, ok := other[k]; !ok || v1 != v2 {
+		if v2, ok := other[k]; ok && v1 != v2 {
 			return false
 		}
 	}
@@ -385,9 +311,8 @@
 		moduleFactories:    make(map[string]ModuleFactory),
 		nameInterface:      NewSimpleNameInterface(),
 		moduleInfo:         make(map[Module]*moduleInfo),
-		globs:              make(map[globKey]pathtools.GlobResult),
+		globs:              make(map[string]GlobPath),
 		fs:                 pathtools.OsFs,
-		finishedMutators:   make(map[*mutatorInfo]bool),
 		ninjaBuildDir:      nil,
 		requiredNinjaMajor: 1,
 		requiredNinjaMinor: 7,
@@ -747,7 +672,6 @@
 
 	type newModuleInfo struct {
 		*moduleInfo
-		deps  []string
 		added chan<- struct{}
 	}
 
@@ -773,12 +697,12 @@
 			// registered by name. This allows load hooks to set and/or modify any aspect
 			// of the module (including names) using information that is not available when
 			// the module factory is called.
-			newModules, newDeps, errs := runAndRemoveLoadHooks(c, config, module, &scopedModuleFactories)
+			newModules, errs := runAndRemoveLoadHooks(c, config, module, &scopedModuleFactories)
 			if len(errs) > 0 {
 				return errs
 			}
 
-			moduleCh <- newModuleInfo{module, newDeps, addedCh}
+			moduleCh <- newModuleInfo{module, addedCh}
 			<-addedCh
 			for _, n := range newModules {
 				errs = addModule(n)
@@ -821,7 +745,6 @@
 		doneCh <- struct{}{}
 	}()
 
-	var hookDeps []string
 loop:
 	for {
 		select {
@@ -829,7 +752,6 @@
 			errs = append(errs, newErrs...)
 		case module := <-moduleCh:
 			newErrs := c.addModule(module.moduleInfo)
-			hookDeps = append(hookDeps, module.deps...)
 			if module.added != nil {
 				module.added <- struct{}{}
 			}
@@ -844,7 +766,6 @@
 		}
 	}
 
-	deps = append(deps, hookDeps...)
 	return deps, errs
 }
 
@@ -1303,44 +1224,15 @@
 	return newLogicModule, newProperties
 }
 
-func newVariant(module *moduleInfo, mutatorName string, variationName string,
-	local bool) variant {
-
-	newVariantName := module.variant.name
-	if variationName != "" {
-		if newVariantName == "" {
-			newVariantName = variationName
-		} else {
-			newVariantName += "_" + variationName
-		}
-	}
-
-	newVariations := module.variant.variations.clone()
-	if newVariations == nil {
-		newVariations = make(variationMap)
-	}
-	newVariations[mutatorName] = variationName
-
-	newDependencyVariations := module.variant.dependencyVariations.clone()
-	if !local {
-		if newDependencyVariations == nil {
-			newDependencyVariations = make(variationMap)
-		}
-		newDependencyVariations[mutatorName] = variationName
-	}
-
-	return variant{newVariantName, newVariations, newDependencyVariations}
-}
-
 func (c *Context) createVariations(origModule *moduleInfo, mutatorName string,
-	defaultVariationName *string, variationNames []string, local bool) (modulesOrAliases, []error) {
+	defaultVariationName *string, variationNames []string) ([]*moduleInfo, []error) {
 
 	if len(variationNames) == 0 {
 		panic(fmt.Errorf("mutator %q passed zero-length variation list for module %q",
 			mutatorName, origModule.Name()))
 	}
 
-	var newModules modulesOrAliases
+	newModules := []*moduleInfo{}
 
 	var errs []error
 
@@ -1357,15 +1249,27 @@
 			newLogicModule, newProperties = c.cloneLogicModule(origModule)
 		}
 
+		newVariant := origModule.variant.clone()
+		if newVariant == nil {
+			newVariant = make(variationMap)
+		}
+		newVariant[mutatorName] = variationName
+
 		m := *origModule
 		newModule := &m
-		newModule.directDeps = append([]depInfo(nil), origModule.directDeps...)
-		newModule.reverseDeps = nil
-		newModule.forwardDeps = nil
+		newModule.directDeps = append([]depInfo{}, origModule.directDeps...)
 		newModule.logicModule = newLogicModule
-		newModule.variant = newVariant(origModule, mutatorName, variationName, local)
+		newModule.variant = newVariant
+		newModule.dependencyVariant = origModule.dependencyVariant.clone()
 		newModule.properties = newProperties
-		newModule.providers = append([]interface{}(nil), origModule.providers...)
+
+		if variationName != "" {
+			if newModule.variantName == "" {
+				newModule.variantName = variationName
+			} else {
+				newModule.variantName += "_" + variationName
+			}
+		}
 
 		newModules = append(newModules, newModule)
 
@@ -1392,16 +1296,16 @@
 		if dep.module.logicModule == nil {
 			var newDep *moduleInfo
 			for _, m := range dep.module.splitModules {
-				if m.moduleOrAliasVariant().variations[mutatorName] == variationName {
-					newDep = m.moduleOrAliasTarget()
+				if m.variant[mutatorName] == variationName {
+					newDep = m
 					break
 				}
 			}
 			if newDep == nil && defaultVariationName != nil {
 				// give it a second chance; match with defaultVariationName
 				for _, m := range dep.module.splitModules {
-					if m.moduleOrAliasVariant().variations[mutatorName] == *defaultVariationName {
-						newDep = m.moduleOrAliasTarget()
+					if m.variant[mutatorName] == *defaultVariationName {
+						newDep = m
 						break
 					}
 				}
@@ -1421,27 +1325,27 @@
 	return errs
 }
 
-func (c *Context) prettyPrintVariant(variations variationMap) string {
-	names := make([]string, 0, len(variations))
+func (c *Context) prettyPrintVariant(variant variationMap) string {
+	names := make([]string, 0, len(variant))
 	for _, m := range c.variantMutatorNames {
-		if v, ok := variations[m]; ok {
+		if v, ok := variant[m]; ok {
 			names = append(names, m+":"+v)
 		}
 	}
 
-	return strings.Join(names, ",")
+	return strings.Join(names, ", ")
 }
 
 func (c *Context) prettyPrintGroupVariants(group *moduleGroup) string {
 	var variants []string
-	for _, moduleOrAlias := range group.modules {
-		if mod := moduleOrAlias.module(); mod != nil {
-			variants = append(variants, c.prettyPrintVariant(mod.variant.variations))
-		} else if alias := moduleOrAlias.alias(); alias != nil {
-			variants = append(variants, c.prettyPrintVariant(alias.variant.variations)+
-				" (alias to "+c.prettyPrintVariant(alias.target.variant.variations)+")")
-		}
+	for _, mod := range group.modules {
+		variants = append(variants, c.prettyPrintVariant(mod.variant))
 	}
+	for _, mod := range group.aliases {
+		variants = append(variants, c.prettyPrintVariant(mod.variant)+
+			"(alias to "+c.prettyPrintVariant(mod.target.variant)+")")
+	}
+	sort.Strings(variants)
 	return strings.Join(variants, "\n  ")
 }
 
@@ -1520,7 +1424,7 @@
 
 	group := &moduleGroup{
 		name:    name,
-		modules: modulesOrAliases{module},
+		modules: []*moduleInfo{module},
 	}
 	module.group = group
 	namespace, errs := c.nameInterface.NewModule(
@@ -1550,8 +1454,6 @@
 
 func (c *Context) resolveDependencies(ctx context.Context, config interface{}) (deps []string, errs []error) {
 	pprof.Do(ctx, pprof.Labels("blueprint", "ResolveDependencies"), func(ctx context.Context) {
-		c.initProviders()
-
 		c.liveGlobals = newLiveTracker(config)
 
 		deps, errs = c.generateSingletonBuildActions(config, c.preSingletonInfo, c.liveGlobals)
@@ -1571,9 +1473,7 @@
 		}
 		deps = append(deps, mutatorDeps...)
 
-		if !c.skipCloneModulesAfterMutators {
-			c.cloneModules()
-		}
+		c.cloneModules()
 
 		c.dependenciesReady = true
 	})
@@ -1608,32 +1508,43 @@
 	}
 }
 
-// findExactVariantOrSingle searches the moduleGroup for a module with the same variant as module,
-// and returns the matching module, or nil if one is not found.  A group with exactly one module
-// is always considered matching.
-func findExactVariantOrSingle(module *moduleInfo, possible *moduleGroup, reverse bool) *moduleInfo {
-	found, _ := findVariant(module, possible, nil, false, reverse)
-	if found == nil {
-		for _, moduleOrAlias := range possible.modules {
-			if m := moduleOrAlias.module(); m != nil {
-				if found != nil {
-					// more than one possible match, give up
-					return nil
-				}
-				found = m
+// findMatchingVariant searches the moduleGroup for a module with the same variant as module,
+// and returns the matching module, or nil if one is not found.
+func (c *Context) findMatchingVariant(module *moduleInfo, possible *moduleGroup, reverse bool) *moduleInfo {
+	if len(possible.modules) == 1 {
+		return possible.modules[0]
+	} else {
+		var variantToMatch variationMap
+		if !reverse {
+			// For forward dependency, ignore local variants by matching against
+			// dependencyVariant which doesn't have the local variants
+			variantToMatch = module.dependencyVariant
+		} else {
+			// For reverse dependency, use all the variants
+			variantToMatch = module.variant
+		}
+		for _, m := range possible.modules {
+			if m.variant.equal(variantToMatch) {
+				return m
+			}
+		}
+		for _, m := range possible.aliases {
+			if m.variant.equal(variantToMatch) {
+				return m.target
 			}
 		}
 	}
-	return found
+
+	return nil
 }
 
-func (c *Context) addDependency(module *moduleInfo, tag DependencyTag, depName string) (*moduleInfo, []error) {
+func (c *Context) addDependency(module *moduleInfo, tag DependencyTag, depName string) []error {
 	if _, ok := tag.(BaseDependencyTag); ok {
 		panic("BaseDependencyTag is not allowed to be used directly!")
 	}
 
 	if depName == module.Name() {
-		return nil, []error{&BlueprintError{
+		return []error{&BlueprintError{
 			Err: fmt.Errorf("%q depends on itself", depName),
 			Pos: module.pos,
 		}}
@@ -1641,24 +1552,24 @@
 
 	possibleDeps := c.moduleGroupFromName(depName, module.namespace())
 	if possibleDeps == nil {
-		return nil, c.discoveredMissingDependencies(module, depName, nil)
+		return c.discoveredMissingDependencies(module, depName)
 	}
 
-	if m := findExactVariantOrSingle(module, possibleDeps, false); m != nil {
+	if m := c.findMatchingVariant(module, possibleDeps, false); m != nil {
 		module.newDirectDeps = append(module.newDirectDeps, depInfo{m, tag})
 		atomic.AddUint32(&c.depsModified, 1)
-		return m, nil
+		return nil
 	}
 
 	if c.allowMissingDependencies {
 		// Allow missing variants.
-		return nil, c.discoveredMissingDependencies(module, depName, module.variant.dependencyVariations)
+		return c.discoveredMissingDependencies(module, depName+c.prettyPrintVariant(module.dependencyVariant))
 	}
 
-	return nil, []error{&BlueprintError{
+	return []error{&BlueprintError{
 		Err: fmt.Errorf("dependency %q of %q missing variant:\n  %s\navailable variants:\n  %s",
 			depName, module.Name(),
-			c.prettyPrintVariant(module.variant.dependencyVariations),
+			c.prettyPrintVariant(module.dependencyVariant),
 			c.prettyPrintGroupVariants(possibleDeps)),
 		Pos: module.pos,
 	}}
@@ -1681,38 +1592,41 @@
 		}}
 	}
 
-	if m := findExactVariantOrSingle(module, possibleDeps, true); m != nil {
+	if m := c.findMatchingVariant(module, possibleDeps, true); m != nil {
 		return m, nil
 	}
 
 	if c.allowMissingDependencies {
 		// Allow missing variants.
-		return module, c.discoveredMissingDependencies(module, destName, module.variant.dependencyVariations)
+		return module, c.discoveredMissingDependencies(module, destName+c.prettyPrintVariant(module.dependencyVariant))
 	}
 
 	return nil, []error{&BlueprintError{
 		Err: fmt.Errorf("reverse dependency %q of %q missing variant:\n  %s\navailable variants:\n  %s",
 			destName, module.Name(),
-			c.prettyPrintVariant(module.variant.dependencyVariations),
+			c.prettyPrintVariant(module.dependencyVariant),
 			c.prettyPrintGroupVariants(possibleDeps)),
 		Pos: module.pos,
 	}}
 }
 
-func findVariant(module *moduleInfo, possibleDeps *moduleGroup, variations []Variation, far bool, reverse bool) (*moduleInfo, variationMap) {
-	// We can't just append variant.Variant to module.dependencyVariant.variantName and
+func (c *Context) addVariationDependency(module *moduleInfo, variations []Variation,
+	tag DependencyTag, depName string, far bool) []error {
+	if _, ok := tag.(BaseDependencyTag); ok {
+		panic("BaseDependencyTag is not allowed to be used directly!")
+	}
+
+	possibleDeps := c.moduleGroupFromName(depName, module.namespace())
+	if possibleDeps == nil {
+		return c.discoveredMissingDependencies(module, depName)
+	}
+
+	// We can't just append variant.Variant to module.dependencyVariants.variantName and
 	// compare the strings because the result won't be in mutator registration order.
 	// Create a new map instead, and then deep compare the maps.
 	var newVariant variationMap
 	if !far {
-		if !reverse {
-			// For forward dependency, ignore local variants by matching against
-			// dependencyVariant which doesn't have the local variants
-			newVariant = module.variant.dependencyVariations.clone()
-		} else {
-			// For reverse dependency, use all the variants
-			newVariant = module.variant.variations.clone()
-		}
+		newVariant = module.dependencyVariant.clone()
 	}
 	for _, v := range variations {
 		if newVariant == nil {
@@ -1723,7 +1637,7 @@
 
 	check := func(variant variationMap) bool {
 		if far {
-			return newVariant.subsetOf(variant)
+			return variant.subset(newVariant)
 		} else {
 			return variant.equal(newVariant)
 		}
@@ -1731,34 +1645,27 @@
 
 	var foundDep *moduleInfo
 	for _, m := range possibleDeps.modules {
-		if check(m.moduleOrAliasVariant().variations) {
-			foundDep = m.moduleOrAliasTarget()
+		if check(m.variant) {
+			foundDep = m
 			break
 		}
 	}
 
-	return foundDep, newVariant
-}
-
-func (c *Context) addVariationDependency(module *moduleInfo, variations []Variation,
-	tag DependencyTag, depName string, far bool) (*moduleInfo, []error) {
-	if _, ok := tag.(BaseDependencyTag); ok {
-		panic("BaseDependencyTag is not allowed to be used directly!")
+	if foundDep == nil {
+		for _, m := range possibleDeps.aliases {
+			if check(m.variant) {
+				foundDep = m.target
+				break
+			}
+		}
 	}
 
-	possibleDeps := c.moduleGroupFromName(depName, module.namespace())
-	if possibleDeps == nil {
-		return nil, c.discoveredMissingDependencies(module, depName, nil)
-	}
-
-	foundDep, newVariant := findVariant(module, possibleDeps, variations, far, false)
-
 	if foundDep == nil {
 		if c.allowMissingDependencies {
 			// Allow missing variants.
-			return nil, c.discoveredMissingDependencies(module, depName, newVariant)
+			return c.discoveredMissingDependencies(module, depName+c.prettyPrintVariant(newVariant))
 		}
-		return nil, []error{&BlueprintError{
+		return []error{&BlueprintError{
 			Err: fmt.Errorf("dependency %q of %q missing variant:\n  %s\navailable variants:\n  %s",
 				depName, module.Name(),
 				c.prettyPrintVariant(newVariant),
@@ -1768,7 +1675,7 @@
 	}
 
 	if module == foundDep {
-		return nil, []error{&BlueprintError{
+		return []error{&BlueprintError{
 			Err: fmt.Errorf("%q depends on itself", depName),
 			Pos: module.pos,
 		}}
@@ -1777,33 +1684,31 @@
 	// that module is earlier in the module list than this one, since we always
 	// run GenerateBuildActions in order for the variants of a module
 	if foundDep.group == module.group && beforeInModuleList(module, foundDep, module.group.modules) {
-		return nil, []error{&BlueprintError{
+		return []error{&BlueprintError{
 			Err: fmt.Errorf("%q depends on later version of itself", depName),
 			Pos: module.pos,
 		}}
 	}
 	module.newDirectDeps = append(module.newDirectDeps, depInfo{foundDep, tag})
 	atomic.AddUint32(&c.depsModified, 1)
-	return foundDep, nil
+	return nil
 }
 
 func (c *Context) addInterVariantDependency(origModule *moduleInfo, tag DependencyTag,
-	from, to Module) *moduleInfo {
+	from, to Module) {
 	if _, ok := tag.(BaseDependencyTag); ok {
 		panic("BaseDependencyTag is not allowed to be used directly!")
 	}
 
 	var fromInfo, toInfo *moduleInfo
-	for _, moduleOrAlias := range origModule.splitModules {
-		if m := moduleOrAlias.module(); m != nil {
-			if m.logicModule == from {
-				fromInfo = m
-			}
-			if m.logicModule == to {
-				toInfo = m
-				if fromInfo != nil {
-					panic(fmt.Errorf("%q depends on later version of itself", origModule.Name()))
-				}
+	for _, m := range origModule.splitModules {
+		if m.logicModule == from {
+			fromInfo = m
+		}
+		if m.logicModule == to {
+			toInfo = m
+			if fromInfo != nil {
+				panic(fmt.Errorf("%q depends on later version of itself", origModule.Name()))
 			}
 		}
 	}
@@ -1815,7 +1720,6 @@
 
 	fromInfo.newDirectDeps = append(fromInfo.newDirectDeps, depInfo{toInfo, tag})
 	atomic.AddUint32(&c.depsModified, 1)
-	return toInfo
 }
 
 // findBlueprintDescendants returns a map linking parent Blueprints files to child Blueprints files
@@ -1864,7 +1768,7 @@
 	// returns the list of modules that are waiting for this module
 	propagate(module *moduleInfo) []*moduleInfo
 	// visit modules in order
-	visit(modules []*moduleInfo, visit func(*moduleInfo, chan<- pauseSpec) bool)
+	visit(modules []*moduleInfo, visit func(*moduleInfo) bool)
 }
 
 type unorderedVisitorImpl struct{}
@@ -1877,9 +1781,9 @@
 	return nil
 }
 
-func (unorderedVisitorImpl) visit(modules []*moduleInfo, visit func(*moduleInfo, chan<- pauseSpec) bool) {
+func (unorderedVisitorImpl) visit(modules []*moduleInfo, visit func(*moduleInfo) bool) {
 	for _, module := range modules {
-		if visit(module, nil) {
+		if visit(module) {
 			return
 		}
 	}
@@ -1895,9 +1799,9 @@
 	return module.reverseDeps
 }
 
-func (bottomUpVisitorImpl) visit(modules []*moduleInfo, visit func(*moduleInfo, chan<- pauseSpec) bool) {
+func (bottomUpVisitorImpl) visit(modules []*moduleInfo, visit func(*moduleInfo) bool) {
 	for _, module := range modules {
-		if visit(module, nil) {
+		if visit(module) {
 			return
 		}
 	}
@@ -1913,10 +1817,10 @@
 	return module.forwardDeps
 }
 
-func (topDownVisitorImpl) visit(modules []*moduleInfo, visit func(*moduleInfo, chan<- pauseSpec) bool) {
+func (topDownVisitorImpl) visit(modules []*moduleInfo, visit func(*moduleInfo) bool) {
 	for i := 0; i < len(modules); i++ {
 		module := modules[len(modules)-1-i]
-		if visit(module, nil) {
+		if visit(module) {
 			return
 		}
 	}
@@ -1927,50 +1831,25 @@
 	topDownVisitor  topDownVisitorImpl
 )
 
-// pauseSpec describes a pause that a module needs to occur until another module has been visited,
-// at which point the unpause channel will be closed.
-type pauseSpec struct {
-	paused  *moduleInfo
-	until   *moduleInfo
-	unpause unpause
-}
-
-type unpause chan struct{}
-
-const parallelVisitLimit = 1000
-
 // Calls visit on each module, guaranteeing that visit is not called on a module until visit on all
-// of its dependencies has finished.  A visit function can write a pauseSpec to the pause channel
-// to wait for another dependency to be visited.  If a visit function returns true to cancel
-// while another visitor is paused, the paused visitor will never be resumed and its goroutine
-// will stay paused forever.
-func parallelVisit(modules []*moduleInfo, order visitOrderer, limit int,
-	visit func(module *moduleInfo, pause chan<- pauseSpec) bool) []error {
-
+// of its dependencies has finished.
+func (c *Context) parallelVisit(order visitOrderer, visit func(group *moduleInfo) bool) {
 	doneCh := make(chan *moduleInfo)
 	cancelCh := make(chan bool)
-	pauseCh := make(chan pauseSpec)
+	count := 0
 	cancel := false
+	var backlog []*moduleInfo
+	const limit = 1000
 
-	var backlog []*moduleInfo      // Visitors that are ready to start but backlogged due to limit.
-	var unpauseBacklog []pauseSpec // Visitors that are ready to unpause but backlogged due to limit.
-
-	active := 0  // Number of visitors running, not counting paused visitors.
-	visited := 0 // Number of finished visitors.
-
-	pauseMap := make(map[*moduleInfo][]pauseSpec)
-
-	for _, module := range modules {
+	for _, module := range c.modulesSorted {
 		module.waitingCount = order.waitCount(module)
 	}
 
-	// Call the visitor on a module if there are fewer active visitors than the parallelism
-	// limit, otherwise add it to the backlog.
-	startOrBacklog := func(module *moduleInfo) {
-		if active < limit {
-			active++
+	visitOne := func(module *moduleInfo) {
+		if count < limit {
+			count++
 			go func() {
-				ret := visit(module, pauseCh)
+				ret := visit(module)
 				if ret {
 					cancelCh <- true
 				}
@@ -1981,195 +1860,34 @@
 		}
 	}
 
-	// Unpause the already-started but paused  visitor on a module if there are fewer active
-	// visitors than the parallelism limit, otherwise add it to the backlog.
-	unpauseOrBacklog := func(pauseSpec pauseSpec) {
-		if active < limit {
-			active++
-			close(pauseSpec.unpause)
-		} else {
-			unpauseBacklog = append(unpauseBacklog, pauseSpec)
-		}
-	}
-
-	// Start any modules in the backlog up to the parallelism limit.  Unpause paused modules first
-	// since they may already be holding resources.
-	unpauseOrStartFromBacklog := func() {
-		for active < limit && len(unpauseBacklog) > 0 {
-			unpause := unpauseBacklog[0]
-			unpauseBacklog = unpauseBacklog[1:]
-			unpauseOrBacklog(unpause)
-		}
-		for active < limit && len(backlog) > 0 {
-			toVisit := backlog[0]
-			backlog = backlog[1:]
-			startOrBacklog(toVisit)
-		}
-	}
-
-	toVisit := len(modules)
-
-	// Start or backlog any modules that are not waiting for any other modules.
-	for _, module := range modules {
+	for _, module := range c.modulesSorted {
 		if module.waitingCount == 0 {
-			startOrBacklog(module)
+			visitOne(module)
 		}
 	}
 
-	for active > 0 {
+	for count > 0 || len(backlog) > 0 {
 		select {
 		case <-cancelCh:
 			cancel = true
 			backlog = nil
 		case doneModule := <-doneCh:
-			active--
+			count--
 			if !cancel {
-				// Mark this module as done.
-				doneModule.waitingCount = -1
-				visited++
-
-				// Unpause or backlog any modules that were waiting for this one.
-				if unpauses, ok := pauseMap[doneModule]; ok {
-					delete(pauseMap, doneModule)
-					for _, unpause := range unpauses {
-						unpauseOrBacklog(unpause)
-					}
+				for count < limit && len(backlog) > 0 {
+					toVisit := backlog[0]
+					backlog = backlog[1:]
+					visitOne(toVisit)
 				}
-
-				// Start any backlogged modules up to limit.
-				unpauseOrStartFromBacklog()
-
-				// Decrement waitingCount on the next modules in the tree based
-				// on propagation order, and start or backlog them if they are
-				// ready to start.
 				for _, module := range order.propagate(doneModule) {
 					module.waitingCount--
 					if module.waitingCount == 0 {
-						startOrBacklog(module)
+						visitOne(module)
 					}
 				}
 			}
-		case pauseSpec := <-pauseCh:
-			if pauseSpec.until.waitingCount == -1 {
-				// Module being paused for is already finished, resume immediately.
-				close(pauseSpec.unpause)
-			} else {
-				// Register for unpausing.
-				pauseMap[pauseSpec.until] = append(pauseMap[pauseSpec.until], pauseSpec)
-
-				// Don't count paused visitors as active so that this can't deadlock
-				// if 1000 visitors are paused simultaneously.
-				active--
-				unpauseOrStartFromBacklog()
-			}
 		}
 	}
-
-	if !cancel {
-		// Invariant check: no backlogged modules, these weren't waiting on anything except
-		// the parallelism limit so they should have run.
-		if len(backlog) > 0 {
-			panic(fmt.Errorf("parallelVisit finished with %d backlogged visitors", len(backlog)))
-		}
-
-		// Invariant check: no backlogged paused modules, these weren't waiting on anything
-		// except the parallelism limit so they should have run.
-		if len(unpauseBacklog) > 0 {
-			panic(fmt.Errorf("parallelVisit finished with %d backlogged unpaused visitors", len(unpauseBacklog)))
-		}
-
-		if len(pauseMap) > 0 {
-			// Probably a deadlock due to a newly added dependency cycle. Start from each module in
-			// the order of the input modules list and perform a depth-first search for the module
-			// it is paused on, ignoring modules that are marked as done.  Note this traverses from
-			// modules to the modules that would have been unblocked when that module finished, i.e
-			// the reverse of the visitOrderer.
-
-			// In order to reduce duplicated work, once a module has been checked and determined
-			// not to be part of a cycle add it and everything that depends on it to the checked
-			// map.
-			checked := make(map[*moduleInfo]struct{})
-
-			var check func(module, end *moduleInfo) []*moduleInfo
-			check = func(module, end *moduleInfo) []*moduleInfo {
-				if module.waitingCount == -1 {
-					// This module was finished, it can't be part of a loop.
-					return nil
-				}
-				if module == end {
-					// This module is the end of the loop, start rolling up the cycle.
-					return []*moduleInfo{module}
-				}
-
-				if _, alreadyChecked := checked[module]; alreadyChecked {
-					return nil
-				}
-
-				for _, dep := range order.propagate(module) {
-					cycle := check(dep, end)
-					if cycle != nil {
-						return append([]*moduleInfo{module}, cycle...)
-					}
-				}
-				for _, depPauseSpec := range pauseMap[module] {
-					cycle := check(depPauseSpec.paused, end)
-					if cycle != nil {
-						return append([]*moduleInfo{module}, cycle...)
-					}
-				}
-
-				checked[module] = struct{}{}
-				return nil
-			}
-
-			// Iterate over the modules list instead of pauseMap to provide deterministic ordering.
-			for _, module := range modules {
-				for _, pauseSpec := range pauseMap[module] {
-					cycle := check(pauseSpec.paused, pauseSpec.until)
-					if len(cycle) > 0 {
-						return cycleError(cycle)
-					}
-				}
-			}
-		}
-
-		// Invariant check: if there was no deadlock and no cancellation every module
-		// should have been visited.
-		if visited != toVisit {
-			panic(fmt.Errorf("parallelVisit ran %d visitors, expected %d", visited, toVisit))
-		}
-
-		// Invariant check: if there was no deadlock and no cancellation  every module
-		// should have been visited, so there is nothing left to be paused on.
-		if len(pauseMap) > 0 {
-			panic(fmt.Errorf("parallelVisit finished with %d paused visitors", len(pauseMap)))
-		}
-	}
-
-	return nil
-}
-
-func cycleError(cycle []*moduleInfo) (errs []error) {
-	// The cycle list is in reverse order because all the 'check' calls append
-	// their own module to the list.
-	errs = append(errs, &BlueprintError{
-		Err: fmt.Errorf("encountered dependency cycle:"),
-		Pos: cycle[len(cycle)-1].pos,
-	})
-
-	// Iterate backwards through the cycle list.
-	curModule := cycle[0]
-	for i := len(cycle) - 1; i >= 0; i-- {
-		nextModule := cycle[i]
-		errs = append(errs, &BlueprintError{
-			Err: fmt.Errorf("    %s depends on %s",
-				curModule, nextModule),
-			Pos: curModule.pos,
-		})
-		curModule = nextModule
-	}
-
-	return errs
 }
 
 // updateDependencies recursively walks the module dependency graph and updates
@@ -2179,7 +1897,6 @@
 // it encounters dependency cycles.  This should called after resolveDependencies,
 // as well as after any mutator pass has called addDependency
 func (c *Context) updateDependencies() (errs []error) {
-	c.cachedDepsModified = true
 	visited := make(map[*moduleInfo]bool)  // modules that were already checked
 	checking := make(map[*moduleInfo]bool) // modules actively being checked
 
@@ -2187,37 +1904,53 @@
 
 	var check func(group *moduleInfo) []*moduleInfo
 
+	cycleError := func(cycle []*moduleInfo) {
+		// We are the "start" of the cycle, so we're responsible
+		// for generating the errors.  The cycle list is in
+		// reverse order because all the 'check' calls append
+		// their own module to the list.
+		errs = append(errs, &BlueprintError{
+			Err: fmt.Errorf("encountered dependency cycle:"),
+			Pos: cycle[len(cycle)-1].pos,
+		})
+
+		// Iterate backwards through the cycle list.
+		curModule := cycle[0]
+		for i := len(cycle) - 1; i >= 0; i-- {
+			nextModule := cycle[i]
+			errs = append(errs, &BlueprintError{
+				Err: fmt.Errorf("    %q depends on %q",
+					curModule.Name(),
+					nextModule.Name()),
+				Pos: curModule.pos,
+			})
+			curModule = nextModule
+		}
+	}
+
 	check = func(module *moduleInfo) []*moduleInfo {
 		visited[module] = true
 		checking[module] = true
 		defer delete(checking, module)
 
-		// Reset the forward and reverse deps without reducing their capacity to avoid reallocation.
-		module.reverseDeps = module.reverseDeps[:0]
-		module.forwardDeps = module.forwardDeps[:0]
+		deps := make(map[*moduleInfo]bool)
 
 		// Add an implicit dependency ordering on all earlier modules in the same module group
 		for _, dep := range module.group.modules {
 			if dep == module {
 				break
 			}
-			if depModule := dep.module(); depModule != nil {
-				module.forwardDeps = append(module.forwardDeps, depModule)
-			}
+			deps[dep] = true
 		}
 
-	outer:
 		for _, dep := range module.directDeps {
-			// use a loop to check for duplicates, average number of directDeps measured to be 9.5.
-			for _, exists := range module.forwardDeps {
-				if dep.module == exists {
-					continue outer
-				}
-			}
-			module.forwardDeps = append(module.forwardDeps, dep.module)
+			deps[dep.module] = true
 		}
 
-		for _, dep := range module.forwardDeps {
+		module.reverseDeps = []*moduleInfo{}
+		module.forwardDeps = []*moduleInfo{}
+
+		for dep := range deps {
 			if checking[dep] {
 				// This is a cycle.
 				return []*moduleInfo{dep, module}
@@ -2228,8 +1961,10 @@
 				if cycle != nil {
 					if cycle[0] == module {
 						// We are the "start" of the cycle, so we're responsible
-						// for generating the errors.
-						errs = append(errs, cycleError(cycle)...)
+						// for generating the errors.  The cycle list is in
+						// reverse order because all the 'check' calls append
+						// their own module to the list.
+						cycleError(cycle)
 
 						// We can continue processing this module's children to
 						// find more cycles.  Since all the modules that were
@@ -2243,6 +1978,7 @@
 				}
 			}
 
+			module.forwardDeps = append(module.forwardDeps, dep)
 			dep.reverseDeps = append(dep.reverseDeps, module)
 		}
 
@@ -2258,7 +1994,7 @@
 				if cycle[len(cycle)-1] != module {
 					panic("inconceivable!")
 				}
-				errs = append(errs, cycleError(cycle)...)
+				cycleError(cycle)
 			}
 		}
 	}
@@ -2268,64 +2004,6 @@
 	return
 }
 
-type jsonVariationMap map[string]string
-
-type jsonModuleName struct {
-	Name                 string
-	Variations           jsonVariationMap
-	DependencyVariations jsonVariationMap
-}
-
-type jsonDep struct {
-	jsonModuleName
-	Tag string
-}
-
-type jsonModule struct {
-	jsonModuleName
-	Deps      []jsonDep
-	Type      string
-	Blueprint string
-}
-
-func toJsonVariationMap(vm variationMap) jsonVariationMap {
-	return jsonVariationMap(vm)
-}
-
-func jsonModuleNameFromModuleInfo(m *moduleInfo) *jsonModuleName {
-	return &jsonModuleName{
-		Name:                 m.Name(),
-		Variations:           toJsonVariationMap(m.variant.variations),
-		DependencyVariations: toJsonVariationMap(m.variant.dependencyVariations),
-	}
-}
-
-func jsonModuleFromModuleInfo(m *moduleInfo) *jsonModule {
-	return &jsonModule{
-		jsonModuleName: *jsonModuleNameFromModuleInfo(m),
-		Deps:           make([]jsonDep, 0),
-		Type:           m.typeName,
-		Blueprint:      m.relBlueprintsFile,
-	}
-}
-
-func (c *Context) PrintJSONGraph(w io.Writer) {
-	modules := make([]*jsonModule, 0)
-	for _, m := range c.modulesSorted {
-		jm := jsonModuleFromModuleInfo(m)
-		for _, d := range m.directDeps {
-			jm.Deps = append(jm.Deps, jsonDep{
-				jsonModuleName: *jsonModuleNameFromModuleInfo(d.module),
-				Tag:            fmt.Sprintf("%T %+v", d.tag, d.tag),
-			})
-		}
-
-		modules = append(modules, jm)
-	}
-
-	json.NewEncoder(w).Encode(modules)
-}
-
 // PrepareBuildActions generates an internal representation of all the build
 // actions that need to be performed.  This process involves invoking the
 // GenerateBuildActions method on each of the Module objects created during the
@@ -2344,7 +2022,6 @@
 // by the modules and singletons via the ModuleContext.AddNinjaFileDeps(),
 // SingletonContext.AddNinjaFileDeps(), and PackageContext.AddNinjaFileDeps()
 // methods.
-
 func (c *Context) PrepareBuildActions(config interface{}) (deps []string, errs []error) {
 	pprof.Do(c.Context, pprof.Labels("blueprint", "PrepareBuildActions"), func(ctx context.Context) {
 		c.buildActionsReady = false
@@ -2385,8 +2062,6 @@
 
 		deps = append(deps, depsPackages...)
 
-		c.memoizeFullNames(c.liveGlobals, pkgNames)
-
 		// This will panic if it finds a problem since it's a programming error.
 		c.checkForVariableReferenceCycles(c.liveGlobals.variables, pkgNames)
 
@@ -2507,12 +2182,12 @@
 
 	errsCh := make(chan []error)
 	globalStateCh := make(chan globalStateChange)
-	newVariationsCh := make(chan modulesOrAliases)
+	newVariationsCh := make(chan []*moduleInfo)
 	done := make(chan bool)
 
 	c.depsModified = 0
 
-	visit := func(module *moduleInfo, pause chan<- pauseSpec) bool {
+	visit := func(module *moduleInfo) bool {
 		if module.splitModules != nil {
 			panic("split module found in sorted module list")
 		}
@@ -2523,12 +2198,9 @@
 				config:  config,
 				module:  module,
 			},
-			name:    mutator.name,
-			pauseCh: pause,
+			name: mutator.name,
 		}
 
-		module.startedMutator = mutator
-
 		func() {
 			defer func() {
 				if r := recover(); r != nil {
@@ -2544,8 +2216,6 @@
 			direction.run(mutator, mctx)
 		}()
 
-		module.finishedMutator = mutator
-
 		if len(mctx.errs) > 0 {
 			errsCh <- mctx.errs
 			return true
@@ -2583,10 +2253,8 @@
 				newModules = append(newModules, globalStateChange.newModules...)
 				deps = append(deps, globalStateChange.deps...)
 			case newVariations := <-newVariationsCh:
-				for _, moduleOrAlias := range newVariations {
-					if m := moduleOrAlias.module(); m != nil {
-						newModuleInfo[m.logicModule] = m
-					}
+				for _, m := range newVariations {
+					newModuleInfo[m.logicModule] = m
 				}
 			case <-done:
 				return
@@ -2594,21 +2262,12 @@
 		}
 	}()
 
-	c.startedMutator = mutator
-
-	var visitErrs []error
 	if mutator.parallel {
-		visitErrs = parallelVisit(c.modulesSorted, direction.orderer(), parallelVisitLimit, visit)
+		c.parallelVisit(direction.orderer(), visit)
 	} else {
 		direction.orderer().visit(c.modulesSorted, visit)
 	}
 
-	if len(visitErrs) > 0 {
-		return nil, visitErrs
-	}
-
-	c.finishedMutators[mutator] = true
-
 	done <- true
 
 	if len(errs) > 0 {
@@ -2619,27 +2278,33 @@
 
 	for _, group := range c.moduleGroups {
 		for i := 0; i < len(group.modules); i++ {
-			module := group.modules[i].module()
-			if module == nil {
-				// Existing alias, skip it
-				continue
-			}
+			module := group.modules[i]
 
 			// Update module group to contain newly split variants
 			if module.splitModules != nil {
 				group.modules, i = spliceModules(group.modules, i, module.splitModules)
 			}
 
+			// Create any new aliases.
+			if module.aliasTarget != nil {
+				group.aliases = append(group.aliases, &moduleAlias{
+					variantName:       module.variantName,
+					variant:           module.variant,
+					dependencyVariant: module.dependencyVariant,
+					target:            module.aliasTarget,
+				})
+			}
+
 			// Fix up any remaining dependencies on modules that were split into variants
 			// by replacing them with the first variant
 			for j, dep := range module.directDeps {
 				if dep.module.logicModule == nil {
-					module.directDeps[j].module = dep.module.splitModules.firstModule()
+					module.directDeps[j].module = dep.module.splitModules[0]
 				}
 			}
 
 			if module.createdBy != nil && module.createdBy.logicModule == nil {
-				module.createdBy = module.createdBy.splitModules.firstModule()
+				module.createdBy = module.createdBy.splitModules[0]
 			}
 
 			// Add in any new direct dependencies that were added by the mutator
@@ -2647,31 +2312,17 @@
 			module.newDirectDeps = nil
 		}
 
-		findAliasTarget := func(variant variant) *moduleInfo {
-			for _, moduleOrAlias := range group.modules {
-				if alias := moduleOrAlias.alias(); alias != nil {
-					if alias.variant.variations.equal(variant.variations) {
-						return alias.target
-					}
-				}
-			}
-			return nil
-		}
-
 		// Forward or delete any dangling aliases.
-		// Use a manual loop instead of range because len(group.modules) can
-		// change inside the loop
-		for i := 0; i < len(group.modules); i++ {
-			if alias := group.modules[i].alias(); alias != nil {
-				if alias.target.logicModule == nil {
-					newTarget := findAliasTarget(alias.target.variant)
-					if newTarget != nil {
-						alias.target = newTarget
-					} else {
-						// The alias was left dangling, remove it.
-						group.modules = append(group.modules[:i], group.modules[i+1:]...)
-						i--
-					}
+		for i := 0; i < len(group.aliases); i++ {
+			alias := group.aliases[i]
+
+			if alias.target.logicModule == nil {
+				if alias.target.aliasTarget != nil {
+					alias.target = alias.target.aliasTarget
+				} else {
+					// The alias was left dangling, remove it.
+					group.aliases = append(group.aliases[:i], group.aliases[i+1:]...)
+					i--
 				}
 			}
 		}
@@ -2723,16 +2374,12 @@
 	ch := make(chan update)
 	doneCh := make(chan bool)
 	go func() {
-		errs := parallelVisit(c.modulesSorted, unorderedVisitorImpl{}, parallelVisitLimit,
-			func(m *moduleInfo, pause chan<- pauseSpec) bool {
-				origLogicModule := m.logicModule
-				m.logicModule, m.properties = c.cloneLogicModule(m)
-				ch <- update{origLogicModule, m}
-				return false
-			})
-		if len(errs) > 0 {
-			panic(errs)
-		}
+		c.parallelVisit(unorderedVisitorImpl{}, func(m *moduleInfo) bool {
+			origLogicModule := m.logicModule
+			m.logicModule, m.properties = c.cloneLogicModule(m)
+			ch <- update{origLogicModule, m}
+			return false
+		})
 		doneCh <- true
 	}()
 
@@ -2750,15 +2397,15 @@
 
 // Removes modules[i] from the list and inserts newModules... where it was located, returning
 // the new slice and the index of the last inserted element
-func spliceModules(modules modulesOrAliases, i int, newModules modulesOrAliases) (modulesOrAliases, int) {
+func spliceModules(modules []*moduleInfo, i int, newModules []*moduleInfo) ([]*moduleInfo, int) {
 	spliceSize := len(newModules)
 	newLen := len(modules) + spliceSize - 1
-	var dest modulesOrAliases
+	var dest []*moduleInfo
 	if cap(modules) >= len(modules)-1+len(newModules) {
 		// We can fit the splice in the existing capacity, do everything in place
 		dest = modules[:newLen]
 	} else {
-		dest = make(modulesOrAliases, newLen)
+		dest = make([]*moduleInfo, newLen)
 		copy(dest, modules[:i])
 	}
 
@@ -2796,77 +2443,71 @@
 		}
 	}()
 
-	visitErrs := parallelVisit(c.modulesSorted, bottomUpVisitor, parallelVisitLimit,
-		func(module *moduleInfo, pause chan<- pauseSpec) bool {
-			uniqueName := c.nameInterface.UniqueName(newNamespaceContext(module), module.group.name)
-			sanitizedName := toNinjaName(uniqueName)
+	c.parallelVisit(bottomUpVisitor, func(module *moduleInfo) bool {
 
-			prefix := moduleNamespacePrefix(sanitizedName + "_" + module.variant.name)
+		uniqueName := c.nameInterface.UniqueName(newNamespaceContext(module), module.group.name)
+		sanitizedName := toNinjaName(uniqueName)
 
-			// The parent scope of the moduleContext's local scope gets overridden to be that of the
-			// calling Go package on a per-call basis.  Since the initial parent scope doesn't matter we
-			// just set it to nil.
-			scope := newLocalScope(nil, prefix)
+		prefix := moduleNamespacePrefix(sanitizedName + "_" + module.variantName)
 
-			mctx := &moduleContext{
-				baseModuleContext: baseModuleContext{
-					context: c,
-					config:  config,
-					module:  module,
-				},
-				scope:              scope,
-				handledMissingDeps: module.missingDeps == nil,
-			}
+		// The parent scope of the moduleContext's local scope gets overridden to be that of the
+		// calling Go package on a per-call basis.  Since the initial parent scope doesn't matter we
+		// just set it to nil.
+		scope := newLocalScope(nil, prefix)
 
-			mctx.module.startedGenerateBuildActions = true
+		mctx := &moduleContext{
+			baseModuleContext: baseModuleContext{
+				context: c,
+				config:  config,
+				module:  module,
+			},
+			scope:              scope,
+			handledMissingDeps: module.missingDeps == nil,
+		}
 
-			func() {
-				defer func() {
-					if r := recover(); r != nil {
-						in := fmt.Sprintf("GenerateBuildActions for %s", module)
-						if err, ok := r.(panicError); ok {
-							err.addIn(in)
-							mctx.error(err)
-						} else {
-							mctx.error(newPanicErrorf(r, in))
-						}
+		func() {
+			defer func() {
+				if r := recover(); r != nil {
+					in := fmt.Sprintf("GenerateBuildActions for %s", module)
+					if err, ok := r.(panicError); ok {
+						err.addIn(in)
+						mctx.error(err)
+					} else {
+						mctx.error(newPanicErrorf(r, in))
 					}
-				}()
-				mctx.module.logicModule.GenerateBuildActions(mctx)
-			}()
-
-			mctx.module.finishedGenerateBuildActions = true
-
-			if len(mctx.errs) > 0 {
-				errsCh <- mctx.errs
-				return true
-			}
-
-			if module.missingDeps != nil && !mctx.handledMissingDeps {
-				var errs []error
-				for _, depName := range module.missingDeps {
-					errs = append(errs, c.missingDependencyError(module, depName))
 				}
-				errsCh <- errs
-				return true
-			}
+			}()
+			mctx.module.logicModule.GenerateBuildActions(mctx)
+		}()
 
-			depsCh <- mctx.ninjaFileDeps
+		if len(mctx.errs) > 0 {
+			errsCh <- mctx.errs
+			return true
+		}
 
-			newErrs := c.processLocalBuildActions(&module.actionDefs,
-				&mctx.actionDefs, liveGlobals)
-			if len(newErrs) > 0 {
-				errsCh <- newErrs
-				return true
+		if module.missingDeps != nil && !mctx.handledMissingDeps {
+			var errs []error
+			for _, depName := range module.missingDeps {
+				errs = append(errs, c.missingDependencyError(module, depName))
 			}
-			return false
-		})
+			errsCh <- errs
+			return true
+		}
+
+		depsCh <- mctx.ninjaFileDeps
+
+		newErrs := c.processLocalBuildActions(&module.actionDefs,
+			&mctx.actionDefs, liveGlobals)
+		if len(newErrs) > 0 {
+			errsCh <- newErrs
+			return true
+		}
+		return false
+	})
 
 	cancelCh <- struct{}{}
 	<-cancelCh
 
-	errs = append(errs, visitErrs...)
-
 	return deps, errs
 }
 
@@ -3004,8 +2645,7 @@
 }
 
 type replace struct {
-	from, to  *moduleInfo
-	predicate ReplaceDependencyPredicate
+	from, to *moduleInfo
 }
 
 type rename struct {
@@ -3021,8 +2661,14 @@
 	}
 
 	for _, m := range group.modules {
-		if module.variant.name == m.moduleOrAliasVariant().name {
-			return m.moduleOrAliasTarget()
+		if module.variantName == m.variantName {
+			return m
+		}
+	}
+
+	for _, m := range group.aliases {
+		if module.variantName == m.variantName {
+			return m.target
 		}
 	}
 
@@ -3045,32 +2691,22 @@
 
 func (c *Context) handleReplacements(replacements []replace) []error {
 	var errs []error
-	changedDeps := false
 	for _, replace := range replacements {
 		for _, m := range replace.from.reverseDeps {
 			for i, d := range m.directDeps {
 				if d.module == replace.from {
-					// If the replacement has a predicate then check it.
-					if replace.predicate == nil || replace.predicate(m.logicModule, d.tag, d.module.logicModule) {
-						m.directDeps[i].module = replace.to
-						changedDeps = true
-					}
+					m.directDeps[i].module = replace.to
 				}
 			}
 		}
 
-	}
-
-	if changedDeps {
 		atomic.AddUint32(&c.depsModified, 1)
 	}
+
 	return errs
 }
 
-func (c *Context) discoveredMissingDependencies(module *moduleInfo, depName string, depVariations variationMap) (errs []error) {
-	if depVariations != nil {
-		depName = depName + "{" + c.prettyPrintVariant(depVariations) + "}"
-	}
+func (c *Context) discoveredMissingDependencies(module *moduleInfo, depName string) (errs []error) {
 	if c.allowMissingDependencies {
 		module.missingDeps = append(module.missingDeps, depName)
 		return nil
@@ -3096,7 +2732,7 @@
 }
 
 func (c *Context) sortedModuleGroups() []*moduleGroup {
-	if c.cachedSortedModuleGroups == nil || c.cachedDepsModified {
+	if c.cachedSortedModuleGroups == nil {
 		unwrap := func(wrappers []ModuleGroup) []*moduleGroup {
 			result := make([]*moduleGroup, 0, len(wrappers))
 			for _, group := range wrappers {
@@ -3106,7 +2742,6 @@
 		}
 
 		c.cachedSortedModuleGroups = unwrap(c.nameInterface.AllModules())
-		c.cachedDepsModified = false
 	}
 
 	return c.cachedSortedModuleGroups
@@ -3123,10 +2758,8 @@
 	}()
 
 	for _, moduleGroup := range c.sortedModuleGroups() {
-		for _, moduleOrAlias := range moduleGroup.modules {
-			if module = moduleOrAlias.module(); module != nil {
-				visit(module.logicModule)
-			}
+		for _, module = range moduleGroup.modules {
+			visit(module.logicModule)
 		}
 	}
 }
@@ -3144,11 +2777,9 @@
 	}()
 
 	for _, moduleGroup := range c.sortedModuleGroups() {
-		for _, moduleOrAlias := range moduleGroup.modules {
-			if module = moduleOrAlias.module(); module != nil {
-				if pred(module.logicModule) {
-					visit(module.logicModule)
-				}
+		for _, module := range moduleGroup.modules {
+			if pred(module.logicModule) {
+				visit(module.logicModule)
 			}
 		}
 	}
@@ -3166,10 +2797,8 @@
 		}
 	}()
 
-	for _, moduleOrAlias := range module.group.modules {
-		if variant = moduleOrAlias.module(); variant != nil {
-			visit(variant.logicModule)
-		}
+	for _, variant = range module.group.modules {
+		visit(variant.logicModule)
 	}
 }
 
@@ -3251,21 +2880,6 @@
 	return pkgNames, deps
 }
 
-// memoizeFullNames stores the full name of each live global variable, rule and pool since each is
-// guaranteed to be used at least twice, once in the definition and once for each usage, and many
-// are used much more than once.
-func (c *Context) memoizeFullNames(liveGlobals *liveTracker, pkgNames map[*packageContext]string) {
-	for v := range liveGlobals.variables {
-		v.memoizeFullName(pkgNames)
-	}
-	for r := range liveGlobals.rules {
-		r.memoizeFullName(pkgNames)
-	}
-	for p := range liveGlobals.pools {
-		p.memoizeFullName(pkgNames)
-	}
-}
-
 func (c *Context) checkForVariableReferenceCycles(
 	variables map[Variable]ninjaString, pkgNames map[*packageContext]string) {
 
@@ -3412,13 +3026,18 @@
 	return module.Name()
 }
 
+func (c *Context) ModulePath(logicModule Module) string {
+	module := c.moduleInfo[logicModule]
+	return module.relBlueprintsFile
+}
+
 func (c *Context) ModuleDir(logicModule Module) string {
-	return filepath.Dir(c.BlueprintFile(logicModule))
+	return filepath.Dir(c.ModulePath(logicModule))
 }
 
 func (c *Context) ModuleSubDir(logicModule Module) string {
 	module := c.moduleInfo[logicModule]
-	return module.variant.name
+	return module.variantName
 }
 
 func (c *Context) ModuleType(logicModule Module) string {
@@ -3426,25 +3045,6 @@
 	return module.typeName
 }
 
-// ModuleProvider returns the value, if any, for the provider for a module.  If the value for the
-// provider was not set it returns the zero value of the type of the provider, which means the
-// return value can always be type-asserted to the type of the provider.  The return value should
-// always be considered read-only.  It panics if called before the appropriate mutator or
-// GenerateBuildActions pass for the provider on the module.  The value returned may be a deep
-// copy of the value originally passed to SetProvider.
-func (c *Context) ModuleProvider(logicModule Module, provider ProviderKey) interface{} {
-	module := c.moduleInfo[logicModule]
-	value, _ := c.provider(module, provider)
-	return value
-}
-
-// ModuleHasProvider returns true if the provider for the given module has been set.
-func (c *Context) ModuleHasProvider(logicModule Module, provider ProviderKey) bool {
-	module := c.moduleInfo[logicModule]
-	_, ok := c.provider(module, provider)
-	return ok
-}
-
 func (c *Context) BlueprintFile(logicModule Module) string {
 	module := c.moduleInfo[logicModule]
 	return module.relBlueprintsFile
@@ -3547,11 +3147,12 @@
 }
 
 func (c *Context) PrimaryModule(module Module) Module {
-	return c.moduleInfo[module].group.modules.firstModule().logicModule
+	return c.moduleInfo[module].group.modules[0].logicModule
 }
 
 func (c *Context) FinalModule(module Module) Module {
-	return c.moduleInfo[module].group.modules.lastModule().logicModule
+	modules := c.moduleInfo[module].group.modules
+	return modules[len(modules)-1].logicModule
 }
 
 func (c *Context) VisitAllModuleVariants(module Module,
@@ -3582,7 +3183,7 @@
 // WriteBuildFile writes the Ninja manifeset text for the generated build
 // actions to w.  If this is called before PrepareBuildActions successfully
 // completes then ErrBuildActionsNotReady is returned.
-func (c *Context) WriteBuildFile(w io.StringWriter) error {
+func (c *Context) WriteBuildFile(w io.Writer) error {
 	var err error
 	pprof.Do(c.Context, pprof.Labels("blueprint", "WriteBuildFile"), func(ctx context.Context) {
 		if !c.buildActionsReady {
@@ -3882,8 +3483,8 @@
 	iName := s[i].module.Name()
 	jName := s[j].module.Name()
 	if iName == jName {
-		iName = s[i].module.variant.name
-		jName = s[j].module.variant.name
+		iName = s[i].module.variantName
+		jName = s[j].module.variantName
 	}
 	return iName < jName
 }
@@ -3907,17 +3508,14 @@
 	iName := s.nameInterface.UniqueName(newNamespaceContext(iMod), iMod.group.name)
 	jName := s.nameInterface.UniqueName(newNamespaceContext(jMod), jMod.group.name)
 	if iName == jName {
-		iVariantName := s.modules[i].variant.name
-		jVariantName := s.modules[j].variant.name
-		if iVariantName == jVariantName {
-			panic(fmt.Sprintf("duplicate module name: %s %s: %#v and %#v\n",
-				iName, iVariantName, iMod.variant.variations, jMod.variant.variations))
-		} else {
-			return iVariantName < jVariantName
-		}
-	} else {
-		return iName < jName
+		iName = s.modules[i].variantName
+		jName = s.modules[j].variantName
 	}
+
+	if iName == jName {
+		panic(fmt.Sprintf("duplicate module name: %s: %#v and %#v\n", iName, iMod, jMod))
+	}
+	return iName < jName
 }
 
 func (s moduleSorter) Swap(i, j int) {
@@ -3962,7 +3560,7 @@
 			"typeName":  module.typeName,
 			"goFactory": factoryName,
 			"pos":       relPos,
-			"variant":   module.variant.name,
+			"variant":   module.variantName,
 		}
 		err = headerTemplate.Execute(buf, infoMap)
 		if err != nil {
@@ -4111,15 +3709,15 @@
 	return nil
 }
 
-func beforeInModuleList(a, b *moduleInfo, list modulesOrAliases) bool {
+func beforeInModuleList(a, b *moduleInfo, list []*moduleInfo) bool {
 	found := false
 	if a == b {
 		return false
 	}
 	for _, l := range list {
-		if l.module() == a {
+		if l == a {
 			found = true
-		} else if l.module() == b {
+		} else if l == b {
 			return found
 		}
 	}
diff --git a/context_test.go b/context_test.go
index d91b89d..0541c06 100644
--- a/context_test.go
+++ b/context_test.go
@@ -238,7 +238,7 @@
 		t.FailNow()
 	}
 
-	topModule := ctx.moduleGroupFromName("A", nil).modules.firstModule()
+	topModule := ctx.moduleGroupFromName("A", nil).modules[0]
 	outputDown, outputUp := walkDependencyGraph(ctx, topModule, false)
 	if outputDown != "BCEFG" {
 		t.Errorf("unexpected walkDeps behaviour: %s\ndown should be: BCEFG", outputDown)
@@ -319,7 +319,7 @@
 		t.FailNow()
 	}
 
-	topModule := ctx.moduleGroupFromName("A", nil).modules.firstModule()
+	topModule := ctx.moduleGroupFromName("A", nil).modules[0]
 	outputDown, outputUp := walkDependencyGraph(ctx, topModule, true)
 	if outputDown != "BCEGHFGG" {
 		t.Errorf("unexpected walkDeps behaviour: %s\ndown should be: BCEGHFGG", outputDown)
@@ -386,7 +386,7 @@
 		t.FailNow()
 	}
 
-	topModule := ctx.moduleGroupFromName("A", nil).modules.firstModule()
+	topModule := ctx.moduleGroupFromName("A", nil).modules[0]
 	outputDown, outputUp := walkDependencyGraph(ctx, topModule, true)
 	expectedDown := "BDCDE"
 	if outputDown != expectedDown {
@@ -432,10 +432,10 @@
 		t.FailNow()
 	}
 
-	a := ctx.moduleGroupFromName("A", nil).modules.firstModule().logicModule.(*fooModule)
-	b := ctx.moduleGroupFromName("B", nil).modules.firstModule().logicModule.(*barModule)
-	c := ctx.moduleGroupFromName("C", nil).modules.firstModule().logicModule.(*barModule)
-	d := ctx.moduleGroupFromName("D", nil).modules.firstModule().logicModule.(*fooModule)
+	a := ctx.moduleGroupFromName("A", nil).modules[0].logicModule.(*fooModule)
+	b := ctx.moduleGroupFromName("B", nil).modules[0].logicModule.(*barModule)
+	c := ctx.moduleGroupFromName("C", nil).modules[0].logicModule.(*barModule)
+	d := ctx.moduleGroupFromName("D", nil).modules[0].logicModule.(*fooModule)
 
 	checkDeps := func(m Module, expected string) {
 		var deps []string
@@ -606,481 +606,3 @@
 		t.Errorf("Incorrect errors; expected:\n%s\ngot:\n%s", expectedErrs, errs)
 	}
 }
-
-func Test_findVariant(t *testing.T) {
-	module := &moduleInfo{
-		variant: variant{
-			name: "normal_local",
-			variations: variationMap{
-				"normal": "normal",
-				"local":  "local",
-			},
-			dependencyVariations: variationMap{
-				"normal": "normal",
-			},
-		},
-	}
-
-	type alias struct {
-		variant variant
-		target  int
-	}
-
-	makeDependencyGroup := func(in ...interface{}) *moduleGroup {
-		group := &moduleGroup{
-			name: "dep",
-		}
-		for _, x := range in {
-			switch m := x.(type) {
-			case *moduleInfo:
-				m.group = group
-				group.modules = append(group.modules, m)
-			case alias:
-				// aliases may need to target modules that haven't been processed
-				// yet, put an empty alias in for now.
-				group.modules = append(group.modules, nil)
-			default:
-				t.Fatalf("unexpected type %T", x)
-			}
-		}
-
-		for i, x := range in {
-			switch m := x.(type) {
-			case *moduleInfo:
-				// already added in the first pass
-			case alias:
-				group.modules[i] = &moduleAlias{
-					variant: m.variant,
-					target:  group.modules[m.target].moduleOrAliasTarget(),
-				}
-			default:
-				t.Fatalf("unexpected type %T", x)
-			}
-		}
-
-		return group
-	}
-
-	tests := []struct {
-		name         string
-		possibleDeps *moduleGroup
-		variations   []Variation
-		far          bool
-		reverse      bool
-		want         string
-	}{
-		{
-			name: "AddVariationDependencies(nil)",
-			// A dependency that matches the non-local variations of the module
-			possibleDeps: makeDependencyGroup(
-				&moduleInfo{
-					variant: variant{
-						name: "normal",
-						variations: variationMap{
-							"normal": "normal",
-						},
-					},
-				},
-			),
-			variations: nil,
-			far:        false,
-			reverse:    false,
-			want:       "normal",
-		},
-		{
-			name: "AddVariationDependencies(nil) to alias",
-			// A dependency with an alias that matches the non-local variations of the module
-			possibleDeps: makeDependencyGroup(
-				alias{
-					variant: variant{
-						name: "normal",
-						variations: variationMap{
-							"normal": "normal",
-						},
-					},
-					target: 1,
-				},
-				&moduleInfo{
-					variant: variant{
-						name: "normal_a",
-						variations: variationMap{
-							"normal": "normal",
-							"a":      "a",
-						},
-					},
-				},
-			),
-			variations: nil,
-			far:        false,
-			reverse:    false,
-			want:       "normal_a",
-		},
-		{
-			name: "AddVariationDependencies(a)",
-			// A dependency with local variations
-			possibleDeps: makeDependencyGroup(
-				&moduleInfo{
-					variant: variant{
-						name: "normal_a",
-						variations: variationMap{
-							"normal": "normal",
-							"a":      "a",
-						},
-					},
-				},
-			),
-			variations: []Variation{{"a", "a"}},
-			far:        false,
-			reverse:    false,
-			want:       "normal_a",
-		},
-		{
-			name: "AddFarVariationDependencies(far)",
-			// A dependency with far variations
-			possibleDeps: makeDependencyGroup(
-				&moduleInfo{
-					variant: variant{
-						name:       "",
-						variations: nil,
-					},
-				},
-				&moduleInfo{
-					variant: variant{
-						name: "far",
-						variations: variationMap{
-							"far": "far",
-						},
-					},
-				},
-			),
-			variations: []Variation{{"far", "far"}},
-			far:        true,
-			reverse:    false,
-			want:       "far",
-		},
-		{
-			name: "AddFarVariationDependencies(far) to alias",
-			// A dependency with far variations and aliases
-			possibleDeps: makeDependencyGroup(
-				alias{
-					variant: variant{
-						name: "far",
-						variations: variationMap{
-							"far": "far",
-						},
-					},
-					target: 2,
-				},
-				&moduleInfo{
-					variant: variant{
-						name: "far_a",
-						variations: variationMap{
-							"far": "far",
-							"a":   "a",
-						},
-					},
-				},
-				&moduleInfo{
-					variant: variant{
-						name: "far_b",
-						variations: variationMap{
-							"far": "far",
-							"b":   "b",
-						},
-					},
-				},
-			),
-			variations: []Variation{{"far", "far"}},
-			far:        true,
-			reverse:    false,
-			want:       "far_b",
-		},
-		{
-			name: "AddFarVariationDependencies(far, b) to missing",
-			// A dependency with far variations and aliases
-			possibleDeps: makeDependencyGroup(
-				alias{
-					variant: variant{
-						name: "far",
-						variations: variationMap{
-							"far": "far",
-						},
-					},
-					target: 1,
-				},
-				&moduleInfo{
-					variant: variant{
-						name: "far_a",
-						variations: variationMap{
-							"far": "far",
-							"a":   "a",
-						},
-					},
-				},
-			),
-			variations: []Variation{{"far", "far"}, {"a", "b"}},
-			far:        true,
-			reverse:    false,
-			want:       "nil",
-		},
-	}
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			got, _ := findVariant(module, tt.possibleDeps, tt.variations, tt.far, tt.reverse)
-			if g, w := got == nil, tt.want == "nil"; g != w {
-				t.Fatalf("findVariant() got = %v, want %v", got, tt.want)
-			}
-			if got != nil {
-				if g, w := got.String(), fmt.Sprintf("module %q variant %q", "dep", tt.want); g != w {
-					t.Errorf("findVariant() got = %v, want %v", g, w)
-				}
-			}
-		})
-	}
-}
-
-func Test_parallelVisit(t *testing.T) {
-	addDep := func(from, to *moduleInfo) {
-		from.directDeps = append(from.directDeps, depInfo{to, nil})
-		from.forwardDeps = append(from.forwardDeps, to)
-		to.reverseDeps = append(to.reverseDeps, from)
-	}
-
-	create := func(name string) *moduleInfo {
-		m := &moduleInfo{
-			group: &moduleGroup{
-				name: name,
-			},
-		}
-		m.group.modules = modulesOrAliases{m}
-		return m
-	}
-	moduleA := create("A")
-	moduleB := create("B")
-	moduleC := create("C")
-	moduleD := create("D")
-	moduleE := create("E")
-	moduleF := create("F")
-	moduleG := create("G")
-
-	// A depends on B, B depends on C.  Nothing depends on D through G, and they don't depend on
-	// anything.
-	addDep(moduleA, moduleB)
-	addDep(moduleB, moduleC)
-
-	t.Run("no modules", func(t *testing.T) {
-		errs := parallelVisit(nil, bottomUpVisitorImpl{}, 1,
-			func(module *moduleInfo, pause chan<- pauseSpec) bool {
-				panic("unexpected call to visitor")
-			})
-		if errs != nil {
-			t.Errorf("expected no errors, got %q", errs)
-		}
-	})
-	t.Run("bottom up", func(t *testing.T) {
-		order := ""
-		errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC}, bottomUpVisitorImpl{}, 1,
-			func(module *moduleInfo, pause chan<- pauseSpec) bool {
-				order += module.group.name
-				return false
-			})
-		if errs != nil {
-			t.Errorf("expected no errors, got %q", errs)
-		}
-		if g, w := order, "CBA"; g != w {
-			t.Errorf("expected order %q, got %q", w, g)
-		}
-	})
-	t.Run("pause", func(t *testing.T) {
-		order := ""
-		errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC, moduleD}, bottomUpVisitorImpl{}, 1,
-			func(module *moduleInfo, pause chan<- pauseSpec) bool {
-				if module == moduleC {
-					// Pause module C on module D
-					unpause := make(chan struct{})
-					pause <- pauseSpec{moduleC, moduleD, unpause}
-					<-unpause
-				}
-				order += module.group.name
-				return false
-			})
-		if errs != nil {
-			t.Errorf("expected no errors, got %q", errs)
-		}
-		if g, w := order, "DCBA"; g != w {
-			t.Errorf("expected order %q, got %q", w, g)
-		}
-	})
-	t.Run("cancel", func(t *testing.T) {
-		order := ""
-		errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC}, bottomUpVisitorImpl{}, 1,
-			func(module *moduleInfo, pause chan<- pauseSpec) bool {
-				order += module.group.name
-				// Cancel in module B
-				return module == moduleB
-			})
-		if errs != nil {
-			t.Errorf("expected no errors, got %q", errs)
-		}
-		if g, w := order, "CB"; g != w {
-			t.Errorf("expected order %q, got %q", w, g)
-		}
-	})
-	t.Run("pause and cancel", func(t *testing.T) {
-		order := ""
-		errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC, moduleD}, bottomUpVisitorImpl{}, 1,
-			func(module *moduleInfo, pause chan<- pauseSpec) bool {
-				if module == moduleC {
-					// Pause module C on module D
-					unpause := make(chan struct{})
-					pause <- pauseSpec{moduleC, moduleD, unpause}
-					<-unpause
-				}
-				order += module.group.name
-				// Cancel in module D
-				return module == moduleD
-			})
-		if errs != nil {
-			t.Errorf("expected no errors, got %q", errs)
-		}
-		if g, w := order, "D"; g != w {
-			t.Errorf("expected order %q, got %q", w, g)
-		}
-	})
-	t.Run("parallel", func(t *testing.T) {
-		order := ""
-		errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC}, bottomUpVisitorImpl{}, 3,
-			func(module *moduleInfo, pause chan<- pauseSpec) bool {
-				order += module.group.name
-				return false
-			})
-		if errs != nil {
-			t.Errorf("expected no errors, got %q", errs)
-		}
-		if g, w := order, "CBA"; g != w {
-			t.Errorf("expected order %q, got %q", w, g)
-		}
-	})
-	t.Run("pause existing", func(t *testing.T) {
-		order := ""
-		errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC}, bottomUpVisitorImpl{}, 3,
-			func(module *moduleInfo, pause chan<- pauseSpec) bool {
-				if module == moduleA {
-					// Pause module A on module B (an existing dependency)
-					unpause := make(chan struct{})
-					pause <- pauseSpec{moduleA, moduleB, unpause}
-					<-unpause
-				}
-				order += module.group.name
-				return false
-			})
-		if errs != nil {
-			t.Errorf("expected no errors, got %q", errs)
-		}
-		if g, w := order, "CBA"; g != w {
-			t.Errorf("expected order %q, got %q", w, g)
-		}
-	})
-	t.Run("cycle", func(t *testing.T) {
-		errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC}, bottomUpVisitorImpl{}, 3,
-			func(module *moduleInfo, pause chan<- pauseSpec) bool {
-				if module == moduleC {
-					// Pause module C on module A (a dependency cycle)
-					unpause := make(chan struct{})
-					pause <- pauseSpec{moduleC, moduleA, unpause}
-					<-unpause
-				}
-				return false
-			})
-		want := []string{
-			`encountered dependency cycle`,
-			`module "C" depends on module "A"`,
-			`module "A" depends on module "B"`,
-			`module "B" depends on module "C"`,
-		}
-		for i := range want {
-			if len(errs) <= i {
-				t.Errorf("missing error %s", want[i])
-			} else if !strings.Contains(errs[i].Error(), want[i]) {
-				t.Errorf("expected error %s, got %s", want[i], errs[i])
-			}
-		}
-		if len(errs) > len(want) {
-			for _, err := range errs[len(want):] {
-				t.Errorf("unexpected error %s", err.Error())
-			}
-		}
-	})
-	t.Run("pause cycle", func(t *testing.T) {
-		errs := parallelVisit([]*moduleInfo{moduleA, moduleB, moduleC, moduleD}, bottomUpVisitorImpl{}, 3,
-			func(module *moduleInfo, pause chan<- pauseSpec) bool {
-				if module == moduleC {
-					// Pause module C on module D
-					unpause := make(chan struct{})
-					pause <- pauseSpec{moduleC, moduleD, unpause}
-					<-unpause
-				}
-				if module == moduleD {
-					// Pause module D on module C (a pause cycle)
-					unpause := make(chan struct{})
-					pause <- pauseSpec{moduleD, moduleC, unpause}
-					<-unpause
-				}
-				return false
-			})
-		want := []string{
-			`encountered dependency cycle`,
-			`module "D" depends on module "C"`,
-			`module "C" depends on module "D"`,
-		}
-		for i := range want {
-			if len(errs) <= i {
-				t.Errorf("missing error %s", want[i])
-			} else if !strings.Contains(errs[i].Error(), want[i]) {
-				t.Errorf("expected error %s, got %s", want[i], errs[i])
-			}
-		}
-		if len(errs) > len(want) {
-			for _, err := range errs[len(want):] {
-				t.Errorf("unexpected error %s", err.Error())
-			}
-		}
-	})
-	t.Run("pause cycle with deps", func(t *testing.T) {
-		pauseDeps := map[*moduleInfo]*moduleInfo{
-			// F and G form a pause cycle
-			moduleF: moduleG,
-			moduleG: moduleF,
-			// D depends on E which depends on the pause cycle, making E the first alphabetical
-			// entry in pauseMap, which is not part of the cycle.
-			moduleD: moduleE,
-			moduleE: moduleF,
-		}
-		errs := parallelVisit([]*moduleInfo{moduleD, moduleE, moduleF, moduleG}, bottomUpVisitorImpl{}, 4,
-			func(module *moduleInfo, pause chan<- pauseSpec) bool {
-				if dep, ok := pauseDeps[module]; ok {
-					unpause := make(chan struct{})
-					pause <- pauseSpec{module, dep, unpause}
-					<-unpause
-				}
-				return false
-			})
-		want := []string{
-			`encountered dependency cycle`,
-			`module "G" depends on module "F"`,
-			`module "F" depends on module "G"`,
-		}
-		for i := range want {
-			if len(errs) <= i {
-				t.Errorf("missing error %s", want[i])
-			} else if !strings.Contains(errs[i].Error(), want[i]) {
-				t.Errorf("expected error %s, got %s", want[i], errs[i])
-			}
-		}
-		if len(errs) > len(want) {
-			for _, err := range errs[len(want):] {
-				t.Errorf("unexpected error %s", err.Error())
-			}
-		}
-	})
-}
diff --git a/glob.go b/glob.go
index 91ae723..4f7e978 100644
--- a/glob.go
+++ b/glob.go
@@ -15,6 +15,7 @@
 package blueprint
 
 import (
+	"crypto/md5"
 	"fmt"
 	"sort"
 	"strings"
@@ -22,95 +23,108 @@
 	"github.com/google/blueprint/pathtools"
 )
 
-func verifyGlob(key globKey, pattern string, excludes []string, g pathtools.GlobResult) {
+type GlobPath struct {
+	Pattern  string
+	Excludes []string
+	Files    []string
+	Deps     []string
+	Name     string
+}
+
+func verifyGlob(fileName, pattern string, excludes []string, g GlobPath) {
 	if pattern != g.Pattern {
-		panic(fmt.Errorf("Mismatched patterns %q and %q for glob key %q", pattern, g.Pattern, key))
+		panic(fmt.Errorf("Mismatched patterns %q and %q for glob file %q", pattern, g.Pattern, fileName))
 	}
 	if len(excludes) != len(g.Excludes) {
-		panic(fmt.Errorf("Mismatched excludes %v and %v for glob key %q", excludes, g.Excludes, key))
+		panic(fmt.Errorf("Mismatched excludes %v and %v for glob file %q", excludes, g.Excludes, fileName))
 	}
 
 	for i := range excludes {
 		if g.Excludes[i] != excludes[i] {
-			panic(fmt.Errorf("Mismatched excludes %v and %v for glob key %q", excludes, g.Excludes, key))
+			panic(fmt.Errorf("Mismatched excludes %v and %v for glob file %q", excludes, g.Excludes, fileName))
 		}
 	}
 }
 
 func (c *Context) glob(pattern string, excludes []string) ([]string, error) {
-	// Sort excludes so that two globs with the same excludes in a different order reuse the same
-	// key.  Make a copy first to avoid modifying the caller's version.
-	excludes = append([]string(nil), excludes...)
-	sort.Strings(excludes)
-
-	key := globToKey(pattern, excludes)
+	fileName := globToFileName(pattern, excludes)
 
 	// Try to get existing glob from the stored results
 	c.globLock.Lock()
-	g, exists := c.globs[key]
+	g, exists := c.globs[fileName]
 	c.globLock.Unlock()
 
 	if exists {
 		// Glob has already been done, double check it is identical
-		verifyGlob(key, pattern, excludes, g)
-		// Return a copy so that modifications don't affect the cached value.
-		return append([]string(nil), g.Matches...), nil
+		verifyGlob(fileName, pattern, excludes, g)
+		return g.Files, nil
 	}
 
 	// Get a globbed file list
-	result, err := c.fs.Glob(pattern, excludes, pathtools.FollowSymlinks)
+	files, deps, err := c.fs.Glob(pattern, excludes, pathtools.FollowSymlinks)
 	if err != nil {
 		return nil, err
 	}
 
 	// Store the results
 	c.globLock.Lock()
-	if g, exists = c.globs[key]; !exists {
-		c.globs[key] = result
+	if g, exists = c.globs[fileName]; !exists {
+		c.globs[fileName] = GlobPath{pattern, excludes, files, deps, fileName}
 	}
 	c.globLock.Unlock()
 
+	// Getting the list raced with another goroutine, throw away the results and use theirs
 	if exists {
-		// Getting the list raced with another goroutine, throw away the results and use theirs
-		verifyGlob(key, pattern, excludes, g)
-		// Return a copy so that modifications don't affect the cached value.
-		return append([]string(nil), g.Matches...), nil
+		verifyGlob(fileName, pattern, excludes, g)
+		return g.Files, nil
 	}
 
-	// Return a copy so that modifications don't affect the cached value.
-	return append([]string(nil), result.Matches...), nil
+	return files, nil
 }
 
-func (c *Context) Globs() pathtools.MultipleGlobResults {
-	keys := make([]globKey, 0, len(c.globs))
+func (c *Context) Globs() []GlobPath {
+	fileNames := make([]string, 0, len(c.globs))
 	for k := range c.globs {
-		keys = append(keys, k)
+		fileNames = append(fileNames, k)
 	}
+	sort.Strings(fileNames)
 
-	sort.Slice(keys, func(i, j int) bool {
-		if keys[i].pattern != keys[j].pattern {
-			return keys[i].pattern < keys[j].pattern
-		}
-		return keys[i].excludes < keys[j].excludes
-	})
-
-	globs := make(pathtools.MultipleGlobResults, len(keys))
-	for i, key := range keys {
-		globs[i] = c.globs[key]
+	globs := make([]GlobPath, len(fileNames))
+	for i, fileName := range fileNames {
+		globs[i] = c.globs[fileName]
 	}
 
 	return globs
 }
 
-// globKey combines a pattern and a list of excludes into a hashable struct to be used as a key in
-// a map.
-type globKey struct {
-	pattern  string
-	excludes string
+func globToString(pattern string) string {
+	ret := ""
+	for _, c := range pattern {
+		switch {
+		case c >= 'a' && c <= 'z',
+			c >= 'A' && c <= 'Z',
+			c >= '0' && c <= '9',
+			c == '_', c == '-', c == '/':
+			ret += string(c)
+		default:
+			ret += "_"
+		}
+	}
+
+	return ret
 }
 
-// globToKey converts a pattern and an excludes list into a globKey struct that is hashable and
-// usable as a key in a map.
-func globToKey(pattern string, excludes []string) globKey {
-	return globKey{pattern, strings.Join(excludes, "|")}
+func globToFileName(pattern string, excludes []string) string {
+	name := globToString(pattern)
+	excludeName := ""
+	for _, e := range excludes {
+		excludeName += "__" + globToString(e)
+	}
+
+	// Prevent file names from reaching ninja's path component limit
+	if strings.Count(name, "/")+strings.Count(excludeName, "/") > 30 {
+		excludeName = fmt.Sprintf("___%x", md5.Sum([]byte(excludeName)))
+	}
+
+	return name + excludeName + ".glob"
 }
diff --git a/live_tracker.go b/live_tracker.go
index 1d48e58..40e1930 100644
--- a/live_tracker.go
+++ b/live_tracker.go
@@ -68,11 +68,6 @@
 		return err
 	}
 
-	err = l.addNinjaStringListDeps(def.Validations)
-	if err != nil {
-		return err
-	}
-
 	for _, value := range def.Variables {
 		err = l.addNinjaStringDeps(value)
 		if err != nil {
diff --git a/microfactory/microfactory.go b/microfactory/microfactory.go
index a0c9a14..a70d3c5 100644
--- a/microfactory/microfactory.go
+++ b/microfactory/microfactory.go
@@ -397,7 +397,6 @@
 	fmt.Fprintln(hash, runtime.GOOS, runtime.GOARCH, goVersion)
 
 	cmd := exec.Command(filepath.Join(goToolDir, "compile"),
-		"-N", "-l", // Disable optimization and inlining so that debugging works better
 		"-o", p.output,
 		"-p", p.Name,
 		"-complete", "-pack", "-nolocalimports")
diff --git a/module_ctx.go b/module_ctx.go
index a074e37..36e05a4 100644
--- a/module_ctx.go
+++ b/module_ctx.go
@@ -246,24 +246,6 @@
 	// invalidated by future mutators.
 	WalkDeps(visit func(Module, Module) bool)
 
-	// PrimaryModule returns the first variant of the current module.  Variants of a module are always visited in
-	// order by mutators and GenerateBuildActions, so the data created by the current mutator can be read from the
-	// Module returned by PrimaryModule without data races.  This can be used to perform singleton actions that are
-	// only done once for all variants of a module.
-	PrimaryModule() Module
-
-	// FinalModule returns the last variant of the current module.  Variants of a module are always visited in
-	// order by mutators and GenerateBuildActions, so the data created by the current mutator can be read from all
-	// variants using VisitAllModuleVariants if the current module == FinalModule().  This can be used to perform
-	// singleton actions that are only done once for all variants of a module.
-	FinalModule() Module
-
-	// VisitAllModuleVariants calls visit for each variant of the current module.  Variants of a module are always
-	// visited in order by mutators and GenerateBuildActions, so the data created by the current mutator can be read
-	// from all variants if the current module == FinalModule().  Otherwise, care must be taken to not access any
-	// data modified by the current mutator.
-	VisitAllModuleVariants(visit func(Module))
-
 	// OtherModuleName returns the name of another Module.  See BaseModuleContext.ModuleName for more information.
 	// It is intended for use inside the visit functions of Visit* and WalkDeps.
 	OtherModuleName(m Module) string
@@ -293,51 +275,6 @@
 	// OtherModuleExists returns true if a module with the specified name exists, as determined by the NameInterface
 	// passed to Context.SetNameInterface, or SimpleNameInterface if it was not called.
 	OtherModuleExists(name string) bool
-
-	// OtherModuleDependencyVariantExists returns true if a module with the
-	// specified name and variant exists. The variant must match the given
-	// variations. It must also match all the non-local variations of the current
-	// module. In other words, it checks for the module that AddVariationDependencies
-	// would add a dependency on with the same arguments.
-	OtherModuleDependencyVariantExists(variations []Variation, name string) bool
-
-	// OtherModuleFarDependencyVariantExists returns true if a module with the
-	// specified name and variant exists. The variant must match the given
-	// variations, but not the non-local variations of the current module. In
-	// other words, it checks for the module that AddFarVariationDependencies
-	// would add a dependency on with the same arguments.
-	OtherModuleFarDependencyVariantExists(variations []Variation, name string) bool
-
-	// OtherModuleReverseDependencyVariantExists returns true if a module with the
-	// specified name exists with the same variations as the current module. In
-	// other words, it checks for the module that AddReverseDependency would add a
-	// dependency on with the same argument.
-	OtherModuleReverseDependencyVariantExists(name string) bool
-
-	// OtherModuleProvider returns the value for a provider for the given module.  If the value is
-	// not set it returns the zero value of the type of the provider, so the return value can always
-	// be type asserted to the type of the provider.  The value returned may be a deep copy of the
-	// value originally passed to SetProvider.
-	OtherModuleProvider(m Module, provider ProviderKey) interface{}
-
-	// OtherModuleHasProvider returns true if the provider for the given module has been set.
-	OtherModuleHasProvider(m Module, provider ProviderKey) bool
-
-	// Provider returns the value for a provider for the current module.  If the value is
-	// not set it returns the zero value of the type of the provider, so the return value can always
-	// be type asserted to the type of the provider.  It panics if called before the appropriate
-	// mutator or GenerateBuildActions pass for the provider.  The value returned may be a deep
-	// copy of the value originally passed to SetProvider.
-	Provider(provider ProviderKey) interface{}
-
-	// HasProvider returns true if the provider for the current module has been set.
-	HasProvider(provider ProviderKey) bool
-
-	// SetProvider sets the value for a provider for the current module.  It panics if not called
-	// during the appropriate mutator or GenerateBuildActions pass for the provider, if the value
-	// is not of the appropriate type, or if the value has already been set.  The value should not
-	// be modified after being passed to SetProvider.
-	SetProvider(provider ProviderKey, value interface{})
 }
 
 type DynamicDependerModuleContext BottomUpMutatorContext
@@ -359,6 +296,24 @@
 	// Build creates a new ninja build statement.
 	Build(pctx PackageContext, params BuildParams)
 
+	// PrimaryModule returns the first variant of the current module.  Variants of a module are always visited in
+	// order by mutators and GenerateBuildActions, so the data created by the current mutator can be read from the
+	// Module returned by PrimaryModule without data races.  This can be used to perform singleton actions that are
+	// only done once for all variants of a module.
+	PrimaryModule() Module
+
+	// FinalModule returns the last variant of the current module.  Variants of a module are always visited in
+	// order by mutators and GenerateBuildActions, so the data created by the current mutator can be read from all
+	// variants using VisitAllModuleVariants if the current module == FinalModule().  This can be used to perform
+	// singleton actions that are only done once for all variants of a module.
+	FinalModule() Module
+
+	// VisitAllModuleVariants calls visit for each variant of the current module.  Variants of a module are always
+	// visited in order by mutators and GenerateBuildActions, so the data created by the current mutator can be read
+	// from all variants if the current module == FinalModule().  Otherwise, care must be taken to not access any
+	// data modified by the current mutator.
+	VisitAllModuleVariants(visit func(Module))
+
 	// GetMissingDependencies returns the list of dependencies that were passed to AddDependencies or related methods,
 	// but do not exist.  It can be used with Context.SetAllowMissingDependencies to allow the primary builder to
 	// handle missing dependencies on its own instead of having Blueprint treat them as an error.
@@ -496,7 +451,7 @@
 
 func (m *baseModuleContext) OtherModuleSubDir(logicModule Module) string {
 	module := m.context.moduleInfo[logicModule]
-	return module.variant.name
+	return module.variantName
 }
 
 func (m *baseModuleContext) OtherModuleType(logicModule Module) string {
@@ -537,59 +492,6 @@
 	return exists
 }
 
-func (m *baseModuleContext) OtherModuleDependencyVariantExists(variations []Variation, name string) bool {
-	possibleDeps := m.context.moduleGroupFromName(name, m.module.namespace())
-	if possibleDeps == nil {
-		return false
-	}
-	found, _ := findVariant(m.module, possibleDeps, variations, false, false)
-	return found != nil
-}
-
-func (m *baseModuleContext) OtherModuleFarDependencyVariantExists(variations []Variation, name string) bool {
-	possibleDeps := m.context.moduleGroupFromName(name, m.module.namespace())
-	if possibleDeps == nil {
-		return false
-	}
-	found, _ := findVariant(m.module, possibleDeps, variations, true, false)
-	return found != nil
-}
-
-func (m *baseModuleContext) OtherModuleReverseDependencyVariantExists(name string) bool {
-	possibleDeps := m.context.moduleGroupFromName(name, m.module.namespace())
-	if possibleDeps == nil {
-		return false
-	}
-	found, _ := findVariant(m.module, possibleDeps, nil, false, true)
-	return found != nil
-}
-
-func (m *baseModuleContext) OtherModuleProvider(logicModule Module, provider ProviderKey) interface{} {
-	module := m.context.moduleInfo[logicModule]
-	value, _ := m.context.provider(module, provider)
-	return value
-}
-
-func (m *baseModuleContext) OtherModuleHasProvider(logicModule Module, provider ProviderKey) bool {
-	module := m.context.moduleInfo[logicModule]
-	_, ok := m.context.provider(module, provider)
-	return ok
-}
-
-func (m *baseModuleContext) Provider(provider ProviderKey) interface{} {
-	value, _ := m.context.provider(m.module, provider)
-	return value
-}
-
-func (m *baseModuleContext) HasProvider(provider ProviderKey) bool {
-	_, ok := m.context.provider(m.module, provider)
-	return ok
-}
-
-func (m *baseModuleContext) SetProvider(provider ProviderKey, value interface{}) {
-	m.context.setProvider(m.module, provider, value)
-}
-
 func (m *baseModuleContext) GetDirectDep(name string) (Module, DependencyTag) {
 	for _, dep := range m.module.directDeps {
 		if dep.module.Name() == name {
@@ -709,18 +611,6 @@
 	m.visitingDep = depInfo{}
 }
 
-func (m *baseModuleContext) PrimaryModule() Module {
-	return m.module.group.modules.firstModule().logicModule
-}
-
-func (m *baseModuleContext) FinalModule() Module {
-	return m.module.group.modules.lastModule().logicModule
-}
-
-func (m *baseModuleContext) VisitAllModuleVariants(visit func(Module)) {
-	m.context.visitAllModuleVariants(m.module, visit)
-}
-
 func (m *baseModuleContext) AddNinjaFileDeps(deps ...string) {
 	m.ninjaFileDeps = append(m.ninjaFileDeps, deps...)
 }
@@ -734,7 +624,7 @@
 }
 
 func (m *moduleContext) ModuleSubDir() string {
-	return m.module.variant.name
+	return m.module.variantName
 }
 
 func (m *moduleContext) Variable(pctx PackageContext, name, value string) {
@@ -774,6 +664,18 @@
 	m.actionDefs.buildDefs = append(m.actionDefs.buildDefs, def)
 }
 
+func (m *moduleContext) PrimaryModule() Module {
+	return m.module.group.modules[0].logicModule
+}
+
+func (m *moduleContext) FinalModule() Module {
+	return m.module.group.modules[len(m.module.group.modules)-1].logicModule
+}
+
+func (m *moduleContext) VisitAllModuleVariants(visit func(Module)) {
+	m.context.visitAllModuleVariants(m.module, visit)
+}
+
 func (m *moduleContext) GetMissingDependencies() []string {
 	m.handledMissingDeps = true
 	return m.module.missingDeps
@@ -789,10 +691,9 @@
 	reverseDeps      []reverseDep
 	rename           []rename
 	replace          []replace
-	newVariations    modulesOrAliases // new variants of existing modules
-	newModules       []*moduleInfo    // brand new modules
+	newVariations    []*moduleInfo // new variants of existing modules
+	newModules       []*moduleInfo // brand new modules
 	defaultVariation *string
-	pauseCh          chan<- pauseSpec
 }
 
 type BaseMutatorContext interface {
@@ -843,15 +744,10 @@
 type BottomUpMutatorContext interface {
 	BaseMutatorContext
 
-	// AddDependency adds a dependency to the given module.  It returns a slice of modules for each
-	// dependency (some entries may be nil).  Does not affect the ordering of the current mutator
-	// pass, but will be ordered correctly for all future mutator passes.
-	//
-	// If the mutator is parallel (see MutatorHandle.Parallel), this method will pause until the
-	// new dependencies have had the current mutator called on them.  If the mutator is not
-	// parallel this method does not affect the ordering of the current mutator pass, but will
-	// be ordered correctly for all future mutator passes.
-	AddDependency(module Module, tag DependencyTag, name ...string) []Module
+	// AddDependency adds a dependency to the given module.
+	// Does not affect the ordering of the current mutator pass, but will be ordered
+	// correctly for all future mutator passes.
+	AddDependency(module Module, tag DependencyTag, name ...string)
 
 	// AddReverseDependency adds a dependency from the destination to the given module.
 	// Does not affect the ordering of the current mutator pass, but will be ordered
@@ -891,30 +787,19 @@
 	SetDefaultDependencyVariation(*string)
 
 	// AddVariationDependencies adds deps as dependencies of the current module, but uses the variations
-	// argument to select which variant of the dependency to use.  It returns a slice of modules for
-	// each dependency (some entries may be nil).  A variant of the dependency must exist that matches
-	// the all of the non-local variations of the current module, plus the variations argument.
-	//
-	// If the mutator is parallel (see MutatorHandle.Parallel), this method will pause until the
-	// new dependencies have had the current mutator called on them.  If the mutator is not
-	// parallel this method does not affect the ordering of the current mutator pass, but will
-	// be ordered correctly for all future mutator passes.
-	AddVariationDependencies([]Variation, DependencyTag, ...string) []Module
+	// argument to select which variant of the dependency to use.  A variant of the dependency must
+	// exist that matches the all of the non-local variations of the current module, plus the variations
+	// argument.
+	AddVariationDependencies([]Variation, DependencyTag, ...string)
 
 	// AddFarVariationDependencies adds deps as dependencies of the current module, but uses the
-	// variations argument to select which variant of the dependency to use.  It returns a slice of
-	// modules for each dependency (some entries may be nil).  A variant of the dependency must
-	// exist that matches the variations argument, but may also have other variations.
+	// variations argument to select which variant of the dependency to use.  A variant of the
+	// dependency must exist that matches the variations argument, but may also have other variations.
 	// For any unspecified variation the first variant will be used.
 	//
 	// Unlike AddVariationDependencies, the variations of the current module are ignored - the
 	// dependency only needs to match the supplied variations.
-	//
-	// If the mutator is parallel (see MutatorHandle.Parallel), this method will pause until the
-	// new dependencies have had the current mutator called on them.  If the mutator is not
-	// parallel this method does not affect the ordering of the current mutator pass, but will
-	// be ordered correctly for all future mutator passes.
-	AddFarVariationDependencies([]Variation, DependencyTag, ...string) []Module
+	AddFarVariationDependencies([]Variation, DependencyTag, ...string)
 
 	// AddInterVariantDependency adds a dependency between two variants of the same module.  Variants are always
 	// ordered in the same orderas they were listed in CreateVariations, and AddInterVariantDependency does not change
@@ -927,36 +812,12 @@
 	// after the mutator pass is finished.
 	ReplaceDependencies(string)
 
-	// ReplaceDependencies replaces all dependencies on the identical variant of the module with the
-	// specified name with the current variant of this module as long as the supplied predicate returns
-	// true.
-	//
-	// Replacements don't take effect until after the mutator pass is finished.
-	ReplaceDependenciesIf(string, ReplaceDependencyPredicate)
-
-	// AliasVariation takes a variationName that was passed to CreateVariations for this module,
-	// and creates an alias from the current variant (before the mutator has run) to the new
-	// variant.  The alias will be valid until the next time a mutator calls CreateVariations or
-	// CreateLocalVariations on this module without also calling AliasVariation.  The alias can
-	// be used to add dependencies on the newly created variant using the variant map from
-	// before CreateVariations was run.
+	// AliasVariation takes a variationName that was passed to CreateVariations for this module, and creates an
+	// alias from the current variant to the new variant.  The alias will be valid until the next time a mutator
+	// calls CreateVariations or CreateLocalVariations on this module without also calling AliasVariation.  The
+	// alias can be used to add dependencies on the newly created variant using the variant map from before
+	// CreateVariations was run.
 	AliasVariation(variationName string)
-
-	// CreateAliasVariation takes a toVariationName that was passed to CreateVariations for this
-	// module, and creates an alias from a new fromVariationName variant the toVariationName
-	// variant.  The alias will be valid until the next time a mutator calls CreateVariations or
-	// CreateLocalVariations on this module without also calling AliasVariation.  The alias can
-	// be used to add dependencies on the toVariationName variant using the fromVariationName
-	// variant.
-	CreateAliasVariation(fromVariationName, toVariationName string)
-
-	// SetVariationProvider sets the value for a provider for the given newly created variant of
-	// the current module, i.e. one of the Modules returned by CreateVariations..  It panics if
-	// not called during the appropriate mutator or GenerateBuildActions pass for the provider,
-	// if the value is not of the appropriate type, or if the module is not a newly created
-	// variant of the current module.  The value should not be modified after being passed to
-	// SetVariationProvider.
-	SetVariationProvider(module Module, provider ProviderKey, value interface{})
 }
 
 // A Mutator function is called for each Module, and can use
@@ -1000,30 +861,21 @@
 	return mctx.createVariations(variationNames, true)
 }
 
-func (mctx *mutatorContext) SetVariationProvider(module Module, provider ProviderKey, value interface{}) {
-	for _, variant := range mctx.newVariations {
-		if m := variant.module(); m != nil && m.logicModule == module {
-			mctx.context.setProvider(m, provider, value)
-			return
-		}
-	}
-	panic(fmt.Errorf("module %q is not a newly created variant of %q", module, mctx.module))
-}
-
-type pendingAlias struct {
-	fromVariant variant
-	target      *moduleInfo
-}
-
 func (mctx *mutatorContext) createVariations(variationNames []string, local bool) []Module {
 	ret := []Module{}
-	modules, errs := mctx.context.createVariations(mctx.module, mctx.name, mctx.defaultVariation, variationNames, local)
+	modules, errs := mctx.context.createVariations(mctx.module, mctx.name, mctx.defaultVariation, variationNames)
 	if len(errs) > 0 {
 		mctx.errs = append(mctx.errs, errs...)
 	}
 
-	for _, module := range modules {
-		ret = append(ret, module.module().logicModule)
+	for i, module := range modules {
+		ret = append(ret, module.logicModule)
+		if !local {
+			if module.dependencyVariant == nil {
+				module.dependencyVariant = make(variationMap)
+			}
+			module.dependencyVariant[mctx.name] = variationNames[i]
+		}
 	}
 
 	if mctx.newVariations != nil {
@@ -1039,65 +891,24 @@
 }
 
 func (mctx *mutatorContext) AliasVariation(variationName string) {
-	for _, moduleOrAlias := range mctx.module.splitModules {
-		if alias := moduleOrAlias.alias(); alias != nil {
-			if alias.variant.variations.equal(mctx.module.variant.variations) {
-				panic(fmt.Errorf("AliasVariation already called"))
-			}
-		}
+	if mctx.module.aliasTarget != nil {
+		panic(fmt.Errorf("AliasVariation already called"))
 	}
 
 	for _, variant := range mctx.newVariations {
-		if variant.moduleOrAliasVariant().variations[mctx.name] == variationName {
-			alias := &moduleAlias{
-				variant: mctx.module.variant,
-				target:  variant.moduleOrAliasTarget(),
-			}
-			// Prepend the alias so that AddFarVariationDependencies subset match matches
-			// the alias before matching the first variation.
-			mctx.module.splitModules = append(modulesOrAliases{alias}, mctx.module.splitModules...)
+		if variant.variant[mctx.name] == variationName {
+			mctx.module.aliasTarget = variant
 			return
 		}
 	}
 
 	var foundVariations []string
 	for _, variant := range mctx.newVariations {
-		foundVariations = append(foundVariations, variant.moduleOrAliasVariant().variations[mctx.name])
+		foundVariations = append(foundVariations, variant.variant[mctx.name])
 	}
 	panic(fmt.Errorf("no %q variation in module variations %q", variationName, foundVariations))
 }
 
-func (mctx *mutatorContext) CreateAliasVariation(aliasVariationName, targetVariationName string) {
-	newVariant := newVariant(mctx.module, mctx.name, aliasVariationName, false)
-
-	for _, moduleOrAlias := range mctx.module.splitModules {
-		if moduleOrAlias.moduleOrAliasVariant().variations.equal(newVariant.variations) {
-			if alias := moduleOrAlias.alias(); alias != nil {
-				panic(fmt.Errorf("can't alias %q to %q, already aliased to %q", aliasVariationName, targetVariationName, alias.target.variant.name))
-			} else {
-				panic(fmt.Errorf("can't alias %q to %q, there is already a variant with that name", aliasVariationName, targetVariationName))
-			}
-		}
-	}
-
-	for _, variant := range mctx.newVariations {
-		if variant.moduleOrAliasVariant().variations[mctx.name] == targetVariationName {
-			// Append the alias here so that it comes after any aliases created by AliasVariation.
-			mctx.module.splitModules = append(mctx.module.splitModules, &moduleAlias{
-				variant: newVariant,
-				target:  variant.moduleOrAliasTarget(),
-			})
-			return
-		}
-	}
-
-	var foundVariations []string
-	for _, variant := range mctx.newVariations {
-		foundVariations = append(foundVariations, variant.moduleOrAliasVariant().variations[mctx.name])
-	}
-	panic(fmt.Errorf("no %q variation in module variations %q", targetVariationName, foundVariations))
-}
-
 func (mctx *mutatorContext) SetDependencyVariation(variationName string) {
 	mctx.context.convertDepsToVariation(mctx.module, mctx.name, variationName, nil)
 }
@@ -1110,21 +921,14 @@
 	return mctx.module.logicModule
 }
 
-func (mctx *mutatorContext) AddDependency(module Module, tag DependencyTag, deps ...string) []Module {
-	depInfos := make([]Module, 0, len(deps))
+func (mctx *mutatorContext) AddDependency(module Module, tag DependencyTag, deps ...string) {
 	for _, dep := range deps {
 		modInfo := mctx.context.moduleInfo[module]
-		depInfo, errs := mctx.context.addDependency(modInfo, tag, dep)
+		errs := mctx.context.addDependency(modInfo, tag, dep)
 		if len(errs) > 0 {
 			mctx.errs = append(mctx.errs, errs...)
 		}
-		if !mctx.pause(depInfo) {
-			// Pausing not supported by this mutator, new dependencies can't be returned.
-			depInfo = nil
-		}
-		depInfos = append(depInfos, maybeLogicModule(depInfo))
 	}
-	return depInfos
 }
 
 func (mctx *mutatorContext) AddReverseDependency(module Module, tag DependencyTag, destName string) {
@@ -1145,39 +949,25 @@
 }
 
 func (mctx *mutatorContext) AddVariationDependencies(variations []Variation, tag DependencyTag,
-	deps ...string) []Module {
+	deps ...string) {
 
-	depInfos := make([]Module, 0, len(deps))
 	for _, dep := range deps {
-		depInfo, errs := mctx.context.addVariationDependency(mctx.module, variations, tag, dep, false)
+		errs := mctx.context.addVariationDependency(mctx.module, variations, tag, dep, false)
 		if len(errs) > 0 {
 			mctx.errs = append(mctx.errs, errs...)
 		}
-		if !mctx.pause(depInfo) {
-			// Pausing not supported by this mutator, new dependencies can't be returned.
-			depInfo = nil
-		}
-		depInfos = append(depInfos, maybeLogicModule(depInfo))
 	}
-	return depInfos
 }
 
 func (mctx *mutatorContext) AddFarVariationDependencies(variations []Variation, tag DependencyTag,
-	deps ...string) []Module {
+	deps ...string) {
 
-	depInfos := make([]Module, 0, len(deps))
 	for _, dep := range deps {
-		depInfo, errs := mctx.context.addVariationDependency(mctx.module, variations, tag, dep, true)
+		errs := mctx.context.addVariationDependency(mctx.module, variations, tag, dep, true)
 		if len(errs) > 0 {
 			mctx.errs = append(mctx.errs, errs...)
 		}
-		if !mctx.pause(depInfo) {
-			// Pausing not supported by this mutator, new dependencies can't be returned.
-			depInfo = nil
-		}
-		depInfos = append(depInfos, maybeLogicModule(depInfo))
 	}
-	return depInfos
 }
 
 func (mctx *mutatorContext) AddInterVariantDependency(tag DependencyTag, from, to Module) {
@@ -1185,23 +975,14 @@
 }
 
 func (mctx *mutatorContext) ReplaceDependencies(name string) {
-	mctx.ReplaceDependenciesIf(name, nil)
-}
-
-type ReplaceDependencyPredicate func(from Module, tag DependencyTag, to Module) bool
-
-func (mctx *mutatorContext) ReplaceDependenciesIf(name string, predicate ReplaceDependencyPredicate) {
 	target := mctx.context.moduleMatchingVariant(mctx.module, name)
 
 	if target == nil {
-		panic(fmt.Errorf("ReplaceDependencies could not find identical variant {%s} for module %s\n"+
-			"available variants:\n  %s",
-			mctx.context.prettyPrintVariant(mctx.module.variant.variations),
-			name,
-			mctx.context.prettyPrintGroupVariants(mctx.context.moduleGroupFromName(name, mctx.module.namespace()))))
+		panic(fmt.Errorf("ReplaceDependencies could not find identical variant %q for module %q",
+			mctx.module.variantName, name))
 	}
 
-	mctx.replace = append(mctx.replace, replace{target, mctx.module, predicate})
+	mctx.replace = append(mctx.replace, replace{target, mctx.module})
 }
 
 func (mctx *mutatorContext) Rename(name string) {
@@ -1228,26 +1009,6 @@
 	return module.logicModule
 }
 
-// pause waits until the given dependency has been visited by the mutator's parallelVisit call.
-// It returns true if the pause was supported, false if the pause was not supported and did not
-// occur, which will happen when the mutator is not parallelizable.  If the dependency is nil
-// it returns true if pausing is supported or false if it is not.
-func (mctx *mutatorContext) pause(dep *moduleInfo) bool {
-	if mctx.pauseCh != nil {
-		if dep != nil {
-			unpause := make(unpause)
-			mctx.pauseCh <- pauseSpec{
-				paused:  mctx.module,
-				until:   dep,
-				unpause: unpause,
-			}
-			<-unpause
-		}
-		return true
-	}
-	return false
-}
-
 // SimpleName is an embeddable object to implement the ModuleContext.Name method using a property
 // called "name".  Modules that embed it must also add SimpleName.Properties to their property
 // structure list.
@@ -1339,7 +1100,7 @@
 }
 
 func runAndRemoveLoadHooks(ctx *Context, config interface{}, module *moduleInfo,
-	scopedModuleFactories *map[string]ModuleFactory) (newModules []*moduleInfo, deps []string, errs []error) {
+	scopedModuleFactories *map[string]ModuleFactory) (newModules []*moduleInfo, errs []error) {
 
 	if v, exists := pendingHooks.Load(module.logicModule); exists {
 		hooks := v.(*[]LoadHook)
@@ -1355,21 +1116,21 @@
 		for _, hook := range *hooks {
 			hook(mctx)
 			newModules = append(newModules, mctx.newModules...)
-			deps = append(deps, mctx.ninjaFileDeps...)
 			errs = append(errs, mctx.errs...)
 		}
 		pendingHooks.Delete(module.logicModule)
 
-		return newModules, deps, errs
+		return newModules, errs
 	}
 
-	return nil, nil, nil
+	return nil, nil
 }
 
 // Check the syntax of a generated blueprint file.
 //
-// This is intended to perform a quick syntactic check for generated blueprint
-// code, where syntactically correct means:
+// This is intended to perform a quick sanity check for generated blueprint
+// code to ensure that it is syntactically correct, where syntactically correct
+// means:
 // * No variable definitions.
 // * Valid module types.
 // * Valid property names.
@@ -1405,11 +1166,3 @@
 
 	return errs
 }
-
-func maybeLogicModule(module *moduleInfo) Module {
-	if module != nil {
-		return module.logicModule
-	} else {
-		return nil
-	}
-}
diff --git a/module_ctx_test.go b/module_ctx_test.go
index d57982e..e98ae82 100644
--- a/module_ctx_test.go
+++ b/module_ctx_test.go
@@ -32,7 +32,7 @@
 func (f *moduleCtxTestModule) GenerateBuildActions(ModuleContext) {
 }
 
-func noAliasMutator(name string) func(ctx BottomUpMutatorContext) {
+func noCreateAliasMutator(name string) func(ctx BottomUpMutatorContext) {
 	return func(ctx BottomUpMutatorContext) {
 		if ctx.ModuleName() == name {
 			ctx.CreateVariations("a", "b")
@@ -40,22 +40,11 @@
 	}
 }
 
-func aliasMutator(name string) func(ctx BottomUpMutatorContext) {
-	return func(ctx BottomUpMutatorContext) {
-		if ctx.ModuleName() == name {
-			ctx.CreateVariations("a", "b")
-			ctx.AliasVariation("b")
-		}
-	}
-}
-
 func createAliasMutator(name string) func(ctx BottomUpMutatorContext) {
 	return func(ctx BottomUpMutatorContext) {
 		if ctx.ModuleName() == name {
 			ctx.CreateVariations("a", "b")
-			ctx.CreateAliasVariation("c", "a")
-			ctx.CreateAliasVariation("d", "b")
-			ctx.CreateAliasVariation("e", "a")
+			ctx.AliasVariation("b")
 		}
 	}
 }
@@ -68,16 +57,7 @@
 	}
 }
 
-func addVariantDepsResultMutator(variants []Variation, tag DependencyTag, from, to string, results map[string][]Module) func(ctx BottomUpMutatorContext) {
-	return func(ctx BottomUpMutatorContext) {
-		if ctx.ModuleName() == from {
-			ret := ctx.AddVariationDependencies(variants, tag, to)
-			results[ctx.ModuleName()] = ret
-		}
-	}
-}
-
-func TestAliasVariation(t *testing.T) {
+func TestAliases(t *testing.T) {
 	runWithFailures := func(ctx *Context, expectedErr string) {
 		t.Helper()
 		bp := `
@@ -135,13 +115,17 @@
 		// Tests a dependency from "foo" to "bar" variant "b" through alias "".
 		ctx := NewContext()
 		ctx.RegisterModuleType("test", newModuleCtxTestModule)
-		ctx.RegisterBottomUpMutator("1", aliasMutator("bar"))
+		ctx.RegisterBottomUpMutator("1", createAliasMutator("bar"))
 		ctx.RegisterBottomUpMutator("2", addVariantDepsMutator(nil, nil, "foo", "bar"))
 
 		run(ctx)
 
-		foo := ctx.moduleGroupFromName("foo", nil).moduleByVariantName("")
-		barB := ctx.moduleGroupFromName("bar", nil).moduleByVariantName("b")
+		foo := ctx.moduleGroupFromName("foo", nil).modules[0]
+		barB := ctx.moduleGroupFromName("bar", nil).modules[1]
+
+		if g, w := barB.variantName, "b"; g != w {
+			t.Fatalf("expected bar.modules[1] variant to be %q, got %q", w, g)
+		}
 
 		if g, w := foo.forwardDeps, []*moduleInfo{barB}; !reflect.DeepEqual(g, w) {
 			t.Fatalf("expected foo deps to be %q, got %q", w, g)
@@ -154,14 +138,18 @@
 		// Tests a dependency from "foo" to "bar" variant "b_b" through alias "".
 		ctx := NewContext()
 		ctx.RegisterModuleType("test", newModuleCtxTestModule)
-		ctx.RegisterBottomUpMutator("1", aliasMutator("bar"))
-		ctx.RegisterBottomUpMutator("2", aliasMutator("bar"))
+		ctx.RegisterBottomUpMutator("1", createAliasMutator("bar"))
+		ctx.RegisterBottomUpMutator("2", createAliasMutator("bar"))
 		ctx.RegisterBottomUpMutator("3", addVariantDepsMutator(nil, nil, "foo", "bar"))
 
 		run(ctx)
 
-		foo := ctx.moduleGroupFromName("foo", nil).moduleByVariantName("")
-		barBB := ctx.moduleGroupFromName("bar", nil).moduleByVariantName("b_b")
+		foo := ctx.moduleGroupFromName("foo", nil).modules[0]
+		barBB := ctx.moduleGroupFromName("bar", nil).modules[3]
+
+		if g, w := barBB.variantName, "b_b"; g != w {
+			t.Fatalf("expected bar.modules[3] variant to be %q, got %q", w, g)
+		}
 
 		if g, w := foo.forwardDeps, []*moduleInfo{barBB}; !reflect.DeepEqual(g, w) {
 			t.Fatalf("expected foo deps to be %q, got %q", w, g)
@@ -174,14 +162,18 @@
 		// Tests a dependency from "foo" to "bar" variant "a_b" through alias "a".
 		ctx := NewContext()
 		ctx.RegisterModuleType("test", newModuleCtxTestModule)
-		ctx.RegisterBottomUpMutator("1", aliasMutator("bar"))
-		ctx.RegisterBottomUpMutator("2", aliasMutator("bar"))
+		ctx.RegisterBottomUpMutator("1", createAliasMutator("bar"))
+		ctx.RegisterBottomUpMutator("2", createAliasMutator("bar"))
 		ctx.RegisterBottomUpMutator("3", addVariantDepsMutator([]Variation{{"1", "a"}}, nil, "foo", "bar"))
 
 		run(ctx)
 
-		foo := ctx.moduleGroupFromName("foo", nil).moduleByVariantName("")
-		barAB := ctx.moduleGroupFromName("bar", nil).moduleByVariantName("a_b")
+		foo := ctx.moduleGroupFromName("foo", nil).modules[0]
+		barAB := ctx.moduleGroupFromName("bar", nil).modules[1]
+
+		if g, w := barAB.variantName, "a_b"; g != w {
+			t.Fatalf("expected bar.modules[1] variant to be %q, got %q", w, g)
+		}
 
 		if g, w := foo.forwardDeps, []*moduleInfo{barAB}; !reflect.DeepEqual(g, w) {
 			t.Fatalf("expected foo deps to be %q, got %q", w, g)
@@ -194,120 +186,13 @@
 		// Tests a dependency from "foo" to removed "bar" alias "" fails.
 		ctx := NewContext()
 		ctx.RegisterModuleType("test", newModuleCtxTestModule)
-		ctx.RegisterBottomUpMutator("1", aliasMutator("bar"))
-		ctx.RegisterBottomUpMutator("2", noAliasMutator("bar"))
+		ctx.RegisterBottomUpMutator("1", createAliasMutator("bar"))
+		ctx.RegisterBottomUpMutator("2", noCreateAliasMutator("bar"))
 		ctx.RegisterBottomUpMutator("3", addVariantDepsMutator(nil, nil, "foo", "bar"))
 
 		runWithFailures(ctx, `dependency "bar" of "foo" missing variant:`+"\n  \n"+
 			"available variants:"+
-			"\n  1:a,2:a\n  1:a,2:b\n  1:b,2:a\n  1:b,2:b")
-	})
-}
-
-func TestCreateAliasVariations(t *testing.T) {
-	runWithFailures := func(ctx *Context, expectedErr string) {
-		t.Helper()
-		bp := `
-			test {
-				name: "foo",
-			}
-
-			test {
-				name: "bar",
-			}
-		`
-
-		mockFS := map[string][]byte{
-			"Blueprints": []byte(bp),
-		}
-
-		ctx.MockFileSystem(mockFS)
-
-		_, errs := ctx.ParseFileList(".", []string{"Blueprints"}, nil)
-		if len(errs) > 0 {
-			t.Errorf("unexpected parse errors:")
-			for _, err := range errs {
-				t.Errorf("  %s", err)
-			}
-		}
-
-		_, errs = ctx.ResolveDependencies(nil)
-		if len(errs) > 0 {
-			if expectedErr == "" {
-				t.Errorf("unexpected dep errors:")
-				for _, err := range errs {
-					t.Errorf("  %s", err)
-				}
-			} else {
-				for _, err := range errs {
-					if strings.Contains(err.Error(), expectedErr) {
-						continue
-					} else {
-						t.Errorf("unexpected dep error: %s", err)
-					}
-				}
-			}
-		} else if expectedErr != "" {
-			t.Errorf("missing dep error: %s", expectedErr)
-		}
-	}
-
-	run := func(ctx *Context) {
-		t.Helper()
-		runWithFailures(ctx, "")
-	}
-
-	t.Run("simple", func(t *testing.T) {
-		// Creates a module "bar" with variants "a" and "b" and alias "c" -> "a", "d" -> "b", and "e" -> "a".
-		// Tests a dependency from "foo" to "bar" variant "b" through alias "d".
-		ctx := NewContext()
-		ctx.RegisterModuleType("test", newModuleCtxTestModule)
-		ctx.RegisterBottomUpMutator("1", createAliasMutator("bar"))
-		ctx.RegisterBottomUpMutator("2", addVariantDepsMutator([]Variation{{"1", "d"}}, nil, "foo", "bar"))
-
-		run(ctx)
-
-		foo := ctx.moduleGroupFromName("foo", nil).moduleByVariantName("")
-		barB := ctx.moduleGroupFromName("bar", nil).moduleByVariantName("b")
-
-		if g, w := foo.forwardDeps, []*moduleInfo{barB}; !reflect.DeepEqual(g, w) {
-			t.Fatalf("expected foo deps to be %q, got %q", w, g)
-		}
-	})
-
-	t.Run("chained", func(t *testing.T) {
-		// Creates a module "bar" with variants "a_a", "a_b", "b_a" and "b_b" and aliases "c" -> "a_b",
-		// "d" -> "b_b", and "d" -> "a_b".
-		// Tests a dependency from "foo" to "bar" variant "b_b" through alias "d".
-		ctx := NewContext()
-		ctx.RegisterModuleType("test", newModuleCtxTestModule)
-		ctx.RegisterBottomUpMutator("1", createAliasMutator("bar"))
-		ctx.RegisterBottomUpMutator("2", aliasMutator("bar"))
-		ctx.RegisterBottomUpMutator("3", addVariantDepsMutator([]Variation{{"1", "d"}}, nil, "foo", "bar"))
-
-		run(ctx)
-
-		foo := ctx.moduleGroupFromName("foo", nil).moduleByVariantName("")
-		barBB := ctx.moduleGroupFromName("bar", nil).moduleByVariantName("b_b")
-
-		if g, w := foo.forwardDeps, []*moduleInfo{barBB}; !reflect.DeepEqual(g, w) {
-			t.Fatalf("expected foo deps to be %q, got %q", w, g)
-		}
-	})
-
-	t.Run("removed dangling alias", func(t *testing.T) {
-		// Creates a module "bar" with variants "a" and "b" and alias "c" -> "a", "d" -> "b", and "e" -> "a",
-		// then splits the variants into "a_a", "a_b", "b_a" and "b_b" without creating new aliases.
-		// Tests a dependency from "foo" to removed "bar" alias "d" fails.
-		ctx := NewContext()
-		ctx.RegisterModuleType("test", newModuleCtxTestModule)
-		ctx.RegisterBottomUpMutator("1", createAliasMutator("bar"))
-		ctx.RegisterBottomUpMutator("2", noAliasMutator("bar"))
-		ctx.RegisterBottomUpMutator("3", addVariantDepsMutator([]Variation{{"1", "d"}}, nil, "foo", "bar"))
-
-		runWithFailures(ctx, `dependency "bar" of "foo" missing variant:`+"\n  1:d\n"+
-			"available variants:"+
-			"\n  1:a,2:a\n  1:a,2:b\n  1:b,2:a\n  1:b,2:b")
+			"\n  1:a, 2:a\n  1:a, 2:b\n  1:b, 2:a\n  1:b, 2:b")
 	})
 }
 
@@ -325,141 +210,6 @@
 	}
 }
 
-func TestAddVariationDependencies(t *testing.T) {
-	runWithFailures := func(ctx *Context, expectedErr string) {
-		t.Helper()
-		bp := `
-			test {
-				name: "foo",
-			}
-
-			test {
-				name: "bar",
-			}
-		`
-
-		mockFS := map[string][]byte{
-			"Blueprints": []byte(bp),
-		}
-
-		ctx.MockFileSystem(mockFS)
-
-		_, errs := ctx.ParseFileList(".", []string{"Blueprints"}, nil)
-		if len(errs) > 0 {
-			t.Errorf("unexpected parse errors:")
-			for _, err := range errs {
-				t.Errorf("  %s", err)
-			}
-		}
-
-		_, errs = ctx.ResolveDependencies(nil)
-		if len(errs) > 0 {
-			if expectedErr == "" {
-				t.Errorf("unexpected dep errors:")
-				for _, err := range errs {
-					t.Errorf("  %s", err)
-				}
-			} else {
-				for _, err := range errs {
-					if strings.Contains(err.Error(), expectedErr) {
-						continue
-					} else {
-						t.Errorf("unexpected dep error: %s", err)
-					}
-				}
-			}
-		} else if expectedErr != "" {
-			t.Errorf("missing dep error: %s", expectedErr)
-		}
-	}
-
-	run := func(ctx *Context) {
-		t.Helper()
-		runWithFailures(ctx, "")
-	}
-
-	t.Run("parallel", func(t *testing.T) {
-		ctx := NewContext()
-		ctx.RegisterModuleType("test", newModuleCtxTestModule)
-		results := make(map[string][]Module)
-		depsMutator := addVariantDepsResultMutator(nil, nil, "foo", "bar", results)
-		ctx.RegisterBottomUpMutator("deps", depsMutator).Parallel()
-
-		run(ctx)
-
-		foo := ctx.moduleGroupFromName("foo", nil).moduleByVariantName("")
-		bar := ctx.moduleGroupFromName("bar", nil).moduleByVariantName("")
-
-		if g, w := foo.forwardDeps, []*moduleInfo{bar}; !reflect.DeepEqual(g, w) {
-			t.Fatalf("expected foo deps to be %q, got %q", w, g)
-		}
-
-		if g, w := results["foo"], []Module{bar.logicModule}; !reflect.DeepEqual(g, w) {
-			t.Fatalf("expected AddVariationDependencies return value to be %q, got %q", w, g)
-		}
-	})
-
-	t.Run("non-parallel", func(t *testing.T) {
-		ctx := NewContext()
-		ctx.RegisterModuleType("test", newModuleCtxTestModule)
-		results := make(map[string][]Module)
-		depsMutator := addVariantDepsResultMutator(nil, nil, "foo", "bar", results)
-		ctx.RegisterBottomUpMutator("deps", depsMutator)
-		run(ctx)
-
-		foo := ctx.moduleGroupFromName("foo", nil).moduleByVariantName("")
-		bar := ctx.moduleGroupFromName("bar", nil).moduleByVariantName("")
-
-		if g, w := foo.forwardDeps, []*moduleInfo{bar}; !reflect.DeepEqual(g, w) {
-			t.Fatalf("expected foo deps to be %q, got %q", w, g)
-		}
-
-		if g, w := results["foo"], []Module{nil}; !reflect.DeepEqual(g, w) {
-			t.Fatalf("expected AddVariationDependencies return value to be %q, got %q", w, g)
-		}
-	})
-
-	t.Run("missing", func(t *testing.T) {
-		ctx := NewContext()
-		ctx.RegisterModuleType("test", newModuleCtxTestModule)
-		results := make(map[string][]Module)
-		depsMutator := addVariantDepsResultMutator(nil, nil, "foo", "baz", results)
-		ctx.RegisterBottomUpMutator("deps", depsMutator).Parallel()
-		runWithFailures(ctx, `"foo" depends on undefined module "baz"`)
-
-		foo := ctx.moduleGroupFromName("foo", nil).moduleByVariantName("")
-
-		if g, w := foo.forwardDeps, []*moduleInfo(nil); !reflect.DeepEqual(g, w) {
-			t.Fatalf("expected foo deps to be %q, got %q", w, g)
-		}
-
-		if g, w := results["foo"], []Module{nil}; !reflect.DeepEqual(g, w) {
-			t.Fatalf("expected AddVariationDependencies return value to be %q, got %q", w, g)
-		}
-	})
-
-	t.Run("allow missing", func(t *testing.T) {
-		ctx := NewContext()
-		ctx.SetAllowMissingDependencies(true)
-		ctx.RegisterModuleType("test", newModuleCtxTestModule)
-		results := make(map[string][]Module)
-		depsMutator := addVariantDepsResultMutator(nil, nil, "foo", "baz", results)
-		ctx.RegisterBottomUpMutator("deps", depsMutator).Parallel()
-		run(ctx)
-
-		foo := ctx.moduleGroupFromName("foo", nil).moduleByVariantName("")
-
-		if g, w := foo.forwardDeps, []*moduleInfo(nil); !reflect.DeepEqual(g, w) {
-			t.Fatalf("expected foo deps to be %q, got %q", w, g)
-		}
-
-		if g, w := results["foo"], []Module{nil}; !reflect.DeepEqual(g, w) {
-			t.Fatalf("expected AddVariationDependencies return value to be %q, got %q", w, g)
-		}
-	})
-
-}
-
 func TestCheckBlueprintSyntax(t *testing.T) {
 	factories := map[string]ModuleFactory{
 		"test": newModuleCtxTestModule,
diff --git a/name_interface.go b/name_interface.go
index 5e7e16e..1849e9d 100644
--- a/name_interface.go
+++ b/name_interface.go
@@ -109,7 +109,7 @@
 		return nil, []error{
 			// seven characters at the start of the second line to align with the string "error: "
 			fmt.Errorf("module %q already defined\n"+
-				"       %s <-- previous definition here", name, group.modules.firstModule().pos),
+				"       %s <-- previous definition here", name, group.modules[0].pos),
 		}
 	}
 
@@ -130,7 +130,7 @@
 			// seven characters at the start of the second line to align with the string "error: "
 			fmt.Errorf("renaming module %q to %q conflicts with existing module\n"+
 				"       %s <-- existing module defined here",
-				oldName, newName, existingGroup.modules.firstModule().pos),
+				oldName, newName, existingGroup.modules[0].pos),
 		}
 	}
 
diff --git a/ninja_defs.go b/ninja_defs.go
index 69233c2..c5d0e4b 100644
--- a/ninja_defs.go
+++ b/ninja_defs.go
@@ -56,16 +56,15 @@
 // definition.
 type RuleParams struct {
 	// These fields correspond to a Ninja variable of the same name.
-	Command        string   // The command that Ninja will run for the rule.
-	Depfile        string   // The dependency file name.
-	Deps           Deps     // The format of the dependency file.
-	Description    string   // The description that Ninja will print for the rule.
-	Generator      bool     // Whether the rule generates the Ninja manifest file.
-	Pool           Pool     // The Ninja pool to which the rule belongs.
-	Restat         bool     // Whether Ninja should re-stat the rule's outputs.
-	Rspfile        string   // The response file.
-	RspfileContent string   // The response file content.
-	SymlinkOutputs []string // The list of Outputs or ImplicitOutputs that are symlinks.
+	Command        string // The command that Ninja will run for the rule.
+	Depfile        string // The dependency file name.
+	Deps           Deps   // The format of the dependency file.
+	Description    string // The description that Ninja will print for the rule.
+	Generator      bool   // Whether the rule generates the Ninja manifest file.
+	Pool           Pool   // The Ninja pool to which the rule belongs.
+	Restat         bool   // Whether Ninja should re-stat the rule's outputs.
+	Rspfile        string // The response file.
+	RspfileContent string // The response file content.
 
 	// These fields are used internally in Blueprint
 	CommandDeps      []string // Command-specific implicit dependencies to prepend to builds
@@ -85,11 +84,9 @@
 	Rule            Rule              // The rule to invoke.
 	Outputs         []string          // The list of explicit output targets.
 	ImplicitOutputs []string          // The list of implicit output targets.
-	SymlinkOutputs  []string          // The list of Outputs or ImplicitOutputs that are symlinks.
 	Inputs          []string          // The list of explicit input dependencies.
 	Implicits       []string          // The list of implicit input dependencies.
 	OrderOnly       []string          // The list of order-only dependencies.
-	Validations     []string          // The list of validations to run when this rule runs.
 	Args            map[string]string // The variable/value pairs to set.
 	Optional        bool              // Skip outputting a default statement
 }
@@ -207,15 +204,6 @@
 		r.Variables["rspfile_content"] = value
 	}
 
-	if len(params.SymlinkOutputs) > 0 {
-		value, err = parseNinjaString(scope, strings.Join(params.SymlinkOutputs, " "))
-		if err != nil {
-			return nil, fmt.Errorf("error parsing SymlinkOutputs param: %s",
-				err)
-		}
-		r.Variables["symlink_outputs"] = value
-	}
-
 	r.CommandDeps, err = parseNinjaStrings(scope, params.CommandDeps)
 	if err != nil {
 		return nil, fmt.Errorf("error parsing CommandDeps param: %s", err)
@@ -269,7 +257,6 @@
 	Inputs          []ninjaString
 	Implicits       []ninjaString
 	OrderOnly       []ninjaString
-	Validations     []ninjaString
 	Args            map[Variable]ninjaString
 	Variables       map[string]ninjaString
 	Optional        bool
@@ -327,11 +314,6 @@
 		return nil, fmt.Errorf("error parsing OrderOnly param: %s", err)
 	}
 
-	b.Validations, err = parseNinjaStrings(scope, params.Validations)
-	if err != nil {
-		return nil, fmt.Errorf("error parsing Validations param: %s", err)
-	}
-
 	b.Optional = params.Optional
 
 	if params.Depfile != "" {
@@ -354,12 +336,6 @@
 		setVariable("description", value)
 	}
 
-	if len(params.SymlinkOutputs) > 0 {
-		setVariable(
-			"symlink_outputs",
-			simpleNinjaString(strings.Join(params.SymlinkOutputs, " ")))
-	}
-
 	argNameScope := rule.scope()
 
 	if len(params.Args) > 0 {
@@ -392,50 +368,49 @@
 	var (
 		comment       = b.Comment
 		rule          = b.Rule.fullName(pkgNames)
-		outputs       = b.Outputs
-		implicitOuts  = b.ImplicitOutputs
-		explicitDeps  = b.Inputs
-		implicitDeps  = b.Implicits
-		orderOnlyDeps = b.OrderOnly
-		validations   = b.Validations
+		outputs       = valueList(b.Outputs, pkgNames, outputEscaper)
+		implicitOuts  = valueList(b.ImplicitOutputs, pkgNames, outputEscaper)
+		explicitDeps  = valueList(b.Inputs, pkgNames, inputEscaper)
+		implicitDeps  = valueList(b.Implicits, pkgNames, inputEscaper)
+		orderOnlyDeps = valueList(b.OrderOnly, pkgNames, inputEscaper)
 	)
 
 	if b.RuleDef != nil {
-		implicitDeps = append(b.RuleDef.CommandDeps, implicitDeps...)
-		orderOnlyDeps = append(b.RuleDef.CommandOrderOnly, orderOnlyDeps...)
+		implicitDeps = append(valueList(b.RuleDef.CommandDeps, pkgNames, inputEscaper), implicitDeps...)
+		orderOnlyDeps = append(valueList(b.RuleDef.CommandOrderOnly, pkgNames, inputEscaper), orderOnlyDeps...)
 	}
 
-	err := nw.Build(comment, rule, outputs, implicitOuts, explicitDeps, implicitDeps, orderOnlyDeps, validations, pkgNames)
+	err := nw.Build(comment, rule, outputs, implicitOuts, explicitDeps, implicitDeps, orderOnlyDeps)
 	if err != nil {
 		return err
 	}
 
+	args := make(map[string]string)
+
+	for argVar, value := range b.Args {
+		args[argVar.fullName(pkgNames)] = value.Value(pkgNames)
+	}
+
 	err = writeVariables(nw, b.Variables, pkgNames)
 	if err != nil {
 		return err
 	}
 
-	type nameValuePair struct {
-		name, value string
+	var keys []string
+	for k := range args {
+		keys = append(keys, k)
 	}
+	sort.Strings(keys)
 
-	args := make([]nameValuePair, 0, len(b.Args))
-
-	for argVar, value := range b.Args {
-		fullName := argVar.fullName(pkgNames)
-		args = append(args, nameValuePair{fullName, value.Value(pkgNames)})
-	}
-	sort.Slice(args, func(i, j int) bool { return args[i].name < args[j].name })
-
-	for _, pair := range args {
-		err = nw.ScopedAssign(pair.name, pair.value)
+	for _, name := range keys {
+		err = nw.ScopedAssign(name, args[name])
 		if err != nil {
 			return err
 		}
 	}
 
 	if !b.Optional {
-		err = nw.Default(pkgNames, outputs...)
+		err = nw.Default(outputs...)
 		if err != nil {
 			return err
 		}
@@ -444,6 +419,16 @@
 	return nw.BlankLine()
 }
 
+func valueList(list []ninjaString, pkgNames map[*packageContext]string,
+	escaper *strings.Replacer) []string {
+
+	result := make([]string, len(list))
+	for i, ninjaStr := range list {
+		result[i] = ninjaStr.ValueWithEscaper(pkgNames, escaper)
+	}
+	return result
+}
+
 func writeVariables(nw *ninjaWriter, variables map[string]ninjaString,
 	pkgNames map[*packageContext]string) error {
 	var keys []string
diff --git a/ninja_strings.go b/ninja_strings.go
index 51a167d..190cae9 100644
--- a/ninja_strings.go
+++ b/ninja_strings.go
@@ -17,7 +17,6 @@
 import (
 	"bytes"
 	"fmt"
-	"io"
 	"strings"
 )
 
@@ -37,7 +36,7 @@
 
 type ninjaString interface {
 	Value(pkgNames map[*packageContext]string) string
-	ValueWithEscaper(w io.StringWriter, pkgNames map[*packageContext]string, escaper *strings.Replacer)
+	ValueWithEscaper(pkgNames map[*packageContext]string, escaper *strings.Replacer) string
 	Eval(variables map[Variable]ninjaString) (string, error)
 	Variables() []Variable
 }
@@ -285,24 +284,26 @@
 }
 
 func (n varNinjaString) Value(pkgNames map[*packageContext]string) string {
-	if len(n.strings) == 1 {
-		return defaultEscaper.Replace(n.strings[0])
-	}
-	str := &strings.Builder{}
-	n.ValueWithEscaper(str, pkgNames, defaultEscaper)
-	return str.String()
+	return n.ValueWithEscaper(pkgNames, defaultEscaper)
 }
 
-func (n varNinjaString) ValueWithEscaper(w io.StringWriter, pkgNames map[*packageContext]string,
-	escaper *strings.Replacer) {
+func (n varNinjaString) ValueWithEscaper(pkgNames map[*packageContext]string,
+	escaper *strings.Replacer) string {
 
-	w.WriteString(escaper.Replace(n.strings[0]))
-	for i, v := range n.variables {
-		w.WriteString("${")
-		w.WriteString(v.fullName(pkgNames))
-		w.WriteString("}")
-		w.WriteString(escaper.Replace(n.strings[i+1]))
+	if len(n.strings) == 1 {
+		return escaper.Replace(n.strings[0])
 	}
+
+	str := strings.Builder{}
+	str.WriteString(escaper.Replace(n.strings[0]))
+	for i, v := range n.variables {
+		str.WriteString("${")
+		str.WriteString(v.fullName(pkgNames))
+		str.WriteString("}")
+		str.WriteString(escaper.Replace(n.strings[i+1]))
+	}
+
+	return str.String()
 }
 
 func (n varNinjaString) Eval(variables map[Variable]ninjaString) (string, error) {
@@ -326,12 +327,12 @@
 }
 
 func (l literalNinjaString) Value(pkgNames map[*packageContext]string) string {
-	return defaultEscaper.Replace(string(l))
+	return l.ValueWithEscaper(pkgNames, defaultEscaper)
 }
 
-func (l literalNinjaString) ValueWithEscaper(w io.StringWriter, pkgNames map[*packageContext]string,
-	escaper *strings.Replacer) {
-	w.WriteString(escaper.Replace(string(l)))
+func (l literalNinjaString) ValueWithEscaper(pkgNames map[*packageContext]string,
+	escaper *strings.Replacer) string {
+	return escaper.Replace(string(l))
 }
 
 func (l literalNinjaString) Eval(variables map[Variable]ninjaString) (string, error) {
diff --git a/ninja_writer.go b/ninja_writer.go
index f9951b4..5366f3f 100644
--- a/ninja_writer.go
+++ b/ninja_writer.go
@@ -15,6 +15,7 @@
 package blueprint
 
 import (
+	"fmt"
 	"io"
 	"strings"
 	"unicode"
@@ -28,18 +29,13 @@
 
 var indentString = strings.Repeat(" ", indentWidth*maxIndentDepth)
 
-type StringWriterWriter interface {
-	io.StringWriter
-	io.Writer
-}
-
 type ninjaWriter struct {
-	writer io.StringWriter
+	writer io.Writer
 
 	justDidBlankLine bool // true if the last operation was a BlankLine
 }
 
-func newNinjaWriter(writer io.StringWriter) *ninjaWriter {
+func newNinjaWriter(writer io.Writer) *ninjaWriter {
 	return &ninjaWriter{
 		writer: writer,
 	}
@@ -76,7 +72,7 @@
 
 		if writeLine {
 			line = strings.TrimSpace("# "+line) + "\n"
-			_, err := n.writer.WriteString(line)
+			_, err := io.WriteString(n.writer, line)
 			if err != nil {
 				return err
 			}
@@ -86,15 +82,7 @@
 
 	if lineStart != len(comment) {
 		line := strings.TrimSpace(comment[lineStart:])
-		_, err := n.writer.WriteString("# ")
-		if err != nil {
-			return err
-		}
-		_, err = n.writer.WriteString(line)
-		if err != nil {
-			return err
-		}
-		_, err = n.writer.WriteString("\n")
+		_, err := fmt.Fprintf(n.writer, "# %s\n", line)
 		if err != nil {
 			return err
 		}
@@ -105,24 +93,25 @@
 
 func (n *ninjaWriter) Pool(name string) error {
 	n.justDidBlankLine = false
-	return n.writeStatement("pool", name)
+	_, err := fmt.Fprintf(n.writer, "pool %s\n", name)
+	return err
 }
 
 func (n *ninjaWriter) Rule(name string) error {
 	n.justDidBlankLine = false
-	return n.writeStatement("rule", name)
+	_, err := fmt.Fprintf(n.writer, "rule %s\n", name)
+	return err
 }
 
 func (n *ninjaWriter) Build(comment string, rule string, outputs, implicitOuts,
-	explicitDeps, implicitDeps, orderOnlyDeps, validations []ninjaString,
-	pkgNames map[*packageContext]string) error {
+	explicitDeps, implicitDeps, orderOnlyDeps []string) error {
 
 	n.justDidBlankLine = false
 
 	const lineWrapLen = len(" $")
 	const maxLineLen = lineWidth - lineWrapLen
 
-	wrapper := &ninjaWriterWithWrap{
+	wrapper := ninjaWriterWithWrap{
 		ninjaWriter: n,
 		maxLineLen:  maxLineLen,
 	}
@@ -137,16 +126,14 @@
 	wrapper.WriteString("build")
 
 	for _, output := range outputs {
-		wrapper.Space()
-		output.ValueWithEscaper(wrapper, pkgNames, outputEscaper)
+		wrapper.WriteStringWithSpace(output)
 	}
 
 	if len(implicitOuts) > 0 {
 		wrapper.WriteStringWithSpace("|")
 
 		for _, out := range implicitOuts {
-			wrapper.Space()
-			out.ValueWithEscaper(wrapper, pkgNames, outputEscaper)
+			wrapper.WriteStringWithSpace(out)
 		}
 	}
 
@@ -155,16 +142,14 @@
 	wrapper.WriteStringWithSpace(rule)
 
 	for _, dep := range explicitDeps {
-		wrapper.Space()
-		dep.ValueWithEscaper(wrapper, pkgNames, inputEscaper)
+		wrapper.WriteStringWithSpace(dep)
 	}
 
 	if len(implicitDeps) > 0 {
 		wrapper.WriteStringWithSpace("|")
 
 		for _, dep := range implicitDeps {
-			wrapper.Space()
-			dep.ValueWithEscaper(wrapper, pkgNames, inputEscaper)
+			wrapper.WriteStringWithSpace(dep)
 		}
 	}
 
@@ -172,17 +157,7 @@
 		wrapper.WriteStringWithSpace("||")
 
 		for _, dep := range orderOnlyDeps {
-			wrapper.Space()
-			dep.ValueWithEscaper(wrapper, pkgNames, inputEscaper)
-		}
-	}
-
-	if len(validations) > 0 {
-		wrapper.WriteStringWithSpace("|@")
-
-		for _, dep := range validations {
-			wrapper.Space()
-			dep.ValueWithEscaper(wrapper, pkgNames, inputEscaper)
+			wrapper.WriteStringWithSpace(dep)
 		}
 	}
 
@@ -191,57 +166,23 @@
 
 func (n *ninjaWriter) Assign(name, value string) error {
 	n.justDidBlankLine = false
-	_, err := n.writer.WriteString(name)
-	if err != nil {
-		return err
-	}
-	_, err = n.writer.WriteString(" = ")
-	if err != nil {
-		return err
-	}
-	_, err = n.writer.WriteString(value)
-	if err != nil {
-		return err
-	}
-	_, err = n.writer.WriteString("\n")
-	if err != nil {
-		return err
-	}
-	return nil
+	_, err := fmt.Fprintf(n.writer, "%s = %s\n", name, value)
+	return err
 }
 
 func (n *ninjaWriter) ScopedAssign(name, value string) error {
 	n.justDidBlankLine = false
-	_, err := n.writer.WriteString(indentString[:indentWidth])
-	if err != nil {
-		return err
-	}
-	_, err = n.writer.WriteString(name)
-	if err != nil {
-		return err
-	}
-	_, err = n.writer.WriteString(" = ")
-	if err != nil {
-		return err
-	}
-	_, err = n.writer.WriteString(value)
-	if err != nil {
-		return err
-	}
-	_, err = n.writer.WriteString("\n")
-	if err != nil {
-		return err
-	}
-	return nil
+	_, err := fmt.Fprintf(n.writer, "%s%s = %s\n", indentString[:indentWidth], name, value)
+	return err
 }
 
-func (n *ninjaWriter) Default(pkgNames map[*packageContext]string, targets ...ninjaString) error {
+func (n *ninjaWriter) Default(targets ...string) error {
 	n.justDidBlankLine = false
 
 	const lineWrapLen = len(" $")
 	const maxLineLen = lineWidth - lineWrapLen
 
-	wrapper := &ninjaWriterWithWrap{
+	wrapper := ninjaWriterWithWrap{
 		ninjaWriter: n,
 		maxLineLen:  maxLineLen,
 	}
@@ -249,8 +190,7 @@
 	wrapper.WriteString("default")
 
 	for _, target := range targets {
-		wrapper.Space()
-		target.ValueWithEscaper(wrapper, pkgNames, outputEscaper)
+		wrapper.WriteString(" " + target)
 	}
 
 	return wrapper.Flush()
@@ -258,157 +198,71 @@
 
 func (n *ninjaWriter) Subninja(file string) error {
 	n.justDidBlankLine = false
-	return n.writeStatement("subninja", file)
+	_, err := fmt.Fprintf(n.writer, "subninja %s\n", file)
+	return err
 }
 
 func (n *ninjaWriter) BlankLine() (err error) {
 	// We don't output multiple blank lines in a row.
 	if !n.justDidBlankLine {
 		n.justDidBlankLine = true
-		_, err = n.writer.WriteString("\n")
+		_, err = io.WriteString(n.writer, "\n")
 	}
 	return err
 }
 
-func (n *ninjaWriter) writeStatement(directive, name string) error {
-	_, err := n.writer.WriteString(directive + " ")
-	if err != nil {
-		return err
-	}
-	_, err = n.writer.WriteString(name)
-	if err != nil {
-		return err
-	}
-	_, err = n.writer.WriteString("\n")
-	if err != nil {
-		return err
-	}
-	return nil
-}
-
-// ninjaWriterWithWrap is an io.StringWriter that writes through to a ninjaWriter, but supports
-// user-readable line wrapping on boundaries when ninjaWriterWithWrap.Space is called.
-// It collects incoming calls to WriteString until either the line length is exceeded, in which case
-// it inserts a wrap before the pending strings and then writes them, or the next call to Space, in
-// which case it writes out the pending strings.
-//
-// WriteString never returns an error, all errors are held until Flush is called.  Once an error has
-// occurred all writes become noops.
 type ninjaWriterWithWrap struct {
 	*ninjaWriter
-	// pending lists the strings that have been written since the last call to Space.
-	pending []string
-
-	// pendingLen accumulates the lengths of the strings in pending.
-	pendingLen int
-
-	// lineLen accumulates the number of bytes on the current line.
-	lineLen int
-
-	// maxLineLen is the length of the line before wrapping.
 	maxLineLen int
-
-	// space is true if the strings in pending should be preceded by a space.
-	space bool
-
-	// err holds any error that has occurred to return in Flush.
-	err error
+	writtenLen int
+	err        error
 }
 
-// WriteString writes the string to buffer, wrapping on a previous Space call if necessary.
-// It never returns an error, all errors are held until Flush is called.
-func (n *ninjaWriterWithWrap) WriteString(s string) (written int, noError error) {
-	// Always return the full length of the string and a nil error.
-	// ninjaWriterWithWrap doesn't return errors to the caller, it saves them until Flush()
-	written = len(s)
-
+func (n *ninjaWriterWithWrap) writeString(s string, space bool) {
 	if n.err != nil {
 		return
 	}
 
-	const spaceLen = 1
-	if !n.space {
-		// No space is pending, so a line wrap can't be inserted before this, so just write
-		// the string.
-		n.lineLen += len(s)
-		_, n.err = n.writer.WriteString(s)
-	} else if n.lineLen+len(s)+spaceLen > n.maxLineLen {
-		// A space is pending, and the pending strings plus the current string would exceed the
-		// maximum line length.  Wrap and indent before the pending space and strings, then write
-		// the pending and current strings.
-		_, n.err = n.writer.WriteString(" $\n")
+	spaceLen := 0
+	if space {
+		spaceLen = 1
+	}
+
+	if n.writtenLen+len(s)+spaceLen > n.maxLineLen {
+		_, n.err = io.WriteString(n.writer, " $\n")
 		if n.err != nil {
 			return
 		}
-		_, n.err = n.writer.WriteString(indentString[:indentWidth*2])
+		_, n.err = io.WriteString(n.writer, indentString[:indentWidth*2])
 		if n.err != nil {
 			return
 		}
-		n.lineLen = indentWidth*2 + n.pendingLen
+		n.writtenLen = indentWidth * 2
 		s = strings.TrimLeftFunc(s, unicode.IsSpace)
-		n.pending = append(n.pending, s)
-		n.writePending()
-
-		n.space = false
-	} else {
-		// A space is pending but the current string would not reach the maximum line length,
-		// add it to the pending list.
-		n.pending = append(n.pending, s)
-		n.pendingLen += len(s)
-		n.lineLen += len(s)
-	}
-
-	return
-}
-
-// Space inserts a space that is also a possible wrapping point into the string.
-func (n *ninjaWriterWithWrap) Space() {
-	if n.err != nil {
-		return
-	}
-	if n.space {
-		// A space was already pending, and the space plus any strings written after the space did
-		// not reach the maxmimum line length, so write out the old space and pending strings.
-		_, n.err = n.writer.WriteString(" ")
-		n.lineLen++
-		n.writePending()
-	}
-	n.space = true
-}
-
-// writePending writes out all the strings stored in pending and resets it.
-func (n *ninjaWriterWithWrap) writePending() {
-	if n.err != nil {
-		return
-	}
-	for _, pending := range n.pending {
-		_, n.err = n.writer.WriteString(pending)
+	} else if space {
+		_, n.err = io.WriteString(n.writer, " ")
 		if n.err != nil {
 			return
 		}
+		n.writtenLen++
 	}
-	// Reset the length of pending back to 0 without reducing its capacity to avoid reallocating
-	// the backing array.
-	n.pending = n.pending[:0]
-	n.pendingLen = 0
+
+	_, n.err = io.WriteString(n.writer, s)
+	n.writtenLen += len(s)
 }
 
-// WriteStringWithSpace is a helper that calls Space and WriteString.
+func (n *ninjaWriterWithWrap) WriteString(s string) {
+	n.writeString(s, false)
+}
+
 func (n *ninjaWriterWithWrap) WriteStringWithSpace(s string) {
-	n.Space()
-	_, _ = n.WriteString(s)
+	n.writeString(s, true)
 }
 
-// Flush writes out any pending space or strings and then a newline.  It also returns any errors
-// that have previously occurred.
 func (n *ninjaWriterWithWrap) Flush() error {
-	if n.space {
-		_, n.err = n.writer.WriteString(" ")
-	}
-	n.writePending()
 	if n.err != nil {
 		return n.err
 	}
-	_, err := n.writer.WriteString("\n")
+	_, err := io.WriteString(n.writer, "\n")
 	return err
 }
diff --git a/ninja_writer_test.go b/ninja_writer_test.go
index 82eeee5..cc880e5 100644
--- a/ninja_writer_test.go
+++ b/ninja_writer_test.go
@@ -16,7 +16,6 @@
 
 import (
 	"bytes"
-	"strings"
 	"testing"
 )
 
@@ -50,26 +49,14 @@
 	},
 	{
 		input: func(w *ninjaWriter) {
-			ck(w.Build("foo comment", "foo", testNinjaStrings("o1", "o2"),
-				testNinjaStrings("io1", "io2"), testNinjaStrings("e1", "e2"),
-				testNinjaStrings("i1", "i2"), testNinjaStrings("oo1", "oo2"),
-				testNinjaStrings("v1", "v2"), nil))
+			ck(w.Build("foo comment", "foo", []string{"o1", "o2"}, []string{"io1", "io2"},
+				[]string{"e1", "e2"}, []string{"i1", "i2"}, []string{"oo1", "oo2"}))
 		},
-		output: "# foo comment\nbuild o1 o2 | io1 io2: foo e1 e2 | i1 i2 || oo1 oo2 |@ v1 v2\n",
+		output: "# foo comment\nbuild o1 o2 | io1 io2: foo e1 e2 | i1 i2 || oo1 oo2\n",
 	},
 	{
 		input: func(w *ninjaWriter) {
-			ck(w.Build("foo comment", "foo",
-				testNinjaStrings(strings.Repeat("o", lineWidth)),
-				nil,
-				testNinjaStrings(strings.Repeat("i", lineWidth)),
-				nil, nil, nil, nil))
-		},
-		output: "# foo comment\nbuild $\n        " + strings.Repeat("o", lineWidth) + ": foo $\n        " + strings.Repeat("i", lineWidth) + "\n",
-	},
-	{
-		input: func(w *ninjaWriter) {
-			ck(w.Default(nil, testNinjaStrings("foo")...))
+			ck(w.Default("foo"))
 		},
 		output: "default foo\n",
 	},
@@ -107,8 +94,7 @@
 			ck(w.ScopedAssign("command", "echo out: $out in: $in _arg: $_arg"))
 			ck(w.ScopedAssign("pool", "p"))
 			ck(w.BlankLine())
-			ck(w.Build("r comment", "r", testNinjaStrings("foo.o"),
-				nil, testNinjaStrings("foo.in"), nil, nil, nil, nil))
+			ck(w.Build("r comment", "r", []string{"foo.o"}, nil, []string{"foo.in"}, nil, nil))
 			ck(w.ScopedAssign("_arg", "arg value"))
 		},
 		output: `pool p
@@ -138,8 +124,3 @@
 		}
 	}
 }
-
-func testNinjaStrings(s ...string) []ninjaString {
-	ret, _ := parseNinjaStrings(nil, s)
-	return ret
-}
diff --git a/package_ctx.go b/package_ctx.go
index af78772..088239e 100644
--- a/package_ctx.go
+++ b/package_ctx.go
@@ -250,10 +250,9 @@
 }
 
 type staticVariable struct {
-	pctx      *packageContext
-	name_     string
-	value_    string
-	fullName_ string
+	pctx   *packageContext
+	name_  string
+	value_ string
 }
 
 // StaticVariable returns a Variable whose value does not depend on any
@@ -272,11 +271,7 @@
 		panic(err)
 	}
 
-	v := &staticVariable{
-		pctx:   p,
-		name_:  name,
-		value_: value,
-	}
+	v := &staticVariable{p, name, value}
 	err = p.scope.AddVariable(v)
 	if err != nil {
 		panic(err)
@@ -294,16 +289,9 @@
 }
 
 func (v *staticVariable) fullName(pkgNames map[*packageContext]string) string {
-	if v.fullName_ != "" {
-		return v.fullName_
-	}
 	return packageNamespacePrefix(pkgNames[v.pctx]) + v.name_
 }
 
-func (v *staticVariable) memoizeFullName(pkgNames map[*packageContext]string) {
-	v.fullName_ = v.fullName(pkgNames)
-}
-
 func (v *staticVariable) value(interface{}) (ninjaString, error) {
 	ninjaStr, err := parseNinjaString(v.pctx.scope, v.value_)
 	if err != nil {
@@ -318,10 +306,9 @@
 }
 
 type variableFunc struct {
-	pctx      *packageContext
-	name_     string
-	value_    func(interface{}) (string, error)
-	fullName_ string
+	pctx   *packageContext
+	name_  string
+	value_ func(interface{}) (string, error)
 }
 
 // VariableFunc returns a Variable whose value is determined by a function that
@@ -345,11 +332,7 @@
 		panic(err)
 	}
 
-	v := &variableFunc{
-		pctx:   p,
-		name_:  name,
-		value_: f,
-	}
+	v := &variableFunc{p, name, f}
 	err = p.scope.AddVariable(v)
 	if err != nil {
 		panic(err)
@@ -388,11 +371,7 @@
 		return resultStr, nil
 	}
 
-	v := &variableFunc{
-		pctx:   p,
-		name_:  name,
-		value_: fun,
-	}
+	v := &variableFunc{p, name, fun}
 	err = p.scope.AddVariable(v)
 	if err != nil {
 		panic(err)
@@ -410,16 +389,9 @@
 }
 
 func (v *variableFunc) fullName(pkgNames map[*packageContext]string) string {
-	if v.fullName_ != "" {
-		return v.fullName_
-	}
 	return packageNamespacePrefix(pkgNames[v.pctx]) + v.name_
 }
 
-func (v *variableFunc) memoizeFullName(pkgNames map[*packageContext]string) {
-	v.fullName_ = v.fullName(pkgNames)
-}
-
 func (v *variableFunc) value(config interface{}) (ninjaString, error) {
 	value, err := v.value_(config)
 	if err != nil {
@@ -480,10 +452,6 @@
 	return v.name_
 }
 
-func (v *argVariable) memoizeFullName(pkgNames map[*packageContext]string) {
-	// Nothing to do, full name is known at initialization.
-}
-
 func (v *argVariable) value(config interface{}) (ninjaString, error) {
 	return nil, errVariableIsArg
 }
@@ -493,10 +461,9 @@
 }
 
 type staticPool struct {
-	pctx      *packageContext
-	name_     string
-	params    PoolParams
-	fullName_ string
+	pctx   *packageContext
+	name_  string
+	params PoolParams
 }
 
 // StaticPool returns a Pool whose value does not depend on any configuration
@@ -516,11 +483,7 @@
 		panic(err)
 	}
 
-	pool := &staticPool{
-		pctx:   p,
-		name_:  name,
-		params: params,
-	}
+	pool := &staticPool{p, name, params}
 	err = p.scope.AddPool(pool)
 	if err != nil {
 		panic(err)
@@ -538,16 +501,9 @@
 }
 
 func (p *staticPool) fullName(pkgNames map[*packageContext]string) string {
-	if p.fullName_ != "" {
-		return p.fullName_
-	}
 	return packageNamespacePrefix(pkgNames[p.pctx]) + p.name_
 }
 
-func (p *staticPool) memoizeFullName(pkgNames map[*packageContext]string) {
-	p.fullName_ = p.fullName(pkgNames)
-}
-
 func (p *staticPool) def(config interface{}) (*poolDef, error) {
 	def, err := parsePoolParams(p.pctx.scope, &p.params)
 	if err != nil {
@@ -564,7 +520,6 @@
 	pctx       *packageContext
 	name_      string
 	paramsFunc func(interface{}) (PoolParams, error)
-	fullName_  string
 }
 
 // PoolFunc returns a Pool whose value is determined by a function that takes a
@@ -587,11 +542,7 @@
 		panic(err)
 	}
 
-	pool := &poolFunc{
-		pctx:       p,
-		name_:      name,
-		paramsFunc: f,
-	}
+	pool := &poolFunc{p, name, f}
 	err = p.scope.AddPool(pool)
 	if err != nil {
 		panic(err)
@@ -609,16 +560,9 @@
 }
 
 func (p *poolFunc) fullName(pkgNames map[*packageContext]string) string {
-	if p.fullName_ != "" {
-		return p.fullName_
-	}
 	return packageNamespacePrefix(pkgNames[p.pctx]) + p.name_
 }
 
-func (p *poolFunc) memoizeFullName(pkgNames map[*packageContext]string) {
-	p.fullName_ = p.fullName(pkgNames)
-}
-
 func (p *poolFunc) def(config interface{}) (*poolDef, error) {
 	params, err := p.paramsFunc(config)
 	if err != nil {
@@ -651,10 +595,6 @@
 	return p.name_
 }
 
-func (p *builtinPool) memoizeFullName(pkgNames map[*packageContext]string) {
-	// Nothing to do, full name is known at initialization.
-}
-
 func (p *builtinPool) def(config interface{}) (*poolDef, error) {
 	return nil, errPoolIsBuiltin
 }
@@ -676,7 +616,6 @@
 	params     RuleParams
 	argNames   map[string]bool
 	scope_     *basicScope
-	fullName_  string
 	sync.Mutex // protects scope_ during lazy creation
 }
 
@@ -744,16 +683,9 @@
 }
 
 func (r *staticRule) fullName(pkgNames map[*packageContext]string) string {
-	if r.fullName_ != "" {
-		return r.fullName_
-	}
 	return packageNamespacePrefix(pkgNames[r.pctx]) + r.name_
 }
 
-func (r *staticRule) memoizeFullName(pkgNames map[*packageContext]string) {
-	r.fullName_ = r.fullName(pkgNames)
-}
-
 func (r *staticRule) def(interface{}) (*ruleDef, error) {
 	def, err := parseRuleParams(r.scope(), &r.params)
 	if err != nil {
@@ -789,7 +721,6 @@
 	paramsFunc func(interface{}) (RuleParams, error)
 	argNames   map[string]bool
 	scope_     *basicScope
-	fullName_  string
 	sync.Mutex // protects scope_ during lazy creation
 }
 
@@ -858,16 +789,9 @@
 }
 
 func (r *ruleFunc) fullName(pkgNames map[*packageContext]string) string {
-	if r.fullName_ != "" {
-		return r.fullName_
-	}
 	return packageNamespacePrefix(pkgNames[r.pctx]) + r.name_
 }
 
-func (r *ruleFunc) memoizeFullName(pkgNames map[*packageContext]string) {
-	r.fullName_ = r.fullName(pkgNames)
-}
-
 func (r *ruleFunc) def(config interface{}) (*ruleDef, error) {
 	params, err := r.paramsFunc(config)
 	if err != nil {
@@ -919,10 +843,6 @@
 	return r.name_
 }
 
-func (r *builtinRule) memoizeFullName(pkgNames map[*packageContext]string) {
-	// Nothing to do, full name is known at initialization.
-}
-
 func (r *builtinRule) def(config interface{}) (*ruleDef, error) {
 	return nil, errRuleIsBuiltin
 }
diff --git a/parser/sort.go b/parser/sort.go
index 0379d45..da594db 100644
--- a/parser/sort.go
+++ b/parser/sort.go
@@ -15,105 +15,10 @@
 package parser
 
 import (
-	"fmt"
 	"sort"
-	"strconv"
-	"strings"
 	"text/scanner"
 )
 
-// numericStringLess compares two strings, returning a lexicographical comparison unless the first
-// difference occurs in a sequence of 1 or more numeric characters, in which case it returns the
-// numerical comparison of the two numbers.
-func numericStringLess(a, b string) bool {
-	isNumeric := func(r rune) bool { return r >= '0' && r <= '9' }
-	isNotNumeric := func(r rune) bool { return !isNumeric(r) }
-
-	minLength := len(a)
-	if len(b) < minLength {
-		minLength = len(b)
-	}
-
-	byteIndex := 0
-	numberStartIndex := -1
-
-	var aByte, bByte byte
-
-	// Start with a byte comparison to find where the strings differ.
-	for ; byteIndex < minLength; byteIndex++ {
-		aByte, bByte = a[byteIndex], b[byteIndex]
-		if aByte != bByte {
-			break
-		}
-		byteIsNumeric := isNumeric(rune(aByte))
-		if numberStartIndex != -1 && !byteIsNumeric {
-			numberStartIndex = -1
-		} else if numberStartIndex == -1 && byteIsNumeric {
-			numberStartIndex = byteIndex
-		}
-	}
-
-	// Handle the case where we reached the end of one or both strings without finding a difference.
-	if byteIndex == minLength {
-		if len(a) < len(b) {
-			// Reached the end of a.  a is a prefix of b.
-			return true
-		} else {
-			// Reached the end of b.  b is a prefix of a or b is equal to a.
-			return false
-		}
-	}
-
-	aByteNumeric := isNumeric(rune(aByte))
-	bByteNumeric := isNumeric(rune(bByte))
-
-	if (aByteNumeric || bByteNumeric) && !(aByteNumeric && bByteNumeric) && numberStartIndex != -1 {
-		// Only one of aByte and bByte is a number, but the previous byte was a number.  That means
-		// one is a longer number with the same prefix, which must be numerically larger.  If bByte
-		// is a number then the number in b is numerically larger than the number in a.
-		return bByteNumeric
-	}
-
-	// If the bytes are both numbers do a numeric comparison.
-	if aByteNumeric && bByteNumeric {
-		// Extract the numbers from each string, starting from the first number after the last
-		// non-number.  This won't be invalid utf8 because we are only looking for the bytes
-		//'0'-'9', which can only occur as single-byte runes in utf8.
-		if numberStartIndex == -1 {
-			numberStartIndex = byteIndex
-		}
-		aNumberString := a[numberStartIndex:]
-		bNumberString := b[numberStartIndex:]
-
-		// Find the first non-number in each, using the full length if there isn't one.
-		endANumbers := strings.IndexFunc(aNumberString, isNotNumeric)
-		endBNumbers := strings.IndexFunc(bNumberString, isNotNumeric)
-		if endANumbers == -1 {
-			endANumbers = len(aNumberString)
-		}
-		if endBNumbers == -1 {
-			endBNumbers = len(bNumberString)
-		}
-
-		// Convert each to an int.
-		aNumber, err := strconv.Atoi(aNumberString[:endANumbers])
-		if err != nil {
-			panic(fmt.Errorf("failed to convert %q from %q to number: %w",
-				aNumberString[:endANumbers], a, err))
-		}
-		bNumber, err := strconv.Atoi(bNumberString[:endBNumbers])
-		if err != nil {
-			panic(fmt.Errorf("failed to convert %q from %q to number: %w",
-				bNumberString[:endBNumbers], b, err))
-		}
-		// Do a numeric comparison.
-		return aNumber < bNumber
-	}
-
-	// At least one is not a number, do a byte comparison.
-	return aByte < bByte
-}
-
 func SortLists(file *File) {
 	for _, def := range file.Defs {
 		if assignment, ok := def.(*Assignment); ok {
@@ -192,7 +97,7 @@
 	if !isListOfPrimitives(values) {
 		return
 	}
-	l := make([]elem, len(values))
+	l := make(elemList, len(values))
 	for i, v := range values {
 		s, ok := v.(*String)
 		if !ok {
@@ -205,9 +110,7 @@
 		l[i] = elem{s.Value, i, v.Pos(), n}
 	}
 
-	sort.SliceStable(l, func(i, j int) bool {
-		return numericStringLess(l[i].s, l[j].s)
-	})
+	sort.Sort(l)
 
 	copyValues := append([]Expression{}, values...)
 	copyComments := make([]*CommentGroup, len(file.Comments))
@@ -247,7 +150,7 @@
 		if !ok {
 			panic("list contains non-string element")
 		}
-		if prev != "" && numericStringLess(s.Value, prev) {
+		if prev > s.Value {
 			return false
 		}
 		prev = s.Value
@@ -263,6 +166,20 @@
 	nextPos scanner.Position
 }
 
+type elemList []elem
+
+func (l elemList) Len() int {
+	return len(l)
+}
+
+func (l elemList) Swap(i, j int) {
+	l[i], l[j] = l[j], l[i]
+}
+
+func (l elemList) Less(i, j int) bool {
+	return l[i].s < l[j].s
+}
+
 type commentsByOffset []*CommentGroup
 
 func (l commentsByOffset) Len() int {
diff --git a/parser/sort_test.go b/parser/sort_test.go
deleted file mode 100644
index 0a9e7fc..0000000
--- a/parser/sort_test.go
+++ /dev/null
@@ -1,96 +0,0 @@
-// Copyright 2021 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 parser
-
-import "testing"
-
-func Test_numericStringLess(t *testing.T) {
-	type args struct {
-		a string
-		b string
-	}
-	tests := []struct {
-		a, b string
-	}{
-		{"a", "b"},
-		{"aa", "ab"},
-		{"aaa", "aba"},
-
-		{"1", "2"},
-		{"1", "11"},
-		{"2", "11"},
-		{"1", "12"},
-
-		{"12", "101"},
-		{"11", "102"},
-
-		{"0", "1"},
-		{"0", "01"},
-		{"1", "02"},
-		{"01", "002"},
-		{"001", "02"},
-	}
-
-	oneTest := func(a, b string, want bool) {
-		t.Helper()
-		if got := numericStringLess(a, b); got != want {
-			t.Errorf("want numericStringLess(%v, %v) = %v, got %v", a, b, want, got)
-		}
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.a+"<"+tt.b, func(t *testing.T) {
-			// a should be less than b
-			oneTest(tt.a, tt.b, true)
-			// b should not be less than a
-			oneTest(tt.b, tt.a, false)
-			// a should not be less than a
-			oneTest(tt.a, tt.a, false)
-			// b should not be less than b
-			oneTest(tt.b, tt.b, false)
-
-			// The same should be true both strings are prefixed with an "a"
-			oneTest("a"+tt.a, "a"+tt.b, true)
-			oneTest("a"+tt.b, "a"+tt.a, false)
-			oneTest("a"+tt.a, "a"+tt.a, false)
-			oneTest("a"+tt.b, "a"+tt.b, false)
-
-			// The same should be true both strings are suffixed with an "a"
-			oneTest(tt.a+"a", tt.b+"a", true)
-			oneTest(tt.b+"a", tt.a+"a", false)
-			oneTest(tt.a+"a", tt.a+"a", false)
-			oneTest(tt.b+"a", tt.b+"a", false)
-
-			// The same should be true both strings are suffixed with a "1"
-			oneTest(tt.a+"1", tt.b+"1", true)
-			oneTest(tt.b+"1", tt.a+"1", false)
-			oneTest(tt.a+"1", tt.a+"1", false)
-			oneTest(tt.b+"1", tt.b+"1", false)
-
-			// The same should be true both strings are prefixed with a "0"
-			oneTest("0"+tt.a, "0"+tt.b, true)
-			oneTest("0"+tt.b, "0"+tt.a, false)
-			oneTest("0"+tt.a, "0"+tt.a, false)
-			oneTest("0"+tt.b, "0"+tt.b, false)
-
-			// The same should be true both strings are suffixed with a "0"
-			oneTest(tt.a+"0", tt.b+"0", true)
-			oneTest(tt.b+"0", tt.a+"0", false)
-			oneTest(tt.a+"0", tt.a+"0", false)
-			oneTest(tt.b+"0", tt.b+"0", false)
-
-		})
-	}
-}
diff --git a/pathtools/fs.go b/pathtools/fs.go
index 806f466..21754d0 100644
--- a/pathtools/fs.go
+++ b/pathtools/fs.go
@@ -95,7 +95,7 @@
 	// Exists returns whether the file exists and whether it is a directory.  Follows symlinks.
 	Exists(name string) (bool, bool, error)
 
-	Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (GlobResult, error)
+	Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (matches, dirs []string, err error)
 	glob(pattern string) (matches []string, err error)
 
 	// IsDir returns true if the path points to a directory, false it it points to a file.  Follows symlinks.
@@ -194,7 +194,7 @@
 	}
 }
 
-func (fs *osFs) Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (GlobResult, error) {
+func (fs *osFs) Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (matches, dirs []string, err error) {
 	return startGlob(fs, pattern, excludes, follow)
 }
 
@@ -346,7 +346,7 @@
 	return false, os.ErrNotExist
 }
 
-func (m *mockFs) Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (GlobResult, error) {
+func (m *mockFs) Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (matches, dirs []string, err error) {
 	return startGlob(m, pattern, excludes, follow)
 }
 
@@ -538,22 +538,10 @@
 			continue
 		}
 		f = filepath.Join(name, f)
-		var info os.FileInfo
-		if follow == DontFollowSymlinks {
-			info, err = fs.Lstat(f)
-			if err != nil {
-				continue
-			}
-			if info.Mode()&os.ModeSymlink != 0 {
-				continue
-			}
-		} else {
-			info, err = fs.Stat(f)
-			if err != nil {
-				continue
-			}
+		if isSymlink, _ := fs.IsSymlink(f); isSymlink && follow == DontFollowSymlinks {
+			continue
 		}
-		if info.IsDir() {
+		if isDir, _ := fs.IsDir(f); isDir {
 			dirs = append(dirs, f)
 			subDirs, err := listDirsRecursiveRelative(fs, f, follow, depth)
 			if err != nil {
diff --git a/pathtools/glob.go b/pathtools/glob.go
index 14cdacf..727b725 100644
--- a/pathtools/glob.go
+++ b/pathtools/glob.go
@@ -15,69 +15,20 @@
 package pathtools
 
 import (
-	"encoding/json"
 	"errors"
 	"fmt"
 	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strings"
-)
 
-// BPGlobArgumentVersion is used to abort argument parsing early when the bpglob argument format
-// has changed but soong_build hasn't had a chance to rerun yet to update build-globs.ninja.
-// Increment it manually when changing the bpglob argument format.  It is located here because
-// pathtools is the only package that is shared between bpglob and bootstrap.
-const BPGlobArgumentVersion = 2
+	"github.com/google/blueprint/deptools"
+)
 
 var GlobMultipleRecursiveErr = errors.New("pattern contains multiple '**'")
 var GlobLastRecursiveErr = errors.New("pattern has '**' as last path element")
 var GlobInvalidRecursiveErr = errors.New("pattern contains other characters between '**' and path separator")
 
-// GlobResult is a container holding the results of a call to Glob.
-type GlobResult struct {
-	// Pattern is the pattern that was passed to Glob.
-	Pattern string
-	// Excludes is the list of excludes that were passed to Glob.
-	Excludes []string
-
-	// Matches is the list of files or directories that matched the pattern but not the excludes.
-	Matches []string
-
-	// Deps is the list of files or directories that must be depended on to regenerate the glob.
-	Deps []string
-}
-
-// FileList returns the list of files matched by a glob for writing to an output file.
-func (result GlobResult) FileList() []byte {
-	return []byte(strings.Join(result.Matches, "\n") + "\n")
-}
-
-// MultipleGlobResults is a list of GlobResult structs.
-type MultipleGlobResults []GlobResult
-
-// FileList returns the list of files matched by a list of multiple globs for writing to an output file.
-func (results MultipleGlobResults) FileList() []byte {
-	multipleMatches := make([][]string, len(results))
-	for i, result := range results {
-		multipleMatches[i] = result.Matches
-	}
-	buf, err := json.Marshal(multipleMatches)
-	if err != nil {
-		panic(fmt.Errorf("failed to marshal glob results to json: %w", err))
-	}
-	return buf
-}
-
-// Deps returns the deps from all of the GlobResults.
-func (results MultipleGlobResults) Deps() []string {
-	var deps []string
-	for _, result := range results {
-		deps = append(deps, result.Deps...)
-	}
-	return deps
-}
-
 // Glob returns the list of files and directories that match the given pattern
 // but do not match the given exclude patterns, along with the list of
 // directories and other dependencies that were searched to construct the file
@@ -89,26 +40,26 @@
 // In general ModuleContext.GlobWithDeps or SingletonContext.GlobWithDeps
 // should be used instead, as they will automatically set up dependencies
 // to rerun the primary builder when the list of matching files changes.
-func Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (GlobResult, error) {
+func Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (matches, deps []string, err error) {
 	return startGlob(OsFs, pattern, excludes, follow)
 }
 
 func startGlob(fs FileSystem, pattern string, excludes []string,
-	follow ShouldFollowSymlinks) (GlobResult, error) {
+	follow ShouldFollowSymlinks) (matches, deps []string, err error) {
 
 	if filepath.Base(pattern) == "**" {
-		return GlobResult{}, GlobLastRecursiveErr
+		return nil, nil, GlobLastRecursiveErr
+	} else {
+		matches, deps, err = glob(fs, pattern, false, follow)
 	}
 
-	matches, deps, err := glob(fs, pattern, false, follow)
-
 	if err != nil {
-		return GlobResult{}, err
+		return nil, nil, err
 	}
 
 	matches, err = filterExcludes(matches, excludes)
 	if err != nil {
-		return GlobResult{}, err
+		return nil, nil, err
 	}
 
 	// If the pattern has wildcards, we added dependencies on the
@@ -125,27 +76,28 @@
 	}
 
 	for i, match := range matches {
-		var info os.FileInfo
-		if follow == DontFollowSymlinks {
-			info, err = fs.Lstat(match)
-		} else {
-			info, err = fs.Stat(match)
-		}
+		isSymlink, err := fs.IsSymlink(match)
 		if err != nil {
-			return GlobResult{}, err
+			return nil, nil, err
 		}
+		if !(isSymlink && follow == DontFollowSymlinks) {
+			isDir, err := fs.IsDir(match)
+			if os.IsNotExist(err) {
+				if isSymlink {
+					return nil, nil, fmt.Errorf("%s: dangling symlink", match)
+				}
+			}
+			if err != nil {
+				return nil, nil, fmt.Errorf("%s: %s", match, err.Error())
+			}
 
-		if info.IsDir() {
-			matches[i] = match + "/"
+			if isDir {
+				matches[i] = match + "/"
+			}
 		}
 	}
 
-	return GlobResult{
-		Pattern:  pattern,
-		Excludes: excludes,
-		Matches:  matches,
-		Deps:     deps,
-	}, nil
+	return matches, deps, nil
 }
 
 // glob is a recursive helper function to handle globbing each level of the pattern individually,
@@ -352,6 +304,30 @@
 	}
 }
 
+func GlobPatternList(patterns []string, prefix string) (globedList []string, depDirs []string, err error) {
+	var (
+		matches []string
+		deps    []string
+	)
+
+	globedList = make([]string, 0)
+	depDirs = make([]string, 0)
+
+	for _, pattern := range patterns {
+		if isWild(pattern) {
+			matches, deps, err = Glob(filepath.Join(prefix, pattern), nil, FollowSymlinks)
+			if err != nil {
+				return nil, nil, err
+			}
+			globedList = append(globedList, matches...)
+			depDirs = append(depDirs, deps...)
+		} else {
+			globedList = append(globedList, filepath.Join(prefix, pattern))
+		}
+	}
+	return globedList, depDirs, nil
+}
+
 // IsGlob returns true if the pattern contains any glob characters (*, ?, or [).
 func IsGlob(pattern string) bool {
 	return strings.IndexAny(pattern, "*?[") >= 0
@@ -368,6 +344,34 @@
 	return false
 }
 
+// GlobWithDepFile finds all files and directories that match glob.  Directories
+// will have a trailing '/'.  It compares the list of matches against the
+// contents of fileListFile, and rewrites fileListFile if it has changed.  It
+// also writes all of the the directories it traversed as dependencies on
+// fileListFile to depFile.
+//
+// The format of glob is either path/*.ext for a single directory glob, or
+// path/**/*.ext for a recursive glob.
+//
+// Returns a list of file paths, and an error.
+//
+// In general ModuleContext.GlobWithDeps or SingletonContext.GlobWithDeps
+// should be used instead, as they will automatically set up dependencies
+// to rerun the primary builder when the list of matching files changes.
+func GlobWithDepFile(glob, fileListFile, depFile string, excludes []string) (files []string, err error) {
+	files, deps, err := Glob(glob, excludes, FollowSymlinks)
+	if err != nil {
+		return nil, err
+	}
+
+	fileList := strings.Join(files, "\n") + "\n"
+
+	WriteFileIfChanged(fileListFile, []byte(fileList), 0666)
+	deptools.WriteDepFile(depFile, fileListFile, deps)
+
+	return
+}
+
 // WriteFileIfChanged wraps ioutil.WriteFile, but only writes the file if
 // the files does not already exist with identical contents.  This can be used
 // along with ninja restat rules to skip rebuilding downstream rules if no
diff --git a/pathtools/glob_test.go b/pathtools/glob_test.go
index d847bad..a3a36ff 100644
--- a/pathtools/glob_test.go
+++ b/pathtools/glob_test.go
@@ -723,7 +723,7 @@
 
 func testGlob(t *testing.T, fs FileSystem, testCase globTestCase, follow ShouldFollowSymlinks) {
 	t.Helper()
-	result, err := fs.Glob(testCase.pattern, testCase.excludes, follow)
+	matches, deps, err := fs.Glob(testCase.pattern, testCase.excludes, follow)
 	if err != testCase.err {
 		if err == nil {
 			t.Fatalf("missing error: %s", testCase.err)
@@ -733,22 +733,22 @@
 		return
 	}
 
-	if !reflect.DeepEqual(result.Matches, testCase.matches) {
+	if !reflect.DeepEqual(matches, testCase.matches) {
 		t.Errorf("incorrect matches list:")
 		t.Errorf(" pattern: %q", testCase.pattern)
 		if testCase.excludes != nil {
 			t.Errorf("excludes: %q", testCase.excludes)
 		}
-		t.Errorf("     got: %#v", result.Matches)
+		t.Errorf("     got: %#v", matches)
 		t.Errorf("expected: %#v", testCase.matches)
 	}
-	if !reflect.DeepEqual(result.Deps, testCase.deps) {
+	if !reflect.DeepEqual(deps, testCase.deps) {
 		t.Errorf("incorrect deps list:")
 		t.Errorf(" pattern: %q", testCase.pattern)
 		if testCase.excludes != nil {
 			t.Errorf("excludes: %q", testCase.excludes)
 		}
-		t.Errorf("     got: %#v", result.Deps)
+		t.Errorf("     got: %#v", deps)
 		t.Errorf("expected: %#v", testCase.deps)
 	}
 }
@@ -904,14 +904,14 @@
 
 			mock := MockFs(mockFiles)
 
-			result, err := mock.Glob(test.pattern, nil, DontFollowSymlinks)
-			t.Log(test.name, test.pattern, result.Matches)
+			matches, _, err := mock.Glob(test.pattern, nil, DontFollowSymlinks)
+			t.Log(test.name, test.pattern, matches)
 			if err != nil {
 				t.Fatal(err)
 			}
 
 			match := false
-			for _, x := range result.Matches {
+			for _, x := range matches {
 				if x == test.name {
 					match = true
 				}
diff --git a/pathtools/lists.go b/pathtools/lists.go
index e1838b3..fbde88a 100644
--- a/pathtools/lists.go
+++ b/pathtools/lists.go
@@ -38,12 +38,10 @@
 	return result
 }
 
-// ReplaceExtension changes the file extension. If the file does not have an
-// extension, the new extension is appended.
 func ReplaceExtension(path string, extension string) string {
-	oldExt := filepath.Ext(path)
-	if oldExt != "" {
-		path = strings.TrimSuffix(path, oldExt)
+	dot := strings.LastIndex(path, ".")
+	if dot == -1 {
+		return path
 	}
-	return path + "." + extension
+	return path[:dot+1] + extension
 }
diff --git a/pathtools/lists_test.go b/pathtools/lists_test.go
deleted file mode 100644
index cce8786..0000000
--- a/pathtools/lists_test.go
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright 2021 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 pathtools
-
-import (
-	"testing"
-)
-
-func TestLists_ReplaceExtension(t *testing.T) {
-
-	testCases := []struct {
-		from, ext, to string
-	}{
-		{"1.jpg", "png", "1.png"},
-		{"1", "png", "1.png"},
-		{"1.", "png", "1.png"},
-		{"2.so", "so.1", "2.so.1"},
-		{"/out/.test/1.png", "jpg", "/out/.test/1.jpg"},
-		{"/out/.test/1", "jpg", "/out/.test/1.jpg"},
-	}
-
-	for _, test := range testCases {
-		t.Run(test.from, func(t *testing.T) {
-			got := ReplaceExtension(test.from, test.ext)
-			if got != test.to {
-				t.Errorf("ReplaceExtension(%v, %v) = %v; want: %v", test.from, test.ext, got, test.to)
-			}
-		})
-	}
-}
diff --git a/proptools/escape.go b/proptools/escape.go
index b8790b5..e7f0456 100644
--- a/proptools/escape.go
+++ b/proptools/escape.go
@@ -56,44 +56,30 @@
 
 }
 
-func shellUnsafeChar(r rune) bool {
-	switch {
-	case 'A' <= r && r <= 'Z',
-		'a' <= r && r <= 'z',
-		'0' <= r && r <= '9',
-		r == '_',
-		r == '+',
-		r == '-',
-		r == '=',
-		r == '.',
-		r == ',',
-		r == '/':
-		return false
-	default:
-		return true
-	}
-}
-
-// ShellEscape takes string that may contain characters that are meaningful to bash and
+// ShellEscapeList takes string that may contain characters that are meaningful to bash and
 // escapes it if necessary by wrapping it in single quotes, and replacing internal single quotes with
 // '\'' (one single quote to end the quoting, a shell-escaped single quote to insert a real single
 // quote, and then a single quote to restarting quoting.
 func ShellEscape(s string) string {
-	shellUnsafeCharNotSpace := func(r rune) bool {
-		return r != ' ' && shellUnsafeChar(r)
+	shellUnsafeChar := func(r rune) bool {
+		switch {
+		case 'A' <= r && r <= 'Z',
+			'a' <= r && r <= 'z',
+			'0' <= r && r <= '9',
+			r == '_',
+			r == '+',
+			r == '-',
+			r == '=',
+			r == '.',
+			r == ',',
+			r == '/',
+			r == ' ':
+			return false
+		default:
+			return true
+		}
 	}
 
-	if strings.IndexFunc(s, shellUnsafeCharNotSpace) == -1 {
-		// No escaping necessary
-		return s
-	}
-
-	return `'` + singleQuoteReplacer.Replace(s) + `'`
-}
-
-// ShellEscapeIncludingSpaces escapes the input `s` in a similar way to ShellEscape except that
-// this treats spaces as meaningful characters.
-func ShellEscapeIncludingSpaces(s string) string {
 	if strings.IndexFunc(s, shellUnsafeChar) == -1 {
 		// No escaping necessary
 		return s
diff --git a/proptools/escape_test.go b/proptools/escape_test.go
index 5823a05..633d711 100644
--- a/proptools/escape_test.go
+++ b/proptools/escape_test.go
@@ -91,24 +91,6 @@
 	},
 }
 
-var shellEscapeIncludingSpacesTestCase = []escapeTestCase{
-	{
-		name: "no escaping",
-		in:   `test`,
-		out:  `test`,
-	},
-	{
-		name: "spacing",
-		in:   `arg1 arg2`,
-		out:  `'arg1 arg2'`,
-	},
-	{
-		name: "single quote",
-		in:   `'arg'`,
-		out:  `''\''arg'\'''`,
-	},
-}
-
 func TestNinjaEscaping(t *testing.T) {
 	for _, testCase := range ninjaEscapeTestCase {
 		got := NinjaEscape(testCase.in)
@@ -127,15 +109,6 @@
 	}
 }
 
-func TestShellEscapeIncludingSpaces(t *testing.T) {
-	for _, testCase := range shellEscapeIncludingSpacesTestCase {
-		got := ShellEscapeIncludingSpaces(testCase.in)
-		if got != testCase.out {
-			t.Errorf("%s: expected `%s` got `%s`", testCase.name, testCase.out, got)
-		}
-	}
-}
-
 func TestExternalShellEscaping(t *testing.T) {
 	if testing.Short() {
 		return
@@ -151,19 +124,3 @@
 		}
 	}
 }
-
-func TestExternalShellEscapeIncludingSpaces(t *testing.T) {
-	if testing.Short() {
-		return
-	}
-	for _, testCase := range shellEscapeIncludingSpacesTestCase {
-		cmd := "echo -n " + ShellEscapeIncludingSpaces(testCase.in)
-		got, err := exec.Command("/bin/sh", "-c", cmd).Output()
-		if err != nil {
-			t.Error(err)
-		}
-		if string(got) != testCase.in {
-			t.Errorf("%s: expected `%s` got `%s`", testCase.name, testCase.in, got)
-		}
-	}
-}
diff --git a/proptools/filter.go b/proptools/filter.go
index 54a20d5..e6b3336 100644
--- a/proptools/filter.go
+++ b/proptools/filter.go
@@ -173,13 +173,7 @@
 		return nil, true
 	}
 
-	// If the predicate selected all fields in the structure then it is generally better to reuse the
-	// original type as it avoids the footprint of creating another type. Also, if the original type
-	// is a named type then it will reduce the size of any structs the caller may create that include
-	// fields of this type. However, the original type should only be reused if it does not exceed
-	// maxNameSize. That is, of course, more likely for an anonymous type than a named one but this
-	// treats them the same.
-	if !filtered && (maxNameSize < 0 || len(prop.String()) < maxNameSize) {
+	if !filtered {
 		if ptr {
 			return []reflect.Type{reflect.PtrTo(prop)}, false
 		}
diff --git a/proptools/filter_test.go b/proptools/filter_test.go
index 1c27cb2..0ea04bb 100644
--- a/proptools/filter_test.go
+++ b/proptools/filter_test.go
@@ -240,12 +240,6 @@
 }
 
 func TestFilterPropertyStructSharded(t *testing.T) {
-	type KeepAllWithAReallyLongNameThatExceedsTheMaxNameSize struct {
-		A *string `keep:"true"`
-		B *string `keep:"true"`
-		C *string `keep:"true"`
-	}
-
 	tests := []struct {
 		name        string
 		maxNameSize int
@@ -272,44 +266,6 @@
 			},
 			filtered: true,
 		},
-		{
-			name:        "anonymous where all match but still needs sharding",
-			maxNameSize: 20,
-			in: &struct {
-				A *string `keep:"true"`
-				B *string `keep:"true"`
-				C *string `keep:"true"`
-			}{},
-			out: []interface{}{
-				&struct {
-					A *string
-				}{},
-				&struct {
-					B *string
-				}{},
-				&struct {
-					C *string
-				}{},
-			},
-			filtered: true,
-		},
-		{
-			name:        "named where all match but still needs sharding",
-			maxNameSize: 20,
-			in:          &KeepAllWithAReallyLongNameThatExceedsTheMaxNameSize{},
-			out: []interface{}{
-				&struct {
-					A *string
-				}{},
-				&struct {
-					B *string
-				}{},
-				&struct {
-					C *string
-				}{},
-			},
-			filtered: true,
-		},
 	}
 
 	for _, test := range tests {
diff --git a/proptools/proptools.go b/proptools/proptools.go
index 1da3ba4..2aa6e32 100644
--- a/proptools/proptools.go
+++ b/proptools/proptools.go
@@ -125,7 +125,3 @@
 func isSlice(t reflect.Type) bool {
 	return t.Kind() == reflect.Slice
 }
-
-func isSliceOfStruct(t reflect.Type) bool {
-	return isSlice(t) && isStruct(t.Elem())
-}
diff --git a/proptools/tag.go b/proptools/tag.go
index b078894..d69853a 100644
--- a/proptools/tag.go
+++ b/proptools/tag.go
@@ -23,17 +23,10 @@
 // HasTag returns true if a StructField has a tag in the form `name:"foo,value"`.
 func HasTag(field reflect.StructField, name, value string) bool {
 	tag := field.Tag.Get(name)
-	for len(tag) > 0 {
-		idx := strings.Index(tag, ",")
-
-		if idx < 0 {
-			return tag == value
-		}
-		if tag[:idx] == value {
+	for _, entry := range strings.Split(tag, ",") {
+		if entry == value {
 			return true
 		}
-
-		tag = tag[idx+1:]
 	}
 
 	return false
@@ -56,8 +49,8 @@
 	for i := 0; i < t.NumField(); i++ {
 		field := t.Field(i)
 		ft := field.Type
-		if isStruct(ft) || isStructPtr(ft) || isSliceOfStruct(ft) {
-			if ft.Kind() == reflect.Ptr || ft.Kind() == reflect.Slice {
+		if isStruct(ft) || isStructPtr(ft) {
+			if ft.Kind() == reflect.Ptr {
 				ft = ft.Elem()
 			}
 			subIndexes := propertyIndexesWithTag(ft, key, value)
diff --git a/proptools/tag_test.go b/proptools/tag_test.go
index d466859..0041c54 100644
--- a/proptools/tag_test.go
+++ b/proptools/tag_test.go
@@ -19,16 +19,16 @@
 	"testing"
 )
 
-type testType struct {
-	NoTag       string
-	EmptyTag    string ``
-	OtherTag    string `foo:"bar"`
-	MatchingTag string `name:"value"`
-	ExtraValues string `name:"foo,value,bar"`
-	ExtraTags   string `foo:"bar" name:"value"`
-}
-
 func TestHasTag(t *testing.T) {
+	type testType struct {
+		NoTag       string
+		EmptyTag    string ``
+		OtherTag    string `foo:"bar"`
+		MatchingTag string `name:"value"`
+		ExtraValues string `name:"foo,value,bar"`
+		ExtraTags   string `foo:"bar" name:"value"`
+	}
+
 	tests := []struct {
 		field string
 		want  bool
@@ -68,39 +68,6 @@
 	}
 }
 
-func BenchmarkHasTag(b *testing.B) {
-	tests := []struct {
-		field string
-	}{
-		{
-			field: "NoTag",
-		},
-		{
-			field: "EmptyTag",
-		},
-		{
-			field: "OtherTag",
-		},
-		{
-			field: "MatchingTag",
-		},
-		{
-			field: "ExtraValues",
-		},
-		{
-			field: "ExtraTags",
-		},
-	}
-	for _, test := range tests {
-		b.Run(test.field, func(b *testing.B) {
-			field, _ := reflect.TypeOf(testType{}).FieldByName(test.field)
-			for i := 0; i < b.N; i++ {
-				HasTag(field, "name", "value")
-			}
-		})
-	}
-}
-
 func TestPropertyIndexesWithTag(t *testing.T) {
 	tests := []struct {
 		name string
@@ -156,31 +123,6 @@
 			want: [][]int{{0, 0}},
 		},
 		{
-			name: "slice of struct",
-			ps: &struct {
-				Other int
-				Foo   []struct {
-					Other int
-					Bar   string `name:"value"`
-				}
-			}{},
-			want: [][]int{{1, 1}},
-		},
-		{
-			name: "slice^2 of struct",
-			ps: &struct {
-				Other int
-				Foo   []struct {
-					Other int
-					Bar   []struct {
-						Other int
-						Baz   string `name:"value"`
-					}
-				}
-			}{},
-			want: [][]int{{1, 1, 1}},
-		},
-		{
 			name: "nil",
 			ps: (*struct {
 				Foo string `name:"value"`
diff --git a/proptools/unpack.go b/proptools/unpack.go
index f6d9e95..4a0858c 100644
--- a/proptools/unpack.go
+++ b/proptools/unpack.go
@@ -280,14 +280,6 @@
 		}
 
 		if isStruct(fieldValue.Type()) {
-			if property.Value.Eval().Type() != parser.MapType {
-				ctx.addError(&UnpackError{
-					fmt.Errorf("can't assign %s value to map property %q",
-						property.Value.Type(), property.Name),
-					property.Value.Pos(),
-				})
-				continue
-			}
 			ctx.unpackToStruct(propertyName, fieldValue)
 			if len(ctx.errs) >= maxUnpackErrors {
 				return
@@ -315,11 +307,8 @@
 	sliceName string, property *parser.Property, sliceType reflect.Type) (reflect.Value, bool) {
 	propValueAsList, ok := property.Value.Eval().(*parser.List)
 	if !ok {
-		ctx.addError(&UnpackError{
-			fmt.Errorf("can't assign %s value to list property %q",
-				property.Value.Type(), property.Name),
-			property.Value.Pos(),
-		})
+		ctx.addError(fmt.Errorf("%s: can't assign %s value to list property %q",
+			property.Value.Pos(), property.Value.Type(), property.Name))
 		return reflect.MakeSlice(sliceType, 0, 0), false
 	}
 	exprs := propValueAsList.Values
@@ -398,33 +387,24 @@
 	case reflect.Bool:
 		b, ok := property.Value.Eval().(*parser.Bool)
 		if !ok {
-			return value, &UnpackError{
-				fmt.Errorf("can't assign %s value to bool property %q",
-					property.Value.Type(), property.Name),
-				property.Value.Pos(),
-			}
+			return value, fmt.Errorf("%s: can't assign %s value to bool property %q",
+				property.Value.Pos(), property.Value.Type(), property.Name)
 		}
 		value = reflect.ValueOf(b.Value)
 
 	case reflect.Int64:
 		b, ok := property.Value.Eval().(*parser.Int64)
 		if !ok {
-			return value, &UnpackError{
-				fmt.Errorf("can't assign %s value to int64 property %q",
-					property.Value.Type(), property.Name),
-				property.Value.Pos(),
-			}
+			return value, fmt.Errorf("%s: can't assign %s value to int64 property %q",
+				property.Value.Pos(), property.Value.Type(), property.Name)
 		}
 		value = reflect.ValueOf(b.Value)
 
 	case reflect.String:
 		s, ok := property.Value.Eval().(*parser.String)
 		if !ok {
-			return value, &UnpackError{
-				fmt.Errorf("can't assign %s value to string property %q",
-					property.Value.Type(), property.Name),
-				property.Value.Pos(),
-			}
+			return value, fmt.Errorf("%s: can't assign %s value to string property %q",
+				property.Value.Pos(), property.Value.Type(), property.Name)
 		}
 		value = reflect.ValueOf(s.Value)
 
diff --git a/proptools/unpack_test.go b/proptools/unpack_test.go
index 7e2751d..6a4d7b4 100644
--- a/proptools/unpack_test.go
+++ b/proptools/unpack_test.go
@@ -24,14 +24,12 @@
 )
 
 var validUnpackTestCases = []struct {
-	name   string
 	input  string
 	output []interface{}
 	empty  []interface{}
 	errs   []error
 }{
 	{
-		name: "blank and unset",
 		input: `
 			m {
 				s: "abc",
@@ -52,7 +50,6 @@
 	},
 
 	{
-		name: "string",
 		input: `
 			m {
 				s: "abc",
@@ -68,7 +65,6 @@
 	},
 
 	{
-		name: "bool",
 		input: `
 			m {
 				isGood: true,
@@ -84,7 +80,6 @@
 	},
 
 	{
-		name: "boolptr",
 		input: `
 			m {
 				isGood: true,
@@ -105,7 +100,6 @@
 	},
 
 	{
-		name: "slice",
 		input: `
 			m {
 				stuff: ["asdf", "jkl;", "qwert",
@@ -129,35 +123,6 @@
 	},
 
 	{
-		name: "double nested",
-		input: `
-			m {
-				nested: {
-					nested: {
-						s: "abc",
-					},
-				},
-			}
-		`,
-		output: []interface{}{
-			&struct {
-				Nested struct {
-					Nested struct {
-						S string
-					}
-				}
-			}{
-				Nested: struct{ Nested struct{ S string } }{
-					Nested: struct{ S string }{
-						S: "abc",
-					},
-				},
-			},
-		},
-	},
-
-	{
-		name: "nested",
 		input: `
 			m {
 				nested: {
@@ -179,7 +144,6 @@
 	},
 
 	{
-		name: "nested interface",
 		input: `
 			m {
 				nested: {
@@ -199,7 +163,6 @@
 	},
 
 	{
-		name: "mixed",
 		input: `
 			m {
 				nested: {
@@ -227,7 +190,6 @@
 	},
 
 	{
-		name: "filter",
 		input: `
 			m {
 				nested: {
@@ -258,7 +220,6 @@
 
 	// List of maps
 	{
-		name: "list of structs",
 		input: `
 			m {
 				mapslist: [
@@ -293,7 +254,6 @@
 
 	// List of pointers to structs
 	{
-		name: "list of pointers to structs",
 		input: `
 			m {
 				mapslist: [
@@ -328,7 +288,6 @@
 
 	// List of lists
 	{
-		name: "list of lists",
 		input: `
 			m {
 				listoflists: [
@@ -351,7 +310,6 @@
 
 	// Multilevel
 	{
-		name: "multilevel",
 		input: `
 			m {
 				name: "mymodule",
@@ -400,7 +358,6 @@
 	},
 	// Anonymous struct
 	{
-		name: "embedded struct",
 		input: `
 			m {
 				s: "abc",
@@ -432,7 +389,6 @@
 
 	// Anonymous interface
 	{
-		name: "embedded interface",
 		input: `
 			m {
 				s: "abc",
@@ -464,7 +420,6 @@
 
 	// Anonymous struct with name collision
 	{
-		name: "embedded name collision",
 		input: `
 			m {
 				s: "abc",
@@ -501,7 +456,6 @@
 
 	// Anonymous interface with name collision
 	{
-		name: "embeded interface name collision",
 		input: `
 			m {
 				s: "abc",
@@ -538,7 +492,6 @@
 
 	// Variables
 	{
-		name: "variables",
 		input: `
 			list = ["abc"]
 			string = "def"
@@ -574,7 +527,6 @@
 
 	// Multiple property structs
 	{
-		name: "multiple",
 		input: `
 			m {
 				nested: {
@@ -608,7 +560,6 @@
 
 	// Nil pointer to struct
 	{
-		name: "nil struct pointer",
 		input: `
 			m {
 				nested: {
@@ -638,7 +589,6 @@
 
 	// Interface containing nil pointer to struct
 	{
-		name: "interface nil struct pointer",
 		input: `
 			m {
 				nested: {
@@ -666,7 +616,6 @@
 
 	// Factory set properties
 	{
-		name: "factory properties",
 		input: `
 			m {
 				string: "abc",
@@ -726,291 +675,61 @@
 
 func TestUnpackProperties(t *testing.T) {
 	for _, testCase := range validUnpackTestCases {
-		t.Run(testCase.name, func(t *testing.T) {
-			r := bytes.NewBufferString(testCase.input)
-			file, errs := parser.ParseAndEval("", r, parser.NewScope(nil))
-			if len(errs) != 0 {
-				t.Errorf("test case: %s", testCase.input)
-				t.Errorf("unexpected parse errors:")
-				for _, err := range errs {
-					t.Errorf("  %s", err)
-				}
-				t.FailNow()
+		r := bytes.NewBufferString(testCase.input)
+		file, errs := parser.ParseAndEval("", r, parser.NewScope(nil))
+		if len(errs) != 0 {
+			t.Errorf("test case: %s", testCase.input)
+			t.Errorf("unexpected parse errors:")
+			for _, err := range errs {
+				t.Errorf("  %s", err)
+			}
+			t.FailNow()
+		}
+
+		for _, def := range file.Defs {
+			module, ok := def.(*parser.Module)
+			if !ok {
+				continue
 			}
 
-			for _, def := range file.Defs {
-				module, ok := def.(*parser.Module)
-				if !ok {
-					continue
-				}
-
-				var output []interface{}
-				if len(testCase.empty) > 0 {
-					for _, p := range testCase.empty {
-						output = append(output, CloneProperties(reflect.ValueOf(p)).Interface())
-					}
-				} else {
-					for _, p := range testCase.output {
-						output = append(output, CloneEmptyProperties(reflect.ValueOf(p)).Interface())
-					}
-				}
-
-				_, errs = UnpackProperties(module.Properties, output...)
-				if len(errs) != 0 && len(testCase.errs) == 0 {
-					t.Errorf("test case: %s", testCase.input)
-					t.Errorf("unexpected unpack errors:")
-					for _, err := range errs {
-						t.Errorf("  %s", err)
-					}
-					t.FailNow()
-				} else if !reflect.DeepEqual(errs, testCase.errs) {
-					t.Errorf("test case: %s", testCase.input)
-					t.Errorf("incorrect errors:")
-					t.Errorf("  expected: %+v", testCase.errs)
-					t.Errorf("       got: %+v", errs)
-				}
-
-				if len(output) != len(testCase.output) {
-					t.Fatalf("incorrect number of property structs, expected %d got %d",
-						len(testCase.output), len(output))
-				}
-
-				for i := range output {
-					got := reflect.ValueOf(output[i]).Interface()
-					if !reflect.DeepEqual(got, testCase.output[i]) {
-						t.Errorf("test case: %s", testCase.input)
-						t.Errorf("incorrect output:")
-						t.Errorf("  expected: %+v", testCase.output[i])
-						t.Errorf("       got: %+v", got)
-					}
-				}
-			}
-		})
-	}
-}
-
-func TestUnpackErrors(t *testing.T) {
-	testCases := []struct {
-		name   string
-		input  string
-		output []interface{}
-		errors []string
-	}{
-		{
-			name: "missing",
-			input: `
-				m {
-					missing: true,
-				}
-			`,
-			output: []interface{}{},
-			errors: []string{`<input>:3:13: unrecognized property "missing"`},
-		},
-		{
-			name: "missing nested",
-			input: `
-				m {
-					nested: {
-						missing: true,
-					},
-				}
-			`,
-			output: []interface{}{
-				&struct {
-					Nested struct{}
-				}{},
-			},
-			errors: []string{`<input>:4:14: unrecognized property "nested.missing"`},
-		},
-		{
-			name: "mutated",
-			input: `
-				m {
-					mutated: true,
-				}
-			`,
-			output: []interface{}{
-				&struct {
-					Mutated bool `blueprint:"mutated"`
-				}{},
-			},
-			errors: []string{`<input>:3:13: mutated field mutated cannot be set in a Blueprint file`},
-		},
-		{
-			name: "nested mutated",
-			input: `
-				m {
-					nested: {
-						mutated: true,
-					},
-				}
-			`,
-			output: []interface{}{
-				&struct {
-					Nested struct {
-						Mutated bool `blueprint:"mutated"`
-					}
-				}{},
-			},
-			errors: []string{`<input>:4:14: mutated field nested.mutated cannot be set in a Blueprint file`},
-		},
-		{
-			name: "duplicate",
-			input: `
-				m {
-					exists: true,
-					exists: true,
-				}
-			`,
-			output: []interface{}{
-				&struct {
-					Exists bool
-				}{},
-			},
-			errors: []string{
-				`<input>:4:12: property "exists" already defined`,
-				`<input>:3:12: <-- previous definition here`,
-			},
-		},
-		{
-			name: "nested duplicate",
-			input: `
-				m {
-					nested: {
-						exists: true,
-						exists: true,
-					},
-				}
-			`,
-			output: []interface{}{
-				&struct {
-					Nested struct {
-						Exists bool
-					}
-				}{},
-			},
-			errors: []string{
-				`<input>:5:13: property "nested.exists" already defined`,
-				`<input>:4:13: <-- previous definition here`,
-			},
-		},
-		{
-			name: "wrong type",
-			input: `
-				m {
-					int: "foo",
-				}
-			`,
-			output: []interface{}{
-				&struct {
-					Int *int64
-				}{},
-			},
-			errors: []string{
-				`<input>:3:11: can't assign string value to int64 property "int"`,
-			},
-		},
-		{
-			name: "wrong type for map",
-			input: `
-				m {
-					map: "foo",
-				}
-			`,
-			output: []interface{}{
-				&struct {
-					Map struct {
-						S string
-					}
-				}{},
-			},
-			errors: []string{
-				`<input>:3:11: can't assign string value to map property "map"`,
-			},
-		},
-		{
-			name: "wrong type for list",
-			input: `
-				m {
-					list: "foo",
-				}
-			`,
-			output: []interface{}{
-				&struct {
-					List []string
-				}{},
-			},
-			errors: []string{
-				`<input>:3:12: can't assign string value to list property "list"`,
-			},
-		},
-		{
-			name: "wrong type for list of maps",
-			input: `
-				m {
-					map_list: "foo",
-				}
-			`,
-			output: []interface{}{
-				&struct {
-					Map_list []struct {
-						S string
-					}
-				}{},
-			},
-			errors: []string{
-				`<input>:3:16: can't assign string value to list property "map_list"`,
-			},
-		},
-	}
-
-	for _, testCase := range testCases {
-		t.Run(testCase.name, func(t *testing.T) {
-			r := bytes.NewBufferString(testCase.input)
-			file, errs := parser.ParseAndEval("", r, parser.NewScope(nil))
-			if len(errs) != 0 {
-				t.Errorf("test case: %s", testCase.input)
-				t.Errorf("unexpected parse errors:")
-				for _, err := range errs {
-					t.Errorf("  %s", err)
-				}
-				t.FailNow()
-			}
-
-			for _, def := range file.Defs {
-				module, ok := def.(*parser.Module)
-				if !ok {
-					continue
-				}
-
-				var output []interface{}
+			var output []interface{}
+			if len(testCase.empty) > 0 {
+				output = testCase.empty
+			} else {
 				for _, p := range testCase.output {
 					output = append(output, CloneEmptyProperties(reflect.ValueOf(p)).Interface())
 				}
-
-				_, errs = UnpackProperties(module.Properties, output...)
-
-				printErrors := false
-				for _, expectedErr := range testCase.errors {
-					foundError := false
-					for _, err := range errs {
-						if err.Error() == expectedErr {
-							foundError = true
-						}
-					}
-					if !foundError {
-						t.Errorf("expected error %s", expectedErr)
-						printErrors = true
-					}
+			}
+			_, errs = UnpackProperties(module.Properties, output...)
+			if len(errs) != 0 && len(testCase.errs) == 0 {
+				t.Errorf("test case: %s", testCase.input)
+				t.Errorf("unexpected unpack errors:")
+				for _, err := range errs {
+					t.Errorf("  %s", err)
 				}
-				if printErrors {
-					t.Errorf("got errors:")
-					for _, err := range errs {
-						t.Errorf("   %s", err.Error())
-					}
+				t.FailNow()
+			} else if !reflect.DeepEqual(errs, testCase.errs) {
+				t.Errorf("test case: %s", testCase.input)
+				t.Errorf("incorrect errors:")
+				t.Errorf("  expected: %+v", testCase.errs)
+				t.Errorf("       got: %+v", errs)
+			}
+
+			if len(output) != len(testCase.output) {
+				t.Fatalf("incorrect number of property structs, expected %d got %d",
+					len(testCase.output), len(output))
+			}
+
+			for i := range output {
+				got := reflect.ValueOf(output[i]).Interface()
+				if !reflect.DeepEqual(got, testCase.output[i]) {
+					t.Errorf("test case: %s", testCase.input)
+					t.Errorf("incorrect output:")
+					t.Errorf("  expected: %+v", testCase.output[i])
+					t.Errorf("       got: %+v", got)
 				}
 			}
-		})
+		}
 	}
 }
 
diff --git a/provider.go b/provider.go
deleted file mode 100644
index b83e1d4..0000000
--- a/provider.go
+++ /dev/null
@@ -1,216 +0,0 @@
-// Copyright 2020 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package blueprint
-
-import (
-	"fmt"
-	"reflect"
-)
-
-// This file implements Providers, modelled after Bazel
-// (https://docs.bazel.build/versions/master/skylark/rules.html#providers).
-// Each provider can be associated with a mutator, in which case the value for the provider for a
-// module can only be set during the mutator call for the module, and the value can only be
-// retrieved after the mutator call for the module. For providers not associated with a mutator, the
-// value can for the provider for a module can only be set during GenerateBuildActions for the
-// module, and the value can only be retrieved after GenerateBuildActions for the module.
-//
-// Providers are globally registered during init() and given a unique ID.  The value of a provider
-// for a module is stored in an []interface{} indexed by the ID.  If the value of a provider has
-// not been set, the value in the []interface{} will be nil.
-//
-// If the storage used by the provider value arrays becomes too large:
-//  sizeof([]interface) * number of providers * number of modules that have a provider value set
-// then the storage can be replaced with something like a bitwise trie.
-//
-// The purpose of providers is to provide a serializable checkpoint between modules to enable
-// Blueprint to skip parts of the analysis phase when inputs haven't changed.  To that end,
-// values passed to providers should be treated as immutable by callers to both the getters and
-// setters.  Go doesn't provide any way to enforce immutability on arbitrary types, so it may be
-// necessary for the getters and setters to make deep copies of the values, likely extending
-// proptools.CloneProperties to do so.
-
-type provider struct {
-	id      int
-	typ     reflect.Type
-	zero    interface{}
-	mutator string
-}
-
-type ProviderKey *provider
-
-var providerRegistry []ProviderKey
-
-// NewProvider returns a ProviderKey for the type of the given example value.  The example value
-// is otherwise unused.
-//
-// The returned ProviderKey can be used to set a value of the ProviderKey's type for a module
-// inside GenerateBuildActions for the module, and to get the value from GenerateBuildActions from
-// any module later in the build graph.
-//
-// Once Go has generics the exampleValue parameter will not be necessary:
-// NewProvider(type T)() ProviderKey(T)
-func NewProvider(exampleValue interface{}) ProviderKey {
-	return NewMutatorProvider(exampleValue, "")
-}
-
-// NewMutatorProvider returns a ProviderKey for the type of the given example value.  The example
-// value is otherwise unused.
-//
-// The returned ProviderKey can be used to set a value of the ProviderKey's type for a module inside
-// the given mutator for the module, and to get the value from GenerateBuildActions from any
-// module later in the build graph in the same mutator, or any module in a later mutator or during
-// GenerateBuildActions.
-//
-// Once Go has generics the exampleValue parameter will not be necessary:
-// NewMutatorProvider(type T)(mutator string) ProviderKey(T)
-func NewMutatorProvider(exampleValue interface{}, mutator string) ProviderKey {
-	checkCalledFromInit()
-
-	typ := reflect.TypeOf(exampleValue)
-	zero := reflect.Zero(typ).Interface()
-
-	provider := &provider{
-		id:      len(providerRegistry),
-		typ:     typ,
-		zero:    zero,
-		mutator: mutator,
-	}
-
-	providerRegistry = append(providerRegistry, provider)
-
-	return provider
-}
-
-// initProviders fills c.providerMutators with the *mutatorInfo associated with each provider ID,
-// if any.
-func (c *Context) initProviders() {
-	c.providerMutators = make([]*mutatorInfo, len(providerRegistry))
-	for _, provider := range providerRegistry {
-		for _, mutator := range c.mutatorInfo {
-			if mutator.name == provider.mutator {
-				c.providerMutators[provider.id] = mutator
-			}
-		}
-	}
-}
-
-// setProvider sets the value for a provider on a moduleInfo.  Verifies that it is called during the
-// appropriate mutator or GenerateBuildActions pass for the provider, and that the value is of the
-// appropriate type.  The value should not be modified after being passed to setProvider.
-//
-// Once Go has generics the value parameter can be typed:
-// setProvider(type T)(m *moduleInfo, provider ProviderKey(T), value T)
-func (c *Context) setProvider(m *moduleInfo, provider ProviderKey, value interface{}) {
-	if provider.mutator == "" {
-		if !m.startedGenerateBuildActions {
-			panic(fmt.Sprintf("Can't set value of provider %s before GenerateBuildActions started",
-				provider.typ))
-		} else if m.finishedGenerateBuildActions {
-			panic(fmt.Sprintf("Can't set value of provider %s after GenerateBuildActions finished",
-				provider.typ))
-		}
-	} else {
-		expectedMutator := c.providerMutators[provider.id]
-		if expectedMutator == nil {
-			panic(fmt.Sprintf("Can't set value of provider %s associated with unregistered mutator %s",
-				provider.typ, provider.mutator))
-		} else if c.mutatorFinishedForModule(expectedMutator, m) {
-			panic(fmt.Sprintf("Can't set value of provider %s after mutator %s finished",
-				provider.typ, provider.mutator))
-		} else if !c.mutatorStartedForModule(expectedMutator, m) {
-			panic(fmt.Sprintf("Can't set value of provider %s before mutator %s started",
-				provider.typ, provider.mutator))
-		}
-	}
-
-	if typ := reflect.TypeOf(value); typ != provider.typ {
-		panic(fmt.Sprintf("Value for provider has incorrect type, wanted %s, got %s",
-			provider.typ, typ))
-	}
-
-	if m.providers == nil {
-		m.providers = make([]interface{}, len(providerRegistry))
-	}
-
-	if m.providers[provider.id] != nil {
-		panic(fmt.Sprintf("Value of provider %s is already set", provider.typ))
-	}
-
-	m.providers[provider.id] = value
-}
-
-// provider returns the value, if any, for a given provider for a module.  Verifies that it is
-// called after the appropriate mutator or GenerateBuildActions pass for the provider on the module.
-// If the value for the provider was not set it returns the zero value of the type of the provider,
-// which means the return value can always be type-asserted to the type of the provider.  The return
-// value should always be considered read-only.
-//
-// Once Go has generics the return value can be typed and the type assert by callers can be dropped:
-// provider(type T)(m *moduleInfo, provider ProviderKey(T)) T
-func (c *Context) provider(m *moduleInfo, provider ProviderKey) (interface{}, bool) {
-	if provider.mutator == "" {
-		if !m.finishedGenerateBuildActions {
-			panic(fmt.Sprintf("Can't get value of provider %s before GenerateBuildActions finished",
-				provider.typ))
-		}
-	} else {
-		expectedMutator := c.providerMutators[provider.id]
-		if expectedMutator != nil && !c.mutatorFinishedForModule(expectedMutator, m) {
-			panic(fmt.Sprintf("Can't get value of provider %s before mutator %s finished",
-				provider.typ, provider.mutator))
-		}
-	}
-
-	if len(m.providers) > provider.id {
-		if p := m.providers[provider.id]; p != nil {
-			return p, true
-		}
-	}
-
-	return provider.zero, false
-}
-
-func (c *Context) mutatorFinishedForModule(mutator *mutatorInfo, m *moduleInfo) bool {
-	if c.finishedMutators[mutator] {
-		// mutator pass finished for all modules
-		return true
-	}
-
-	if c.startedMutator == mutator {
-		// mutator pass started, check if it is finished for this module
-		return m.finishedMutator == mutator
-	}
-
-	// mutator pass hasn't started
-	return false
-}
-
-func (c *Context) mutatorStartedForModule(mutator *mutatorInfo, m *moduleInfo) bool {
-	if c.finishedMutators[mutator] {
-		// mutator pass finished for all modules
-		return true
-	}
-
-	if c.startedMutator == mutator {
-		// mutator pass is currently running
-		if m.startedMutator == mutator {
-			// mutator has started for this module
-			return true
-		}
-	}
-
-	return false
-}
diff --git a/provider_test.go b/provider_test.go
deleted file mode 100644
index 8f8def4..0000000
--- a/provider_test.go
+++ /dev/null
@@ -1,420 +0,0 @@
-// Copyright 2020 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package blueprint
-
-import (
-	"fmt"
-	"reflect"
-	"strings"
-	"testing"
-)
-
-type providerTestModule struct {
-	SimpleName
-	properties struct {
-		Deps []string
-	}
-
-	mutatorProviderValues              []string
-	generateBuildActionsProviderValues []string
-}
-
-func newProviderTestModule() (Module, []interface{}) {
-	m := &providerTestModule{}
-	return m, []interface{}{&m.properties, &m.SimpleName.Properties}
-}
-
-type providerTestMutatorInfo struct {
-	Values []string
-}
-
-type providerTestGenerateBuildActionsInfo struct {
-	Value string
-}
-
-type providerTestUnsetInfo string
-
-var providerTestMutatorInfoProvider = NewMutatorProvider(&providerTestMutatorInfo{}, "provider_mutator")
-var providerTestGenerateBuildActionsInfoProvider = NewProvider(&providerTestGenerateBuildActionsInfo{})
-var providerTestUnsetInfoProvider = NewMutatorProvider((providerTestUnsetInfo)(""), "provider_mutator")
-var providerTestUnusedMutatorProvider = NewMutatorProvider(&struct{ unused string }{}, "nonexistent_mutator")
-
-func (p *providerTestModule) GenerateBuildActions(ctx ModuleContext) {
-	unset := ctx.Provider(providerTestUnsetInfoProvider).(providerTestUnsetInfo)
-	if unset != "" {
-		panic(fmt.Sprintf("expected zero value for providerTestGenerateBuildActionsInfoProvider before it was set, got %q",
-			unset))
-	}
-
-	_ = ctx.Provider(providerTestUnusedMutatorProvider)
-
-	ctx.SetProvider(providerTestGenerateBuildActionsInfoProvider, &providerTestGenerateBuildActionsInfo{
-		Value: ctx.ModuleName(),
-	})
-
-	mp := ctx.Provider(providerTestMutatorInfoProvider).(*providerTestMutatorInfo)
-	if mp != nil {
-		p.mutatorProviderValues = mp.Values
-	}
-
-	ctx.VisitDirectDeps(func(module Module) {
-		gbap := ctx.OtherModuleProvider(module, providerTestGenerateBuildActionsInfoProvider).(*providerTestGenerateBuildActionsInfo)
-		if gbap != nil {
-			p.generateBuildActionsProviderValues = append(p.generateBuildActionsProviderValues, gbap.Value)
-		}
-	})
-}
-
-func providerTestDepsMutator(ctx BottomUpMutatorContext) {
-	if p, ok := ctx.Module().(*providerTestModule); ok {
-		ctx.AddDependency(ctx.Module(), nil, p.properties.Deps...)
-	}
-}
-
-func providerTestMutator(ctx BottomUpMutatorContext) {
-	values := []string{strings.ToLower(ctx.ModuleName())}
-
-	ctx.VisitDirectDeps(func(module Module) {
-		mp := ctx.OtherModuleProvider(module, providerTestMutatorInfoProvider).(*providerTestMutatorInfo)
-		if mp != nil {
-			values = append(values, mp.Values...)
-		}
-	})
-
-	ctx.SetProvider(providerTestMutatorInfoProvider, &providerTestMutatorInfo{
-		Values: values,
-	})
-}
-
-func providerTestAfterMutator(ctx BottomUpMutatorContext) {
-	_ = ctx.Provider(providerTestMutatorInfoProvider)
-}
-
-func TestProviders(t *testing.T) {
-	ctx := NewContext()
-	ctx.RegisterModuleType("provider_module", newProviderTestModule)
-	ctx.RegisterBottomUpMutator("provider_deps_mutator", providerTestDepsMutator)
-	ctx.RegisterBottomUpMutator("provider_mutator", providerTestMutator)
-	ctx.RegisterBottomUpMutator("provider_after_mutator", providerTestAfterMutator)
-
-	ctx.MockFileSystem(map[string][]byte{
-		"Blueprints": []byte(`
-			provider_module {
-				name: "A",
-				deps: ["B"],
-			}
-	
-			provider_module {
-				name: "B",
-				deps: ["C", "D"],
-			}
-	
-			provider_module {
-				name: "C",
-				deps: ["D"],
-			}
-	
-			provider_module {
-				name: "D",
-			}
-		`),
-	})
-
-	_, errs := ctx.ParseBlueprintsFiles("Blueprints", nil)
-	if len(errs) == 0 {
-		_, errs = ctx.ResolveDependencies(nil)
-	}
-	if len(errs) == 0 {
-		_, errs = ctx.PrepareBuildActions(nil)
-	}
-	if len(errs) > 0 {
-		t.Errorf("unexpected errors:")
-		for _, err := range errs {
-			t.Errorf("  %s", err)
-		}
-		t.FailNow()
-	}
-
-	aModule := ctx.moduleGroupFromName("A", nil).moduleByVariantName("").logicModule.(*providerTestModule)
-	if g, w := aModule.generateBuildActionsProviderValues, []string{"B"}; !reflect.DeepEqual(g, w) {
-		t.Errorf("expected A.generateBuildActionsProviderValues %q, got %q", w, g)
-	}
-	if g, w := aModule.mutatorProviderValues, []string{"a", "b", "c", "d", "d"}; !reflect.DeepEqual(g, w) {
-		t.Errorf("expected A.mutatorProviderValues %q, got %q", w, g)
-	}
-
-	bModule := ctx.moduleGroupFromName("B", nil).moduleByVariantName("").logicModule.(*providerTestModule)
-	if g, w := bModule.generateBuildActionsProviderValues, []string{"C", "D"}; !reflect.DeepEqual(g, w) {
-		t.Errorf("expected B.generateBuildActionsProviderValues %q, got %q", w, g)
-	}
-	if g, w := bModule.mutatorProviderValues, []string{"b", "c", "d", "d"}; !reflect.DeepEqual(g, w) {
-		t.Errorf("expected B.mutatorProviderValues %q, got %q", w, g)
-	}
-}
-
-type invalidProviderUsageMutatorInfo string
-type invalidProviderUsageGenerateBuildActionsInfo string
-
-var invalidProviderUsageMutatorInfoProvider = NewMutatorProvider(invalidProviderUsageMutatorInfo(""), "mutator_under_test")
-var invalidProviderUsageGenerateBuildActionsInfoProvider = NewProvider(invalidProviderUsageGenerateBuildActionsInfo(""))
-
-type invalidProviderUsageTestModule struct {
-	parent *invalidProviderUsageTestModule
-
-	SimpleName
-	properties struct {
-		Deps []string
-
-		Early_mutator_set_of_mutator_provider       bool
-		Late_mutator_set_of_mutator_provider        bool
-		Late_build_actions_set_of_mutator_provider  bool
-		Early_mutator_set_of_build_actions_provider bool
-
-		Early_mutator_get_of_mutator_provider       bool
-		Early_module_get_of_mutator_provider        bool
-		Early_mutator_get_of_build_actions_provider bool
-		Early_module_get_of_build_actions_provider  bool
-
-		Duplicate_set bool
-	}
-}
-
-func invalidProviderUsageDepsMutator(ctx BottomUpMutatorContext) {
-	if i, ok := ctx.Module().(*invalidProviderUsageTestModule); ok {
-		ctx.AddDependency(ctx.Module(), nil, i.properties.Deps...)
-	}
-}
-
-func invalidProviderUsageParentMutator(ctx TopDownMutatorContext) {
-	if i, ok := ctx.Module().(*invalidProviderUsageTestModule); ok {
-		ctx.VisitDirectDeps(func(module Module) {
-			module.(*invalidProviderUsageTestModule).parent = i
-		})
-	}
-}
-
-func invalidProviderUsageBeforeMutator(ctx BottomUpMutatorContext) {
-	if i, ok := ctx.Module().(*invalidProviderUsageTestModule); ok {
-		if i.properties.Early_mutator_set_of_mutator_provider {
-			// A mutator attempting to set the value of a provider associated with a later mutator.
-			ctx.SetProvider(invalidProviderUsageMutatorInfoProvider, invalidProviderUsageMutatorInfo(""))
-		}
-		if i.properties.Early_mutator_get_of_mutator_provider {
-			// A mutator attempting to get the value of a provider associated with a later mutator.
-			_ = ctx.Provider(invalidProviderUsageMutatorInfoProvider)
-		}
-	}
-}
-
-func invalidProviderUsageMutatorUnderTest(ctx TopDownMutatorContext) {
-	if i, ok := ctx.Module().(*invalidProviderUsageTestModule); ok {
-		if i.properties.Early_mutator_set_of_build_actions_provider {
-			// A mutator attempting to set the value of a non-mutator provider.
-			ctx.SetProvider(invalidProviderUsageGenerateBuildActionsInfoProvider, invalidProviderUsageGenerateBuildActionsInfo(""))
-		}
-		if i.properties.Early_mutator_get_of_build_actions_provider {
-			// A mutator attempting to get the value of a non-mutator provider.
-			_ = ctx.Provider(invalidProviderUsageGenerateBuildActionsInfoProvider)
-		}
-		if i.properties.Early_module_get_of_mutator_provider {
-			// A mutator attempting to get the value of a provider associated with this mutator on
-			// a module for which this mutator hasn't run.  This is a top down mutator so
-			// dependencies haven't run yet.
-			ctx.VisitDirectDeps(func(module Module) {
-				_ = ctx.OtherModuleProvider(module, invalidProviderUsageMutatorInfoProvider)
-			})
-		}
-	}
-}
-
-func invalidProviderUsageAfterMutator(ctx BottomUpMutatorContext) {
-	if i, ok := ctx.Module().(*invalidProviderUsageTestModule); ok {
-		if i.properties.Late_mutator_set_of_mutator_provider {
-			// A mutator trying to set the value of a provider associated with an earlier mutator.
-			ctx.SetProvider(invalidProviderUsageMutatorInfoProvider, invalidProviderUsageMutatorInfo(""))
-		}
-		if i.properties.Late_mutator_set_of_mutator_provider {
-			// A mutator trying to set the value of a provider associated with an earlier mutator.
-			ctx.SetProvider(invalidProviderUsageMutatorInfoProvider, invalidProviderUsageMutatorInfo(""))
-		}
-	}
-}
-
-func (i *invalidProviderUsageTestModule) GenerateBuildActions(ctx ModuleContext) {
-	if i.properties.Late_build_actions_set_of_mutator_provider {
-		// A GenerateBuildActions trying to set the value of a provider associated with a mutator.
-		ctx.SetProvider(invalidProviderUsageMutatorInfoProvider, invalidProviderUsageMutatorInfo(""))
-	}
-	if i.properties.Early_module_get_of_build_actions_provider {
-		// A GenerateBuildActions trying to get the value of a provider on a module for which
-		// GenerateBuildActions hasn't run.
-		_ = ctx.OtherModuleProvider(i.parent, invalidProviderUsageGenerateBuildActionsInfoProvider)
-	}
-	if i.properties.Duplicate_set {
-		ctx.SetProvider(invalidProviderUsageGenerateBuildActionsInfoProvider, invalidProviderUsageGenerateBuildActionsInfo(""))
-		ctx.SetProvider(invalidProviderUsageGenerateBuildActionsInfoProvider, invalidProviderUsageGenerateBuildActionsInfo(""))
-	}
-}
-
-func TestInvalidProvidersUsage(t *testing.T) {
-	run := func(t *testing.T, module string, prop string, panicMsg string) {
-		t.Helper()
-		ctx := NewContext()
-		ctx.RegisterModuleType("invalid_provider_usage_test_module", func() (Module, []interface{}) {
-			m := &invalidProviderUsageTestModule{}
-			return m, []interface{}{&m.properties, &m.SimpleName.Properties}
-		})
-		ctx.RegisterBottomUpMutator("deps", invalidProviderUsageDepsMutator)
-		ctx.RegisterBottomUpMutator("before", invalidProviderUsageBeforeMutator)
-		ctx.RegisterTopDownMutator("mutator_under_test", invalidProviderUsageMutatorUnderTest)
-		ctx.RegisterBottomUpMutator("after", invalidProviderUsageAfterMutator)
-		ctx.RegisterTopDownMutator("parent", invalidProviderUsageParentMutator)
-
-		// Don't invalidate the parent pointer and before GenerateBuildActions.
-		ctx.skipCloneModulesAfterMutators = true
-
-		var parentBP, moduleUnderTestBP, childBP string
-
-		prop += ": true,"
-
-		switch module {
-		case "parent":
-			parentBP = prop
-		case "module_under_test":
-			moduleUnderTestBP = prop
-		case "child":
-			childBP = prop
-		}
-
-		bp := fmt.Sprintf(`
-			invalid_provider_usage_test_module {
-				name: "parent",
-				deps: ["module_under_test"],
-				%s
-			}
-	
-			invalid_provider_usage_test_module {
-				name: "module_under_test",
-				deps: ["child"],
-				%s
-			}
-	
-			invalid_provider_usage_test_module {
-				name: "child",
-				%s
-			}
-
-		`,
-			parentBP,
-			moduleUnderTestBP,
-			childBP)
-
-		ctx.MockFileSystem(map[string][]byte{
-			"Blueprints": []byte(bp),
-		})
-
-		_, errs := ctx.ParseBlueprintsFiles("Blueprints", nil)
-
-		if len(errs) == 0 {
-			_, errs = ctx.ResolveDependencies(nil)
-		}
-
-		if len(errs) == 0 {
-			_, errs = ctx.PrepareBuildActions(nil)
-		}
-
-		if len(errs) == 0 {
-			t.Fatal("expected an error")
-		}
-
-		if len(errs) > 1 {
-			t.Errorf("expected a single error, got %d:", len(errs))
-			for i, err := range errs {
-				t.Errorf("%d:  %s", i, err)
-			}
-			t.FailNow()
-		}
-
-		if panicErr, ok := errs[0].(panicError); ok {
-			if panicErr.panic != panicMsg {
-				t.Fatalf("expected panic %q, got %q", panicMsg, panicErr.panic)
-			}
-		} else {
-			t.Fatalf("expected a panicError, got %T: %s", errs[0], errs[0].Error())
-		}
-
-	}
-
-	tests := []struct {
-		prop   string
-		module string
-
-		panicMsg string
-		skip     string
-	}{
-		{
-			prop:     "early_mutator_set_of_mutator_provider",
-			module:   "module_under_test",
-			panicMsg: "Can't set value of provider blueprint.invalidProviderUsageMutatorInfo before mutator mutator_under_test started",
-		},
-		{
-			prop:     "late_mutator_set_of_mutator_provider",
-			module:   "module_under_test",
-			panicMsg: "Can't set value of provider blueprint.invalidProviderUsageMutatorInfo after mutator mutator_under_test finished",
-		},
-		{
-			prop:     "late_build_actions_set_of_mutator_provider",
-			module:   "module_under_test",
-			panicMsg: "Can't set value of provider blueprint.invalidProviderUsageMutatorInfo after mutator mutator_under_test finished",
-		},
-		{
-			prop:     "early_mutator_set_of_build_actions_provider",
-			module:   "module_under_test",
-			panicMsg: "Can't set value of provider blueprint.invalidProviderUsageGenerateBuildActionsInfo before GenerateBuildActions started",
-		},
-
-		{
-			prop:     "early_mutator_get_of_mutator_provider",
-			module:   "module_under_test",
-			panicMsg: "Can't get value of provider blueprint.invalidProviderUsageMutatorInfo before mutator mutator_under_test finished",
-		},
-		{
-			prop:     "early_module_get_of_mutator_provider",
-			module:   "module_under_test",
-			panicMsg: "Can't get value of provider blueprint.invalidProviderUsageMutatorInfo before mutator mutator_under_test finished",
-		},
-		{
-			prop:     "early_mutator_get_of_build_actions_provider",
-			module:   "module_under_test",
-			panicMsg: "Can't get value of provider blueprint.invalidProviderUsageGenerateBuildActionsInfo before GenerateBuildActions finished",
-		},
-		{
-			prop:     "early_module_get_of_build_actions_provider",
-			module:   "module_under_test",
-			panicMsg: "Can't get value of provider blueprint.invalidProviderUsageGenerateBuildActionsInfo before GenerateBuildActions finished",
-		},
-		{
-			prop:     "duplicate_set",
-			module:   "module_under_test",
-			panicMsg: "Value of provider blueprint.invalidProviderUsageGenerateBuildActionsInfo is already set",
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.prop, func(t *testing.T) {
-			run(t, tt.module, tt.prop, tt.panicMsg)
-		})
-	}
-}
diff --git a/scope.go b/scope.go
index 3f39eb7..0a520d9 100644
--- a/scope.go
+++ b/scope.go
@@ -28,7 +28,6 @@
 	packageContext() *packageContext
 	name() string                                        // "foo"
 	fullName(pkgNames map[*packageContext]string) string // "pkg.foo" or "path.to.pkg.foo"
-	memoizeFullName(pkgNames map[*packageContext]string) // precompute fullName if desired
 	value(config interface{}) (ninjaString, error)
 	String() string
 }
@@ -39,7 +38,6 @@
 	packageContext() *packageContext
 	name() string                                        // "foo"
 	fullName(pkgNames map[*packageContext]string) string // "pkg.foo" or "path.to.pkg.foo"
-	memoizeFullName(pkgNames map[*packageContext]string) // precompute fullName if desired
 	def(config interface{}) (*poolDef, error)
 	String() string
 }
@@ -50,7 +48,6 @@
 	packageContext() *packageContext
 	name() string                                        // "foo"
 	fullName(pkgNames map[*packageContext]string) string // "pkg.foo" or "path.to.pkg.foo"
-	memoizeFullName(pkgNames map[*packageContext]string) // precompute fullName if desired
 	def(config interface{}) (*ruleDef, error)
 	scope() *basicScope
 	isArg(argName string) bool
@@ -297,9 +294,9 @@
 	}
 
 	v := &localVariable{
-		fullName_: s.namePrefix + name,
-		name_:     name,
-		value_:    ninjaValue,
+		namePrefix: s.namePrefix,
+		name_:      name,
+		value_:     ninjaValue,
 	}
 
 	err = s.scope.AddVariable(v)
@@ -336,11 +333,11 @@
 	}
 
 	r := &localRule{
-		fullName_: s.namePrefix + name,
-		name_:     name,
-		def_:      def,
-		argNames:  argNamesSet,
-		scope_:    ruleScope,
+		namePrefix: s.namePrefix,
+		name_:      name,
+		def_:       def,
+		argNames:   argNamesSet,
+		scope_:     ruleScope,
 	}
 
 	err = s.scope.AddRule(r)
@@ -352,9 +349,9 @@
 }
 
 type localVariable struct {
-	fullName_ string
-	name_     string
-	value_    ninjaString
+	namePrefix string
+	name_      string
+	value_     ninjaString
 }
 
 func (l *localVariable) packageContext() *packageContext {
@@ -366,11 +363,7 @@
 }
 
 func (l *localVariable) fullName(pkgNames map[*packageContext]string) string {
-	return l.fullName_
-}
-
-func (l *localVariable) memoizeFullName(pkgNames map[*packageContext]string) {
-	// Nothing to do, full name is known at initialization.
+	return l.namePrefix + l.name_
 }
 
 func (l *localVariable) value(interface{}) (ninjaString, error) {
@@ -378,15 +371,15 @@
 }
 
 func (l *localVariable) String() string {
-	return "<local var>:" + l.fullName_
+	return "<local var>:" + l.namePrefix + l.name_
 }
 
 type localRule struct {
-	fullName_ string
-	name_     string
-	def_      *ruleDef
-	argNames  map[string]bool
-	scope_    *basicScope
+	namePrefix string
+	name_      string
+	def_       *ruleDef
+	argNames   map[string]bool
+	scope_     *basicScope
 }
 
 func (l *localRule) packageContext() *packageContext {
@@ -398,11 +391,7 @@
 }
 
 func (l *localRule) fullName(pkgNames map[*packageContext]string) string {
-	return l.fullName_
-}
-
-func (l *localRule) memoizeFullName(pkgNames map[*packageContext]string) {
-	// Nothing to do, full name is known at initialization.
+	return l.namePrefix + l.name_
 }
 
 func (l *localRule) def(interface{}) (*ruleDef, error) {
@@ -418,5 +407,5 @@
 }
 
 func (r *localRule) String() string {
-	return "<local rule>:" + r.fullName_
+	return "<local rule>:" + r.namePrefix + r.name_
 }
diff --git a/singleton_ctx.go b/singleton_ctx.go
index a4e7153..3c0a24c 100644
--- a/singleton_ctx.go
+++ b/singleton_ctx.go
@@ -47,16 +47,6 @@
 	// BlueprintFile returns the path of the Blueprint file that defined the given module.
 	BlueprintFile(module Module) string
 
-	// ModuleProvider returns the value, if any, for the provider for a module.  If the value for the
-	// provider was not set it returns the zero value of the type of the provider, which means the
-	// return value can always be type-asserted to the type of the provider.  The return value should
-	// always be considered read-only.  It panics if called before the appropriate mutator or
-	// GenerateBuildActions pass for the provider on the module.
-	ModuleProvider(module Module, provider ProviderKey) interface{}
-
-	// ModuleHasProvider returns true if the provider for the given module has been set.
-	ModuleHasProvider(m Module, provider ProviderKey) bool
-
 	// ModuleErrorf reports an error at the line number of the module type in the module definition.
 	ModuleErrorf(module Module, format string, args ...interface{})
 
@@ -198,15 +188,6 @@
 	return s.context.ModuleType(logicModule)
 }
 
-func (s *singletonContext) ModuleProvider(logicModule Module, provider ProviderKey) interface{} {
-	return s.context.ModuleProvider(logicModule, provider)
-}
-
-// ModuleHasProvider returns true if the provider for the given module has been set.
-func (s *singletonContext) ModuleHasProvider(logicModule Module, provider ProviderKey) bool {
-	return s.context.ModuleHasProvider(logicModule, provider)
-}
-
 func (s *singletonContext) BlueprintFile(logicModule Module) string {
 	return s.context.BlueprintFile(logicModule)
 }
diff --git a/splice_modules_test.go b/splice_modules_test.go
index 473999a..a67aeb1 100644
--- a/splice_modules_test.go
+++ b/splice_modules_test.go
@@ -20,91 +20,91 @@
 )
 
 var (
-	testModuleA = &moduleInfo{variant: variant{name: "testModuleA"}}
-	testModuleB = &moduleInfo{variant: variant{name: "testModuleB"}}
-	testModuleC = &moduleInfo{variant: variant{name: "testModuleC"}}
-	testModuleD = &moduleInfo{variant: variant{name: "testModuleD"}}
-	testModuleE = &moduleInfo{variant: variant{name: "testModuleE"}}
-	testModuleF = &moduleInfo{variant: variant{name: "testModuleF"}}
+	testModuleA = &moduleInfo{variantName: "testModuleA"}
+	testModuleB = &moduleInfo{variantName: "testModuleB"}
+	testModuleC = &moduleInfo{variantName: "testModuleC"}
+	testModuleD = &moduleInfo{variantName: "testModuleD"}
+	testModuleE = &moduleInfo{variantName: "testModuleE"}
+	testModuleF = &moduleInfo{variantName: "testModuleF"}
 )
 
 var spliceModulesTestCases = []struct {
-	in         modulesOrAliases
+	in         []*moduleInfo
 	at         int
-	with       modulesOrAliases
-	out        modulesOrAliases
+	with       []*moduleInfo
+	out        []*moduleInfo
 	outAt      int
 	reallocate bool
 }{
 	{
 		// Insert at the beginning
-		in:         modulesOrAliases{testModuleA, testModuleB, testModuleC},
+		in:         []*moduleInfo{testModuleA, testModuleB, testModuleC},
 		at:         0,
-		with:       modulesOrAliases{testModuleD, testModuleE},
-		out:        modulesOrAliases{testModuleD, testModuleE, testModuleB, testModuleC},
+		with:       []*moduleInfo{testModuleD, testModuleE},
+		out:        []*moduleInfo{testModuleD, testModuleE, testModuleB, testModuleC},
 		outAt:      1,
 		reallocate: true,
 	},
 	{
 		// Insert in the middle
-		in:         modulesOrAliases{testModuleA, testModuleB, testModuleC},
+		in:         []*moduleInfo{testModuleA, testModuleB, testModuleC},
 		at:         1,
-		with:       modulesOrAliases{testModuleD, testModuleE},
-		out:        modulesOrAliases{testModuleA, testModuleD, testModuleE, testModuleC},
+		with:       []*moduleInfo{testModuleD, testModuleE},
+		out:        []*moduleInfo{testModuleA, testModuleD, testModuleE, testModuleC},
 		outAt:      2,
 		reallocate: true,
 	},
 	{
 		// Insert at the end
-		in:         modulesOrAliases{testModuleA, testModuleB, testModuleC},
+		in:         []*moduleInfo{testModuleA, testModuleB, testModuleC},
 		at:         2,
-		with:       modulesOrAliases{testModuleD, testModuleE},
-		out:        modulesOrAliases{testModuleA, testModuleB, testModuleD, testModuleE},
+		with:       []*moduleInfo{testModuleD, testModuleE},
+		out:        []*moduleInfo{testModuleA, testModuleB, testModuleD, testModuleE},
 		outAt:      3,
 		reallocate: true,
 	},
 	{
 		// Insert over a single element
-		in:         modulesOrAliases{testModuleA},
+		in:         []*moduleInfo{testModuleA},
 		at:         0,
-		with:       modulesOrAliases{testModuleD, testModuleE},
-		out:        modulesOrAliases{testModuleD, testModuleE},
+		with:       []*moduleInfo{testModuleD, testModuleE},
+		out:        []*moduleInfo{testModuleD, testModuleE},
 		outAt:      1,
 		reallocate: true,
 	},
 	{
 		// Insert at the beginning without reallocating
-		in:         modulesOrAliases{testModuleA, testModuleB, testModuleC, nil}[0:3],
+		in:         []*moduleInfo{testModuleA, testModuleB, testModuleC, nil}[0:3],
 		at:         0,
-		with:       modulesOrAliases{testModuleD, testModuleE},
-		out:        modulesOrAliases{testModuleD, testModuleE, testModuleB, testModuleC},
+		with:       []*moduleInfo{testModuleD, testModuleE},
+		out:        []*moduleInfo{testModuleD, testModuleE, testModuleB, testModuleC},
 		outAt:      1,
 		reallocate: false,
 	},
 	{
 		// Insert in the middle without reallocating
-		in:         modulesOrAliases{testModuleA, testModuleB, testModuleC, nil}[0:3],
+		in:         []*moduleInfo{testModuleA, testModuleB, testModuleC, nil}[0:3],
 		at:         1,
-		with:       modulesOrAliases{testModuleD, testModuleE},
-		out:        modulesOrAliases{testModuleA, testModuleD, testModuleE, testModuleC},
+		with:       []*moduleInfo{testModuleD, testModuleE},
+		out:        []*moduleInfo{testModuleA, testModuleD, testModuleE, testModuleC},
 		outAt:      2,
 		reallocate: false,
 	},
 	{
 		// Insert at the end without reallocating
-		in:         modulesOrAliases{testModuleA, testModuleB, testModuleC, nil}[0:3],
+		in:         []*moduleInfo{testModuleA, testModuleB, testModuleC, nil}[0:3],
 		at:         2,
-		with:       modulesOrAliases{testModuleD, testModuleE},
-		out:        modulesOrAliases{testModuleA, testModuleB, testModuleD, testModuleE},
+		with:       []*moduleInfo{testModuleD, testModuleE},
+		out:        []*moduleInfo{testModuleA, testModuleB, testModuleD, testModuleE},
 		outAt:      3,
 		reallocate: false,
 	},
 	{
 		// Insert over a single element without reallocating
-		in:         modulesOrAliases{testModuleA, nil}[0:1],
+		in:         []*moduleInfo{testModuleA, nil}[0:1],
 		at:         0,
-		with:       modulesOrAliases{testModuleD, testModuleE},
-		out:        modulesOrAliases{testModuleD, testModuleE},
+		with:       []*moduleInfo{testModuleD, testModuleE},
+		out:        []*moduleInfo{testModuleD, testModuleE},
 		outAt:      1,
 		reallocate: false,
 	},
@@ -112,7 +112,7 @@
 
 func TestSpliceModules(t *testing.T) {
 	for _, testCase := range spliceModulesTestCases {
-		in := make(modulesOrAliases, len(testCase.in), cap(testCase.in))
+		in := make([]*moduleInfo, len(testCase.in), cap(testCase.in))
 		copy(in, testCase.in)
 		origIn := in
 		got, gotAt := spliceModules(in, testCase.at, testCase.with)
@@ -139,6 +139,6 @@
 	}
 }
 
-func sameArray(a, b modulesOrAliases) bool {
+func sameArray(a, b []*moduleInfo) bool {
 	return &a[0:cap(a)][cap(a)-1] == &b[0:cap(b)][cap(b)-1]
 }
diff --git a/visit_test.go b/visit_test.go
index 1c74b93..efaadba 100644
--- a/visit_test.go
+++ b/visit_test.go
@@ -149,13 +149,13 @@
 func TestVisit(t *testing.T) {
 	ctx := setupVisitTest(t)
 
-	topModule := ctx.moduleGroupFromName("A", nil).modules.firstModule().logicModule.(*visitModule)
+	topModule := ctx.moduleGroupFromName("A", nil).modules[0].logicModule.(*visitModule)
 	assertString(t, topModule.properties.VisitDepsDepthFirst, "FEDCB")
 	assertString(t, topModule.properties.VisitDepsDepthFirstIf, "FEDC")
 	assertString(t, topModule.properties.VisitDirectDeps, "B")
 	assertString(t, topModule.properties.VisitDirectDepsIf, "")
 
-	eModule := ctx.moduleGroupFromName("E", nil).modules.firstModule().logicModule.(*visitModule)
+	eModule := ctx.moduleGroupFromName("E", nil).modules[0].logicModule.(*visitModule)
 	assertString(t, eModule.properties.VisitDepsDepthFirst, "F")
 	assertString(t, eModule.properties.VisitDepsDepthFirstIf, "F")
 	assertString(t, eModule.properties.VisitDirectDeps, "FF")