Golang程序  |  1002行  |  30.43 KB

// Copyright 2014 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 bootstrap

import (
	"fmt"
	"path/filepath"
	"strings"

	"github.com/google/blueprint"
	"github.com/google/blueprint/pathtools"
)

const bootstrapSubDir = ".bootstrap"
const miniBootstrapSubDir = ".minibootstrap"

var (
	pctx = blueprint.NewPackageContext("github.com/google/blueprint/bootstrap")

	goTestMainCmd   = pctx.StaticVariable("goTestMainCmd", filepath.Join(bootstrapDir, "bin", "gotestmain"))
	goTestRunnerCmd = pctx.StaticVariable("goTestRunnerCmd", filepath.Join(bootstrapDir, "bin", "gotestrunner"))
	chooseStageCmd  = pctx.StaticVariable("chooseStageCmd", filepath.Join(bootstrapDir, "bin", "choosestage"))
	pluginGenSrcCmd = pctx.StaticVariable("pluginGenSrcCmd", filepath.Join(bootstrapDir, "bin", "loadplugins"))

	compile = pctx.StaticRule("compile",
		blueprint.RuleParams{
			Command: "GOROOT='$goRoot' $compileCmd -o $out -p $pkgPath -complete " +
				"$incFlags -pack $in",
			CommandDeps: []string{"$compileCmd"},
			Description: "compile $out",
		},
		"pkgPath", "incFlags")

	link = pctx.StaticRule("link",
		blueprint.RuleParams{
			Command:     "GOROOT='$goRoot' $linkCmd -o $out $libDirFlags $in",
			CommandDeps: []string{"$linkCmd"},
			Description: "link $out",
		},
		"libDirFlags")

	goTestMain = pctx.StaticRule("gotestmain",
		blueprint.RuleParams{
			Command:     "$goTestMainCmd -o $out -pkg $pkg $in",
			CommandDeps: []string{"$goTestMainCmd"},
			Description: "gotestmain $out",
		},
		"pkg")

	pluginGenSrc = pctx.StaticRule("pluginGenSrc",
		blueprint.RuleParams{
			Command:     "$pluginGenSrcCmd -o $out -p $pkg $plugins",
			CommandDeps: []string{"$pluginGenSrcCmd"},
			Description: "create $out",
		},
		"pkg", "plugins")

	test = pctx.StaticRule("test",
		blueprint.RuleParams{
			Command:     "$goTestRunnerCmd -p $pkgSrcDir -f $out -- $in -test.short",
			CommandDeps: []string{"$goTestRunnerCmd"},
			Description: "test $pkg",
		},
		"pkg", "pkgSrcDir")

	cp = pctx.StaticRule("cp",
		blueprint.RuleParams{
			Command:     "cp $in $out",
			Description: "cp $out",
		},
		"generator")

	bootstrap = pctx.StaticRule("bootstrap",
		blueprint.RuleParams{
			Command:     "BUILDDIR=$buildDir $bootstrapCmd -i $in",
			CommandDeps: []string{"$bootstrapCmd"},
			Description: "bootstrap $in",
			Generator:   true,
		})

	chooseStage = pctx.StaticRule("chooseStage",
		blueprint.RuleParams{
			Command:     "$chooseStageCmd --current $current --bootstrap $bootstrapManifest -o $out $in",
			CommandDeps: []string{"$chooseStageCmd", "$bootstrapManifest"},
			Description: "choosing next stage",
		},
		"current", "generator")

	touch = pctx.StaticRule("touch",
		blueprint.RuleParams{
			Command:     "touch $out",
			Description: "touch $out",
		},
		"depfile", "generator")

	// Work around a Ninja issue.  See https://github.com/martine/ninja/pull/634
	phony = pctx.StaticRule("phony",
		blueprint.RuleParams{
			Command:     "# phony $out",
			Description: "phony $out",
			Generator:   true,
		},
		"depfile")

	binDir     = pctx.StaticVariable("BinDir", filepath.Join(bootstrapDir, "bin"))
	minibpFile = filepath.Join("$BinDir", "minibp")

	docsDir = filepath.Join(bootstrapDir, "docs")

	bootstrapDir     = filepath.Join("$buildDir", bootstrapSubDir)
	miniBootstrapDir = filepath.Join("$buildDir", miniBootstrapSubDir)
)

type bootstrapGoCore interface {
	BuildStage() Stage
	SetBuildStage(Stage)
}

