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 "errors" 20 "fmt" 21 "io" 22 "os" 23 "runtime" 24 "runtime/debug" 25 "runtime/pprof" 26 "runtime/trace" 27 "strings" 28 29 "github.com/google/blueprint" 30 "github.com/google/blueprint/proptools" 31) 32 33type Args struct { 34 ModuleListFile string 35 OutFile string 36 37 EmptyNinjaFile bool 38 39 NoGC bool 40 Cpuprofile string 41 Memprofile string 42 TraceFile string 43 44 // Debug data json file 45 ModuleDebugFile string 46 IncrementalBuildActions bool 47} 48 49// RegisterGoModuleTypes adds module types to build tools written in golang 50func RegisterGoModuleTypes(ctx *blueprint.Context) { 51 ctx.RegisterModuleType("bootstrap_go_package", newGoPackageModuleFactory()) 52 ctx.RegisterModuleType("blueprint_go_binary", newGoBinaryModuleFactory()) 53} 54 55// GoModuleTypesAreWrapped is called by Soong before calling RunBlueprint to provide its own wrapped 56// implementations of bootstrap_go_package and blueprint_go_bianry. 57func GoModuleTypesAreWrapped() { 58 goModuleTypesAreWrapped = true 59} 60 61var goModuleTypesAreWrapped = false 62 63// RunBlueprint emits `args.OutFile` (a Ninja file) and returns the list of 64// its dependencies. These can be written to a `${args.OutFile}.d` file 65// so that it is correctly rebuilt when needed in case Blueprint is itself 66// invoked from Ninja 67func RunBlueprint(args Args, stopBefore StopBefore, ctx *blueprint.Context, config interface{}) ([]string, error) { 68 runtime.GOMAXPROCS(runtime.NumCPU()) 69 70 if args.NoGC { 71 debug.SetGCPercent(-1) 72 } 73 74 if args.Cpuprofile != "" { 75 f, err := os.Create(blueprint.JoinPath(ctx.SrcDir(), args.Cpuprofile)) 76 if err != nil { 77 return nil, fmt.Errorf("error opening cpuprofile: %s", err) 78 } 79 pprof.StartCPUProfile(f) 80 defer f.Close() 81 defer pprof.StopCPUProfile() 82 } 83 84 if args.TraceFile != "" { 85 f, err := os.Create(blueprint.JoinPath(ctx.SrcDir(), args.TraceFile)) 86 if err != nil { 87 return nil, fmt.Errorf("error opening trace: %s", err) 88 } 89 trace.Start(f) 90 defer f.Close() 91 defer trace.Stop() 92 } 93 94 if args.ModuleListFile == "" { 95 return nil, fmt.Errorf("-l <moduleListFile> is required and must be nonempty") 96 } 97 ctx.SetModuleListFile(args.ModuleListFile) 98 99 var ninjaDeps []string 100 ninjaDeps = append(ninjaDeps, args.ModuleListFile) 101 102 ctx.BeginEvent("list_modules") 103 var filesToParse []string 104 if f, err := ctx.ListModulePaths("."); err != nil { 105 return nil, fmt.Errorf("could not enumerate files: %v\n", err.Error()) 106 } else { 107 filesToParse = f 108 } 109 ctx.EndEvent("list_modules") 110 111 ctx.RegisterBottomUpMutator("bootstrap_deps", BootstrapDeps).UsesReverseDependencies() 112 ctx.RegisterSingletonType("bootstrap", newSingletonFactory(), false) 113 if !goModuleTypesAreWrapped { 114 RegisterGoModuleTypes(ctx) 115 } 116 117 ctx.BeginEvent("parse_bp") 118 if blueprintFiles, errs := ctx.ParseFileList(".", filesToParse, config); len(errs) > 0 { 119 return nil, colorizeErrs(errs) 120 } else { 121 ctx.EndEvent("parse_bp") 122 ninjaDeps = append(ninjaDeps, blueprintFiles...) 123 } 124 125 if resolvedDeps, errs := ctx.ResolveDependencies(config); len(errs) > 0 { 126 return nil, colorizeErrs(errs) 127 } else { 128 ninjaDeps = append(ninjaDeps, resolvedDeps...) 129 } 130 131 if stopBefore == StopBeforePrepareBuildActions { 132 return ninjaDeps, nil 133 } 134 135 if ctx.BeforePrepareBuildActionsHook != nil { 136 if err := ctx.BeforePrepareBuildActionsHook(); err != nil { 137 return nil, colorizeErrs([]error{err}) 138 } 139 } 140 141 if ctx.GetIncrementalAnalysis() { 142 var err error = nil 143 err = ctx.RestoreAllBuildActions(config.(BootstrapConfig).SoongOutDir()) 144 if err != nil { 145 return nil, colorizeErrs([]error{err}) 146 } 147 } 148 149 if buildActionsDeps, errs := ctx.PrepareBuildActions(config); len(errs) > 0 { 150 return nil, colorizeErrs(errs) 151 } else { 152 ninjaDeps = append(ninjaDeps, buildActionsDeps...) 153 } 154 155 if args.ModuleDebugFile != "" { 156 ctx.GenerateModuleDebugInfo(args.ModuleDebugFile) 157 } 158 159 if stopBefore == StopBeforeWriteNinja { 160 return ninjaDeps, nil 161 } 162 163 providersValidationChan := make(chan []error, 1) 164 go func() { 165 providersValidationChan <- ctx.VerifyProvidersWereUnchanged() 166 }() 167 168 var out blueprint.StringWriterWriter 169 var f *os.File 170 var buf *bufio.Writer 171 172 ctx.BeginEvent("write_files") 173 defer ctx.EndEvent("write_files") 174 if args.EmptyNinjaFile { 175 if err := os.WriteFile(blueprint.JoinPath(ctx.SrcDir(), args.OutFile), []byte(nil), blueprint.OutFilePermissions); err != nil { 176 return nil, fmt.Errorf("error writing empty Ninja file: %s", err) 177 } 178 out = io.Discard.(blueprint.StringWriterWriter) 179 } else { 180 f, err := os.OpenFile(blueprint.JoinPath(ctx.SrcDir(), args.OutFile), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, blueprint.OutFilePermissions) 181 if err != nil { 182 return nil, fmt.Errorf("error opening Ninja file: %s", err) 183 } 184 defer f.Close() 185 buf = bufio.NewWriterSize(f, 16*1024*1024) 186 out = buf 187 } 188 189 if err := ctx.WriteBuildFile(out, !strings.Contains(args.OutFile, "bootstrap.ninja") && !args.EmptyNinjaFile, args.OutFile); err != nil { 190 return nil, fmt.Errorf("error writing Ninja file contents: %s", err) 191 } 192 193 if buf != nil { 194 if err := buf.Flush(); err != nil { 195 return nil, fmt.Errorf("error flushing Ninja file contents: %s", err) 196 } 197 } 198 199 if f != nil { 200 if err := f.Close(); err != nil { 201 return nil, fmt.Errorf("error closing Ninja file: %s", err) 202 } 203 } 204 205 // TODO(b/357140398): parallelize this with other ninja file writing work. 206 if ctx.GetIncrementalEnabled() { 207 if err := ctx.CacheAllBuildActions(config.(BootstrapConfig).SoongOutDir()); err != nil { 208 return nil, fmt.Errorf("error cache build actions: %s", err) 209 } 210 } 211 212 providerValidationErrors := <-providersValidationChan 213 if providerValidationErrors != nil { 214 return nil, proptools.MergeErrors(providerValidationErrors) 215 } 216 217 if args.Memprofile != "" { 218 f, err := os.Create(blueprint.JoinPath(ctx.SrcDir(), args.Memprofile)) 219 if err != nil { 220 return nil, fmt.Errorf("error opening memprofile: %s", err) 221 } 222 defer f.Close() 223 pprof.WriteHeapProfile(f) 224 } 225 226 return ninjaDeps, nil 227} 228 229func colorizeErrs(errs []error) error { 230 red := "\x1b[31m" 231 unred := "\x1b[0m" 232 233 var colorizedErrs []error 234 for _, err := range errs { 235 switch err := err.(type) { 236 case *blueprint.BlueprintError, 237 *blueprint.ModuleError, 238 *blueprint.PropertyError: 239 colorizedErrs = append(colorizedErrs, fmt.Errorf("%serror:%s %w", red, unred, err)) 240 default: 241 colorizedErrs = append(colorizedErrs, fmt.Errorf("%sinternal error:%s %s", red, unred, err)) 242 } 243 } 244 245 return errors.Join(colorizedErrs...) 246} 247