// 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")
}