func propagateStageBootstrap(mctx blueprint.TopDownMutatorContext) {
	if mod, ok := mctx.Module().(bootstrapGoCore); !ok || mod.BuildStage() != StageBootstrap {
		return
	}

	mctx.VisitDirectDeps(func(mod blueprint.Module) {
		if m, ok := mod.(bootstrapGoCore); ok {
			m.SetBuildStage(StageBootstrap)
		}
	})
}

func pluginDeps(ctx blueprint.BottomUpMutatorContext) {
	if pkg, ok := ctx.Module().(*goPackage); ok {
		for _, plugin := range pkg.properties.PluginFor {
			ctx.AddReverseDependency(ctx.Module(), plugin)
		}
	}
}

type goPackageProducer interface {
	GoPkgRoot() string
	GoPackageTarget() string
}

func isGoPackageProducer(module blueprint.Module) bool {
	_, ok := module.(goPackageProducer)
	return ok
}

type goTestProducer interface {
	GoTestTarget() string
	BuildStage() Stage
}

func isGoTestProducer(module blueprint.Module) bool {
	_, ok := module.(goTestProducer)
	return ok
}

type goPluginProvider interface {
	GoPkgPath() string
	IsPluginFor(string) bool
}

func isGoPluginFor(name string) func(blueprint.Module) bool {
	return func(module blueprint.Module) bool {
		if plugin, ok := module.(goPluginProvider); ok {
			return plugin.IsPluginFor(name)
		}
		return false
	}
}

func isBootstrapModule(module blueprint.Module) bool {
	_, isPackage := module.(*goPackage)
	_, isBinary := module.(*goBinary)
	return isPackage || isBinary
}

func isBootstrapBinaryModule(module blueprint.Module) bool {
	_, isBinary := module.(*goBinary)
	return isBinary
}

// A goPackage is a module for building Go packages.
type goPackage struct {
	properties struct {
		PkgPath   string
		Srcs      []string
		TestSrcs  []string
		PluginFor []string
	}

	// The root dir in which the package .a file is located.  The full .a file
	// path will be "packageRoot/PkgPath.a"
	pkgRoot string

	// The path of the .a file that is to be built.
	archiveFile string

	// The path of the test .a file that is to be built.
	testArchiveFile string

	// The bootstrap Config
	config *Config

	// The stage in which this module should be built
	buildStage Stage
}

var _ goPackageProducer = (*goPackage)(nil)

func newGoPackageModuleFactory(config *Config) func() (blueprint.Module, []interface{}) {
	return func() (blueprint.Module, []interface{}) {
		module := &goPackage{
			buildStage: StagePrimary,
			config:     config,
		}
		return module, []interface{}{&module.properties}
	}
}

func (g *goPackage) GoPkgPath() string {
	return g.properties.PkgPath
}

func (g *goPackage) GoPkgRoot() string {
	return g.pkgRoot
}

func (g *goPackage) GoPackageTarget() string {
	return g.archiveFile
}

func (g *goPackage) GoTestTarget() string {
	return g.testArchiveFile
}

func (g *goPackage) BuildStage() Stage {
	return g.buildStage
}

func (g *goPackage) SetBuildStage(buildStage Stage) {
	g.buildStage = buildStage
}

func (g *goPackage) IsPluginFor(name string) bool {
	for _, plugin := range g.properties.PluginFor {
		if plugin == name {
			return true
		}
	}
	return false
}

func (g *goPackage) GenerateBuildActions(ctx blueprint.ModuleContext) {
	var (
		name       = ctx.ModuleName()
		hasPlugins = false
		pluginSrc  = ""
		genSrcs    = []string{}
	)

	if g.properties.PkgPath == "" {
		ctx.ModuleErrorf("module %s did not specify a valid pkgPath", name)
		return
	}

	g.pkgRoot = packageRoot(ctx)
	g.archiveFile = filepath.Join(g.pkgRoot,
		filepath.FromSlash(g.properties.PkgPath)+".a")
	if len(g.properties.TestSrcs) > 0 && g.config.runGoTests {
		g.testArchiveFile = filepath.Join(testRoot(ctx),
			filepath.FromSlash(g.properties.PkgPath)+".a")
	}

	ctx.VisitDepsDepthFirstIf(isGoPluginFor(name),
		func(module blueprint.Module) { hasPlugins = true })
	if hasPlugins {
		pluginSrc = filepath.Join(moduleGenSrcDir(ctx), "plugin.go")
		genSrcs = append(genSrcs, pluginSrc)
	}

	// We only actually want to build the builder modules if we're running as
	// minibp (i.e. we're generating a bootstrap Ninja file).  This is to break
	// the circular dependence that occurs when the builder requires a new Ninja
	// file to be built, but building a new ninja file requires the builder to
	// be built.
	if g.config.stage == g.BuildStage() {
		var deps []string

		if hasPlugins && !buildGoPluginLoader(ctx, g.properties.PkgPath, pluginSrc, g.config.stage) {
			return
		}

		if g.config.runGoTests {
			deps = buildGoTest(ctx, testRoot(ctx), g.testArchiveFile,
				g.properties.PkgPath, g.properties.Srcs, genSrcs,
				g.properties.TestSrcs)
		}

		buildGoPackage(ctx, g.pkgRoot, g.properties.PkgPath, g.archiveFile,
			g.properties.Srcs, genSrcs, deps)
	} else if g.config.stage != StageBootstrap {
		if len(g.properties.TestSrcs) > 0 && g.config.runGoTests {
			phonyGoTarget(ctx, g.testArchiveFile, g.properties.TestSrcs, nil, nil)
		}
		phonyGoTarget(ctx, g.archiveFile, g.properties.Srcs, genSrcs, nil)
	}
}

