Snap for 6877830 from f94644fe3d680095d6ac38d8333e76d3c64841ee to sdk-release

Change-Id: I98a77c7c6768be20af639d3d4c0e4c192d3ff6bf
diff --git a/Blueprints b/Blueprints
index ecc0792..25c22ab 100644
--- a/Blueprints
+++ b/Blueprints
@@ -17,6 +17,7 @@
         "ninja_strings.go",
         "ninja_writer.go",
         "package_ctx.go",
+        "provider.go",
         "scope.go",
         "singleton_ctx.go",
     ],
@@ -26,6 +27,7 @@
         "module_ctx_test.go",
         "ninja_strings_test.go",
         "ninja_writer_test.go",
+        "provider_test.go",
         "splice_modules_test.go",
         "visit_test.go",
     ],
diff --git a/bootstrap.bash b/bootstrap.bash
index 08b85b5..b08bf1e 100755
--- a/bootstrap.bash
+++ b/bootstrap.bash
@@ -67,12 +67,14 @@
     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:ht" opt; do
+while getopts ":b:hnt" opt; do
     case $opt in
         b) BUILDDIR="$OPTARG";;
+        n) USE_VALIDATIONS=true;;
         t) RUN_TESTS=true;;
         h)
             usage
@@ -93,6 +95,9 @@
 # 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 bb85e7d..64fa8e5 100644
--- a/bootstrap/bootstrap.go
+++ b/bootstrap/bootstrap.go
@@ -180,8 +180,10 @@
 
 func pluginDeps(ctx blueprint.BottomUpMutatorContext) {
 	if pkg, ok := ctx.Module().(*goPackage); ok {
-		for _, plugin := range pkg.properties.PluginFor {
-			ctx.AddReverseDependency(ctx.Module(), nil, plugin)
+		if ctx.PrimaryModule() == ctx.Module() {
+			for _, plugin := range pkg.properties.PluginFor {
+				ctx.AddReverseDependency(ctx.Module(), nil, plugin)
+			}
 		}
 	}
 }
@@ -211,7 +213,7 @@
 	}
 }
 
-func isBootstrapModule(module blueprint.Module) bool {
+func IsBootstrapModule(module blueprint.Module) bool {
 	_, isPackage := module.(*goPackage)
 	_, isBinary := module.(*goBinary)
 	return isPackage || isBinary
@@ -268,6 +270,9 @@
 }
 
 func (g *goPackage) DynamicDependencies(ctx blueprint.DynamicDependerModuleContext) []string {
+	if ctx.Module() != ctx.PrimaryModule() {
+		return nil
+	}
 	return g.properties.Deps
 }
 
@@ -297,6 +302,16 @@
 }
 
 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
@@ -338,7 +353,7 @@
 			filepath.FromSlash(g.properties.PkgPath)+".a")
 		g.testResultFile = buildGoTest(ctx, testRoot(ctx, g.config), testArchiveFile,
 			g.properties.PkgPath, srcs, genSrcs,
-			testSrcs)
+			testSrcs, g.config.useValidations)
 	}
 
 	buildGoPackage(ctx, g.pkgRoot, g.properties.PkgPath, g.archiveFile,
@@ -386,6 +401,9 @@
 }
 
 func (g *goBinary) DynamicDependencies(ctx blueprint.DynamicDependerModuleContext) []string {
+	if ctx.Module() != ctx.PrimaryModule() {
+		return nil
+	}
 	return g.properties.Deps
 }
 
@@ -395,6 +413,14 @@
 }
 
 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)
@@ -421,7 +447,7 @@
 		genSrcs = append(genSrcs, pluginSrc)
 	}
 
-	var deps []string
+	var testDeps []string
 
 	if hasPlugins && !buildGoPluginLoader(ctx, "main", pluginSrc) {
 		return
@@ -437,8 +463,8 @@
 	}
 
 	if g.config.runGoTests {
-		deps = buildGoTest(ctx, testRoot(ctx, g.config), testArchiveFile,
-			name, srcs, genSrcs, testSrcs)
+		testDeps = buildGoTest(ctx, testRoot(ctx, g.config), testArchiveFile,
+			name, srcs, genSrcs, testSrcs, g.config.useValidations)
 	}
 
 	buildGoPackage(ctx, objDir, "main", archiveFile, srcs, genSrcs)
@@ -451,7 +477,7 @@
 			linkDeps = append(linkDeps, dep.GoPackageTarget())
 			libDir := dep.GoPkgRoot()
 			libDirFlags = append(libDirFlags, "-L "+libDir)
-			deps = append(deps, dep.GoTestTargets()...)
+			testDeps = append(testDeps, dep.GoTestTargets()...)
 		})
 
 	linkArgs := map[string]string{}
@@ -468,12 +494,20 @@
 		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: deps,
-		Optional:  !g.properties.Default,
+		Rule:        cp,
+		Outputs:     []string{g.installPath},
+		Inputs:      []string{aoutFile},
+		OrderOnly:   orderOnlyDeps,
+		Validations: validationDeps,
+		Optional:    !g.properties.Default,
 	})
 }
 
@@ -538,7 +572,7 @@
 }
 
 func buildGoTest(ctx blueprint.ModuleContext, testRoot, testPkgArchive,
-	pkgPath string, srcs, genSrcs, testSrcs []string) []string {
+	pkgPath string, srcs, genSrcs, testSrcs []string, useValidations bool) []string {
 
 	if len(testSrcs) == 0 {
 		return nil
@@ -600,11 +634,19 @@
 		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: testDeps,
+		Rule:        test,
+		Outputs:     []string{testPassed},
+		Inputs:      []string{testFile},
+		OrderOnly:   orderOnlyDeps,
+		Validations: validationDeps,
 		Args: map[string]string{
 			"pkg":       pkgPath,
 			"pkgSrcDir": filepath.Dir(testFiles[0]),
@@ -637,13 +679,15 @@
 	var blueprintTools []string
 	ctx.VisitAllModulesIf(isBootstrapBinaryModule,
 		func(module blueprint.Module) {
-			binaryModule := module.(*goBinary)
+			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)
+				if binaryModule.properties.Tool_dir {
+					blueprintTools = append(blueprintTools, binaryModule.InstallPath())
+				}
+				if binaryModule.properties.PrimaryBuilder {
+					primaryBuilders = append(primaryBuilders, binaryModule)
+				}
 			}
 		})
 
diff --git a/bootstrap/bpdoc/bpdoc_test.go b/bootstrap/bpdoc/bpdoc_test.go
index 687d97b..3856933 100644
--- a/bootstrap/bpdoc/bpdoc_test.go
+++ b/bootstrap/bpdoc/bpdoc_test.go
@@ -1,6 +1,7 @@
 package bpdoc
 
 import (
+	"fmt"
 	"reflect"
 	"testing"
 )
@@ -44,3 +45,37 @@
 		}
 	}
 }
