Merge pull request #213 from danw/bpglob_bootstrap
Run globs during earlier bootstrap phases
diff --git a/blueprint_impl.bash b/blueprint_impl.bash
index 514ba31..6f5abba 100644
--- a/blueprint_impl.bash
+++ b/blueprint_impl.bash
@@ -28,6 +28,8 @@
BUILDDIR="${BUILDDIR}/.minibootstrap" build_go minibp github.com/google/blueprint/bootstrap/minibp
+BUILDDIR="${BUILDDIR}/.minibootstrap" build_go bpglob github.com/google/blueprint/bootstrap/bpglob
+
# Build the bootstrap build.ninja
"${NINJA}" -w dupbuild=err -f "${BUILDDIR}/.minibootstrap/build.ninja"
diff --git a/bootstrap.bash b/bootstrap.bash
index 24f16c2..c02fe81 100755
--- a/bootstrap.bash
+++ b/bootstrap.bash
@@ -107,6 +107,10 @@
echo "builddir = $NINJA_BUILDDIR" >> $BUILDDIR/.minibootstrap/build.ninja
echo "include $BLUEPRINTDIR/bootstrap/build.ninja" >> $BUILDDIR/.minibootstrap/build.ninja
+if [ ! -f "$BUILDDIR/.minibootstrap/build-globs.ninja" ]; then
+ touch "$BUILDDIR/.minibootstrap/build-globs.ninja"
+fi
+
echo "BLUEPRINT_BOOTSTRAP_VERSION=2" > $BUILDDIR/.blueprint.bootstrap
echo "SRCDIR=\"${SRCDIR}\"" >> $BUILDDIR/.blueprint.bootstrap
echo "BLUEPRINTDIR=\"${BLUEPRINTDIR}\"" >> $BUILDDIR/.blueprint.bootstrap
diff --git a/bootstrap/bootstrap.go b/bootstrap/bootstrap.go
index e9a8f01..92b8a32 100644
--- a/bootstrap/bootstrap.go
+++ b/bootstrap/bootstrap.go
@@ -17,6 +17,8 @@
import (
"fmt"
"go/build"
+ "io/ioutil"
+ "os"
"path/filepath"
"runtime"
"strings"
@@ -118,14 +120,14 @@
generateBuildNinja = pctx.StaticRule("build.ninja",
blueprint.RuleParams{
- Command: "$builder $extra -b $buildDir -n $ninjaBuildDir -d $out.d -o $out $in",
+ Command: "$builder $extra -b $buildDir -n $ninjaBuildDir -d $out.d -globFile $globFile -o $out $in",
CommandDeps: []string{"$builder"},
Description: "$builder $out",
Deps: blueprint.DepsGCC,
Depfile: "$out.d",
Restat: true,
},
- "builder", "extra", "generator")
+ "builder", "extra", "generator", "globFile")
// Work around a Ninja issue. See https://github.com/martine/ninja/pull/634
phony = pctx.StaticRule("phony",
@@ -668,32 +670,33 @@
topLevelBlueprints := filepath.Join("$srcDir",
filepath.Base(s.config.topLevelBlueprintsFile))
- mainNinjaFile := filepath.Join("$buildDir", "build.ninja")
- primaryBuilderNinjaFile := filepath.Join(bootstrapDir, "build.ninja")
-
ctx.SetNinjaBuildDir(pctx, "${ninjaBuildDir}")
- // Build the main build.ninja
- ctx.Build(pctx, blueprint.BuildParams{
- Rule: generateBuildNinja,
- Outputs: []string{mainNinjaFile},
- Inputs: []string{topLevelBlueprints},
- Args: map[string]string{
- "builder": primaryBuilderFile,
- "extra": primaryBuilderExtraFlags,
- },
- })
+ if s.config.stage == StagePrimary {
+ mainNinjaFile := filepath.Join("$buildDir", "build.ninja")
+ primaryBuilderNinjaGlobFile := filepath.Join(BuildDir, bootstrapSubDir, "build-globs.ninja")
- // Add a way to rebuild the primary build.ninja so that globs works
- ctx.Build(pctx, blueprint.BuildParams{
- Rule: generateBuildNinja,
- Outputs: []string{primaryBuilderNinjaFile},
- Inputs: []string{topLevelBlueprints},
- Args: map[string]string{
- "builder": minibpFile,
- "extra": extraSharedFlagString,
- },
- })
+ if _, err := os.Stat(primaryBuilderNinjaGlobFile); os.IsNotExist(err) {
+ err = ioutil.WriteFile(primaryBuilderNinjaGlobFile, nil, 0666)
+ if err != nil {
+ ctx.Errorf("Failed to create empty ninja file: %s", err)
+ }
+ }
+
+ ctx.AddSubninja(primaryBuilderNinjaGlobFile)
+
+ // Build the main build.ninja
+ ctx.Build(pctx, blueprint.BuildParams{
+ Rule: generateBuildNinja,
+ Outputs: []string{mainNinjaFile},
+ Inputs: []string{topLevelBlueprints},
+ Args: map[string]string{
+ "builder": primaryBuilderFile,
+ "extra": primaryBuilderExtraFlags,
+ "globFile": primaryBuilderNinjaGlobFile,
+ },
+ })
+ }
if s.config.stage == StageMain {
if primaryBuilderName == "minibp" {
diff --git a/bootstrap/build.ninja b/bootstrap/build.ninja
index b338843..5787c72 100644
--- a/bootstrap/build.ninja
+++ b/bootstrap/build.ninja
@@ -7,8 +7,11 @@
ninja_required_version = 1.7.0
+myGlobs = ${bootstrapBuildDir}/.minibootstrap/build-globs.ninja
+subninja ${myGlobs}
+
rule build.ninja
- command = ${builder} ${extraArgs} -b ${bootstrapBuildDir} -n ${builddir} -d ${out}.d -o ${out} ${in}
+ command = ${builder} ${extraArgs} -b ${bootstrapBuildDir} -n ${builddir} -d ${out}.d -globFile ${myGlobs} -o ${out} ${in}
deps = gcc
depfile = ${out}.d
description = ${builder} ${out}
diff --git a/bootstrap/cleanup.go b/bootstrap/cleanup.go
index a4a18c7..4a8ce25 100644
--- a/bootstrap/cleanup.go
+++ b/bootstrap/cleanup.go
@@ -30,9 +30,9 @@
// removeAbandonedFilesUnder removes any files that appear in the Ninja log, and
// are prefixed with one of the `under` entries, but that are not currently
-// build targets.
+// build targets, or in `exempt`
func removeAbandonedFilesUnder(ctx *blueprint.Context, config *Config,
- srcDir string, under []string) error {
+ srcDir string, under, exempt []string) error {
if len(under) == 0 {
return nil
@@ -57,6 +57,10 @@
replacedTarget := replacer.Replace(target)
targets[filepath.Clean(replacedTarget)] = true
}
+ for _, target := range exempt {
+ replacedTarget := replacer.Replace(target)
+ targets[filepath.Clean(replacedTarget)] = true
+ }
filePaths, err := parseNinjaLog(ninjaBuildDir, under)
if err != nil {
diff --git a/bootstrap/command.go b/bootstrap/command.go
index 04eb535..ac3fd00 100644
--- a/bootstrap/command.go
+++ b/bootstrap/command.go
@@ -32,6 +32,7 @@
var (
outFile string
+ globFile string
depFile string
docFile string
cpuprofile string
@@ -48,6 +49,7 @@
func init() {
flag.StringVar(&outFile, "o", "build.ninja", "the Ninja file to output")
+ flag.StringVar(&globFile, "globFile", "build-globs.ninja", "the Ninja file of globs to output")
flag.StringVar(&BuildDir, "b", ".", "the build output directory")
flag.StringVar(&NinjaBuildDir, "n", "", "the ninja builddir directory")
flag.StringVar(&depFile, "d", "", "the dependency file to output")
@@ -179,6 +181,18 @@
fatalf("error writing %s: %s", outFile, err)
}
+ if globFile != "" {
+ buffer, errs := generateGlobNinjaFile(ctx.Globs)
+ if len(errs) > 0 {
+ fatalErrors(errs)
+ }
+
+ err = ioutil.WriteFile(globFile, buffer, outFilePermissions)
+ if err != nil {
+ fatalf("error writing %s: %s", outFile, err)
+ }
+ }
+
if depFile != "" {
err := deptools.WriteDepFile(depFile, outFile, deps)
if err != nil {
@@ -187,8 +201,8 @@
}
if c, ok := config.(ConfigRemoveAbandonedFilesUnder); ok {
- under := c.RemoveAbandonedFilesUnder()
- err := removeAbandonedFilesUnder(ctx, bootstrapConfig, SrcDir, under)
+ under, except := c.RemoveAbandonedFilesUnder()
+ err := removeAbandonedFilesUnder(ctx, bootstrapConfig, SrcDir, under, except)
if err != nil {
fatalf("error removing abandoned files: %s", err)
}
diff --git a/bootstrap/config.go b/bootstrap/config.go
index 5785ea7..790ac4b 100644
--- a/bootstrap/config.go
+++ b/bootstrap/config.go
@@ -69,10 +69,11 @@
}
type ConfigRemoveAbandonedFilesUnder interface {
- // RemoveAbandonedFilesUnder should return a slice of path prefixes that
- // will be cleaned of files that are no longer active targets, but are
- // listed in the .ninja_log.
- RemoveAbandonedFilesUnder() []string
+ // RemoveAbandonedFilesUnder should return two slices:
+ // - a slice of path prefixes that will be cleaned of files that are no
+ // longer active targets, but are listed in the .ninja_log.
+ // - a slice of paths that are exempt from cleaning
+ RemoveAbandonedFilesUnder() (under, except []string)
}
type ConfigBlueprintToolLocation interface {
diff --git a/bootstrap/doc.go b/bootstrap/doc.go
index 3c7108e..69a1784 100644
--- a/bootstrap/doc.go
+++ b/bootstrap/doc.go
@@ -120,23 +120,27 @@
// - Runs .bootstrap/build.ninja to build and run the primary builder
// - Runs build.ninja to build your code
//
-// Microfactory takes care of building an up to date version of `minibp` under
-// the .minibootstrap/ directory.
+// Microfactory takes care of building an up to date version of `minibp` and
+// `bpglob` under the .minibootstrap/ directory.
//
// During <builddir>/.minibootstrap/build.ninja, the following actions are
// taken, if necessary:
//
// - Run minibp to generate .bootstrap/build.ninja (Primary stage)
+// - Includes .minibootstrap/build-globs.ninja, which defines rules to
+// run bpglob during incremental builds. These outputs are listed in
+// the dependency file output by minibp.
//
// During the <builddir>/.bootstrap/build.ninja, the following actions are
// taken, if necessary:
//
-// - Rebuild .bootstrap/build.ninja, usually due to globs changing --
-// other dependencies will trigger it to be built during minibootstrap
// - Build the primary builder, anything marked `default: true`, and
// any dependencies.
// - Run the primary builder to generate build.ninja
// - Run the primary builder to extract documentation
+// - Includes .bootstrap/build-globs.ninja, which defines rules to run
+// bpglob during incremental builds. These outputs are listed in the
+// dependency file output by the primary builder.
//
// Then the main stage is at <builddir>/build.ninja, and will contain all the
// rules generated by the primary builder. In addition, the bootstrap code
diff --git a/bootstrap/glob.go b/bootstrap/glob.go
index 160ef58..9841611 100644
--- a/bootstrap/glob.go
+++ b/bootstrap/glob.go
@@ -15,6 +15,7 @@
package bootstrap
import (
+ "bytes"
"fmt"
"path/filepath"
"strings"
@@ -40,7 +41,7 @@
// in a build failure with a "missing and no known rule to make it" error.
var (
- globCmd = filepath.Join("$BinDir", "bpglob")
+ globCmd = filepath.Join(miniBootstrapDir, "bpglob")
// globRule rule traverses directories to produce a list of files that match $glob
// and writes it to $out if it has changed, and writes the directories to $out.d
@@ -111,6 +112,7 @@
// primary builder if the results change.
type globSingleton struct {
globLister func() []blueprint.GlobPath
+ writeRule bool
}
func globSingletonFactory(ctx *blueprint.Context) func() blueprint.Singleton {
@@ -124,15 +126,52 @@
func (s *globSingleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
for _, g := range s.globLister() {
fileListFile := filepath.Join(BuildDir, ".glob", g.Name)
- depFile := fileListFile + ".d"
- fileList := strings.Join(g.Files, "\n") + "\n"
- pathtools.WriteFileIfChanged(fileListFile, []byte(fileList), 0666)
- deptools.WriteDepFile(depFile, fileListFile, g.Deps)
+ if s.writeRule {
+ depFile := fileListFile + ".d"
- GlobFile(ctx, g.Pattern, g.Excludes, fileListFile, depFile)
+ fileList := strings.Join(g.Files, "\n") + "\n"
+ pathtools.WriteFileIfChanged(fileListFile, []byte(fileList), 0666)
+ deptools.WriteDepFile(depFile, fileListFile, g.Deps)
- // Make build.ninja depend on the fileListFile
- ctx.AddNinjaFileDeps(fileListFile)
+ GlobFile(ctx, g.Pattern, g.Excludes, fileListFile, depFile)
+ } else {
+ // Make build.ninja depend on the fileListFile
+ ctx.AddNinjaFileDeps(fileListFile)
+ }
}
}
+
+func generateGlobNinjaFile(globLister func() []blueprint.GlobPath) ([]byte, []error) {
+ ctx := blueprint.NewContext()
+ ctx.RegisterSingletonType("glob", func() blueprint.Singleton {
+ return &globSingleton{
+ globLister: globLister,
+ writeRule: true,
+ }
+ })
+
+ extraDeps, errs := ctx.ResolveDependencies(nil)
+ if len(extraDeps) > 0 {
+ return nil, []error{fmt.Errorf("shouldn't have extra deps")}
+ }
+ if len(errs) > 0 {
+ return nil, errs
+ }
+
+ extraDeps, errs = ctx.PrepareBuildActions(nil)
+ if len(extraDeps) > 0 {
+ return nil, []error{fmt.Errorf("shouldn't have extra deps")}
+ }
+ if len(errs) > 0 {
+ return nil, errs
+ }
+
+ buf := bytes.NewBuffer(nil)
+ err := ctx.WriteBuildFile(buf)
+ if err != nil {
+ return nil, []error{err}
+ }
+
+ return buf.Bytes(), nil
+}
diff --git a/bootstrap/minibp/main.go b/bootstrap/minibp/main.go
index 72ed9f6..1714739 100644
--- a/bootstrap/minibp/main.go
+++ b/bootstrap/minibp/main.go
@@ -37,8 +37,12 @@
return c.generatingPrimaryBuilder
}
-func (c Config) RemoveAbandonedFilesUnder() []string {
- return []string{filepath.Join(bootstrap.BuildDir, ".bootstrap")}
+func (c Config) RemoveAbandonedFilesUnder() (under, exempt []string) {
+ if c.generatingPrimaryBuilder {
+ under = []string{filepath.Join(bootstrap.BuildDir, ".bootstrap")}
+ exempt = []string{filepath.Join(bootstrap.BuildDir, ".bootstrap", "build.ninja")}
+ }
+ return
}
func main() {
diff --git a/context.go b/context.go
index 919377c..69f26f6 100644
--- a/context.go
+++ b/context.go
@@ -102,6 +102,8 @@
requiredNinjaMinor int // For the ninja_required_version variable
requiredNinjaMicro int // For the ninja_required_version variable
+ subninjas []string
+
// set lazily by sortedModuleGroups
cachedSortedModuleGroups []*moduleGroup
@@ -2939,6 +2941,11 @@
return err
}
+ err = c.writeSubninjas(nw)
+ if err != nil {
+ return err
+ }
+
// TODO: Group the globals by package.
err = c.writeGlobalVariables(nw)
@@ -3048,6 +3055,13 @@
return nw.BlankLine()
}
+func (c *Context) writeSubninjas(nw *ninjaWriter) error {
+ for _, subninja := range c.subninjas {
+ nw.Subninja(subninja)
+ }
+ return nw.BlankLine()
+}
+
func (c *Context) writeBuildDir(nw *ninjaWriter) error {
if c.ninjaBuildDir != nil {
err := nw.Assign("builddir", c.ninjaBuildDir.Value(c.pkgNames))
diff --git a/ninja_writer.go b/ninja_writer.go
index a61667d..5902986 100644
--- a/ninja_writer.go
+++ b/ninja_writer.go
@@ -193,6 +193,12 @@
return wrapper.Flush()
}
+func (n *ninjaWriter) Subninja(file string) error {
+ n.justDidBlankLine = false
+ _, err := fmt.Fprintf(n.writer, "subninja %s\n", file)
+ return err
+}
+
func (n *ninjaWriter) BlankLine() (err error) {
// We don't output multiple blank lines in a row.
if !n.justDidBlankLine {
diff --git a/ninja_writer_test.go b/ninja_writer_test.go
index 44e4ff8..cc880e5 100644
--- a/ninja_writer_test.go
+++ b/ninja_writer_test.go
@@ -74,6 +74,12 @@
},
{
input: func(w *ninjaWriter) {
+ ck(w.Subninja("build.ninja"))
+ },
+ output: "subninja build.ninja\n",
+ },
+ {
+ input: func(w *ninjaWriter) {
ck(w.BlankLine())
},
output: "\n",
diff --git a/singleton_ctx.go b/singleton_ctx.go
index bbfce00..333811d 100644
--- a/singleton_ctx.go
+++ b/singleton_ctx.go
@@ -47,6 +47,10 @@
// set at most one time for a single build, later calls are ignored.
SetNinjaBuildDir(pctx PackageContext, value string)
+ // AddSubninja adds a ninja file to include with subninja. This should likely
+ // only ever be used inside bootstrap to handle glob rules.
+ AddSubninja(file string)
+
// Eval takes a string with embedded ninja variables, and returns a string
// with all of the variables recursively expanded. Any variables references
// are expanded in the scope of the PackageContext.
@@ -203,6 +207,10 @@
s.context.setNinjaBuildDir(ninjaValue)
}
+func (s *singletonContext) AddSubninja(file string) {
+ s.context.subninjas = append(s.context.subninjas, file)
+}
+
func (s *singletonContext) VisitAllModules(visit func(Module)) {
s.context.VisitAllModules(visit)
}