// A goBinary is a module for building executable binaries from Go sources.
type goBinary struct {
	properties struct {
		Srcs           []string
		TestSrcs       []string
		PrimaryBuilder bool
	}

	// The path of the test .a file that is to be built.
	testArchiveFile string

	// The bootstrap Config
	config *Config

	// The stage in which this module should be built
	buildStage Stage
}

func newGoBinaryModuleFactory(config *Config, buildStage Stage) func() (blueprint.Module, []interface{}) {
	return func() (blueprint.Module, []interface{}) {
		module := &goBinary{
			config:     config,
			buildStage: buildStage,
		}
		return module, []interface{}{&module.properties}
	}
}

func (g *goBinary) GoTestTarget() string {
	return g.testArchiveFile
}

func (g *goBinary) BuildStage() Stage {
	return g.buildStage
}

func (g *goBinary) SetBuildStage(buildStage Stage) {
	g.buildStage = buildStage
}

func (g *goBinary) GenerateBuildActions(ctx blueprint.ModuleContext) {
	var (
		name        = ctx.ModuleName()
		objDir      = moduleObjDir(ctx)
		archiveFile = filepath.Join(objDir, name+".a")
		aoutFile    = filepath.Join(objDir, "a.out")
		binaryFile  = filepath.Join("$BinDir", name)
		hasPlugins  = false
		pluginSrc   = ""
		genSrcs     = []string{}
	)

	if len(g.properties.TestSrcs) > 0 && g.config.runGoTests {
		g.testArchiveFile = filepath.Join(testRoot(ctx), name+".a")
	}

	ctx.VisitDepsDepthFirstIf(isGoPluginFor(name),
		func(module blueprint.Module) { hasPlugins = true })
	if hasPlugins {
		pluginSrc = filepath.Join(moduleGenSrcDir(ctx), "plugin.go")
		genSrcs = append(genSrcs, pluginSrc)
	}

	// We only actually want to build the builder modules if we're running as
	// minibp (i.e. we're generating a bootstrap Ninja file).  This is to break
	// the circular dependence that occurs when the builder requires a new Ninja
	// file to be built, but building a new ninja file requires the builder to
	// be built.
	if g.config.stage == g.BuildStage() {
		var deps []string

		if hasPlugins && !buildGoPluginLoader(ctx, "main", pluginSrc, g.config.stage) {
			return
		}

		if g.config.runGoTests {
			deps = buildGoTest(ctx, testRoot(ctx), g.testArchiveFile,
				name, g.properties.Srcs, genSrcs, g.properties.TestSrcs)
		}

		buildGoPackage(ctx, objDir, name, archiveFile, g.properties.Srcs, genSrcs, deps)

		var libDirFlags []string
		ctx.VisitDepsDepthFirstIf(isGoPackageProducer,
			func(module blueprint.Module) {
				dep := module.(goPackageProducer)
				libDir := dep.GoPkgRoot()
				libDirFlags = append(libDirFlags, "-L "+libDir)
			})

		linkArgs := map[string]string{}
		if len(libDirFlags) > 0 {
			linkArgs["libDirFlags"] = strings.Join(libDirFlags, " ")
		}

		ctx.Build(pctx, blueprint.BuildParams{
			Rule:    link,
			Outputs: []string{aoutFile},
			Inputs:  []string{archiveFile},
			Args:    linkArgs,
		})

		ctx.Build(pctx, blueprint.BuildParams{
			Rule:    cp,
			Outputs: []string{binaryFile},
			Inputs:  []string{aoutFile},
		})
	} else if g.config.stage != StageBootstrap {
		if len(g.properties.TestSrcs) > 0 && g.config.runGoTests {
			phonyGoTarget(ctx, g.testArchiveFile, g.properties.TestSrcs, nil, nil)
		}

		intermediates := []string{aoutFile, archiveFile}
		phonyGoTarget(ctx, binaryFile, g.properties.Srcs, genSrcs, intermediates)
	}
}