+
+func TestAllPackages(t *testing.T) {
+	packages, err := AllPackages(pkgFiles, moduleTypeNameFactories, moduleTypeNamePropertyStructs)
+	if err != nil {
+		t.Errorf("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]
+
+	for _, m := range pkg.ModuleTypes {
+		for _, p := range m.PropertyStructs {
+			for _, err := range noMutatedProperties(p.Properties) {
+				t.Errorf("%s", err)
+			}
+		}
+	}
+}
+
+func noMutatedProperties(properties []Property) []error {
+	errs := []error{}
+	for _, p := range properties {
+		if hasTag(p.Tag, "blueprint", "mutated") {
+			err := fmt.Errorf("Property %s has `blueprint:\"mutated\" tag but should have been excluded.", p)
+			errs = append(errs, err)
+		}
+
+		errs = append(errs, noMutatedProperties(p.Properties)...)
+	}
+	return errs
+}
diff --git a/bootstrap/bpdoc/properties.go b/bootstrap/bpdoc/properties.go
index 9256d8e..dcbb80e 100644
--- a/bootstrap/bpdoc/properties.go
+++ b/bootstrap/bpdoc/properties.go
@@ -251,6 +251,7 @@
 	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 4045cb1..085bcdf 100644
--- a/bootstrap/bpdoc/properties_test.go
+++ b/bootstrap/bpdoc/properties_test.go
@@ -28,11 +28,8 @@
 
 	ps.ExcludeByTag("tag1", "a")
 
-	expected := []string{"c"}
-	actual := []string{}
-	for _, p := range ps.Properties {
-		actual = append(actual, p.Name)
-	}
+	expected := []string{"c", "d", "g"}
+	actual := actualProperties(t, ps.Properties)
 	if !reflect.DeepEqual(expected, actual) {
 		t.Errorf("unexpected ExcludeByTag result, expected: %q, actual: %q", expected, actual)
 	}
@@ -47,12 +44,20 @@
 
 	ps.IncludeByTag("tag1", "c")
 
-	expected := []string{"b", "c"}
-	actual := []string{}
-	for _, p := range ps.Properties {
-		actual = append(actual, p.Name)
-	}
+	expected := []string{"b", "c", "d", "f", "g"}
+	actual := actualProperties(t, ps.Properties)
 	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 0d608b3..8493e14 100644
--- a/bootstrap/bpdoc/reader_test.go
+++ b/bootstrap/bpdoc/reader_test.go
@@ -16,6 +16,7 @@
 package bpdoc
 
 import (
+	"html/template"
 	"reflect"
 	"runtime"
 	"testing"
@@ -23,11 +24,29 @@
 	"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{}}
+}
+
+// for bpdoc_test.go
+type complexProps struct {
+	A         string
+	B_mutated string `blueprint:"mutated"`
+
+	Nested struct {
+		C         string
+		D_mutated string `blueprint:"mutated"`
+	}
+}
+
 // props docs.
 type props struct {
 	// A docs.
@@ -39,10 +58,18 @@
 	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)
@@ -57,21 +84,34 @@
 	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)
-	mt, err := r.ModuleType("foo_module", reflect.ValueOf(fooFactory))
-	if err != nil {
-		t.Fatal(err)
-	}
+	for m := range moduleTypeNameFactories {
+		mt, err := r.ModuleType(m+"_module", moduleTypeNameFactories[m])
+		if err != nil {
+			t.Fatal(err)
+		}
 
-	if mt.Text != "foo docs.\n\n" {
-		t.Errorf("unexpected docs %q", mt.Text)
-	}
+		expectedText := template.HTML(m + " docs.\n\n")
+		if mt.Text != expectedText {
+			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/command.go b/bootstrap/command.go
index 1f73ce3..dc655e8 100644
--- a/bootstrap/command.go
+++ b/bootstrap/command.go
@@ -40,6 +40,7 @@
 	memprofile     string
 	traceFile      string
 	runGoTests     bool
+	useValidations bool
 	noGC           bool
 	emptyNinjaFile bool
 	BuildDir       string
@@ -61,6 +62,7 @@
 	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.BoolVar(&useValidations, "use-validations", false, "use validations to depend on go tests")
 	flag.StringVar(&ModuleListFile, "l", "", "file that lists filepaths to parse")
 	flag.BoolVar(&emptyNinjaFile, "empty-ninja-file", false, "write out a 0-byte ninja file")
 }
@@ -131,6 +133,7 @@
 		topLevelBlueprintsFile: flag.Arg(0),
 		emptyNinjaFile:         emptyNinjaFile,
 		runGoTests:             runGoTests,
+		useValidations:         useValidations,
 		moduleListFile:         ModuleListFile,
 	}
 
diff --git a/bootstrap/config.go b/bootstrap/config.go
index 9499aeb..ace0ae6 100644
--- a/bootstrap/config.go
+++ b/bootstrap/config.go
@@ -107,5 +107,6 @@
 
 	emptyNinjaFile bool
 	runGoTests     bool
+	useValidations bool
 	moduleListFile string
 }
diff --git a/bpmodify/bpmodify.go b/bpmodify/bpmodify.go
index 29e97d1..0190f19 100644
--- a/bpmodify/bpmodify.go
+++ b/bpmodify/bpmodify.go
@@ -22,18 +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")
-	parameter       = flag.String("parameter", "deps", "name of parameter to modify on each module")
-	targetedModules = new(identSet)
-	addIdents       = new(identSet)
-	removeIdents    = new(identSet)
+	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)
 )
 
 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.Usage = usage
