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 15 package bootstrap 16 17 import ( 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 42 var ( 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 61 type 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. 69 func 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 82 func 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. 112 type globSingleton struct { 113 globLister func() []blueprint.GlobPath 114 } 115 116 func globSingletonFactory(ctx *blueprint.Context) func() blueprint.Singleton { 117 return func() blueprint.Singleton { 118 return &globSingleton{ 119 globLister: ctx.Globs, 120 } 121 } 122 } 123 124 func (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