diff options
Diffstat (limited to 'bootstrap')
| -rw-r--r-- | bootstrap/bpglob/bpglob.go | 77 | ||||
| -rw-r--r-- | bootstrap/command.go | 2 | ||||
| -rw-r--r-- | bootstrap/glob.go | 144 |
3 files changed, 223 insertions, 0 deletions
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) + } +} |