@@ -140,24 +142,74 @@
 
 func processModule(module *parser.Module, moduleName string,
 	file *parser.File) (modified bool, errs []error) {
-
-	for _, prop := range module.Properties {
-		if prop.Name == *parameter {
-			modified, errs = processParameter(prop.Value, *parameter, moduleName, file)
-			return
+	prop, err := getRecursiveProperty(module, targetedProperty.name(), targetedProperty.prefixes())
+	if err != nil {
+		return false, []error{err}
+	}
+	if prop == nil {
+		if len(addIdents.idents) == 0 {
+			// 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
+		}
+		// Else we are adding something to a non-existing prop, so we need to create it first.
+		prop, modified, err = createRecursiveProperty(module, targetedProperty.name(), targetedProperty.prefixes())
+		if err != nil {
+			// Here should be unreachable, but still handle it for completeness.
+			return false, []error{err}
 		}
 	}
-
-	prop := parser.Property{Name: *parameter, Value: &parser.List{}}
-	modified, errs = processParameter(prop.Value, *parameter, moduleName, file)
-
-	if modified {
-		module.Properties = append(module.Properties, &prop)
-	}
-
+	m, errs := processParameter(prop.Value, targetedProperty.String(), moduleName, file)
+	modified = modified || m
 	return modified, errs
 }
 
+func getRecursiveProperty(module *parser.Module, name string, prefixes []string) (prop *parser.Property, err error) {
+	prop, _, err = getOrCreateRecursiveProperty(module, name, prefixes, false)
+	return prop, err
+}
+
+func createRecursiveProperty(module *parser.Module, name string, prefixes []string) (prop *parser.Property, modified bool, err error) {
+	return getOrCreateRecursiveProperty(module, name, prefixes, true)
+}
+
+func getOrCreateRecursiveProperty(module *parser.Module, name string, prefixes []string,
+	createIfNotFound bool) (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 createIfNotFound {
+			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 createIfNotFound {
+		prop = &parser.Property{Name: name, Value: &parser.List{}}
+		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 {
@@ -232,6 +284,10 @@
 
 	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"))
@@ -318,3 +374,38 @@
 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
new file mode 100644
index 0000000..a498a14
--- /dev/null
+++ b/bpmodify/bpmodify_test.go
@@ -0,0 +1,259 @@
+// 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"
+)
+
+var testCases = []struct {
+	input     string
+	output    string
+	property  string
+	addSet    string
+	removeSet string
+}{
+	{
+		`
+		cc_foo {
+			name: "foo",
+		}
+		`,
+		`
+		cc_foo {
+			name: "foo",
+			deps: ["bar"],
+		}
+		`,
+		"deps",
+		"bar",
+		"",
+	},
+	{
+		`
+		cc_foo {
+			name: "foo",
+			deps: ["bar"],
+		}
+		`,
+		`
+		cc_foo {
+			name: "foo",
+			deps: [],
+		}
+		`,
+		"deps",
+		"",
+		"bar",
+	},
+	{
+		`
+		cc_foo {
+			name: "foo",
+		}
+		`,
+		`
+		cc_foo {
+			name: "foo",
+			arch: {
+				arm: {
+					deps: [
+						"dep2",
+						"nested_dep",],
+				},
+			},
+		}
+		`,
+		"arch.arm.deps",
+		"nested_dep,dep2",
+		"",
+	},
+	{
+		`
+		cc_foo {
+			name: "foo",
+			arch: {
+				arm: {
+					deps: [
+						"dep2",
+						"nested_dep",
+					],
+				},
+			},
+		}
+		`,
+		`
+		cc_foo {
+			name: "foo",
+			arch: {
+				arm: {
+					deps: [
+					],
+				},
+			},
+		}
+		`,
+		"arch.arm.deps",
+		"",
+		"nested_dep,dep2",
+	},
+	{
+		`
+		cc_foo {
+			name: "foo",
+			arch: {
+				arm: {
+					deps: [
+						"nested_dep",
+						"dep2",
+					],
+				},
+			},
+		}
+		`,
+		`
+		cc_foo {
+			name: "foo",
+			arch: {
+				arm: {
+					deps: [
+						"nested_dep",
+						"dep2",
+					],
+				},
+			},
+		}
+		`,
+		"arch.arm.deps",
+		"dep2,dep2",
+		"",
+	},
+	{
+		`
+		cc_foo {
+			name: "foo",
+			arch: {
+				arm: {
+					deps: [
+						"nested_dep",
+						"dep2",
+					],
+				},
+			},
+		}
+		`,
+		`
+		cc_foo {
+			name: "foo",
+			arch: {
+				arm: {
+					deps: [
+						"nested_dep",
+						"dep2",
+					],
+				},
+			},
+		}
+		`,
+		"arch.arm.deps",
+		"",
+		"dep3,dep4",
+	},
+	{
+		`
+		cc_foo {
+			name: "foo",
+		}
+		`,
+		`
+		cc_foo {
+			name: "foo",
+		}
+		`,
+		"deps",
+		"",
+		"bar",
+	},
+	{
+		`
+		cc_foo {
+			name: "foo",
+			arch: {},
+		}
+		`,
+		`
+		cc_foo {
+			name: "foo",
+			arch: {},
+		}
+		`,
+		"arch.arm.deps",
+		"",
+		"dep3,dep4",
+	},
+}
+
+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 {
+		targetedProperty.Set(testCase.property)
+		addIdents.Set(testCase.addSet)
+		removeIdents.Set(testCase.removeSet)
+
+		inAst, errs := parser.ParseAndEval("", strings.NewReader(testCase.input), parser.NewScope(nil))
+		if len(errs) > 0 {
+			t.Errorf("test case %d:", i)
+			for _, err := range errs {
+				t.Errorf("  %s", err)
+			}
+			t.Errorf("failed to parse:")
+			t.Errorf("%+v", testCase)
+			continue
+		}
+
+		if inModule, ok := inAst.Defs[0].(*parser.Module); !ok {
+			t.Errorf("test case %d:", i)
+			t.Errorf("  input must only contain a single module definition: %s", testCase.input)
+			continue
+		} 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 adcda9b..f12856f 100644
--- a/context.go
+++ b/context.go
@@ -117,6 +117,19 @@
 	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
@@ -159,22 +172,69 @@
 }
 
 type moduleAlias struct {
-	variantName       string
-	variant           variationMap
-	dependencyVariant variationMap
-	target            *moduleInfo
+	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!"))
 }
 
 type moduleGroup struct {
 	name      string
 	ninjaName string
 
-	modules []*moduleInfo
-	aliases []*moduleAlias
+	modules modulesOrAliases
 
 	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
@@ -184,9 +244,7 @@
 	propertyPos       map[string]scanner.Position
 	createdBy         *moduleInfo
 
-	variantName       string
-	variant           variationMap
-	dependencyVariant variationMap
+	variant variant
 
 	logicModule Module
 	group       *moduleGroup
@@ -201,15 +259,28 @@
 	forwardDeps []*moduleInfo
 	directDeps  []depInfo
 
-	// used by parallelVisitAllBottomUp
+	// used by parallelVisit
 	waitingCount int
 
 	// set during each runMutator
-	splitModules []*moduleInfo
-	aliasTarget  *moduleInfo
+	splitModules modulesOrAliases
 
 	// 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 {
@@ -232,8 +303,8 @@
 
 func (module *moduleInfo) String() string {
 	s := fmt.Sprintf("module %q", module.Name())
-	if module.variantName != "" {
-		s += fmt.Sprintf(" variant %q", module.variantName)
+	if module.variant.name != "" {
+		s += fmt.Sprintf(" variant %q", module.variant.name)
 	}
 	if module.createdBy != nil {
 		s += fmt.Sprintf(" (created by %s)", module.createdBy)
@@ -273,10 +344,10 @@
 }
 
 // Compare this variationMap to another one.  Returns true if the every entry in this map
-// is either the same in the other map or doesn't exist in the other map.
-func (vm variationMap) subset(other variationMap) bool {
+// exists and has the same value in the other map.
+func (vm variationMap) subsetOf(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
 		}
 	}
@@ -313,6 +384,7 @@
 		moduleInfo:         make(map[Module]*moduleInfo),
 		globs:              make(map[string]GlobPath),
 		fs:                 pathtools.OsFs,
+		finishedMutators:   make(map[*mutatorInfo]bool),
 		ninjaBuildDir:      nil,
 		requiredNinjaMajor: 1,
 		requiredNinjaMinor: 7,
@@ -1224,15 +1296,44 @@
 	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) ([]*moduleInfo, []error) {
+	defaultVariationName *string, variationNames []string, local bool) (modulesOrAliases, []error) {
 
 	if len(variationNames) == 0 {
 		panic(fmt.Errorf("mutator %q passed zero-length variation list for module %q",
 			mutatorName, origModule.Name()))
 	}
 
-	newModules := []*moduleInfo{}
+	var newModules modulesOrAliases
 
 	var errs []error
 
@@ -1249,27 +1350,13 @@
 			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{}, origModule.directDeps...)
+		newModule.directDeps = append([]depInfo(nil), origModule.directDeps...)
 		newModule.logicModule = newLogicModule
-		newModule.variant = newVariant
-		newModule.dependencyVariant = origModule.dependencyVariant.clone()
+		newModule.variant = newVariant(origModule, mutatorName, variationName, local)
 		newModule.properties = newProperties
-
-		if variationName != "" {
-			if newModule.variantName == "" {
-				newModule.variantName = variationName
-			} else {
-				newModule.variantName += "_" + variationName
-			}
-		}
+		newModule.providers = append([]interface{}(nil), origModule.providers...)
 
 		newModules = append(newModules, newModule)
 
@@ -1296,16 +1383,16 @@
 		if dep.module.logicModule == nil {
 			var newDep *moduleInfo
 			for _, m := range dep.module.splitModules {
-				if m.variant[mutatorName] == variationName {
-					newDep = m
+				if m.moduleOrAliasVariant().variations[mutatorName] == variationName {
+					newDep = m.moduleOrAliasTarget()
 					break
 				}
 			}
 			if newDep == nil && defaultVariationName != nil {
 				// give it a second chance; match with defaultVariationName
 				for _, m := range dep.module.splitModules {
-					if m.variant[mutatorName] == *defaultVariationName {
-						newDep = m
+					if m.moduleOrAliasVariant().variations[mutatorName] == *defaultVariationName {
+						newDep = m.moduleOrAliasTarget()
 						break
 					}
 				}
@@ -1325,10 +1412,10 @@
 	return errs
 }
 
-func (c *Context) prettyPrintVariant(variant variationMap) string {
-	names := make([]string, 0, len(variant))
+func (c *Context) prettyPrintVariant(variations variationMap) string {
+	names := make([]string, 0, len(variations))
 	for _, m := range c.variantMutatorNames {
-		if v, ok := variant[m]; ok {
+		if v, ok := variations[m]; ok {
 			names = append(names, m+":"+v)
 		}
 	}
@@ -1338,14 +1425,14 @@
 
 func (c *Context) prettyPrintGroupVariants(group *moduleGroup) string {
 	var variants []string
-	for _, mod := range group.modules {
-		variants = append(variants, c.prettyPrintVariant(mod.variant))
+	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.aliases {
-		variants = append(variants, c.prettyPrintVariant(mod.variant)+
-			"(alias to "+c.prettyPrintVariant(mod.target.variant)+")")
-	}
-	sort.Strings(variants)
 	return strings.Join(variants, "\n  ")
 }
 
@@ -1424,7 +1511,7 @@
 
 	group := &moduleGroup{
 		name:    name,
-		modules: []*moduleInfo{module},
+		modules: modulesOrAliases{module},
 	}
 	module.group = group
 	namespace, errs := c.nameInterface.NewModule(
@@ -1454,6 +1541,8 @@
 
 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)
@@ -1473,7 +1562,9 @@
 		}
 		deps = append(deps, mutatorDeps...)
 
-		c.cloneModules()
+		if !c.skipCloneModulesAfterMutators {
+			c.cloneModules()
+		}
 
 		c.dependenciesReady = true
 	})
@@ -1508,43 +1599,32 @@
 	}
 }
 
-// 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
+// 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
 			}
 		}
 	}
-
-	return nil
+	return found
 }
 
-func (c *Context) addDependency(module *moduleInfo, tag DependencyTag, depName string) []error {
+func (c *Context) addDependency(module *moduleInfo, tag DependencyTag, depName string) (*moduleInfo, []error) {
 	if _, ok := tag.(BaseDependencyTag); ok {
 		panic("BaseDependencyTag is not allowed to be used directly!")
 	}
 
 	if depName == module.Name() {
-		return []error{&BlueprintError{
+		return nil, []error{&BlueprintError{
 			Err: fmt.Errorf("%q depends on itself", depName),
 			Pos: module.pos,
 		}}
@@ -1552,24 +1632,24 @@
 
 	possibleDeps := c.moduleGroupFromName(depName, module.namespace())
 	if possibleDeps == nil {
-		return c.discoveredMissingDependencies(module, depName)
+		return nil, c.discoveredMissingDependencies(module, depName)
 	}
 
-	if m := c.findMatchingVariant(module, possibleDeps, false); m != nil {
+	if m := findExactVariantOrSingle(module, possibleDeps, false); m != nil {
 		module.newDirectDeps = append(module.newDirectDeps, depInfo{m, tag})
 		atomic.AddUint32(&c.depsModified, 1)
-		return nil
+		return m, nil
 	}
 
 	if c.allowMissingDependencies {
 		// Allow missing variants.
-		return c.discoveredMissingDependencies(module, depName+c.prettyPrintVariant(module.dependencyVariant))
+		return nil, c.discoveredMissingDependencies(module, depName+c.prettyPrintVariant(module.variant.dependencyVariations))
 	}
 
-	return []error{&BlueprintError{
+	return nil, []error{&BlueprintError{
 		Err: fmt.Errorf("dependency %q of %q missing variant:\n  %s\navailable variants:\n  %s",
 			depName, module.Name(),
-			c.prettyPrintVariant(module.dependencyVariant),
+			c.prettyPrintVariant(module.variant.dependencyVariations),
 			c.prettyPrintGroupVariants(possibleDeps)),
 		Pos: module.pos,
 	}}
@@ -1592,26 +1672,26 @@
 		}}
 	}
 
-	if m := c.findMatchingVariant(module, possibleDeps, true); m != nil {
+	if m := findExactVariantOrSingle(module, possibleDeps, true); m != nil {
 		return m, nil
 	}
 
 	if c.allowMissingDependencies {
 		// Allow missing variants.
-		return module, c.discoveredMissingDependencies(module, destName+c.prettyPrintVariant(module.dependencyVariant))
+		return module, c.discoveredMissingDependencies(module, destName+c.prettyPrintVariant(module.variant.dependencyVariations))
 	}
 
 	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.dependencyVariant),
+			c.prettyPrintVariant(module.variant.dependencyVariations),
 			c.prettyPrintGroupVariants(possibleDeps)),
 		Pos: module.pos,
 	}}
 }
 
-func (c *Context) findVariant(module *moduleInfo, possibleDeps *moduleGroup, variations []Variation, far bool, reverse bool) (*moduleInfo, variationMap) {
-	// We can't just append variant.Variant to module.dependencyVariants.variantName and
+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
 	// 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
@@ -1619,10 +1699,10 @@
 		if !reverse {
 			// For forward dependency, ignore local variants by matching against
 			// dependencyVariant which doesn't have the local variants
-			newVariant = module.dependencyVariant.clone()
+			newVariant = module.variant.dependencyVariations.clone()
 		} else {
 			// For reverse dependency, use all the variants
-			newVariant = module.variant.clone()
+			newVariant = module.variant.variations.clone()
 		}
 	}
 	for _, v := range variations {
@@ -1634,7 +1714,7 @@
 
 	check := func(variant variationMap) bool {
 		if far {
-			return variant.subset(newVariant)
+			return newVariant.subsetOf(variant)
 		} else {
 			return variant.equal(newVariant)
 		}
@@ -1642,43 +1722,34 @@
 
 	var foundDep *moduleInfo
 	for _, m := range possibleDeps.modules {
-		if check(m.variant) {
-			foundDep = m
+		if check(m.moduleOrAliasVariant().variations) {
+			foundDep = m.moduleOrAliasTarget()
 			break
 		}
 	}
 
-	if foundDep == nil {
-		for _, m := range possibleDeps.aliases {
-			if check(m.variant) {
-				foundDep = m.target
-				break
-			}
-		}
-	}
-
 	return foundDep, newVariant
 }
 
 func (c *Context) addVariationDependency(module *moduleInfo, variations []Variation,
-	tag DependencyTag, depName string, far bool) []error {
+	tag DependencyTag, depName string, far bool) (*moduleInfo, []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)
+		return nil, c.discoveredMissingDependencies(module, depName)
 	}
 
-	foundDep, newVariant := c.findVariant(module, possibleDeps, variations, far, false)
+	foundDep, newVariant := findVariant(module, possibleDeps, variations, far, false)
 
 	if foundDep == nil {
 		if c.allowMissingDependencies {
 			// Allow missing variants.
-			return c.discoveredMissingDependencies(module, depName+c.prettyPrintVariant(newVariant))
+			return nil, c.discoveredMissingDependencies(module, depName+c.prettyPrintVariant(newVariant))
 		}
-		return []error{&BlueprintError{
+		return nil, []error{&BlueprintError{
 			Err: fmt.Errorf("dependency %q of %q missing variant:\n  %s\navailable variants:\n  %s",
 				depName, module.Name(),
 				c.prettyPrintVariant(newVariant),
@@ -1688,7 +1759,7 @@
 	}
 
 	if module == foundDep {
-		return []error{&BlueprintError{
+		return nil, []error{&BlueprintError{
 			Err: fmt.Errorf("%q depends on itself", depName),
 			Pos: module.pos,
 		}}
@@ -1697,31 +1768,33 @@
 	// 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 []error{&BlueprintError{
+		return nil, []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 nil
+	return foundDep, nil
 }
 
 func (c *Context) addInterVariantDependency(origModule *moduleInfo, tag DependencyTag,
-	from, to Module) {
+	from, to Module) *moduleInfo {
 	if _, ok := tag.(BaseDependencyTag); ok {
 		panic("BaseDependencyTag is not allowed to be used directly!")
 	}
 
 	var fromInfo, toInfo *moduleInfo
-	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()))
+	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()))
+				}
 			}
 		}
 	}
@@ -1733,6 +1806,7 @@
 
 	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
@@ -1781,7 +1855,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) bool)
+	visit(modules []*moduleInfo, visit func(*moduleInfo, chan<- pauseSpec) bool)
 }
 
 type unorderedVisitorImpl struct{}
@@ -1794,9 +1868,9 @@
 	return nil
 }
 
-func (unorderedVisitorImpl) visit(modules []*moduleInfo, visit func(*moduleInfo) bool) {
+func (unorderedVisitorImpl) visit(modules []*moduleInfo, visit func(*moduleInfo, chan<- pauseSpec) bool) {
 	for _, module := range modules {
-		if visit(module) {
+		if visit(module, nil) {
 			return
 		}
 	}
@@ -1812,9 +1886,9 @@
 	return module.reverseDeps
 }
 
-func (bottomUpVisitorImpl) visit(modules []*moduleInfo, visit func(*moduleInfo) bool) {
+func (bottomUpVisitorImpl) visit(modules []*moduleInfo, visit func(*moduleInfo, chan<- pauseSpec) bool) {
 	for _, module := range modules {
-		if visit(module) {
+		if visit(module, nil) {
 			return
 		}
 	}
@@ -1830,10 +1904,10 @@
 	return module.forwardDeps
 }
 
-func (topDownVisitorImpl) visit(modules []*moduleInfo, visit func(*moduleInfo) bool) {
+func (topDownVisitorImpl) visit(modules []*moduleInfo, visit func(*moduleInfo, chan<- pauseSpec) bool) {
 	for i := 0; i < len(modules); i++ {
 		module := modules[len(modules)-1-i]
-		if visit(module) {
+		if visit(module, nil) {
 			return
 		}
 	}
@@ -1844,25 +1918,50 @@
 	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.
-func (c *Context) parallelVisit(order visitOrderer, visit func(group *moduleInfo) bool) {
+// 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 {
+
 	doneCh := make(chan *moduleInfo)
 	cancelCh := make(chan bool)
-	count := 0
+	pauseCh := make(chan pauseSpec)
 	cancel := false
-	var backlog []*moduleInfo
-	const limit = 1000
 
-	for _, module := range c.modulesSorted {
+	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 {
 		module.waitingCount = order.waitCount(module)
 	}
 
-	visitOne := func(module *moduleInfo) {
-		if count < limit {
-			count++
+	// 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++
 			go func() {
-				ret := visit(module)
+				ret := visit(module, pauseCh)
 				if ret {
 					cancelCh <- true
 				}
@@ -1873,34 +1972,190 @@
 		}
 	}
 
-	for _, module := range c.modulesSorted {
-		if module.waitingCount == 0 {
-			visitOne(module)
+	// 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)
 		}
 	}
 
-	for count > 0 || len(backlog) > 0 {
+	// 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 {
+		if module.waitingCount == 0 {
+			startOrBacklog(module)
+		}
+	}
+
+	for active > 0 {
 		select {
 		case <-cancelCh:
 			cancel = true
 			backlog = nil
 		case doneModule := <-doneCh:
-			count--
+			active--
 			if !cancel {
-				for count < limit && len(backlog) > 0 {
-					toVisit := backlog[0]
-					backlog = backlog[1:]
-					visitOne(toVisit)
+				// 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)
+					}
 				}
+
+				// 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 {
-						visitOne(module)
+						startOrBacklog(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 a semi-random module being paused for 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.
+			var start, end *moduleInfo
+			for _, pauseSpecs := range pauseMap {
+				for _, pauseSpec := range pauseSpecs {
+					if start == nil || start.String() > pauseSpec.paused.String() {
+						start = pauseSpec.paused
+						end = pauseSpec.until
+					}
+				}
+			}
+
+			var check func(group *moduleInfo) []*moduleInfo
+			check = func(module *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}
+				}
+
+				for _, dep := range order.propagate(module) {
+					cycle := check(dep)
+					if cycle != nil {
+						return append([]*moduleInfo{module}, cycle...)
+					}
+				}
+				for _, depPauseSpec := range pauseMap[module] {
+					cycle := check(depPauseSpec.paused)
+					if cycle != nil {
+						return append([]*moduleInfo{module}, cycle...)
+					}
+				}
+
+				return nil
+			}
+
+			cycle := check(start)
+			if cycle != nil {
+				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("    %q depends on %q",
+				curModule.Name(),
+				nextModule.Name()),
+			Pos: curModule.pos,
+		})
+		curModule = nextModule
+	}
+
+	return errs
 }
 
 // updateDependencies recursively walks the module dependency graph and updates
@@ -1917,30 +2172,6 @@
 
 	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
@@ -1953,7 +2184,9 @@
 			if dep == module {
 				break
 			}
-			deps[dep] = true
+			if depModule := dep.module(); depModule != nil {
+				deps[depModule] = true
+			}
 		}
 
 		for _, dep := range module.directDeps {
@@ -1974,10 +2207,8 @@
 				if cycle != nil {
 					if cycle[0] == module {
 						// 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.
-						cycleError(cycle)
+						// for generating the errors.
+						errs = append(errs, cycleError(cycle)...)
 
 						// We can continue processing this module's children to
 						// find more cycles.  Since all the modules that were
@@ -2007,7 +2238,7 @@
 				if cycle[len(cycle)-1] != module {
 					panic("inconceivable!")
 				}
-				cycleError(cycle)
+				errs = append(errs, cycleError(cycle)...)
 			}
 		}
 	}
@@ -2195,12 +2426,12 @@
 
 	errsCh := make(chan []error)
 	globalStateCh := make(chan globalStateChange)
-	newVariationsCh := make(chan []*moduleInfo)
+	newVariationsCh := make(chan modulesOrAliases)
 	done := make(chan bool)
 
 	c.depsModified = 0
 
-	visit := func(module *moduleInfo) bool {
+	visit := func(module *moduleInfo, pause chan<- pauseSpec) bool {
 		if module.splitModules != nil {
 			panic("split module found in sorted module list")
 		}
@@ -2211,9 +2442,12 @@
 				config:  config,
 				module:  module,
 			},
-			name: mutator.name,
+			name:    mutator.name,
+			pauseCh: pause,
 		}
 
+		module.startedMutator = mutator
+
 		func() {
 			defer func() {
 				if r := recover(); r != nil {
@@ -2229,6 +2463,8 @@
 			direction.run(mutator, mctx)
 		}()
 
+		module.finishedMutator = mutator
+
 		if len(mctx.errs) > 0 {
 			errsCh <- mctx.errs
 			return true
@@ -2266,8 +2502,10 @@
 				newModules = append(newModules, globalStateChange.newModules...)
 				deps = append(deps, globalStateChange.deps...)
 			case newVariations := <-newVariationsCh:
-				for _, m := range newVariations {
-					newModuleInfo[m.logicModule] = m
+				for _, moduleOrAlias := range newVariations {
+					if m := moduleOrAlias.module(); m != nil {
+						newModuleInfo[m.logicModule] = m
+					}
 				}
 			case <-done:
 				return
@@ -2275,12 +2513,21 @@
 		}
 	}()
 
+	c.startedMutator = mutator
+
+	var visitErrs []error
 	if mutator.parallel {
-		c.parallelVisit(direction.orderer(), visit)
+		visitErrs = parallelVisit(c.modulesSorted, direction.orderer(), parallelVisitLimit, 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 {
@@ -2291,33 +2538,27 @@
 
 	for _, group := range c.moduleGroups {
 		for i := 0; i < len(group.modules); i++ {
-			module := group.modules[i]
+			module := group.modules[i].module()
+			if module == nil {
+				// Existing alias, skip it
+				continue
+			}
 
 			// 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[0]
+					module.directDeps[j].module = dep.module.splitModules.firstModule()
 				}
 			}
 
 			if module.createdBy != nil && module.createdBy.logicModule == nil {
-				module.createdBy = module.createdBy.splitModules[0]
+				module.createdBy = module.createdBy.splitModules.firstModule()
 			}
 
 			// Add in any new direct dependencies that were added by the mutator
@@ -2325,17 +2566,31 @@
 			module.newDirectDeps = nil
 		}
 
-		// Forward or delete any dangling aliases.
-		for i := 0; i < len(group.aliases); i++ {
-			alias := group.aliases[i]
+		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
+		}
 
-			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--
+		// 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--
+					}
 				}
 			}
 		}
@@ -2387,12 +2642,16 @@
 	ch := make(chan update)
 	doneCh := make(chan bool)
 	go func() {
-		c.parallelVisit(unorderedVisitorImpl{}, func(m *moduleInfo) bool {
-			origLogicModule := m.logicModule
-			m.logicModule, m.properties = c.cloneLogicModule(m)
-			ch <- update{origLogicModule, m}
-			return false
-		})
+		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)
+		}
 		doneCh <- true
 	}()
 
@@ -2410,15 +2669,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 []*moduleInfo, i int, newModules []*moduleInfo) ([]*moduleInfo, int) {
+func spliceModules(modules modulesOrAliases, i int, newModules modulesOrAliases) (modulesOrAliases, int) {
 	spliceSize := len(newModules)
 	newLen := len(modules) + spliceSize - 1
-	var dest []*moduleInfo
+	var dest modulesOrAliases
 	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([]*moduleInfo, newLen)
+		dest = make(modulesOrAliases, newLen)
 		copy(dest, modules[:i])
 	}
 
@@ -2456,71 +2715,77 @@
 		}
 	}()
 
-	c.parallelVisit(bottomUpVisitor, func(module *moduleInfo) bool {
+	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)
 
-		uniqueName := c.nameInterface.UniqueName(newNamespaceContext(module), module.group.name)
-		sanitizedName := toNinjaName(uniqueName)
+			prefix := moduleNamespacePrefix(sanitizedName + "_" + module.variant.name)
 
-		prefix := moduleNamespacePrefix(sanitizedName + "_" + module.variantName)
+			// 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)
 
-		// 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 := &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))
-					}
-				}
-			}()
-			mctx.module.logicModule.GenerateBuildActions(mctx)
-		}()
-
-		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))
+			mctx := &moduleContext{
+				baseModuleContext: baseModuleContext{
+					context: c,
+					config:  config,
+					module:  module,
+				},
+				scope:              scope,
+				handledMissingDeps: module.missingDeps == nil,
 			}
-			errsCh <- errs
-			return true
-		}
 
