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 "bytes" 19 "fmt" 20 "path/filepath" 21 "strings" 22 23 "github.com/google/blueprint" 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(miniBootstrapDir, "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 writeRule bool 115} 116 117func globSingletonFactory(ctx *blueprint.Context) func() blueprint.Singleton { 118 return func() blueprint.Singleton { 119 return &globSingleton{ 120 globLister: ctx.Globs, 121 } 122 } 123} 124 125func (s *globSingleton) GenerateBuildActions(ctx blueprint.SingletonContext) { 126 for _, g := range s.globLister() { 127 fileListFile := filepath.Join(BuildDir, ".glob", g.Name) 128 129 if s.writeRule { 130 depFile := fileListFile + ".d" 131 132 // We need to write the file list here so that it has an older modified date 133 // than the build.ninja (otherwise we'd run the primary builder twice on 134 // every new glob) 135 // 136 // We don't need to write the depfile because we're guaranteed that ninja 137 // will run the command at least once (to record it into the ninja_log), so 138 // the depfile will be loaded from that execution. 139 fileList := strings.Join(g.Files, "\n") + "\n" 140 err := pathtools.WriteFileIfChanged(absolutePath(fileListFile), []byte(fileList), 0666) 141 if err != nil { 142 panic(fmt.Errorf("error writing %s: %s", fileListFile, err)) 143 } 144 145 GlobFile(ctx, g.Pattern, g.Excludes, fileListFile, depFile) 146 } else { 147 // Make build.ninja depend on the fileListFile 148 ctx.AddNinjaFileDeps(fileListFile) 149 } 150 } 151} 152 153func generateGlobNinjaFile(globLister func() []blueprint.GlobPath) ([]byte, []error) { 154 ctx := blueprint.NewContext() 155 ctx.RegisterSingletonType("glob", func() blueprint.Singleton { 156 return &globSingleton{ 157 globLister: globLister, 158 writeRule: true, 159 } 160 }) 161 162 extraDeps, errs := ctx.ResolveDependencies(nil) 163 if len(extraDeps) > 0 { 164 return nil, []error{fmt.Errorf("shouldn't have extra deps")} 165 } 166 if len(errs) > 0 { 167 return nil, errs 168 } 169 170 extraDeps, errs = ctx.PrepareBuildActions(nil) 171 if len(extraDeps) > 0 { 172 return nil, []error{fmt.Errorf("shouldn't have extra deps")} 173 } 174 if len(errs) > 0 { 175 return nil, errs 176 } 177 178 buf := bytes.NewBuffer(nil) 179 err := ctx.WriteBuildFile(buf) 180 if err != nil { 181 return nil, []error{err} 182 } 183 184 return buf.Bytes(), nil 185} 186