aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorColin Cross <ccross@android.com>2016-11-01 11:10:51 -0700
committerColin Cross <ccross@android.com>2016-11-03 13:54:03 -0700
commit127d2eae8b7e706540e40c0422bfd41b063ca3e7 (patch)
tree64137bae7834b5439dd499ea76c29d6fbefc77ce
parentb589835c0dbaf1ad0e830c6043a0481ca916b084 (diff)
downloadandroid_build_blueprint-127d2eae8b7e706540e40c0422bfd41b063ca3e7.tar.gz
android_build_blueprint-127d2eae8b7e706540e40c0422bfd41b063ca3e7.tar.bz2
android_build_blueprint-127d2eae8b7e706540e40c0422bfd41b063ca3e7.zip
Import globbing from Soong
Add globbing with dependency checking to blueprint. Calling ModuleContext.GlobWithDeps or SingletonContext.GlobWithDeps will return a list of files that match the globs, while also adding efficient dependencies to rerun the primary builder if a file that matches the glob is added or removed. Also use the globbing support for optional_subdirs=, subdirs= and build= lines in blueprints files. The globbing slightly changes the behavior of subname= lines, it no longer falls back to looking for a file called "Blueprints". Blueprint files that need to include a subdirectory with a different name can use build= instead of subdir= to directly include them. The Blueprints file is updated to reset subname="Blueprints" in case we want to include subdirectories inside blueprint and the primary builder has changed the subname. Also adds a new test directory that contains a simple primary builder tree to test regeneration for globbing, and runs the tests in travis. Change-Id: I83ce525fd11e11579cc58ba5308d01ca8eea7bc6
-rw-r--r--.gitignore1
-rw-r--r--.travis.yml1
-rw-r--r--Blueprints11
-rw-r--r--bootstrap/bpglob/bpglob.go77
-rw-r--r--bootstrap/command.go2
-rw-r--r--bootstrap/glob.go144
-rw-r--r--build.ninja.in70
-rw-r--r--context.go147
-rw-r--r--context_test.go2
-rw-r--r--glob.go116
-rw-r--r--module_ctx.go11
-rw-r--r--pathtools/glob.go110
-rw-r--r--singleton_ctx.go11
-rw-r--r--tests/test_tree/Blueprints3
-rw-r--r--tests/test_tree/a/Blueprints1
l---------tests/test_tree/blueprint1
-rw-r--r--tests/test_tree/build.ninja.in335
-rwxr-xr-xtests/test_tree_tests.sh78
18 files changed, 1012 insertions, 109 deletions
diff --git a/.gitignore b/.gitignore
index de99854..3a9ef96 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
out.test
+src.test
diff --git a/.travis.yml b/.travis.yml
index ecb8c5c..2824056 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -23,3 +23,4 @@ script:
- ./blueprint.bash
- diff -us ../build.ninja.in .minibootstrap/build.ninja.in
- ../tests/test.sh
+ - ../tests/test_tree_tests.sh
diff --git a/Blueprints b/Blueprints
index f0a0f87..702d0de 100644
--- a/Blueprints
+++ b/Blueprints
@@ -9,6 +9,7 @@ bootstrap_go_package(
srcs = [
"context.go",
"fs.go",
+ "glob.go",
"live_tracker.go",
"mangle.go",
"module_ctx.go",
@@ -55,6 +56,9 @@ bootstrap_go_package(
bootstrap_go_package(
name = "blueprint-pathtools",
pkgPath = "github.com/google/blueprint/pathtools",
+ deps = [
+ "blueprint-deptools",
+ ],
srcs = [
"pathtools/lists.go",
"pathtools/glob.go",
@@ -97,6 +101,7 @@ bootstrap_go_package(
"bootstrap/command.go",
"bootstrap/config.go",
"bootstrap/doc.go",
+ "bootstrap/glob.go",
"bootstrap/writedocs.go",
],
)
@@ -122,6 +127,12 @@ bootstrap_core_go_binary(
srcs = ["bootstrap/minibp/main.go"],
)
+bootstrap_core_go_binary(
+ name = "bpglob",
+ deps = ["blueprint-pathtools"],
+ srcs = ["bootstrap/bpglob/bpglob.go"],
+)
+
blueprint_go_binary(
name = "bpfmt",
deps = ["blueprint-parser"],
diff --git a/bootstrap/bpglob/bpglob.go b/bootstrap/bpglob/bpglob.go
new file mode 100644
index 0000000..cb6633b
--- /dev/null
+++ b/bootstrap/bpglob/bpglob.go
@@ -0,0 +1,77 @@
+// Copyright 2015 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.
+
+// bpglob is the command line tool that checks if the list of files matching a glob has
+// changed, and only updates the output file list if it has changed. It is used to optimize
+// out build.ninja regenerations when non-matching files are added. See
+// github.com/google/blueprint/bootstrap/glob.go for a longer description.
+package main
+
+import (
+ "flag"
+ "fmt"
+ "os"
+
+ "github.com/google/blueprint/pathtools"
+)
+
+var (
+ out = flag.String("o", "", "file to write list of files that match glob")
+
+ excludes multiArg
+)
+
+func init() {
+ flag.Var(&excludes, "e", "pattern to exclude from results")
+}
+
+type multiArg []string
+
+func (m *multiArg) String() string {
+ return `""`
+}
+
+func (m *multiArg) Set(s string) error {
+ *m = append(*m, s)
+ return nil
+}
+
+func (m *multiArg) Get() interface{} {
+ return m
+}
+
+func usage() {
+ fmt.Fprintf(os.Stderr, "usage: bpglob -o out glob\n")
+ flag.PrintDefaults()
+ os.Exit(2)
+}
+
+func main() {
+ flag.Parse()
+
+ if *out == "" {
+ fmt.Fprintf(os.Stderr, "error: -o is required\n")
+ usage()
+ }
+
+ if flag.NArg() != 1 {
+ usage()
+ }
+
+ _, err := pathtools.GlobWithDepFile(flag.Arg(0), *out, *out+".d", excludes)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "error: %s\n", err.Error())
+ os.Exit(1)
+ }
+}
diff --git a/bootstrap/command.go b/bootstrap/command.go
index cc55b28..7c220e5 100644
--- a/bootstrap/command.go
+++ b/bootstrap/command.go
@@ -117,6 +117,8 @@ func Main(ctx *blueprint.Context, config interface{}, extraNinjaFileDeps ...stri
ctx.RegisterTopDownMutator("bootstrap_stage", propagateStageBootstrap)
ctx.RegisterSingletonType("bootstrap", newSingletonFactory(bootstrapConfig))
+ ctx.RegisterSingletonType("glob", globSingletonFactory(ctx))
+
deps, errs := ctx.ParseBlueprintsFiles(bootstrapConfig.topLevelBlueprintsFile)
if len(errs) > 0 {
fatalErrors(errs)
diff --git a/bootstrap/glob.go b/bootstrap/glob.go
new file mode 100644
index 0000000..2e209d9
--- /dev/null
+++ b/bootstrap/glob.go
@@ -0,0 +1,144 @@
+// Copyright 2016 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/deptools"
+ "github.com/google/blueprint/pathtools"
+)
+
+// This file supports globbing source files in Blueprints files.
+//
+// The build.ninja file needs to be regenerated any time a file matching the glob is added
+// or removed. The naive solution is to have the build.ninja file depend on all the
+// traversed directories, but this will cause the regeneration step to run every time a
+// non-matching file is added to a traversed directory, including backup files created by
+// editors.
+//
+// The solution implemented here optimizes out regenerations when the directory modifications
+// don't match the glob by having the build.ninja file depend on an intermedate file that
+// is only updated when a file matching the glob is added or removed. The intermediate file
+// depends on the traversed directories via a depfile. The depfile is used to avoid build
+// errors if a directory is deleted - a direct dependency on the deleted directory would result
+// in a build failure with a "missing and no known rule to make it" error.
+
+var (
+ globCmd = filepath.Join("$BinDir", "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
+ GlobRule = pctx.StaticRule("GlobRule",
+ blueprint.RuleParams{
+ Command: fmt.Sprintf(`%s -o $out $excludes "$glob"`, globCmd),
+ CommandDeps: []string{globCmd},
+ Description: "glob $glob",
+
+ Restat: true,
+ Deps: blueprint.DepsGCC,
+ Depfile: "$out.d",
+ },
+ "glob", "excludes")
+)
+
+// GlobFileContext is the subset of ModuleContext and SingletonContext needed by GlobFile
+type GlobFileContext interface {
+ Build(pctx blueprint.PackageContext, params blueprint.BuildParams)
+}
+
+// GlobFile creates a rule to write to fileListFile a list of the files that match the specified
+// pattern but do not match any of the patterns specified in excludes. The file will include
+// appropriate dependencies written to depFile to regenerate the file if and only if the list of
+// matching files has changed.
+func GlobFile(ctx GlobFileContext, pattern string, excludes []string,
+ fileListFile, depFile string) {
+
+ ctx.Build(pctx, blueprint.BuildParams{
+ Rule: GlobRule,
+ Outputs: []string{fileListFile},
+ Args: map[string]string{
+ "glob": pattern,
+ "excludes": joinWithPrefixAndQuote(excludes, "-e "),
+ },
+ })
+}
+
+func joinWithPrefixAndQuote(strs []string, prefix string) string {
+ if len(strs) == 0 {
+ return ""
+ }
+
+ if len(strs) == 1 {
+ return prefix + `"` + strs[0] + `"`
+ }
+
+ n := len(" ") * (len(strs) - 1)
+ for _, s := range strs {
+ n += len(prefix) + len(s) + len(`""`)
+ }
+
+ ret := make([]byte, 0, n)
+ for i, s := range strs {
+ if i != 0 {
+ ret = append(ret, ' ')
+ }
+ ret = append(ret, prefix...)
+ ret = append(ret, '"')
+ ret = append(ret, s...)
+ ret = append(ret, '"')
+ }
+ return string(ret)
+}
+
+// globSingleton collects any glob patterns that were seen by Context and writes out rules to
+// re-evaluate them whenever the contents of the searched directories change, and retrigger the
+// primary builder if the results change.
+type globSingleton struct {
+ globLister func() []blueprint.GlobPath
+}
+
+func globSingletonFactory(ctx *blueprint.Context) func() blueprint.Singleton {
+ return func() blueprint.Singleton {
+ return &globSingleton{
+ globLister: ctx.Globs,
+ }
+ }
+}
+
+func (s *globSingleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
+ if config, ok := ctx.Config().(ConfigInterface); ok && config.GeneratingBootstrapper() {
+ // Skip singleton for bootstrap.bash -r case to avoid putting unnecessary glob lines into
+ // the bootstrap manifest
+ return
+ }
+
+ 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)
+
+ GlobFile(ctx, g.Pattern, g.Excludes, fileListFile, depFile)
+
+ // Make build.ninja depend on the fileListFile
+ ctx.AddNinjaFileDeps(fileListFile)
+ }
+}
diff --git a/build.ninja.in b/build.ninja.in
index 8b3fe98..ec32fcf 100644
--- a/build.ninja.in
+++ b/build.ninja.in
@@ -58,8 +58,9 @@ rule g.bootstrap.link
build $
${g.bootstrap.buildDir}/.bootstrap/blueprint/pkg/github.com/google/blueprint.a $
: g.bootstrap.compile ${g.bootstrap.srcDir}/context.go $
- ${g.bootstrap.srcDir}/fs.go ${g.bootstrap.srcDir}/live_tracker.go $
- ${g.bootstrap.srcDir}/mangle.go ${g.bootstrap.srcDir}/module_ctx.go $
+ ${g.bootstrap.srcDir}/fs.go ${g.bootstrap.srcDir}/glob.go $
+ ${g.bootstrap.srcDir}/live_tracker.go ${g.bootstrap.srcDir}/mangle.go $
+ ${g.bootstrap.srcDir}/module_ctx.go $
${g.bootstrap.srcDir}/ninja_defs.go $
${g.bootstrap.srcDir}/ninja_strings.go $
${g.bootstrap.srcDir}/ninja_writer.go $
@@ -67,9 +68,10 @@ build $
${g.bootstrap.srcDir}/singleton_ctx.go ${g.bootstrap.srcDir}/unpack.go $
| ${g.bootstrap.compileCmd} $
${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg/github.com/google/blueprint/parser.a $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a $
${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a $
${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg/github.com/google/blueprint/proptools.a
- incFlags = -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg
+ incFlags = -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg
pkgPath = github.com/google/blueprint
default $
${g.bootstrap.buildDir}/.bootstrap/blueprint/pkg/github.com/google/blueprint.a
@@ -79,7 +81,7 @@ default $
# Variant:
# Type: bootstrap_go_package
# Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1
-# Defined: Blueprints:85:1
+# Defined: Blueprints:89:1
build $
${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap/pkg/github.com/google/blueprint/bootstrap.a $
@@ -88,15 +90,16 @@ build $
${g.bootstrap.srcDir}/bootstrap/command.go $
${g.bootstrap.srcDir}/bootstrap/config.go $
${g.bootstrap.srcDir}/bootstrap/doc.go $
+ ${g.bootstrap.srcDir}/bootstrap/glob.go $
${g.bootstrap.srcDir}/bootstrap/writedocs.go | $
${g.bootstrap.compileCmd} $
${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg/github.com/google/blueprint/parser.a $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a $
${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a $
${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg/github.com/google/blueprint/proptools.a $
${g.bootstrap.buildDir}/.bootstrap/blueprint/pkg/github.com/google/blueprint.a $
- ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a $
${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap-bpdoc/pkg/github.com/google/blueprint/bootstrap/bpdoc.a
- incFlags = -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap-bpdoc/pkg
+ incFlags = -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap-bpdoc/pkg
pkgPath = github.com/google/blueprint/bootstrap
default $
${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap/pkg/github.com/google/blueprint/bootstrap.a
@@ -106,17 +109,18 @@ default $
# Variant:
# Type: bootstrap_go_package
# Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1
-# Defined: Blueprints:104:1
+# Defined: Blueprints:109:1
build $
${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap-bpdoc/pkg/github.com/google/blueprint/bootstrap/bpdoc.a $
: g.bootstrap.compile ${g.bootstrap.srcDir}/bootstrap/bpdoc/bpdoc.go | $
${g.bootstrap.compileCmd} $
${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg/github.com/google/blueprint/parser.a $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a $
${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a $
${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg/github.com/google/blueprint/proptools.a $
${g.bootstrap.buildDir}/.bootstrap/blueprint/pkg/github.com/google/blueprint.a
- incFlags = -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint/pkg
+ incFlags = -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint/pkg
pkgPath = github.com/google/blueprint/bootstrap/bpdoc
default $
${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap-bpdoc/pkg/github.com/google/blueprint/bootstrap/bpdoc.a
@@ -126,7 +130,7 @@ default $
# Variant:
# Type: bootstrap_go_package
# Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1
-# Defined: Blueprints:49:1
+# Defined: Blueprints:50:1
build $
${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a $
@@ -141,7 +145,7 @@ default $
# Variant:
# Type: bootstrap_go_package
# Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1
-# Defined: Blueprints:33:1
+# Defined: Blueprints:34:1
build $
${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg/github.com/google/blueprint/parser.a $
@@ -159,12 +163,14 @@ default $
# Variant:
# Type: bootstrap_go_package
# Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1
-# Defined: Blueprints:55:1
+# Defined: Blueprints:56:1
build $
${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a $
: g.bootstrap.compile ${g.bootstrap.srcDir}/pathtools/lists.go $
- ${g.bootstrap.srcDir}/pathtools/glob.go | ${g.bootstrap.compileCmd}
+ ${g.bootstrap.srcDir}/pathtools/glob.go | ${g.bootstrap.compileCmd} $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a
+ incFlags = -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg
pkgPath = github.com/google/blueprint/pathtools
default $
${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a
@@ -174,7 +180,7 @@ default $
# Variant:
# Type: bootstrap_go_package
# Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1
-# Defined: Blueprints:67:1
+# Defined: Blueprints:71:1
build $
${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg/github.com/google/blueprint/proptools.a $
@@ -189,11 +195,37 @@ default $
${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg/github.com/google/blueprint/proptools.a
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# Module: bpglob
+# Variant:
+# Type: bootstrap_core_go_binary
+# Factory: github.com/google/blueprint/bootstrap.newGoBinaryModuleFactory.func1
+# Defined: Blueprints:130:1
+
+build ${g.bootstrap.buildDir}/.bootstrap/bpglob/obj/bpglob.a: $
+ g.bootstrap.compile ${g.bootstrap.srcDir}/bootstrap/bpglob/bpglob.go | $
+ ${g.bootstrap.compileCmd} $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a
+ incFlags = -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg
+ pkgPath = bpglob
+default ${g.bootstrap.buildDir}/.bootstrap/bpglob/obj/bpglob.a
+
+build ${g.bootstrap.buildDir}/.bootstrap/bpglob/obj/a.out: g.bootstrap.link $
+ ${g.bootstrap.buildDir}/.bootstrap/bpglob/obj/bpglob.a | $
+ ${g.bootstrap.linkCmd}
+ libDirFlags = -L ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg -L ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg
+default ${g.bootstrap.buildDir}/.bootstrap/bpglob/obj/a.out
+
+build ${g.bootstrap.BinDir}/bpglob: g.bootstrap.cp $
+ ${g.bootstrap.buildDir}/.bootstrap/bpglob/obj/a.out
+default ${g.bootstrap.BinDir}/bpglob
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Module: gotestmain
# Variant:
# Type: bootstrap_core_go_binary
# Factory: github.com/google/blueprint/bootstrap.newGoBinaryModuleFactory.func1
-# Defined: Blueprints:137:1
+# Defined: Blueprints:148:1
build ${g.bootstrap.buildDir}/.bootstrap/gotestmain/obj/gotestmain.a: $
g.bootstrap.compile ${g.bootstrap.srcDir}/gotestmain/gotestmain.go | $
@@ -216,7 +248,7 @@ default ${g.bootstrap.BinDir}/gotestmain
# Variant:
# Type: bootstrap_core_go_binary
# Factory: github.com/google/blueprint/bootstrap.newGoBinaryModuleFactory.func1
-# Defined: Blueprints:142:1
+# Defined: Blueprints:153:1
build ${g.bootstrap.buildDir}/.bootstrap/gotestrunner/obj/gotestrunner.a: $
g.bootstrap.compile ${g.bootstrap.srcDir}/gotestrunner/gotestrunner.go $
@@ -239,26 +271,26 @@ default ${g.bootstrap.BinDir}/gotestrunner
# Variant:
# Type: bootstrap_core_go_binary
# Factory: github.com/google/blueprint/bootstrap.newGoBinaryModuleFactory.func1
-# Defined: Blueprints:116:1
+# Defined: Blueprints:121:1
build ${g.bootstrap.buildDir}/.bootstrap/minibp/obj/minibp.a: $
g.bootstrap.compile ${g.bootstrap.srcDir}/bootstrap/minibp/main.go | $
${g.bootstrap.compileCmd} $
${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg/github.com/google/blueprint/parser.a $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a $
${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a $
${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg/github.com/google/blueprint/proptools.a $
${g.bootstrap.buildDir}/.bootstrap/blueprint/pkg/github.com/google/blueprint.a $
- ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a $
${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap-bpdoc/pkg/github.com/google/blueprint/bootstrap/bpdoc.a $
${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap/pkg/github.com/google/blueprint/bootstrap.a
- incFlags = -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap-bpdoc/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap/pkg
+ incFlags = -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap-bpdoc/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap/pkg
pkgPath = minibp
default ${g.bootstrap.buildDir}/.bootstrap/minibp/obj/minibp.a
build ${g.bootstrap.buildDir}/.bootstrap/minibp/obj/a.out: g.bootstrap.link $
${g.bootstrap.buildDir}/.bootstrap/minibp/obj/minibp.a | $
${g.bootstrap.linkCmd}
- libDirFlags = -L ${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg -L ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg -L ${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg -L ${g.bootstrap.buildDir}/.bootstrap/blueprint/pkg -L ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg -L ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap-bpdoc/pkg -L ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap/pkg
+ libDirFlags = -L ${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg -L ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg -L ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg -L ${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg -L ${g.bootstrap.buildDir}/.bootstrap/blueprint/pkg -L ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap-bpdoc/pkg -L ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap/pkg
default ${g.bootstrap.buildDir}/.bootstrap/minibp/obj/a.out
build ${g.bootstrap.BinDir}/minibp: g.bootstrap.cp $
diff --git a/context.go b/context.go
index 48856cf..7e54150 100644
--- a/context.go
+++ b/context.go
@@ -25,6 +25,7 @@ import (
"sort"
"strconv"
"strings"
+ "sync"
"sync/atomic"
"text/scanner"
"text/template"
@@ -105,6 +106,9 @@ type Context struct {
renames []rename
replacements []replace
+ globs map[string]GlobPath
+ globLock sync.Mutex
+
fs fileSystem
}
@@ -270,6 +274,7 @@ func NewContext() *Context {
moduleNames: make(map[string]*moduleGroup),
moduleInfo: make(map[Module]*moduleInfo),
moduleNinjaNames: make(map[string]*moduleGroup),
+ globs: make(map[string]GlobPath),
fs: fs,
}
@@ -524,12 +529,11 @@ func (c *Context) SetAllowMissingDependencies(allowMissingDependencies bool) {
// filename specifies the path to the Blueprints file. These paths are used for
// error reporting and for determining the module's directory.
func (c *Context) parse(rootDir, filename string, r io.Reader,
- scope *parser.Scope) (file *parser.File, subBlueprints []stringAndScope, deps []string,
- errs []error) {
+ scope *parser.Scope) (file *parser.File, subBlueprints []stringAndScope, errs []error) {
relBlueprintsFile, err := filepath.Rel(rootDir, filename)
if err != nil {
- return nil, nil, nil, []error{err}
+ return nil, nil, []error{err}
}
scope = parser.NewScope(scope)
@@ -550,7 +554,7 @@ func (c *Context) parse(rootDir, filename string, r io.Reader,
// If there were any parse errors don't bother trying to interpret the
// result.
- return nil, nil, nil, errs
+ return nil, nil, errs
}
file.Name = relBlueprintsFile
@@ -570,24 +574,28 @@ func (c *Context) parse(rootDir, filename string, r io.Reader,
}
subBlueprintsName, _, err := getStringFromScope(scope, "subname")
+ if err != nil {
+ errs = append(errs, err)
+ }
+
+ if subBlueprintsName == "" {
+ subBlueprintsName = "Blueprints"
+ }
var blueprints []string
- newBlueprints, newDeps, newErrs := c.findBuildBlueprints(filepath.Dir(filename), build, buildPos)
+ newBlueprints, newErrs := c.findBuildBlueprints(filepath.Dir(filename), build, buildPos)
blueprints = append(blueprints, newBlueprints...)
- deps = append(deps, newDeps...)
errs = append(errs, newErrs...)
- newBlueprints, newDeps, newErrs = c.findSubdirBlueprints(filepath.Dir(filename), subdirs, subdirsPos,
+ newBlueprints, newErrs = c.findSubdirBlueprints(filepath.Dir(filename), subdirs, subdirsPos,
subBlueprintsName, false)
blueprints = append(blueprints, newBlueprints...)
- deps = append(deps, newDeps...)
errs = append(errs, newErrs...)
- newBlueprints, newDeps, newErrs = c.findSubdirBlueprints(filepath.Dir(filename), optionalSubdirs,
+ newBlueprints, newErrs = c.findSubdirBlueprints(filepath.Dir(filename), optionalSubdirs,
optionalSubdirsPos, subBlueprintsName, true)
blueprints = append(blueprints, newBlueprints...)
- deps = append(deps, newDeps...)
errs = append(errs, newErrs...)
subBlueprintsAndScope := make([]stringAndScope, len(blueprints))
@@ -595,7 +603,7 @@ func (c *Context) parse(rootDir, filename string, r io.Reader,
subBlueprintsAndScope[i] = stringAndScope{b, scope}
}
- return file, subBlueprintsAndScope, deps, errs
+ return file, subBlueprintsAndScope, errs
}
type stringAndScope struct {
@@ -790,7 +798,7 @@ func (c *Context) parseBlueprintsFile(filename string, scope *parser.Scope, root
}
}()
- file, subBlueprints, deps, errs := c.parse(rootDir, filename, f, scope)
+ file, subBlueprints, errs := c.parse(rootDir, filename, f, scope)
if len(errs) > 0 {
errsCh <- errs
} else {
@@ -799,22 +807,31 @@ func (c *Context) parseBlueprintsFile(filename string, scope *parser.Scope, root
for _, b := range subBlueprints {
blueprintsCh <- b
- }
-
- for _, d := range deps {
- depsCh <- d
+ depsCh <- b.string
}
}
func (c *Context) findBuildBlueprints(dir string, build []string,
- buildPos scanner.Position) (blueprints, deps []string, errs []error) {
+ buildPos scanner.Position) ([]string, []error) {
+
+ var blueprints []string
+ var errs []error
for _, file := range build {
- globPattern := filepath.Join(dir, file)
- matches, matchedDirs, err := pathtools.Glob(globPattern)
+ pattern := filepath.Join(dir, file)
+ var matches []string
+ var err error
+
+ if pathtools.IsGlob(pattern) {
+ matches, err = c.glob(pattern, nil)
+ } else {
+ // Not a glob, but use filepath.Glob to check if it exists
+ matches, err = filepath.Glob(pattern)
+ }
+
if err != nil {
errs = append(errs, &BlueprintError{
- Err: fmt.Errorf("%q: %s", globPattern, err.Error()),
+ Err: fmt.Errorf("%q: %s", pattern, err.Error()),
Pos: buildPos,
})
continue
@@ -822,47 +839,40 @@ func (c *Context) findBuildBlueprints(dir string, build []string,
if len(matches) == 0 {
errs = append(errs, &BlueprintError{
- Err: fmt.Errorf("%q: not found", globPattern),
+ Err: fmt.Errorf("%q: not found", pattern),
Pos: buildPos,
})
}
- // Depend on all searched directories so we pick up future changes.
- deps = append(deps, matchedDirs...)
-
for _, foundBlueprints := range matches {
- exists, dir, err := c.fs.Exists(foundBlueprints)
- if err != nil {
- errs = append(errs, err)
- } else if !exists {
- errs = append(errs, &BlueprintError{
- Err: fmt.Errorf("%q not found", foundBlueprints),
- })
- continue
- } else if dir {
- errs = append(errs, &BlueprintError{
- Err: fmt.Errorf("%q is a directory", foundBlueprints),
- })
- continue
- }
-
blueprints = append(blueprints, foundBlueprints)
- deps = append(deps, foundBlueprints)
}
}
- return blueprints, deps, errs
+ return blueprints, errs
}
func (c *Context) findSubdirBlueprints(dir string, subdirs []string, subdirsPos scanner.Position,
- subBlueprintsName string, optional bool) (blueprints, deps []string, errs []error) {
+ subBlueprintsName string, optional bool) ([]string, []error) {
+
+ var blueprints []string
+ var errs []error
for _, subdir := range subdirs {
- globPattern := filepath.Join(dir, subdir)
- matches, matchedDirs, err := pathtools.Glob(globPattern)
+ pattern := filepath.Join(dir, subdir, subBlueprintsName)
+ var matches []string
+ var err error
+
+ if pathtools.IsGlob(pattern) {
+ matches, err = c.glob(pattern, nil)
+ } else {
+ // Not a glob, but use filepath.Glob to check if it exists
+ matches, err = filepath.Glob(pattern)
+ }
+
if err != nil {
errs = append(errs, &BlueprintError{
- Err: fmt.Errorf("%q: %s", globPattern, err.Error()),
+ Err: fmt.Errorf("%q: %s", pattern, err.Error()),
Pos: subdirsPos,
})
continue
@@ -870,56 +880,17 @@ func (c *Context) findSubdirBlueprints(dir string, subdirs []string, subdirsPos
if len(matches) == 0 && !optional {
errs = append(errs, &BlueprintError{
- Err: fmt.Errorf("%q: not found", globPattern),
+ Err: fmt.Errorf("%q: not found", pattern),
Pos: subdirsPos,
})
}
- // Depend on all searched directories so we pick up future changes.
- deps = append(deps, matchedDirs...)
-
- for _, foundSubdir := range matches {
- exists, dir, subdirStatErr := c.fs.Exists(foundSubdir)
- if subdirStatErr != nil {
- errs = append(errs, subdirStatErr)
- continue
- }
-
- // Skip files
- if !dir {
- continue
- }
-
- var subBlueprints string
- if subBlueprintsName != "" {
- subBlueprints = filepath.Join(foundSubdir, subBlueprintsName)
- exists, _, err = c.fs.Exists(subBlueprints)
- }
-
- if err == nil && (!exists || subBlueprints == "") {
- subBlueprints = filepath.Join(foundSubdir, "Blueprints")
- exists, _, err = c.fs.Exists(subBlueprints)
- }
-
- if err != nil {
- errs = append(errs, err)
- continue
- }
-
- if !exists {
- // There is no Blueprints file in this subdirectory. We
- // need to add the directory to the list of dependencies
- // so that if someone adds a Blueprints file in the
- // future we'll pick it up.
- deps = append(deps, foundSubdir)
- } else {
- deps = append(deps, subBlueprints)
- blueprints = append(blueprints, subBlueprints)
- }
+ for _, subBlueprints := range matches {
+ blueprints = append(blueprints, subBlueprints)
}
}
- return blueprints, deps, errs
+ return blueprints, errs
}
func getLocalStringListFromScope(scope *parser.Scope, v string) ([]string, scanner.Position, error) {
diff --git a/context_test.go b/context_test.go
index 5d9f4bc..b05c541 100644
--- a/context_test.go
+++ b/context_test.go
@@ -95,7 +95,7 @@ func TestContextParse(t *testing.T) {
}
`)
- _, _, _, errs := ctx.parse(".", "Blueprint", r, nil)
+ _, _, errs := ctx.parse(".", "Blueprint", r, nil)
if len(errs) > 0 {
t.Errorf("unexpected parse errors:")
for _, err := range errs {
diff --git a/glob.go b/glob.go
new file mode 100644
index 0000000..dad5edf
--- /dev/null
+++ b/glob.go
@@ -0,0 +1,116 @@
+// Copyright 2015 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"
+ "sort"
+
+ "github.com/google/blueprint/pathtools"
+)
+
+type GlobPath struct {
+ Pattern string
+ Excludes []string
+ Files []string
+ Deps []string
+ Name string
+}
+
+func verifyGlob(fileName, pattern string, excludes []string, g GlobPath) {
+ if pattern != g.Pattern {
+ panic(fmt.Errorf("Mismatched patterns %q and %q for glob file %q", pattern, g.Pattern, fileName))
+ }
+ if !reflect.DeepEqual(g.Excludes, excludes) {
+ panic(fmt.Errorf("Mismatched excludes %v and %v for glob file %q", excludes, g.Excludes, fileName))
+ }
+}
+
+func (c *Context) glob(pattern string, excludes []string) ([]string, error) {
+ fileName := globToFileName(pattern, excludes)
+
+ // Try to get existing glob from the stored results
+ c.globLock.Lock()
+ g, exists := c.globs[fileName]
+ c.globLock.Unlock()
+
+ if exists {
+ // Glob has already been done, double check it is identical
+ verifyGlob(fileName, pattern, excludes, g)
+ return g.Files, nil
+ }
+
+ // Get a globbed file list
+ files, deps, err := pathtools.GlobWithExcludes(pattern, excludes)
+ if err != nil {
+ return nil, err
+ }
+
+ // Store the results
+ c.globLock.Lock()
+ if g, exists = c.globs[fileName]; !exists {
+ c.globs[fileName] = GlobPath{pattern, excludes, files, deps, fileName}
+ }
+ c.globLock.Unlock()
+
+ // Getting the list raced with another goroutine, throw away the results and use theirs
+ if exists {
+ verifyGlob(fileName, pattern, excludes, g)
+ return g.Files, nil
+ }
+
+ return files, nil
+}
+
+func (c *Context) Globs() []GlobPath {
+ fileNames := make([]string, 0, len(c.globs))
+ for k := range c.globs {
+ fileNames = append(fileNames, k)
+ }
+ sort.Strings(fileNames)
+
+ globs := make([]GlobPath, len(fileNames))
+ for i, fileName := range fileNames {
+ globs[i] = c.globs[fileName]
+ }
+
+ return globs
+}
+
+func globToString(pattern string) string {
+ ret := ""
+ for _, c := range pattern {
+ switch {
+ case c >= 'a' && c <= 'z',
+ c >= 'A' && c <= 'Z',
+ c >= '0' && c <= '9',
+ c == '_', c == '-', c == '/':
+ ret += string(c)
+ default:
+ ret += "_"
+ }
+ }
+
+ return ret
+}
+
+func globToFileName(pattern string, excludes []string) string {
+ ret := globToString(pattern)
+ for _, e := range excludes {
+ ret += "__" + globToString(e)
+ }
+ return ret + ".glob"
+}
diff --git a/module_ctx.go b/module_ctx.go
index cfbb2db..f8a42f0 100644
--- a/module_ctx.go
+++ b/module_ctx.go
@@ -128,6 +128,12 @@ type BaseModuleContext interface {
PropertyErrorf(property, fmt string, args ...interface{})
Failed() bool
+ // GlobWithDeps returns a list of files that match the specified pattern but do not match any
+ // of the patterns in excludes. It also adds efficient dependencies to rerun the primary
+ // builder whenever a file matching the pattern as added or removed, without rerunning if a
+ // file that does not match the pattern is added to a searched directory.
+ GlobWithDeps(pattern string, excludes []string) ([]string, error)
+
moduleInfo() *moduleInfo
error(err error)
}
@@ -246,6 +252,11 @@ func (d *baseModuleContext) Failed() bool {
return len(d.errs) > 0
}
+func (d *baseModuleContext) GlobWithDeps(pattern string,
+ excludes []string) ([]string, error) {
+ return d.context.glob(pattern, excludes)
+}
+
var _ ModuleContext = (*moduleContext)(nil)
type moduleContext struct {
diff --git a/pathtools/glob.go b/pathtools/glob.go
index 5541643..8e0a7e0 100644
--- a/pathtools/glob.go
+++ b/pathtools/glob.go
@@ -16,9 +16,12 @@ package pathtools
import (
"errors"
+ "io/ioutil"
"os"
"path/filepath"
"strings"
+
+ "github.com/google/blueprint/deptools"
)
var GlobMultipleRecursiveErr = errors.New("pattern contains multiple **")
@@ -28,7 +31,12 @@ var GlobLastRecursiveErr = errors.New("pattern ** as last path element")
// list of directories that were searched to construct the file list.
// The supported glob patterns are equivalent to filepath.Glob, with an
// extension that recursive glob (** matching zero or more complete path
-// entries) is supported.
+// entries) is supported. Glob also returns a list of directories that were
+// searched.
+//
+// In general ModuleContext.GlobWithDeps or SingletonContext.GlobWithDeps
+// should be used instead, as they will automatically set up dependencies
+// to rerun the primary builder when the list of matching files changes.
func Glob(pattern string) (matches, dirs []string, err error) {
return GlobWithExcludes(pattern, nil)
}
@@ -38,6 +46,11 @@ func Glob(pattern string) (matches, dirs []string, err error) {
// that were searched to construct the file list. The supported glob and
// exclude patterns are equivalent to filepath.Glob, with an extension that
// recursive glob (** matching zero or more complete path entries) is supported.
+// GlobWIthExcludes also returns a list of directories that were searched.
+//
+// In general ModuleContext.GlobWithDeps or SingletonContext.GlobWithDeps
+// should be used instead, as they will automatically set up dependencies
+// to rerun the primary builder when the list of matching files changes.
func GlobWithExcludes(pattern string, excludes []string) (matches, dirs []string, err error) {
if filepath.Base(pattern) == "**" {
return nil, nil, GlobLastRecursiveErr
@@ -287,3 +300,98 @@ func GlobPatternList(patterns []string, prefix string) (globedList []string, dep
}
return globedList, depDirs, nil
}
+
+// IsGlob returns true if the pattern contains any glob characters (*, ?, or [).
+func IsGlob(pattern string) bool {
+ return strings.IndexAny(pattern, "*?[") >= 0
+}
+
+// HasGlob returns true if any string in the list contains any glob characters (*, ?, or [).
+func HasGlob(in []string) bool {
+ for _, s := range in {
+ if IsGlob(s) {
+ return true
+ }
+ }
+
+ return false
+}
+
+// GlobWithDepFile finds all files that match glob. It compares the list of files
+// against the contents of fileListFile, and rewrites fileListFile if it has changed. It also
+// writes all of the the directories it traversed as a depenencies on fileListFile to depFile.
+//
+// The format of glob is either path/*.ext for a single directory glob, or path/**/*.ext
+// for a recursive glob.
+//
+// Returns a list of file paths, and an error.
+//
+// In general ModuleContext.GlobWithDeps or SingletonContext.GlobWithDeps
+// should be used instead, as they will automatically set up dependencies
+// to rerun the primary builder when the list of matching files changes.
+func GlobWithDepFile(glob, fileListFile, depFile string, excludes []string) (files []string, err error) {
+ files, dirs, err := GlobWithExcludes(glob, excludes)
+ if err != nil {
+ return nil, err
+ }
+
+ fileList := strings.Join(files, "\n") + "\n"
+
+ WriteFileIfChanged(fileListFile, []byte(fileList), 0666)
+ deptools.WriteDepFile(depFile, fileListFile, dirs)
+
+ return
+}
+
+// WriteFileIfChanged wraps ioutil.WriteFile, but only writes the file if
+// the files does not already exist with identical contents. This can be used
+// along with ninja restat rules to skip rebuilding downstream rules if no
+// changes were made by a rule.
+func WriteFileIfChanged(filename string, data []byte, perm os.FileMode) error {
+ var isChanged bool
+
+ dir := filepath.Dir(filename)
+ err := os.MkdirAll(dir, 0777)
+ if err != nil {
+ return err
+ }
+
+ info, err := os.Stat(filename)
+ if err != nil {
+ if os.IsNotExist(err) {
+ // The file does not exist yet.
+ isChanged = true
+ } else {
+ return err
+ }
+ } else {
+ if info.Size() != int64(len(data)) {
+ isChanged = true
+ } else {
+ oldData, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return err
+ }
+
+ if len(oldData) != len(data) {
+ isChanged = true
+ } else {
+ for i := range data {
+ if oldData[i] != data[i] {
+ isChanged = true
+ break
+ }
+ }
+ }
+ }
+ }
+
+ if isChanged {
+ err = ioutil.WriteFile(filename, data, perm)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
diff --git a/singleton_ctx.go b/singleton_ctx.go
index 007e180..fc4a781 100644
--- a/singleton_ctx.go
+++ b/singleton_ctx.go
@@ -62,6 +62,12 @@ type SingletonContext interface {
FinalModule(module Module) Module
AddNinjaFileDeps(deps ...string)
+
+ // GlobWithDeps returns a list of files that match the specified pattern but do not match any
+ // of the patterns in excludes. It also adds efficient dependencies to rerun the primary
+ // builder whenever a file matching the pattern as added or removed, without rerunning if a
+ // file that does not match the pattern is added to a searched directory.
+ GlobWithDeps(pattern string, excludes []string) ([]string, error)
}
var _ SingletonContext = (*singletonContext)(nil)
@@ -228,3 +234,8 @@ func (s *singletonContext) VisitAllModuleVariants(module Module, visit func(Modu
func (s *singletonContext) AddNinjaFileDeps(deps ...string) {
s.ninjaFileDeps = append(s.ninjaFileDeps, deps...)
}
+
+func (s *singletonContext) GlobWithDeps(pattern string,
+ excludes []string) ([]string, error) {
+ return s.context.glob(pattern, excludes)
+}
diff --git a/tests/test_tree/Blueprints b/tests/test_tree/Blueprints
new file mode 100644
index 0000000..d5e1358
--- /dev/null
+++ b/tests/test_tree/Blueprints
@@ -0,0 +1,3 @@
+// Root Blueprints file for test_tree
+
+subdirs=["*"]
diff --git a/tests/test_tree/a/Blueprints b/tests/test_tree/a/Blueprints
new file mode 100644
index 0000000..836d6ba
--- /dev/null
+++ b/tests/test_tree/a/Blueprints
@@ -0,0 +1 @@
+// a/Blueprints
diff --git a/tests/test_tree/blueprint b/tests/test_tree/blueprint
new file mode 120000
index 0000000..c25bddb
--- /dev/null
+++ b/tests/test_tree/blueprint
@@ -0,0 +1 @@
+../.. \ No newline at end of file
diff --git a/tests/test_tree/build.ninja.in b/tests/test_tree/build.ninja.in
new file mode 100644
index 0000000..5604b40
--- /dev/null
+++ b/tests/test_tree/build.ninja.in
@@ -0,0 +1,335 @@
+# ******************************************************************************
+# *** This file is generated and should not be edited ***
+# ******************************************************************************
+#
+# This file contains variables, rules, and pools with name prefixes indicating
+# they were generated by the following Go packages:
+#
+# bootstrap [from Go package github.com/google/blueprint/bootstrap]
+#
+ninja_required_version = 1.7.0
+
+g.bootstrap.buildDir = @@BuildDir@@
+
+g.bootstrap.BinDir = ${g.bootstrap.buildDir}/.bootstrap/bin
+
+g.bootstrap.bootstrapCmd = @@Bootstrap@@
+
+g.bootstrap.compileCmd = @@GoCompile@@
+
+g.bootstrap.goRoot = @@GoRoot@@
+
+g.bootstrap.linkCmd = @@GoLink@@
+
+g.bootstrap.srcDir = @@SrcDir@@
+
+builddir = ${g.bootstrap.buildDir}/.minibootstrap
+
+rule g.bootstrap.bootstrap
+ command = BUILDDIR=${g.bootstrap.buildDir} ${g.bootstrap.bootstrapCmd} -i ${in}
+ description = bootstrap ${in}
+ generator = true
+
+rule g.bootstrap.build.ninja
+ command = ${builder} ${extra} -b ${g.bootstrap.buildDir} -d ${out}.d -o ${out} ${in}
+ depfile = ${out}.d
+ description = ${builder} ${out}
+ restat = true
+
+rule g.bootstrap.compile
+ command = GOROOT='${g.bootstrap.goRoot}' ${g.bootstrap.compileCmd} -o ${out} -p ${pkgPath} -complete ${incFlags} -pack ${in}
+ description = compile ${out}
+
+rule g.bootstrap.cp
+ command = cp ${in} ${out}
+ description = cp ${out}
+
+rule g.bootstrap.link
+ command = GOROOT='${g.bootstrap.goRoot}' ${g.bootstrap.linkCmd} -o ${out} ${libDirFlags} ${in}
+ description = link ${out}
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# Module: blueprint
+# Variant:
+# Type: bootstrap_go_package
+# Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1
+# Defined: blueprint/Blueprints:1:1
+
+build $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint/pkg/github.com/google/blueprint.a $
+ : g.bootstrap.compile ${g.bootstrap.srcDir}/blueprint/context.go $
+ ${g.bootstrap.srcDir}/blueprint/fs.go $
+ ${g.bootstrap.srcDir}/blueprint/glob.go $
+ ${g.bootstrap.srcDir}/blueprint/live_tracker.go $
+ ${g.bootstrap.srcDir}/blueprint/mangle.go $
+ ${g.bootstrap.srcDir}/blueprint/module_ctx.go $
+ ${g.bootstrap.srcDir}/blueprint/ninja_defs.go $
+ ${g.bootstrap.srcDir}/blueprint/ninja_strings.go $
+ ${g.bootstrap.srcDir}/blueprint/ninja_writer.go $
+ ${g.bootstrap.srcDir}/blueprint/package_ctx.go $
+ ${g.bootstrap.srcDir}/blueprint/scope.go $
+ ${g.bootstrap.srcDir}/blueprint/singleton_ctx.go $
+ ${g.bootstrap.srcDir}/blueprint/unpack.go | ${g.bootstrap.compileCmd} $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg/github.com/google/blueprint/parser.a $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg/github.com/google/blueprint/proptools.a
+ incFlags = -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg
+ pkgPath = github.com/google/blueprint
+default $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint/pkg/github.com/google/blueprint.a
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# Module: blueprint-bootstrap
+# Variant:
+# Type: bootstrap_go_package
+# Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1
+# Defined: blueprint/Blueprints:89:1
+
+build $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap/pkg/github.com/google/blueprint/bootstrap.a $
+ : g.bootstrap.compile $
+ ${g.bootstrap.srcDir}/blueprint/bootstrap/bootstrap.go $
+ ${g.bootstrap.srcDir}/blueprint/bootstrap/cleanup.go $
+ ${g.bootstrap.srcDir}/blueprint/bootstrap/command.go $
+ ${g.bootstrap.srcDir}/blueprint/bootstrap/config.go $
+ ${g.bootstrap.srcDir}/blueprint/bootstrap/doc.go $
+ ${g.bootstrap.srcDir}/blueprint/bootstrap/glob.go $
+ ${g.bootstrap.srcDir}/blueprint/bootstrap/writedocs.go | $
+ ${g.bootstrap.compileCmd} $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg/github.com/google/blueprint/parser.a $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg/github.com/google/blueprint/proptools.a $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint/pkg/github.com/google/blueprint.a $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap-bpdoc/pkg/github.com/google/blueprint/bootstrap/bpdoc.a
+ incFlags = -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap-bpdoc/pkg
+ pkgPath = github.com/google/blueprint/bootstrap
+default $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap/pkg/github.com/google/blueprint/bootstrap.a
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# Module: blueprint-bootstrap-bpdoc
+# Variant:
+# Type: bootstrap_go_package
+# Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1
+# Defined: blueprint/Blueprints:109:1
+
+build $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap-bpdoc/pkg/github.com/google/blueprint/bootstrap/bpdoc.a $
+ : g.bootstrap.compile $
+ ${g.bootstrap.srcDir}/blueprint/bootstrap/bpdoc/bpdoc.go | $
+ ${g.bootstrap.compileCmd} $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg/github.com/google/blueprint/parser.a $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg/github.com/google/blueprint/proptools.a $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint/pkg/github.com/google/blueprint.a
+ incFlags = -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint/pkg
+ pkgPath = github.com/google/blueprint/bootstrap/bpdoc
+default $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap-bpdoc/pkg/github.com/google/blueprint/bootstrap/bpdoc.a
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# Module: blueprint-deptools
+# Variant:
+# Type: bootstrap_go_package
+# Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1
+# Defined: blueprint/Blueprints:50:1
+
+build $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a $
+ : g.bootstrap.compile $
+ ${g.bootstrap.srcDir}/blueprint/deptools/depfile.go | $
+ ${g.bootstrap.compileCmd}
+ pkgPath = github.com/google/blueprint/deptools
+default $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# Module: blueprint-parser
+# Variant:
+# Type: bootstrap_go_package
+# Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1
+# Defined: blueprint/Blueprints:34:1
+
+build $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg/github.com/google/blueprint/parser.a $
+ : g.bootstrap.compile ${g.bootstrap.srcDir}/blueprint/parser/ast.go $
+ ${g.bootstrap.srcDir}/blueprint/parser/modify.go $
+ ${g.bootstrap.srcDir}/blueprint/parser/parser.go $
+ ${g.bootstrap.srcDir}/blueprint/parser/printer.go $
+ ${g.bootstrap.srcDir}/blueprint/parser/sort.go | $
+ ${g.bootstrap.compileCmd}
+ pkgPath = github.com/google/blueprint/parser
+default $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg/github.com/google/blueprint/parser.a
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# Module: blueprint-pathtools
+# Variant:
+# Type: bootstrap_go_package
+# Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1
+# Defined: blueprint/Blueprints:56:1
+
+build $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a $
+ : g.bootstrap.compile $
+ ${g.bootstrap.srcDir}/blueprint/pathtools/lists.go $
+ ${g.bootstrap.srcDir}/blueprint/pathtools/glob.go | $
+ ${g.bootstrap.compileCmd} $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a
+ incFlags = -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg
+ pkgPath = github.com/google/blueprint/pathtools
+default $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# Module: blueprint-proptools
+# Variant:
+# Type: bootstrap_go_package
+# Factory: github.com/google/blueprint/bootstrap.newGoPackageModuleFactory.func1
+# Defined: blueprint/Blueprints:71:1
+
+build $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg/github.com/google/blueprint/proptools.a $
+ : g.bootstrap.compile $
+ ${g.bootstrap.srcDir}/blueprint/proptools/clone.go $
+ ${g.bootstrap.srcDir}/blueprint/proptools/escape.go $
+ ${g.bootstrap.srcDir}/blueprint/proptools/extend.go $
+ ${g.bootstrap.srcDir}/blueprint/proptools/proptools.go $
+ ${g.bootstrap.srcDir}/blueprint/proptools/typeequal.go | $
+ ${g.bootstrap.compileCmd}
+ pkgPath = github.com/google/blueprint/proptools
+default $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg/github.com/google/blueprint/proptools.a
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# Module: bpglob
+# Variant:
+# Type: bootstrap_core_go_binary
+# Factory: github.com/google/blueprint/bootstrap.newGoBinaryModuleFactory.func1
+# Defined: blueprint/Blueprints:130:1
+
+build ${g.bootstrap.buildDir}/.bootstrap/bpglob/obj/bpglob.a: $
+ g.bootstrap.compile $
+ ${g.bootstrap.srcDir}/blueprint/bootstrap/bpglob/bpglob.go | $
+ ${g.bootstrap.compileCmd} $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a
+ incFlags = -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg
+ pkgPath = bpglob
+default ${g.bootstrap.buildDir}/.bootstrap/bpglob/obj/bpglob.a
+
+build ${g.bootstrap.buildDir}/.bootstrap/bpglob/obj/a.out: g.bootstrap.link $
+ ${g.bootstrap.buildDir}/.bootstrap/bpglob/obj/bpglob.a | $
+ ${g.bootstrap.linkCmd}
+ libDirFlags = -L ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg -L ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg
+default ${g.bootstrap.buildDir}/.bootstrap/bpglob/obj/a.out
+
+build ${g.bootstrap.BinDir}/bpglob: g.bootstrap.cp $
+ ${g.bootstrap.buildDir}/.bootstrap/bpglob/obj/a.out
+default ${g.bootstrap.BinDir}/bpglob
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# Module: gotestmain
+# Variant:
+# Type: bootstrap_core_go_binary
+# Factory: github.com/google/blueprint/bootstrap.newGoBinaryModuleFactory.func1
+# Defined: blueprint/Blueprints:148:1
+
+build ${g.bootstrap.buildDir}/.bootstrap/gotestmain/obj/gotestmain.a: $
+ g.bootstrap.compile $
+ ${g.bootstrap.srcDir}/blueprint/gotestmain/gotestmain.go | $
+ ${g.bootstrap.compileCmd}
+ pkgPath = gotestmain
+default ${g.bootstrap.buildDir}/.bootstrap/gotestmain/obj/gotestmain.a
+
+build ${g.bootstrap.buildDir}/.bootstrap/gotestmain/obj/a.out: $
+ g.bootstrap.link $
+ ${g.bootstrap.buildDir}/.bootstrap/gotestmain/obj/gotestmain.a | $
+ ${g.bootstrap.linkCmd}
+default ${g.bootstrap.buildDir}/.bootstrap/gotestmain/obj/a.out
+
+build ${g.bootstrap.BinDir}/gotestmain: g.bootstrap.cp $
+ ${g.bootstrap.buildDir}/.bootstrap/gotestmain/obj/a.out
+default ${g.bootstrap.BinDir}/gotestmain
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# Module: gotestrunner
+# Variant:
+# Type: bootstrap_core_go_binary
+# Factory: github.com/google/blueprint/bootstrap.newGoBinaryModuleFactory.func1
+# Defined: blueprint/Blueprints:153:1
+
+build ${g.bootstrap.buildDir}/.bootstrap/gotestrunner/obj/gotestrunner.a: $
+ g.bootstrap.compile $
+ ${g.bootstrap.srcDir}/blueprint/gotestrunner/gotestrunner.go | $
+ ${g.bootstrap.compileCmd}
+ pkgPath = gotestrunner
+default ${g.bootstrap.buildDir}/.bootstrap/gotestrunner/obj/gotestrunner.a
+
+build ${g.bootstrap.buildDir}/.bootstrap/gotestrunner/obj/a.out: $
+ g.bootstrap.link $
+ ${g.bootstrap.buildDir}/.bootstrap/gotestrunner/obj/gotestrunner.a | $
+ ${g.bootstrap.linkCmd}
+default ${g.bootstrap.buildDir}/.bootstrap/gotestrunner/obj/a.out
+
+build ${g.bootstrap.BinDir}/gotestrunner: g.bootstrap.cp $
+ ${g.bootstrap.buildDir}/.bootstrap/gotestrunner/obj/a.out
+default ${g.bootstrap.BinDir}/gotestrunner
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# Module: minibp
+# Variant:
+# Type: bootstrap_core_go_binary
+# Factory: github.com/google/blueprint/bootstrap.newGoBinaryModuleFactory.func1
+# Defined: blueprint/Blueprints:121:1
+
+build ${g.bootstrap.buildDir}/.bootstrap/minibp/obj/minibp.a: $
+ g.bootstrap.compile $
+ ${g.bootstrap.srcDir}/blueprint/bootstrap/minibp/main.go | $
+ ${g.bootstrap.compileCmd} $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg/github.com/google/blueprint/parser.a $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg/github.com/google/blueprint/deptools.a $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg/github.com/google/blueprint/pathtools.a $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg/github.com/google/blueprint/proptools.a $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint/pkg/github.com/google/blueprint.a $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap-bpdoc/pkg/github.com/google/blueprint/bootstrap/bpdoc.a $
+ ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap/pkg/github.com/google/blueprint/bootstrap.a
+ incFlags = -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap-bpdoc/pkg -I ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap/pkg
+ pkgPath = minibp
+default ${g.bootstrap.buildDir}/.bootstrap/minibp/obj/minibp.a
+
+build ${g.bootstrap.buildDir}/.bootstrap/minibp/obj/a.out: g.bootstrap.link $
+ ${g.bootstrap.buildDir}/.bootstrap/minibp/obj/minibp.a | $
+ ${g.bootstrap.linkCmd}
+ libDirFlags = -L ${g.bootstrap.buildDir}/.bootstrap/blueprint-parser/pkg -L ${g.bootstrap.buildDir}/.bootstrap/blueprint-deptools/pkg -L ${g.bootstrap.buildDir}/.bootstrap/blueprint-pathtools/pkg -L ${g.bootstrap.buildDir}/.bootstrap/blueprint-proptools/pkg -L ${g.bootstrap.buildDir}/.bootstrap/blueprint/pkg -L ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap-bpdoc/pkg -L ${g.bootstrap.buildDir}/.bootstrap/blueprint-bootstrap/pkg
+default ${g.bootstrap.buildDir}/.bootstrap/minibp/obj/a.out
+
+build ${g.bootstrap.BinDir}/minibp: g.bootstrap.cp $
+ ${g.bootstrap.buildDir}/.bootstrap/minibp/obj/a.out
+default ${g.bootstrap.BinDir}/minibp
+
+# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
+# Singleton: bootstrap
+# Factory: github.com/google/blueprint/bootstrap.newSingletonFactory.func1
+
+build ${g.bootstrap.buildDir}/.bootstrap/build.ninja: g.bootstrap.build.ninja $
+ ${g.bootstrap.srcDir}/Blueprints | ${builder}
+ builder = ${g.bootstrap.BinDir}/minibp
+ extra = --build-primary
+default ${g.bootstrap.buildDir}/.bootstrap/build.ninja
+
+build ${g.bootstrap.buildDir}/.minibootstrap/build.ninja.in: $
+ g.bootstrap.build.ninja ${g.bootstrap.srcDir}/Blueprints | ${builder}
+ builder = ${g.bootstrap.BinDir}/minibp
+ extra =
+default ${g.bootstrap.buildDir}/.minibootstrap/build.ninja.in
+
+build ${g.bootstrap.buildDir}/.minibootstrap/build.ninja: $
+ g.bootstrap.bootstrap $
+ ${g.bootstrap.buildDir}/.minibootstrap/build.ninja.in | $
+ ${g.bootstrap.bootstrapCmd}
+default ${g.bootstrap.buildDir}/.minibootstrap/build.ninja
+
diff --git a/tests/test_tree_tests.sh b/tests/test_tree_tests.sh
new file mode 100755
index 0000000..a269fdf
--- /dev/null
+++ b/tests/test_tree_tests.sh
@@ -0,0 +1,78 @@
+#!/bin/bash -ex
+
+function mtime() {
+ stat -c %Y $1
+}
+
+# Go to top of blueprint tree
+TOP=$(dirname ${BASH_SOURCE[0]})/..
+cd ${TOP}
+
+rm -rf out.test
+mkdir out.test
+
+rm -rf src.test
+mkdir src.test
+cp -r tests/test_tree src.test/test_tree
+
+cd out.test
+export SRCDIR=../src.test/test_tree
+${SRCDIR}/blueprint/bootstrap.bash
+./blueprint.bash
+
+if ! cmp -s ${SRCDIR}/build.ninja.in .minibootstrap/build.ninja.in; then
+ echo "tests/test_tree/build.ninja.in and .minibootstrap/build.ninja.in should be the same" >&2
+ exit 1
+fi
+
+OLDTIME=$(mtime build.ninja)
+
+sleep 2
+./blueprint.bash
+
+if [ ${OLDTIME} != $(mtime build.ninja) ]; then
+ echo "unnecessary build.ninja regeneration for null build" >&2
+ exit 1
+fi
+
+mkdir ${SRCDIR}/newglob
+
+sleep 2
+./blueprint.bash
+
+if [ ${OLDTIME} != $(mtime build.ninja) ]; then
+ echo "unnecessary build.ninja regeneration for glob addition" >&2
+ exit 1
+fi
+
+touch ${SRCDIR}/newglob/Blueprints
+
+sleep 2
+./blueprint.bash
+
+if [ ${OLDTIME} = $(mtime build.ninja) ]; then
+ echo "Failed to rebuild for glob addition" >&2
+ exit 1
+fi
+
+OLDTIME=$(mtime build.ninja)
+rm ${SRCDIR}/newglob/Blueprints
+
+sleep 2
+./blueprint.bash
+
+if [ ${OLDTIME} = $(mtime build.ninja) ]; then
+ echo "Failed to rebuild for glob removal" >&2
+ exit 1
+fi
+
+OLDTIME=$(mtime build.ninja)
+rmdir ${SRCDIR}/newglob
+
+sleep 2
+./blueprint.bash
+
+if [ ${OLDTIME} != $(mtime build.ninja) ]; then
+ echo "unnecessary build.ninja regeneration for glob removal" >&2
+ exit 1
+fi