-		depsCh <- mctx.ninjaFileDeps
+			mctx.module.startedGenerateBuildActions = true
 
-		newErrs := c.processLocalBuildActions(&module.actionDefs,
-			&mctx.actionDefs, liveGlobals)
-		if len(newErrs) > 0 {
-			errsCh <- newErrs
-			return true
-		}
-		return false
-	})
+			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
+			}
+
+			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
 }
 
@@ -2675,14 +2940,8 @@
 	}
 
 	for _, m := range group.modules {
-		if module.variantName == m.variantName {
-			return m
-		}
-	}
-
-	for _, m := range group.aliases {
-		if module.variantName == m.variantName {
-			return m.target
+		if module.variant.name == m.moduleOrAliasVariant().name {
+			return m.moduleOrAliasTarget()
 		}
 	}
 
@@ -2779,8 +3038,10 @@
 	}()
 
 	for _, moduleGroup := range c.sortedModuleGroups() {
-		for _, module = range moduleGroup.modules {
-			visit(module.logicModule)
+		for _, moduleOrAlias := range moduleGroup.modules {
+			if module = moduleOrAlias.module(); module != nil {
+				visit(module.logicModule)
+			}
 		}
 	}
 }
@@ -2798,9 +3059,11 @@
 	}()
 
 	for _, moduleGroup := range c.sortedModuleGroups() {
-		for _, module := range moduleGroup.modules {
-			if pred(module.logicModule) {
-				visit(module.logicModule)
+		for _, moduleOrAlias := range moduleGroup.modules {
+			if module = moduleOrAlias.module(); module != nil {
+				if pred(module.logicModule) {
+					visit(module.logicModule)
+				}
 			}
 		}
 	}
@@ -2818,8 +3081,10 @@
 		}
 	}()
 