func buildGoPluginLoader(ctx blueprint.ModuleContext, pkgPath, pluginSrc string, stage Stage) bool {
	ret := true
	name := ctx.ModuleName()

	var pluginPaths []string
	ctx.VisitDepsDepthFirstIf(isGoPluginFor(name),
		func(module blueprint.Module) {
			plugin := module.(goPluginProvider)
			pluginPaths = append(pluginPaths, plugin.GoPkgPath())
			if stage == StageBootstrap {
				ctx.OtherModuleErrorf(module, "plugin %q may not be included in core module %q",
					ctx.OtherModuleName(module), name)
				ret = false
			}
		})

	ctx.Build(pctx, blueprint.BuildParams{
		Rule:    pluginGenSrc,
		Outputs: []string{pluginSrc},
		Args: map[string]string{
			"pkg":     pkgPath,
			"plugins": strings.Join(pluginPaths, " "),
		},
	})

	return ret
}

func buildGoPackage(ctx blueprint.ModuleContext, pkgRoot string,
	pkgPath string, archiveFile string, srcs []string, genSrcs []string, orderDeps []string) {

	srcDir := moduleSrcDir(ctx)
	srcFiles := pathtools.PrefixPaths(srcs, srcDir)
	srcFiles = append(srcFiles, genSrcs...)

	var incFlags []string
	var deps []string
	ctx.VisitDepsDepthFirstIf(isGoPackageProducer,
		func(module blueprint.Module) {
			dep := module.(goPackageProducer)
			incDir := dep.GoPkgRoot()
			target := dep.GoPackageTarget()
			incFlags = append(incFlags, "-I "+incDir)
			deps = append(deps, target)
		})

	compileArgs := map[string]string{
		"pkgPath": pkgPath,
	}

	if len(incFlags) > 0 {
		compileArgs["incFlags"] = strings.Join(incFlags, " ")
	}

	ctx.Build(pctx, blueprint.BuildParams{
		Rule:      compile,
		Outputs:   []string{archiveFile},
		Inputs:    srcFiles,
		OrderOnly: orderDeps,
		Implicits: deps,
		Args:      compileArgs,
	})
}

func buildGoTest(ctx blueprint.ModuleContext, testRoot, testPkgArchive,
	pkgPath string, srcs, genSrcs, testSrcs []string) []string {

	if len(testSrcs) == 0 {
		return nil
	}

	srcDir := moduleSrcDir(ctx)
	testFiles := pathtools.PrefixPaths(testSrcs, srcDir)

	mainFile := filepath.Join(testRoot, "test.go")
	testArchive := filepath.Join(testRoot, "test.a")
	testFile := filepath.Join(testRoot, "test")
	testPassed := filepath.Join(testRoot, "test.passed")

	buildGoPackage(ctx, testRoot, pkgPath, testPkgArchive,
		append(srcs, testSrcs...), genSrcs, nil)

	ctx.Build(pctx, blueprint.BuildParams{
		Rule:    goTestMain,
		Outputs: []string{mainFile},
		Inputs:  testFiles,
		Args: map[string]string{
			"pkg": pkgPath,
		},
	})

	libDirFlags := []string{"-L " + testRoot}
	ctx.VisitDepsDepthFirstIf(isGoPackageProducer,
		func(module blueprint.Module) {
			dep := module.(goPackageProducer)
			libDir := dep.GoPkgRoot()
			libDirFlags = append(libDirFlags, "-L "+libDir)
		})

	ctx.Build(pctx, blueprint.BuildParams{
		Rule:      compile,
		Outputs:   []string{testArchive},
		Inputs:    []string{mainFile},
		Implicits: []string{testPkgArchive},
		Args: map[string]string{
			"pkgPath":  "main",
			"incFlags": "-I " + testRoot,
		},
	})

	ctx.Build(pctx, blueprint.BuildParams{
		Rule:    link,
		Outputs: []string{testFile},
		Inputs:  []string{testArchive},
		Args: map[string]string{
			"libDirFlags": strings.Join(libDirFlags, " "),
		},
	})

	ctx.Build(pctx, blueprint.BuildParams{
		Rule:    test,
		Outputs: []string{testPassed},
		Inputs:  []string{testFile},
		Args: map[string]string{
			"pkg":       pkgPath,
			"pkgSrcDir": filepath.Dir(testFiles[0]),
		},
	})

	return []string{testPassed}
}

