1// Copyright 2016 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package bootstrap 16 17import ( 18 "fmt" 19 "path/filepath" 20 "strings" 21 22 "github.com/google/blueprint" 23 "github.com/google/blueprint/deptools" 24 "github.com/google/blueprint/pathtools" 25) 26 27// This file supports globbing source files in Blueprints files. 28// 29// The build.ninja file needs to be regenerated any time a file matching the glob is added 30// or removed. The naive solution is to have the build.ninja file depend on all the 31// traversed directories, but this will cause the regeneration step to run every time a 32// non-matching file is added to a traversed directory, including backup files created by 33// editors. 34// 35// The solution implemented here optimizes out regenerations when the directory modifications 36// don't match the glob by having the build.ninja file depend on an intermedate file that 37// is only updated when a file matching the glob is added or removed. The intermediate file 38// depends on the traversed directories via a depfile. The depfile is used to avoid build 39// errors if a directory is deleted - a direct dependency on the deleted directory would result 40// in a build failure with a "missing and no known rule to make it" error. 41 42var ( 43 globCmd = filepath.Join("$BinDir", "bpglob") 44 45 // globRule rule traverses directories to produce a list of files that match $glob 46 // and writes it to $out if it has changed, and writes the directories to $out.d 47 GlobRule = pctx.StaticRule("GlobRule", 48 blueprint.RuleParams{ 49 Command: fmt.Sprintf(`%s -o $out $excludes "$glob"`, globCmd), 50 CommandDeps: []string{globCmd}, 51 Description: "glob $glob", 52 53 Restat: true, 54 Deps: blueprint.DepsGCC, 55 Depfile: "$out.d", 56 }, 57 "glob", "excludes") 58) 59 60// GlobFileContext is the subset of ModuleContext and SingletonContext needed by GlobFile 61type GlobFileContext interface { 62 Build(pctx blueprint.PackageContext, params blueprint.BuildParams) 63} 64 65// GlobFile creates a rule to write to fileListFile a list of the files that match the specified 66// pattern but do not match any of the patterns specified in excludes. The file will include 67// appropriate dependencies written to depFile to regenerate the file if and only if the list of 68// matching files has changed. 69func GlobFile(ctx GlobFileContext, pattern string, excludes []string, 70 fileListFile, depFile string) { 71 72 ctx.Build(pctx, blueprint.BuildParams{ 73 Rule: GlobRule, 74 Outputs: []string{fileListFile}, 75 Args: map[string]string{ 76 "glob": pattern, 77 "excludes": joinWithPrefixAndQuote(excludes, "-e "), 78 }, 79 }) 80} 81 82func joinWithPrefixAndQuote(strs []string, prefix string) string { 83 if len(strs) == 0 { 84 return "" 85 } 86 87 if len(strs) == 1 { 88 return prefix + `"` + strs[0] + `"` 89 } 90 91 n := len(" ") * (len(strs) - 1) 92 for _, s := range strs { 93 n += len(prefix) + len(s) + len(`""`) 94 } 95 96 ret := make([]byte, 0, n) 97 for i, s := range strs { 98 if i != 0 { 99 ret = append(ret, ' ') 100 } 101 ret = append(ret, prefix...) 102 ret = append(ret, '"') 103 ret = append(ret, s...) 104 ret = append(ret, '"') 105 } 106 return string(ret) 107} 108 109// globSingleton collects any glob patterns that were seen by Context and writes out rules to 110// re-evaluate them whenever the contents of the searched directories change, and retrigger the 111// primary builder if the results change. 112type globSingleton struct { 113 globLister func() []blueprint.GlobPath 114} 115 116func globSingletonFactory(ctx *blueprint.Context) func() blueprint.Singleton { 117 return func() blueprint.Singleton { 118 return &globSingleton{ 119 globLister: ctx.Globs, 120 } 121 } 122} 123 124func (s *globSingleton) GenerateBuildActions(ctx blueprint.SingletonContext) { 125 if config, ok := ctx.Config().(ConfigInterface); ok && config.GeneratingBootstrapper() { 126 // Skip singleton for bootstrap.bash -r case to avoid putting unnecessary glob lines into 127 // the bootstrap manifest 128 return 129 } 130 131 for _, g := range s.globLister() { 132 fileListFile := filepath.Join(BuildDir, ".glob", g.Name) 133 depFile := fileListFile + ".d" 134 135 fileList := strings.Join(g.Files, "\n") + "\n" 136 pathtools.WriteFileIfChanged(fileListFile, []byte(fileList), 0666) 137 deptools.WriteDepFile(depFile, fileListFile, g.Deps) 138 139 GlobFile(ctx, g.Pattern, g.Excludes, fileListFile, depFile) 140 141 // Make build.ninja depend on the fileListFile 142 ctx.AddNinjaFileDeps(fileListFile) 143 } 144} 145