-	for _, variant = range module.group.modules {
-		visit(variant.logicModule)
+	for _, moduleOrAlias := range module.group.modules {
+		if variant = moduleOrAlias.module(); variant != nil {
+			visit(variant.logicModule)
+		}
 	}
 }
 
@@ -3047,18 +3312,13 @@
 	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.ModulePath(logicModule))
+	return filepath.Dir(c.BlueprintFile(logicModule))
 }
 
 func (c *Context) ModuleSubDir(logicModule Module) string {
 	module := c.moduleInfo[logicModule]
-	return module.variantName
+	return module.variant.name
 }
 
 func (c *Context) ModuleType(logicModule Module) string {
@@ -3066,6 +3326,25 @@
 	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
@@ -3168,12 +3447,11 @@
 }
 
 func (c *Context) PrimaryModule(module Module) Module {
-	return c.moduleInfo[module].group.modules[0].logicModule
+	return c.moduleInfo[module].group.modules.firstModule().logicModule
 }
 
 func (c *Context) FinalModule(module Module) Module {
-	modules := c.moduleInfo[module].group.modules
-	return modules[len(modules)-1].logicModule
+	return c.moduleInfo[module].group.modules.lastModule().logicModule
 }
 
 func (c *Context) VisitAllModuleVariants(module Module,
@@ -3504,8 +3782,8 @@
 	iName := s[i].module.Name()
 	jName := s[j].module.Name()
 	if iName == jName {
-		iName = s[i].module.variantName
-		jName = s[j].module.variantName
+		iName = s[i].module.variant.name
+		jName = s[j].module.variant.name
 	}
 	return iName < jName
 }