func phonyGoTarget(ctx blueprint.ModuleContext, target string, srcs []string,
	gensrcs []string, intermediates []string) {

	var depTargets []string
	ctx.VisitDepsDepthFirstIf(isGoPackageProducer,
		func(module blueprint.Module) {
			dep := module.(goPackageProducer)
			target := dep.GoPackageTarget()
			depTargets = append(depTargets, target)
		})

	moduleDir := ctx.ModuleDir()
	srcs = pathtools.PrefixPaths(srcs, filepath.Join("$srcDir", moduleDir))
	srcs = append(srcs, gensrcs...)

	ctx.Build(pctx, blueprint.BuildParams{
		Rule:      phony,
		Outputs:   []string{target},
		Inputs:    srcs,
		Implicits: depTargets,
	})

	// If one of the source files gets deleted or renamed that will prevent the
	// re-bootstrapping happening because it depends on the missing source file.
	// To get around this we add a build statement using the built-in phony rule
	// for each source file, which will cause Ninja to treat it as dirty if its
	// missing.
	for _, src := range srcs {
		ctx.Build(pctx, blueprint.BuildParams{
			Rule:    blueprint.Phony,
			Outputs: []string{src},
		})
	}

	// If there is no rule to build the intermediate files of a bootstrap go package
	// the cleanup phase of the primary builder will delete the intermediate files,
	// forcing an unnecessary rebuild.  Add phony rules for all of them.
	for _, intermediate := range intermediates {
		ctx.Build(pctx, blueprint.BuildParams{
			Rule:    blueprint.Phony,
			Outputs: []string{intermediate},
		})
	}

}

type singleton struct {
	// The bootstrap Config
	config *Config
}

func newSingletonFactory(config *Config) func() blueprint.Singleton {
	return func() blueprint.Singleton {
		return &singleton{
			config: config,
		}
	}
}

