1// Copyright 2014 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 "bufio" 19 "flag" 20 "fmt" 21 "io" 22 "io/ioutil" 23 "os" 24 "path/filepath" 25 "runtime" 26 "runtime/debug" 27 "runtime/pprof" 28 "runtime/trace" 29 30 "github.com/google/blueprint" 31 "github.com/google/blueprint/deptools" 32) 33 34type Args struct { 35 OutFile string 36 GlobFile string 37 DepFile string 38 DocFile string 39 Cpuprofile string 40 Memprofile string 41 DelveListen string 42 DelvePath string 43 TraceFile string 44 RunGoTests bool 45 UseValidations bool 46 NoGC bool 47 EmptyNinjaFile bool 48 BuildDir string 49 ModuleListFile string 50 NinjaBuildDir string 51 TopFile string 52 GeneratingPrimaryBuilder bool 53 54 PrimaryBuilderInvocations []PrimaryBuilderInvocation 55} 56 57var ( 58 CmdlineArgs Args 59 absSrcDir string 60) 61 62func init() { 63 flag.StringVar(&CmdlineArgs.OutFile, "o", "build.ninja", "the Ninja file to output") 64 flag.StringVar(&CmdlineArgs.GlobFile, "globFile", "build-globs.ninja", "the Ninja file of globs to output") 65 flag.StringVar(&CmdlineArgs.BuildDir, "b", ".", "the build output directory") 66 flag.StringVar(&CmdlineArgs.NinjaBuildDir, "n", "", "the ninja builddir directory") 67 flag.StringVar(&CmdlineArgs.DepFile, "d", "", "the dependency file to output") 68 flag.StringVar(&CmdlineArgs.DocFile, "docs", "", "build documentation file to output") 69 flag.StringVar(&CmdlineArgs.Cpuprofile, "cpuprofile", "", "write cpu profile to file") 70 flag.StringVar(&CmdlineArgs.TraceFile, "trace", "", "write trace to file") 71 flag.StringVar(&CmdlineArgs.Memprofile, "memprofile", "", "write memory profile to file") 72 flag.BoolVar(&CmdlineArgs.NoGC, "nogc", false, "turn off GC for debugging") 73 flag.BoolVar(&CmdlineArgs.RunGoTests, "t", false, "build and run go tests during bootstrap") 74 flag.BoolVar(&CmdlineArgs.UseValidations, "use-validations", false, "use validations to depend on go tests") 75 flag.StringVar(&CmdlineArgs.ModuleListFile, "l", "", "file that lists filepaths to parse") 76 flag.BoolVar(&CmdlineArgs.EmptyNinjaFile, "empty-ninja-file", false, "write out a 0-byte ninja file") 77} 78 79func Main(ctx *blueprint.Context, config interface{}, generatingPrimaryBuilder bool) { 80 if !flag.Parsed() { 81 flag.Parse() 82 } 83 84 if flag.NArg() != 1 { 85 fatalf("no Blueprints file specified") 86 } 87 88 CmdlineArgs.TopFile = flag.Arg(0) 89 CmdlineArgs.GeneratingPrimaryBuilder = generatingPrimaryBuilder 90 ninjaDeps := RunBlueprint(CmdlineArgs, ctx, config) 91 err := deptools.WriteDepFile(CmdlineArgs.DepFile, CmdlineArgs.OutFile, ninjaDeps) 92 if err != nil { 93 fatalf("Cannot write depfile '%s': %s", CmdlineArgs.DepFile, err) 94 } 95} 96 97func PrimaryBuilderExtraFlags(args Args, globFile, mainNinjaFile string) []string { 98 result := make([]string, 0) 99 100 if args.RunGoTests { 101 result = append(result, "-t") 102 } 103 104 result = append(result, "-l", args.ModuleListFile) 105 result = append(result, "-globFile", globFile) 106 result = append(result, "-o", mainNinjaFile) 107 108 if args.EmptyNinjaFile { 109 result = append(result, "--empty-ninja-file") 110 } 111 112 if args.DelveListen != "" { 113 result = append(result, "--delve_listen", args.DelveListen) 114 } 115 116 if args.DelvePath != "" { 117 result = append(result, "--delve_path", args.DelvePath) 118 } 119 120 return result 121} 122 123func writeEmptyGlobFile(path string) { 124 err := os.MkdirAll(filepath.Dir(path), 0777) 125 if err != nil { 126 fatalf("Failed to create parent directories of empty ninja glob file '%s': %s", path, err) 127 } 128 129 if _, err := os.Stat(path); os.IsNotExist(err) { 130 err = ioutil.WriteFile(path, nil, 0666) 131 if err != nil { 132 fatalf("Failed to create empty ninja glob file '%s': %s", path, err) 133 } 134 } 135} 136 137// Returns the list of dependencies the emitted Ninja files has. These can be 138// written to the .d file for the output so that it is correctly rebuilt when 139// needed in case Blueprint is itself invoked from Ninja 140func RunBlueprint(args Args, ctx *blueprint.Context, config interface{}) []string { 141 runtime.GOMAXPROCS(runtime.NumCPU()) 142 143 if args.NoGC { 144 debug.SetGCPercent(-1) 145 } 146 147 absSrcDir = ctx.SrcDir() 148 149 if args.Cpuprofile != "" { 150 f, err := os.Create(absolutePath(args.Cpuprofile)) 151 if err != nil { 152 fatalf("error opening cpuprofile: %s", err) 153 } 154 pprof.StartCPUProfile(f) 155 defer f.Close() 156 defer pprof.StopCPUProfile() 157 } 158 159 if args.TraceFile != "" { 160 f, err := os.Create(absolutePath(args.TraceFile)) 161 if err != nil { 162 fatalf("error opening trace: %s", err) 163 } 164 trace.Start(f) 165 defer f.Close() 166 defer trace.Stop() 167 } 168 169 srcDir := filepath.Dir(args.TopFile) 170 171 ninjaDeps := make([]string, 0) 172 173 if args.ModuleListFile != "" { 174 ctx.SetModuleListFile(args.ModuleListFile) 175 ninjaDeps = append(ninjaDeps, args.ModuleListFile) 176 } else { 177 fatalf("-l <moduleListFile> is required and must be nonempty") 178 } 179 filesToParse, err := ctx.ListModulePaths(srcDir) 180 if err != nil { 181 fatalf("could not enumerate files: %v\n", err.Error()) 182 } 183 184 buildDir := config.(BootstrapConfig).BuildDir() 185 186 stage := StageMain 187 if args.GeneratingPrimaryBuilder { 188 stage = StagePrimary 189 } 190 191 primaryBuilderNinjaGlobFile := absolutePath(filepath.Join(args.BuildDir, bootstrapSubDir, "build-globs.ninja")) 192 mainNinjaFile := filepath.Join("$buildDir", "build.ninja") 193 194 writeEmptyGlobFile(primaryBuilderNinjaGlobFile) 195 196 var invocations []PrimaryBuilderInvocation 197 198 if args.PrimaryBuilderInvocations != nil { 199 invocations = args.PrimaryBuilderInvocations 200 } else { 201 primaryBuilderArgs := PrimaryBuilderExtraFlags(args, primaryBuilderNinjaGlobFile, mainNinjaFile) 202 primaryBuilderArgs = append(primaryBuilderArgs, args.TopFile) 203 204 invocations = []PrimaryBuilderInvocation{{ 205 Inputs: []string{args.TopFile}, 206 Outputs: []string{mainNinjaFile}, 207 Args: primaryBuilderArgs, 208 }} 209 } 210 211 bootstrapConfig := &Config{ 212 stage: stage, 213 214 topLevelBlueprintsFile: args.TopFile, 215 globFile: primaryBuilderNinjaGlobFile, 216 runGoTests: args.RunGoTests, 217 useValidations: args.UseValidations, 218 primaryBuilderInvocations: invocations, 219 } 220 221 ctx.RegisterBottomUpMutator("bootstrap_plugin_deps", pluginDeps) 222 ctx.RegisterModuleType("bootstrap_go_package", newGoPackageModuleFactory(bootstrapConfig)) 223 ctx.RegisterModuleType("bootstrap_go_binary", newGoBinaryModuleFactory(bootstrapConfig, false)) 224 ctx.RegisterModuleType("blueprint_go_binary", newGoBinaryModuleFactory(bootstrapConfig, true)) 225 ctx.RegisterSingletonType("bootstrap", newSingletonFactory(bootstrapConfig)) 226 227 ctx.RegisterSingletonType("glob", globSingletonFactory(bootstrapConfig, ctx)) 228 229 blueprintFiles, errs := ctx.ParseFileList(filepath.Dir(args.TopFile), filesToParse, config) 230 if len(errs) > 0 { 231 fatalErrors(errs) 232 } 233 234 // Add extra ninja file dependencies 235 ninjaDeps = append(ninjaDeps, blueprintFiles...) 236 237 extraDeps, errs := ctx.ResolveDependencies(config) 238 if len(errs) > 0 { 239 fatalErrors(errs) 240 } 241 ninjaDeps = append(ninjaDeps, extraDeps...) 242 243 if args.DocFile != "" { 244 err := writeDocs(ctx, config, absolutePath(args.DocFile)) 245 if err != nil { 246 fatalErrors([]error{err}) 247 } 248 return nil 249 } 250 251 if c, ok := config.(ConfigStopBefore); ok { 252 if c.StopBefore() == StopBeforePrepareBuildActions { 253 return ninjaDeps 254 } 255 } 256 257 extraDeps, errs = ctx.PrepareBuildActions(config) 258 if len(errs) > 0 { 259 fatalErrors(errs) 260 } 261 ninjaDeps = append(ninjaDeps, extraDeps...) 262 263 if c, ok := config.(ConfigStopBefore); ok { 264 if c.StopBefore() == StopBeforeWriteNinja { 265 return ninjaDeps 266 } 267 } 268 269 const outFilePermissions = 0666 270 var out io.StringWriter 271 var f *os.File 272 var buf *bufio.Writer 273 274 if args.EmptyNinjaFile { 275 if err := ioutil.WriteFile(absolutePath(args.OutFile), []byte(nil), outFilePermissions); err != nil { 276 fatalf("error writing empty Ninja file: %s", err) 277 } 278 } 279 280 if stage != StageMain || !args.EmptyNinjaFile { 281 f, err = os.OpenFile(absolutePath(args.OutFile), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, outFilePermissions) 282 if err != nil { 283 fatalf("error opening Ninja file: %s", err) 284 } 285 buf = bufio.NewWriterSize(f, 16*1024*1024) 286 out = buf 287 } else { 288 out = ioutil.Discard.(io.StringWriter) 289 } 290 291 if args.GlobFile != "" { 292 buffer, errs := generateGlobNinjaFile(bootstrapConfig, config, ctx.Globs) 293 if len(errs) > 0 { 294 fatalErrors(errs) 295 } 296 297 err = ioutil.WriteFile(absolutePath(args.GlobFile), buffer, outFilePermissions) 298 if err != nil { 299 fatalf("error writing %s: %s", args.GlobFile, err) 300 } 301 } 302 303 err = ctx.WriteBuildFile(out) 304 if err != nil { 305 fatalf("error writing Ninja file contents: %s", err) 306 } 307 308 if buf != nil { 309 err = buf.Flush() 310 if err != nil { 311 fatalf("error flushing Ninja file contents: %s", err) 312 } 313 } 314 315 if f != nil { 316 err = f.Close() 317 if err != nil { 318 fatalf("error closing Ninja file: %s", err) 319 } 320 } 321 322 if c, ok := config.(ConfigRemoveAbandonedFilesUnder); ok { 323 under, except := c.RemoveAbandonedFilesUnder(buildDir) 324 err := removeAbandonedFilesUnder(ctx, srcDir, buildDir, under, except) 325 if err != nil { 326 fatalf("error removing abandoned files: %s", err) 327 } 328 } 329 330 if args.Memprofile != "" { 331 f, err := os.Create(absolutePath(args.Memprofile)) 332 if err != nil { 333 fatalf("error opening memprofile: %s", err) 334 } 335 defer f.Close() 336 pprof.WriteHeapProfile(f) 337 } 338 339 return ninjaDeps 340} 341 342func fatalf(format string, args ...interface{}) { 343 fmt.Printf(format, args...) 344 fmt.Print("\n") 345 os.Exit(1) 346} 347 348func fatalErrors(errs []error) { 349 red := "\x1b[31m" 350 unred := "\x1b[0m" 351 352 for _, err := range errs { 353 switch err := err.(type) { 354 case *blueprint.BlueprintError, 355 *blueprint.ModuleError, 356 *blueprint.PropertyError: 357 fmt.Printf("%serror:%s %s\n", red, unred, err.Error()) 358 default: 359 fmt.Printf("%sinternal error:%s %s\n", red, unred, err) 360 } 361 } 362 os.Exit(1) 363} 364 365func absolutePath(path string) string { 366 if filepath.IsAbs(path) { 367 return path 368 } 369 return filepath.Join(absSrcDir, path) 370} 371