@@ -3529,14 +3807,17 @@
 	iName := s.nameInterface.UniqueName(newNamespaceContext(iMod), iMod.group.name)
 	jName := s.nameInterface.UniqueName(newNamespaceContext(jMod), jMod.group.name)
 	if iName == jName {
-		iName = s.modules[i].variantName
-		jName = s.modules[j].variantName
+		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
 	}
-
-	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) {
@@ -3581,7 +3862,7 @@
 			"typeName":  module.typeName,
 			"goFactory": factoryName,
 			"pos":       relPos,
-			"variant":   module.variantName,
+			"variant":   module.variant.name,
 		}
 		err = headerTemplate.Execute(buf, infoMap)
 		if err != nil {
@@ -3730,15 +4011,15 @@
 	return nil
 }
 
-func beforeInModuleList(a, b *moduleInfo, list []*moduleInfo) bool {
+func beforeInModuleList(a, b *moduleInfo, list modulesOrAliases) bool {
 	found := false
 	if a == b {
 		return false
 	}
 	for _, l := range list {
-		if l == a {
+		if l.module() == a {
 			found = true
-		} else if l == b {
+		} else if l.module() == b {
 			return found
 		}
 	}
diff --git a/context_test.go b/context_test.go
index 0541c06..dd5ec38 100644
--- a/context_test.go
+++ b/context_test.go
@@ -238,7 +238,7 @@
 		t.FailNow()
 	}
 
-	topModule := ctx.moduleGroupFromName("A", nil).modules[0]
+	topModule := ctx.moduleGroupFromName("A", nil).modules.firstModule()
 	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[0]
+	topModule := ctx.moduleGroupFromName("A", nil).modules.firstModule()
 	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[0]
+	topModule := ctx.moduleGroupFromName("A", nil).modules.firstModule()
 	outputDown, outputUp := walkDependencyGraph(ctx, topModule, true)
 	expectedDown := "BDCDE"
 	if outputDown != expectedDown {
@@ -432,10 +432,10 @@
 		t.FailNow()
 	}
 
-	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)
+	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)
 
 	checkDeps := func(m Module, expected string) {
 		var deps []string
@@ -606,3 +606,451 @@
 		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) {
+	moduleA := &moduleInfo{
+		group: &moduleGroup{
+			name: "A",
+		},
+	}
+	moduleB := &moduleInfo{
+		group: &moduleGroup{
+			name: "B",
+		},
+	}
+	moduleC := &moduleInfo{
+		group: &moduleGroup{
+			name: "C",
+		},
+	}
+	moduleD := &moduleInfo{
+		group: &moduleGroup{
+			name: "D",
+		},
+	}
+	moduleA.group.modules = modulesOrAliases{moduleA}
+	moduleB.group.modules = modulesOrAliases{moduleB}
+	moduleC.group.modules = modulesOrAliases{moduleC}
+	moduleD.group.modules = modulesOrAliases{moduleD}
+
+	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)
+	}
+
+	// A depends on B, B depends on C.  Nothing depends on D, and D doesn'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`,
+			`"C" depends on "A"`,
+			`"A" depends on "B"`,
+			`"B" depends on "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`,
+			`"C" depends on "D"`,
+			`"D" depends on "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())
+			}
+		}
+	})
+}
diff --git a/live_tracker.go b/live_tracker.go
index 40e1930..1d48e58 100644
--- a/live_tracker.go
+++ b/live_tracker.go
@@ -68,6 +68,11 @@
 		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/module_ctx.go b/module_ctx.go
index 9de2676..d0f8c39 100644
--- a/module_ctx.go
+++ b/module_ctx.go
@@ -246,6 +246,24 @@
 	// 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
@@ -288,6 +306,31 @@
 	// other words, it checks for the module 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
@@ -309,24 +352,6 @@
 	// 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.
@@ -464,7 +489,7 @@
 
 func (m *baseModuleContext) OtherModuleSubDir(logicModule Module) string {
 	module := m.context.moduleInfo[logicModule]
-	return module.variantName
+	return module.variant.name
 }
 
 func (m *baseModuleContext) OtherModuleType(logicModule Module) string {
@@ -510,7 +535,7 @@
 	if possibleDeps == nil {
 		return false
 	}
-	found, _ := m.context.findVariant(m.module, possibleDeps, variations, false, false)
+	found, _ := findVariant(m.module, possibleDeps, variations, false, false)
 	return found != nil
 }
 
@@ -519,10 +544,36 @@
 	if possibleDeps == nil {
 		return false
 	}
-	found, _ := m.context.findVariant(m.module, possibleDeps, nil, false, true)
+	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 {
@@ -642,6 +693,18 @@
 	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...)
 }
@@ -655,7 +718,7 @@
 }
 
 func (m *moduleContext) ModuleSubDir() string {
-	return m.module.variantName
+	return m.module.variant.name
 }
 
 func (m *moduleContext) Variable(pctx PackageContext, name, value string) {
@@ -695,18 +758,6 @@
 	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
@@ -722,9 +773,10 @@
 	reverseDeps      []reverseDep
 	rename           []rename
 	replace          []replace
-	newVariations    []*moduleInfo // new variants of existing modules
-	newModules       []*moduleInfo // brand new modules
+	newVariations    modulesOrAliases // new variants of existing modules
+	newModules       []*moduleInfo    // brand new modules
 	defaultVariation *string
+	pauseCh          chan<- pauseSpec
 }
 
 type BaseMutatorContext interface {
@@ -775,10 +827,15 @@
 type BottomUpMutatorContext interface {
 	BaseMutatorContext
 
-	// 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)
+	// 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
 
 	// 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
@@ -818,19 +875,30 @@
 	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.  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)
+	// 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
 
 	// AddFarVariationDependencies adds deps as dependencies of the current module, but uses the
-	// 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.
+	// 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.
 	// 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.
-	AddFarVariationDependencies([]Variation, DependencyTag, ...string)
+	//
+	// 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
 
 	// 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
@@ -850,12 +918,29 @@
 	// 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 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 (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(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
@@ -899,21 +984,30 @@
 	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)
+	modules, errs := mctx.context.createVariations(mctx.module, mctx.name, mctx.defaultVariation, variationNames, local)
 	if len(errs) > 0 {
 		mctx.errs = append(mctx.errs, errs...)
 	}
 
-	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]
-		}
+	for _, module := range modules {
+		ret = append(ret, module.module().logicModule)
 	}
 
 	if mctx.newVariations != nil {
@@ -929,24 +1023,65 @@
 }
 
 func (mctx *mutatorContext) AliasVariation(variationName string) {
-	if mctx.module.aliasTarget != nil {
-		panic(fmt.Errorf("AliasVariation already called"))
+	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"))
+			}
+		}
 	}
 
 	for _, variant := range mctx.newVariations {
-		if variant.variant[mctx.name] == variationName {
-			mctx.module.aliasTarget = variant
+		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...)
 			return
 		}
 	}
 
 	var foundVariations []string
 	for _, variant := range mctx.newVariations {
-		foundVariations = append(foundVariations, variant.variant[mctx.name])
+		foundVariations = append(foundVariations, variant.moduleOrAliasVariant().variations[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)
 }
@@ -959,14 +1094,17 @@
 	return mctx.module.logicModule
 }
 
-func (mctx *mutatorContext) AddDependency(module Module, tag DependencyTag, deps ...string) {
+func (mctx *mutatorContext) AddDependency(module Module, tag DependencyTag, deps ...string) []Module {
+	depInfos := make([]Module, 0, len(deps))
 	for _, dep := range deps {
 		modInfo := mctx.context.moduleInfo[module]
-		errs := mctx.context.addDependency(modInfo, tag, dep)
+		depInfo, errs := mctx.context.addDependency(modInfo, tag, dep)
 		if len(errs) > 0 {
 			mctx.errs = append(mctx.errs, errs...)
 		}
+		depInfos = append(depInfos, maybeLogicModule(depInfo))
 	}
+	return depInfos
 }
 
 func (mctx *mutatorContext) AddReverseDependency(module Module, tag DependencyTag, destName string) {
@@ -987,25 +1125,31 @@
 }
 
 func (mctx *mutatorContext) AddVariationDependencies(variations []Variation, tag DependencyTag,
-	deps ...string) {
+	deps ...string) []Module {
 
+	depInfos := make([]Module, 0, len(deps))
 	for _, dep := range deps {
-		errs := mctx.context.addVariationDependency(mctx.module, variations, tag, dep, false)
+		depInfo, errs := mctx.context.addVariationDependency(mctx.module, variations, tag, dep, false)
 		if len(errs) > 0 {
 			mctx.errs = append(mctx.errs, errs...)
 		}
+		depInfos = append(depInfos, maybeLogicModule(depInfo))
 	}
+	return depInfos
 }
 
 func (mctx *mutatorContext) AddFarVariationDependencies(variations []Variation, tag DependencyTag,
-	deps ...string) {
+	deps ...string) []Module {
 
+	depInfos := make([]Module, 0, len(deps))
 	for _, dep := range deps {
-		errs := mctx.context.addVariationDependency(mctx.module, variations, tag, dep, true)
+		depInfo, errs := mctx.context.addVariationDependency(mctx.module, variations, tag, dep, true)
 		if len(errs) > 0 {
 			mctx.errs = append(mctx.errs, errs...)
 		}
+		depInfos = append(depInfos, maybeLogicModule(depInfo))
 	}
+	return depInfos
 }
 
 func (mctx *mutatorContext) AddInterVariantDependency(tag DependencyTag, from, to Module) {
@@ -1023,7 +1167,7 @@
 
 	if target == nil {
 		panic(fmt.Errorf("ReplaceDependencies could not find identical variant %q for module %q",
-			mctx.module.variantName, name))
+			mctx.module.variant.name, name))
 	}
 
 	mctx.replace = append(mctx.replace, replace{target, mctx.module, predicate})
@@ -1053,6 +1197,23 @@
 	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.
+func (mctx *mutatorContext) pause(dep *moduleInfo) bool {
+	if mctx.pauseCh != 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.
@@ -1172,9 +1333,8 @@
 
 // Check the syntax of a generated blueprint file.
 //
-// This is intended to perform a quick sanity check for generated blueprint
-// code to ensure that it is syntactically correct, where syntactically correct
-// means:
+// This is intended to perform a quick syntactic check for generated blueprint
+// code, where syntactically correct means:
 // * No variable definitions.
 // * Valid module types.
 // * Valid property names.
@@ -1210,3 +1370,11 @@
 
 	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 e98ae82..62a89c1 100644
--- a/module_ctx_test.go
+++ b/module_ctx_test.go
@@ -32,7 +32,7 @@
 func (f *moduleCtxTestModule) GenerateBuildActions(ModuleContext) {
 }
 
-func noCreateAliasMutator(name string) func(ctx BottomUpMutatorContext) {
+func noAliasMutator(name string) func(ctx BottomUpMutatorContext) {
 	return func(ctx BottomUpMutatorContext) {
 		if ctx.ModuleName() == name {
 			ctx.CreateVariations("a", "b")
@@ -40,11 +40,22 @@
 	}
 }
 
+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.AliasVariation("b")
+			ctx.CreateAliasVariation("c", "a")
+			ctx.CreateAliasVariation("d", "b")
+			ctx.CreateAliasVariation("e", "a")
 		}
 	}
 }
@@ -57,7 +68,7 @@
 	}
 }
 
-func TestAliases(t *testing.T) {
+func TestAliasVariation(t *testing.T) {
 	runWithFailures := func(ctx *Context, expectedErr string) {
 		t.Helper()
 		bp := `