func (s *singleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
	// Find the module that's marked as the "primary builder", which means it's
	// creating the binary that we'll use to generate the non-bootstrap
	// build.ninja file.
	var primaryBuilders []*goBinary
	// rebootstrapDeps contains modules that will be built in StageBootstrap
	var rebootstrapDeps []string
	// primaryRebootstrapDeps contains modules that will be built in StagePrimary
	var primaryRebootstrapDeps []string
	ctx.VisitAllModulesIf(isBootstrapBinaryModule,
		func(module blueprint.Module) {
			binaryModule := module.(*goBinary)
			binaryModuleName := ctx.ModuleName(binaryModule)
			binaryModulePath := filepath.Join("$BinDir", binaryModuleName)

			if binaryModule.BuildStage() == StageBootstrap {
				rebootstrapDeps = append(rebootstrapDeps, binaryModulePath)
			} else {
				primaryRebootstrapDeps = append(primaryRebootstrapDeps, binaryModulePath)
			}
			if binaryModule.properties.PrimaryBuilder {
				primaryBuilders = append(primaryBuilders, binaryModule)
			}
		})

	var primaryBuilderName, primaryBuilderExtraFlags string
	switch len(primaryBuilders) {
	case 0:
		// If there's no primary builder module then that means we'll use minibp
		// as the primary builder.  We can trigger its primary builder mode with
		// the -p flag.
		primaryBuilderName = "minibp"
		primaryBuilderExtraFlags = "-p"

	case 1:
		primaryBuilderName = ctx.ModuleName(primaryBuilders[0])

	default:
		ctx.Errorf("multiple primary builder modules present:")
		for _, primaryBuilder := range primaryBuilders {
			ctx.ModuleErrorf(primaryBuilder, "<-- module %s",
				ctx.ModuleName(primaryBuilder))
		}
		return
	}

	primaryBuilderFile := filepath.Join("$BinDir", primaryBuilderName)

	if s.config.runGoTests {
		primaryBuilderExtraFlags += " -t"
	}

	// Get the filename of the top-level Blueprints file to pass to minibp.
	topLevelBlueprints := filepath.Join("$srcDir",
		filepath.Base(s.config.topLevelBlueprintsFile))

	rebootstrapDeps = append(rebootstrapDeps, topLevelBlueprints)
	primaryRebootstrapDeps = append(primaryRebootstrapDeps, topLevelBlueprints)

	mainNinjaFile := filepath.Join(bootstrapDir, "main.ninja.in")
	mainNinjaTimestampFile := mainNinjaFile + ".timestamp"
	mainNinjaTimestampDepFile := mainNinjaTimestampFile + ".d"
	primaryBuilderNinjaFile := filepath.Join(bootstrapDir, "primary.ninja.in")
	primaryBuilderNinjaTimestampFile := primaryBuilderNinjaFile + ".timestamp"
	primaryBuilderNinjaTimestampDepFile := primaryBuilderNinjaTimestampFile + ".d"
	bootstrapNinjaFile := filepath.Join(bootstrapDir, "bootstrap.ninja.in")
	docsFile := filepath.Join(docsDir, primaryBuilderName+".html")

	primaryRebootstrapDeps = append(primaryRebootstrapDeps, docsFile)

	// If the tests change, be sure to re-run them. These need to be
	// dependencies for the ninja file so that it's updated after these
	// run. Otherwise we'd never leave the bootstrap stage, since the
	// timestamp file would be newer than the ninja file.
	ctx.VisitAllModulesIf(isGoTestProducer,
		func(module blueprint.Module) {
			testModule := module.(goTestProducer)
			target := testModule.GoTestTarget()
			if target != "" {
				if testModule.BuildStage() == StageBootstrap {
					rebootstrapDeps = append(rebootstrapDeps, target)
				} else {
					primaryRebootstrapDeps = append(primaryRebootstrapDeps, target)
				}
			}
		})

	switch s.config.stage {
	case StageBootstrap:
		// We're generating a bootstrapper Ninja file, so we need to set things
		// up to rebuild the build.ninja file using the primary builder.

		// BuildDir must be different between the three stages, otherwise the
		// cleanup process will remove files from the other builds.
		ctx.SetNinjaBuildDir(pctx, miniBootstrapDir)

		// Generate the Ninja file to build the primary builder. Save the
		// timestamps and deps, so that we can come back to this stage if
		// it needs to be regenerated.
		primarybp := ctx.Rule(pctx, "primarybp",
			blueprint.RuleParams{
				Command: fmt.Sprintf("%s --build-primary $runTests -m $bootstrapManifest "+
					"--timestamp $timestamp --timestampdep $timestampdep "+
					"-b $buildDir -d $outfile.d -o $outfile $in", minibpFile),
				Description: "minibp $outfile",
				Depfile:     "$outfile.d",
			},
			"runTests", "timestamp", "timestampdep", "outfile")

		args := map[string]string{
			"outfile":      primaryBuilderNinjaFile,
			"timestamp":    primaryBuilderNinjaTimestampFile,
			"timestampdep": primaryBuilderNinjaTimestampDepFile,
		}

		if s.config.runGoTests {
			args["runTests"] = "-t"
		}

		ctx.Build(pctx, blueprint.BuildParams{
			Rule:      primarybp,
			Outputs:   []string{primaryBuilderNinjaFile, primaryBuilderNinjaTimestampFile},
			Inputs:    []string{topLevelBlueprints},
			Implicits: rebootstrapDeps,
			Args:      args,
		})

		// Rebuild the bootstrap Ninja file using the minibp that we just built.
		// If this produces a difference, choosestage will retrigger this stage.
		minibp := ctx.Rule(pctx, "minibp",
			blueprint.RuleParams{
				Command: fmt.Sprintf("%s $runTests -m $bootstrapManifest "+
					"-b $buildDir -d $out.d -o $out $in", minibpFile),
				// $bootstrapManifest is here so that when it is updated, we
				// force a rebuild of bootstrap.ninja.in. chooseStage should
				// have already copied the new version over, but kept the old
				// timestamps to force this regeneration.
				CommandDeps: []string{"$bootstrapManifest", minibpFile},
				Description: "minibp $out",
				Generator:   true,
				Depfile:     "$out.d",
			},
			"runTests")

		args = map[string]string{}

		if s.config.runGoTests {
			args["runTests"] = "-t"
		}

		ctx.Build(pctx, blueprint.BuildParams{
			Rule:    minibp,
			Outputs: []string{bootstrapNinjaFile},
			Inputs:  []string{topLevelBlueprints},
			Args:    args,
		})

		// When the current build.ninja file is a bootstrapper, we always want
		// to have it replace itself with a non-bootstrapper build.ninja.  To
		// accomplish that we depend on a file that should never exist and
		// "build" it using Ninja's built-in phony rule.
		notAFile := filepath.Join(bootstrapDir, "notAFile")
		ctx.Build(pctx, blueprint.BuildParams{
			Rule:    blueprint.Phony,
			Outputs: []string{notAFile},
		})

		ctx.Build(pctx, blueprint.BuildParams{
			Rule:      chooseStage,
			Outputs:   []string{filepath.Join(bootstrapDir, "build.ninja.in")},
			Inputs:    []string{bootstrapNinjaFile, primaryBuilderNinjaFile},
			Implicits: []string{notAFile},
			Args: map[string]string{
				"current": bootstrapNinjaFile,
			},
		})

	case StagePrimary:
		// We're generating a bootstrapper Ninja file, so we need to set things
		// up to rebuild the build.ninja file using the primary builder.

		// BuildDir must be different between the three stages, otherwise the
		// cleanup process will remove files from the other builds.
		ctx.SetNinjaBuildDir(pctx, bootstrapDir)

		// We generate the depfile here that includes the dependencies for all
		// the Blueprints files that contribute to generating the big build
		// manifest (build.ninja file).  This depfile will be used by the non-
		// bootstrap build manifest to determine whether it should touch the
		// timestamp file to trigger a re-bootstrap.
		bigbp := ctx.Rule(pctx, "bigbp",
			blueprint.RuleParams{
				Command: fmt.Sprintf("%s %s -m $bootstrapManifest "+
					"--timestamp $timestamp --timestampdep $timestampdep "+
					"-b $buildDir -d $outfile.d -o $outfile $in", primaryBuilderFile,
					primaryBuilderExtraFlags),
				Description: fmt.Sprintf("%s $outfile", primaryBuilderName),
				Depfile:     "$outfile.d",
			},
			"timestamp", "timestampdep", "outfile")

		ctx.Build(pctx, blueprint.BuildParams{
			Rule:      bigbp,
			Outputs:   []string{mainNinjaFile, mainNinjaTimestampFile},
			Inputs:    []string{topLevelBlueprints},
			Implicits: primaryRebootstrapDeps,
			Args: map[string]string{
				"timestamp":    mainNinjaTimestampFile,
				"timestampdep": mainNinjaTimestampDepFile,
				"outfile":      mainNinjaFile,
			},
		})

		// Generate build system docs for the primary builder.  Generating docs reads the source
		// files used to build the primary builder, but that dependency will be picked up through
		// the dependency on the primary builder itself.  There are no dependencies on the
		// Blueprints files, as any relevant changes to the Blueprints files would have caused
		// a rebuild of the primary builder.
		bigbpDocs := ctx.Rule(pctx, "bigbpDocs",
			blueprint.RuleParams{
				Command: fmt.Sprintf("%s %s -b $buildDir --docs $out %s", primaryBuilderFile,
					primaryBuilderExtraFlags, topLevelBlueprints),
				CommandDeps: []string{primaryBuilderFile},
				Description: fmt.Sprintf("%s docs $out", primaryBuilderName),
			})

		ctx.Build(pctx, blueprint.BuildParams{
			Rule:    bigbpDocs,
			Outputs: []string{docsFile},
		})

		// Detect whether we need to rebuild the primary stage by going back to
		// the bootstrapper. If this is newer than the primaryBuilderNinjaFile,
		// then chooseStage will trigger a rebuild of primaryBuilderNinjaFile by
		// returning to the bootstrap stage.
		ctx.Build(pctx, blueprint.BuildParams{
			Rule:      touch,
			Outputs:   []string{primaryBuilderNinjaTimestampFile},
			Implicits: rebootstrapDeps,
			Args: map[string]string{
				"depfile":   primaryBuilderNinjaTimestampDepFile,
				"generator": "true",
			},
		})

		// When the current build.ninja file is a bootstrapper, we always want
		// to have it replace itself with a non-bootstrapper build.ninja.  To
		// accomplish that we depend on a file that should never exist and
		// "build" it using Ninja's built-in phony rule.
		notAFile := filepath.Join(bootstrapDir, "notAFile")
		ctx.Build(pctx, blueprint.BuildParams{
			Rule:    blueprint.Phony,
			Outputs: []string{notAFile},
		})

		ctx.Build(pctx, blueprint.BuildParams{
			Rule:      chooseStage,
			Outputs:   []string{filepath.Join(bootstrapDir, "build.ninja.in")},
			Inputs:    []string{bootstrapNinjaFile, primaryBuilderNinjaFile, mainNinjaFile},
			Implicits: []string{notAFile, primaryBuilderNinjaTimestampFile},
			Args: map[string]string{
				"current": primaryBuilderNinjaFile,
			},
		})

		// Create this phony rule so that upgrades don't delete these during
		// cleanup
		ctx.Build(pctx, blueprint.BuildParams{
			Rule:    blueprint.Phony,
			Outputs: []string{bootstrapNinjaFile},
		})

	case StageMain:
		ctx.SetNinjaBuildDir(pctx, "${buildDir}")

		// We're generating a non-bootstrapper Ninja file, so we need to set it
		// up to re-bootstrap if necessary. We do this by making build.ninja.in
		// depend on the various Ninja files, the source build.ninja.in, and
		// on the timestamp files.
		//
		// The timestamp files themselves are set up with the same dependencies
		// as their Ninja files, including their own depfile. If any of the
		// dependencies need to be updated, we'll touch the timestamp file,
		// which will tell choosestage to switch to the stage that rebuilds
		// that Ninja file.
		ctx.Build(pctx, blueprint.BuildParams{
			Rule:      touch,
			Outputs:   []string{primaryBuilderNinjaTimestampFile},
			Implicits: rebootstrapDeps,
			Args: map[string]string{
				"depfile":   primaryBuilderNinjaTimestampDepFile,
				"generator": "true",
			},
		})

		ctx.Build(pctx, blueprint.BuildParams{
			Rule:      touch,
			Outputs:   []string{mainNinjaTimestampFile},
			Implicits: primaryRebootstrapDeps,
			Args: map[string]string{
				"depfile":   mainNinjaTimestampDepFile,
				"generator": "true",
			},
		})

		ctx.Build(pctx, blueprint.BuildParams{
			Rule:      chooseStage,
			Outputs:   []string{filepath.Join(bootstrapDir, "build.ninja.in")},
			Inputs:    []string{bootstrapNinjaFile, primaryBuilderNinjaFile, mainNinjaFile},
			Implicits: []string{primaryBuilderNinjaTimestampFile, mainNinjaTimestampFile},
			Args: map[string]string{
				"current":   mainNinjaFile,
				"generator": "true",
			},
		})

		// Create this phony rule so that upgrades don't delete these during
		// cleanup
		ctx.Build(pctx, blueprint.BuildParams{
			Rule:    blueprint.Phony,
			Outputs: []string{mainNinjaFile, docsFile, "$bootstrapManifest"},
		})

		if primaryBuilderName == "minibp" {
			// This is a standalone Blueprint build, so we copy the minibp
			// binary to the "bin" directory to make it easier to find.
			finalMinibp := filepath.Join("$buildDir", "bin", primaryBuilderName)
			ctx.Build(pctx, blueprint.BuildParams{
				Rule:    cp,
				Inputs:  []string{primaryBuilderFile},
				Outputs: []string{finalMinibp},
			})
		}
	}

	ctx.Build(pctx, blueprint.BuildParams{
		Rule:    bootstrap,
		Outputs: []string{"$buildDir/build.ninja"},
		Inputs:  []string{filepath.Join(bootstrapDir, "build.ninja.in")},
	})
}

// packageRoot returns the module-specific package root directory path.  This
// directory is where the final package .a files are output and where dependant
// modules search for this package via -I arguments.
func packageRoot(ctx blueprint.ModuleContext) string {
	return filepath.Join(bootstrapDir, ctx.ModuleName(), "pkg")
}

// testRoot returns the module-specific package root directory path used for
// building tests. The .a files generated here will include everything from
// packageRoot, plus the test-only code.
func testRoot(ctx blueprint.ModuleContext) string {
	return filepath.Join(bootstrapDir, ctx.ModuleName(), "test")
}

// moduleSrcDir returns the path of the directory that all source file paths are
// specified relative to.
func moduleSrcDir(ctx blueprint.ModuleContext) string {
	return filepath.Join("$srcDir", ctx.ModuleDir())
}

// moduleObjDir returns the module-specific object directory path.
func moduleObjDir(ctx blueprint.ModuleContext) string {
	return filepath.Join(bootstrapDir, ctx.ModuleName(), "obj")
}

// moduleGenSrcDir returns the module-specific generated sources path.
func moduleGenSrcDir(ctx blueprint.ModuleContext) string {
	return filepath.Join(bootstrapDir, ctx.ModuleName(), "gen")
}