@@ -115,17 +126,13 @@
 		// Tests a dependency from "foo" to "bar" variant "b" through alias "".
 		ctx := NewContext()
 		ctx.RegisterModuleType("test", newModuleCtxTestModule)
-		ctx.RegisterBottomUpMutator("1", createAliasMutator("bar"))
+		ctx.RegisterBottomUpMutator("1", aliasMutator("bar"))
 		ctx.RegisterBottomUpMutator("2", addVariantDepsMutator(nil, nil, "foo", "bar"))
 
 		run(ctx)
 
-		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)
-		}
+		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)
@@ -138,18 +145,14 @@
 		// Tests a dependency from "foo" to "bar" variant "b_b" through alias "".
 		ctx := NewContext()
 		ctx.RegisterModuleType("test", newModuleCtxTestModule)
-		ctx.RegisterBottomUpMutator("1", createAliasMutator("bar"))
-		ctx.RegisterBottomUpMutator("2", createAliasMutator("bar"))
+		ctx.RegisterBottomUpMutator("1", aliasMutator("bar"))
+		ctx.RegisterBottomUpMutator("2", aliasMutator("bar"))
 		ctx.RegisterBottomUpMutator("3", addVariantDepsMutator(nil, nil, "foo", "bar"))
 
 		run(ctx)
 
-		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)
-		}
+		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)
@@ -162,18 +165,14 @@
 		// Tests a dependency from "foo" to "bar" variant "a_b" through alias "a".
 		ctx := NewContext()
 		ctx.RegisterModuleType("test", newModuleCtxTestModule)
-		ctx.RegisterBottomUpMutator("1", createAliasMutator("bar"))
-		ctx.RegisterBottomUpMutator("2", createAliasMutator("bar"))
+		ctx.RegisterBottomUpMutator("1", aliasMutator("bar"))
+		ctx.RegisterBottomUpMutator("2", aliasMutator("bar"))
 		ctx.RegisterBottomUpMutator("3", addVariantDepsMutator([]Variation{{"1", "a"}}, nil, "foo", "bar"))
 
 		run(ctx)
 
-		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)
-		}
+		foo := ctx.moduleGroupFromName("foo", nil).moduleByVariantName("")
+		barAB := ctx.moduleGroupFromName("bar", nil).moduleByVariantName("a_b")
 
 		if g, w := foo.forwardDeps, []*moduleInfo{barAB}; !reflect.DeepEqual(g, w) {
 			t.Fatalf("expected foo deps to be %q, got %q", w, g)
@@ -186,8 +185,8 @@
 		// Tests a dependency from "foo" to removed "bar" alias "" fails.
 		ctx := NewContext()
 		ctx.RegisterModuleType("test", newModuleCtxTestModule)
-		ctx.RegisterBottomUpMutator("1", createAliasMutator("bar"))
-		ctx.RegisterBottomUpMutator("2", noCreateAliasMutator("bar"))
+		ctx.RegisterBottomUpMutator("1", aliasMutator("bar"))
+		ctx.RegisterBottomUpMutator("2", noAliasMutator("bar"))
 		ctx.RegisterBottomUpMutator("3", addVariantDepsMutator(nil, nil, "foo", "bar"))
 
 		runWithFailures(ctx, `dependency "bar" of "foo" missing variant:`+"\n  \n"+
@@ -196,6 +195,113 @@
 	})
 }
 
+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")
+	})
+}
+
 func expectedErrors(t *testing.T, errs []error, expectedMessages ...string) {
 	t.Helper()
 	if len(errs) != len(expectedMessages) {
diff --git a/name_interface.go b/name_interface.go
index 1849e9d..5e7e16e 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[0].pos),
+				"       %s <-- previous definition here", name, group.modules.firstModule().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[0].pos),
+				oldName, newName, existingGroup.modules.firstModule().pos),
 		}
 	}
 
diff --git a/ninja_defs.go b/ninja_defs.go
index c5d0e4b..8c5db57 100644
--- a/ninja_defs.go
+++ b/ninja_defs.go
@@ -87,6 +87,7 @@
 	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
 }
@@ -257,6 +258,7 @@
 	Inputs          []ninjaString
 	Implicits       []ninjaString
 	OrderOnly       []ninjaString
+	Validations     []ninjaString
 	Args            map[Variable]ninjaString
 	Variables       map[string]ninjaString
 	Optional        bool
@@ -314,6 +316,11 @@
 		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 != "" {
@@ -373,6 +380,7 @@
 		explicitDeps  = valueList(b.Inputs, pkgNames, inputEscaper)
 		implicitDeps  = valueList(b.Implicits, pkgNames, inputEscaper)
 		orderOnlyDeps = valueList(b.OrderOnly, pkgNames, inputEscaper)
+		validations   = valueList(b.Validations, pkgNames, inputEscaper)
 	)
 
 	if b.RuleDef != nil {
@@ -380,7 +388,7 @@
 		orderOnlyDeps = append(valueList(b.RuleDef.CommandOrderOnly, pkgNames, inputEscaper), orderOnlyDeps...)
 	}
 
-	err := nw.Build(comment, rule, outputs, implicitOuts, explicitDeps, implicitDeps, orderOnlyDeps)
+	err := nw.Build(comment, rule, outputs, implicitOuts, explicitDeps, implicitDeps, orderOnlyDeps, validations)
 	if err != nil {
 		return err
 	}
diff --git a/ninja_writer.go b/ninja_writer.go
index 5366f3f..5e69c08 100644
--- a/ninja_writer.go
+++ b/ninja_writer.go
@@ -104,7 +104,7 @@
 }
 
 func (n *ninjaWriter) Build(comment string, rule string, outputs, implicitOuts,
-	explicitDeps, implicitDeps, orderOnlyDeps []string) error {
+	explicitDeps, implicitDeps, orderOnlyDeps, validations []string) error {
 
 	n.justDidBlankLine = false
 
@@ -161,6 +161,14 @@
 		}
 	}
 
+	if len(validations) > 0 {
+		wrapper.WriteStringWithSpace("|@")
+
+		for _, dep := range validations {
+			wrapper.WriteStringWithSpace(dep)
+		}
+	}
+
 	return wrapper.Flush()
 }
 
diff --git a/ninja_writer_test.go b/ninja_writer_test.go
index cc880e5..48ecb7c 100644
--- a/ninja_writer_test.go
+++ b/ninja_writer_test.go
@@ -50,9 +50,9 @@
 	{
 		input: func(w *ninjaWriter) {
 			ck(w.Build("foo comment", "foo", []string{"o1", "o2"}, []string{"io1", "io2"},
-				[]string{"e1", "e2"}, []string{"i1", "i2"}, []string{"oo1", "oo2"}))
+				[]string{"e1", "e2"}, []string{"i1", "i2"}, []string{"oo1", "oo2"}, []string{"v1", "v2"}))
 		},
-		output: "# foo comment\nbuild o1 o2 | io1 io2: foo e1 e2 | i1 i2 || oo1 oo2\n",
+		output: "# foo comment\nbuild o1 o2 | io1 io2: foo e1 e2 | i1 i2 || oo1 oo2 |@ v1 v2\n",
 	},
 	{
 		input: func(w *ninjaWriter) {
@@ -94,7 +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", []string{"foo.o"}, nil, []string{"foo.in"}, nil, nil))
+			ck(w.Build("r comment", "r", []string{"foo.o"}, nil, []string{"foo.in"}, nil, nil, nil))
 			ck(w.ScopedAssign("_arg", "arg value"))
 		},
 		output: `pool p
diff --git a/provider.go b/provider.go
new file mode 100644
index 0000000..b83e1d4
--- /dev/null
+++ b/provider.go
@@ -0,0 +1,216 @@
+// 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
new file mode 100644
index 0000000..8f8def4
--- /dev/null
+++ b/provider_test.go
@@ -0,0 +1,420 @@
+// 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/singleton_ctx.go b/singleton_ctx.go
index 3c0a24c..a4e7153 100644
--- a/singleton_ctx.go
+++ b/singleton_ctx.go
@@ -47,6 +47,16 @@
 	// 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{})
 
@@ -188,6 +198,15 @@
 	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 a67aeb1..473999a 100644
--- a/splice_modules_test.go
+++ b/splice_modules_test.go
@@ -20,91 +20,91 @@
 )
 
 var (
-	testModuleA = &moduleInfo{variantName: "testModuleA"}
-	testModuleB = &moduleInfo{variantName: "testModuleB"}
-	testModuleC = &moduleInfo{variantName: "testModuleC"}
-	testModuleD = &moduleInfo{variantName: "testModuleD"}
-	testModuleE = &moduleInfo{variantName: "testModuleE"}
-	testModuleF = &moduleInfo{variantName: "testModuleF"}
+	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"}}
 )
 
 var spliceModulesTestCases = []struct {
-	in         []*moduleInfo
+	in         modulesOrAliases
 	at         int
-	with       []*moduleInfo
-	out        []*moduleInfo
+	with       modulesOrAliases
+	out        modulesOrAliases
 	outAt      int
 	reallocate bool
 }{
 	{
 		// Insert at the beginning
-		in:         []*moduleInfo{testModuleA, testModuleB, testModuleC},
+		in:         modulesOrAliases{testModuleA, testModuleB, testModuleC},
 		at:         0,
-		with:       []*moduleInfo{testModuleD, testModuleE},
-		out:        []*moduleInfo{testModuleD, testModuleE, testModuleB, testModuleC},
+		with:       modulesOrAliases{testModuleD, testModuleE},
+		out:        modulesOrAliases{testModuleD, testModuleE, testModuleB, testModuleC},
 		outAt:      1,
 		reallocate: true,
 	},
 	{
 		// Insert in the middle
-		in:         []*moduleInfo{testModuleA, testModuleB, testModuleC},
+		in:         modulesOrAliases{testModuleA, testModuleB, testModuleC},
 		at:         1,
-		with:       []*moduleInfo{testModuleD, testModuleE},
-		out:        []*moduleInfo{testModuleA, testModuleD, testModuleE, testModuleC},
+		with:       modulesOrAliases{testModuleD, testModuleE},
+		out:        modulesOrAliases{testModuleA, testModuleD, testModuleE, testModuleC},
 		outAt:      2,
 		reallocate: true,
 	},
 	{
 		// Insert at the end
-		in:         []*moduleInfo{testModuleA, testModuleB, testModuleC},
+		in:         modulesOrAliases{testModuleA, testModuleB, testModuleC},
 		at:         2,
-		with:       []*moduleInfo{testModuleD, testModuleE},
-		out:        []*moduleInfo{testModuleA, testModuleB, testModuleD, testModuleE},
+		with:       modulesOrAliases{testModuleD, testModuleE},
+		out:        modulesOrAliases{testModuleA, testModuleB, testModuleD, testModuleE},
 		outAt:      3,
 		reallocate: true,
 	},
 	{
 		// Insert over a single element
-		in:         []*moduleInfo{testModuleA},
+		in:         modulesOrAliases{testModuleA},
 		at:         0,
-		with:       []*moduleInfo{testModuleD, testModuleE},
-		out:        []*moduleInfo{testModuleD, testModuleE},
+		with:       modulesOrAliases{testModuleD, testModuleE},
+		out:        modulesOrAliases{testModuleD, testModuleE},
 		outAt:      1,
 		reallocate: true,
 	},
 	{
 		// Insert at the beginning without reallocating
-		in:         []*moduleInfo{testModuleA, testModuleB, testModuleC, nil}[0:3],
+		in:         modulesOrAliases{testModuleA, testModuleB, testModuleC, nil}[0:3],
 		at:         0,
-		with:       []*moduleInfo{testModuleD, testModuleE},
-		out:        []*moduleInfo{testModuleD, testModuleE, testModuleB, testModuleC},
+		with:       modulesOrAliases{testModuleD, testModuleE},
+		out:        modulesOrAliases{testModuleD, testModuleE, testModuleB, testModuleC},
 		outAt:      1,
 		reallocate: false,
 	},
 	{
 		// Insert in the middle without reallocating
-		in:         []*moduleInfo{testModuleA, testModuleB, testModuleC, nil}[0:3],
+		in:         modulesOrAliases{testModuleA, testModuleB, testModuleC, nil}[0:3],
 		at:         1,
-		with:       []*moduleInfo{testModuleD, testModuleE},
-		out:        []*moduleInfo{testModuleA, testModuleD, testModuleE, testModuleC},
+		with:       modulesOrAliases{testModuleD, testModuleE},
+		out:        modulesOrAliases{testModuleA, testModuleD, testModuleE, testModuleC},
 		outAt:      2,
 		reallocate: false,
 	},
 	{
 		// Insert at the end without reallocating
-		in:         []*moduleInfo{testModuleA, testModuleB, testModuleC, nil}[0:3],
+		in:         modulesOrAliases{testModuleA, testModuleB, testModuleC, nil}[0:3],
 		at:         2,
-		with:       []*moduleInfo{testModuleD, testModuleE},
-		out:        []*moduleInfo{testModuleA, testModuleB, testModuleD, testModuleE},
+		with:       modulesOrAliases{testModuleD, testModuleE},
+		out:        modulesOrAliases{testModuleA, testModuleB, testModuleD, testModuleE},
 		outAt:      3,
 		reallocate: false,
 	},
 	{
 		// Insert over a single element without reallocating
-		in:         []*moduleInfo{testModuleA, nil}[0:1],
+		in:         modulesOrAliases{testModuleA, nil}[0:1],
 		at:         0,
-		with:       []*moduleInfo{testModuleD, testModuleE},
-		out:        []*moduleInfo{testModuleD, testModuleE},
+		with:       modulesOrAliases{testModuleD, testModuleE},
+		out:        modulesOrAliases{testModuleD, testModuleE},
 		outAt:      1,
 		reallocate: false,
 	},
@@ -112,7 +112,7 @@
 
 func TestSpliceModules(t *testing.T) {
 	for _, testCase := range spliceModulesTestCases {
-		in := make([]*moduleInfo, len(testCase.in), cap(testCase.in))
+		in := make(modulesOrAliases, 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 []*moduleInfo) bool {
+func sameArray(a, b modulesOrAliases) 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 efaadba..1c74b93 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[0].logicModule.(*visitModule)
+	topModule := ctx.moduleGroupFromName("A", nil).modules.firstModule().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[0].logicModule.(*visitModule)
+	eModule := ctx.moduleGroupFromName("E", nil).modules.firstModule().logicModule.(*visitModule)
 	assertString(t, eModule.properties.VisitDepsDepthFirst, "F")
 	assertString(t, eModule.properties.VisitDepsDepthFirstIf, "F")
 	assertString(t, eModule.properties.VisitDirectDeps, "FF")