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
34var (
35	outFile        string
36	globFile       string
37	depFile        string
38	docFile        string
39	cpuprofile     string
40	memprofile     string
41	traceFile      string
42	runGoTests     bool
43	useValidations bool
44	noGC           bool
45	emptyNinjaFile bool
46	BuildDir       string
47	ModuleListFile string
48	NinjaBuildDir  string
49	SrcDir         string
50	absSrcDir      string
51)
52
53func init() {
54	flag.StringVar(&outFile, "o", "build.ninja", "the Ninja file to output")
55	flag.StringVar(&globFile, "globFile", "build-globs.ninja", "the Ninja file of globs to output")
56	flag.StringVar(&BuildDir, "b", ".", "the build output directory")
57	flag.StringVar(&NinjaBuildDir, "n", "", "the ninja builddir directory")
58	flag.StringVar(&depFile, "d", "", "the dependency file to output")
59	flag.StringVar(&docFile, "docs", "", "build documentation file to output")
60	flag.StringVar(&cpuprofile, "cpuprofile", "", "write cpu profile to file")
61	flag.StringVar(&traceFile, "trace", "", "write trace to file")
62	flag.StringVar(&memprofile, "memprofile", "", "write memory profile to file")
63	flag.BoolVar(&noGC, "nogc", false, "turn off GC for debugging")
64	flag.BoolVar(&runGoTests, "t", false, "build and run go tests during bootstrap")
65	flag.BoolVar(&useValidations, "use-validations", false, "use validations to depend on go tests")
66	flag.StringVar(&ModuleListFile, "l", "", "file that lists filepaths to parse")
67	flag.BoolVar(&emptyNinjaFile, "empty-ninja-file", false, "write out a 0-byte ninja file")
68}
69
70func Main(ctx *blueprint.Context, config interface{}, extraNinjaFileDeps ...string) {
71	if !flag.Parsed() {
72		flag.Parse()
73	}
74
75	runtime.GOMAXPROCS(runtime.NumCPU())
76
77	if noGC {
78		debug.SetGCPercent(-1)
79	}
80
81	absSrcDir = ctx.SrcDir()
82
83	if cpuprofile != "" {
84		f, err := os.Create(absolutePath(cpuprofile))
85		if err != nil {
86			fatalf("error opening cpuprofile: %s", err)
87		}
88		pprof.StartCPUProfile(f)
89		defer f.Close()
90		defer pprof.StopCPUProfile()
91	}
92
93	if traceFile != "" {
94		f, err := os.Create(absolutePath(traceFile))
95		if err != nil {
96			fatalf("error opening trace: %s", err)
97		}
98		trace.Start(f)
99		defer f.Close()
100		defer trace.Stop()
101	}
102
103	if flag.NArg() != 1 {
104		fatalf("no Blueprints file specified")
105	}
106
107	SrcDir = filepath.Dir(flag.Arg(0))
108	if ModuleListFile != "" {
109		ctx.SetModuleListFile(ModuleListFile)
110		extraNinjaFileDeps = append(extraNinjaFileDeps, ModuleListFile)
111	} else {
112		fatalf("-l <moduleListFile> is required and must be nonempty")
113	}
114	filesToParse, err := ctx.ListModulePaths(SrcDir)
115	if err != nil {
116		fatalf("could not enumerate files: %v\n", err.Error())
117	}
118
119	if NinjaBuildDir == "" {
120		NinjaBuildDir = BuildDir
121	}
122
123	stage := StageMain
124	if c, ok := config.(ConfigInterface); ok {
125		if c.GeneratingPrimaryBuilder() {
126			stage = StagePrimary
127		}
128	}
129
130	bootstrapConfig := &Config{
131		stage: stage,
132
133		topLevelBlueprintsFile: flag.Arg(0),
134		emptyNinjaFile:         emptyNinjaFile,
135		runGoTests:             runGoTests,
136		useValidations:         useValidations,
137		moduleListFile:         ModuleListFile,
138	}
139
140	ctx.RegisterBottomUpMutator("bootstrap_plugin_deps", pluginDeps)
141	ctx.RegisterModuleType("bootstrap_go_package", newGoPackageModuleFactory(bootstrapConfig))
142	ctx.RegisterModuleType("bootstrap_go_binary", newGoBinaryModuleFactory(bootstrapConfig, false))
143	ctx.RegisterModuleType("blueprint_go_binary", newGoBinaryModuleFactory(bootstrapConfig, true))
144	ctx.RegisterSingletonType("bootstrap", newSingletonFactory(bootstrapConfig))
145
146	ctx.RegisterSingletonType("glob", globSingletonFactory(ctx))
147
148	deps, errs := ctx.ParseFileList(filepath.Dir(bootstrapConfig.topLevelBlueprintsFile), filesToParse, config)
149	if len(errs) > 0 {
150		fatalErrors(errs)
151	}
152
153	// Add extra ninja file dependencies
154	deps = append(deps, extraNinjaFileDeps...)
155
156	extraDeps, errs := ctx.ResolveDependencies(config)
157	if len(errs) > 0 {
158		fatalErrors(errs)
159	}
160	deps = append(deps, extraDeps...)
161
162	if docFile != "" {
163		err := writeDocs(ctx, absolutePath(docFile))
164		if err != nil {
165			fatalErrors([]error{err})
166		}
167		return
168	}
169
170	if c, ok := config.(ConfigStopBefore); ok {
171		if c.StopBefore() == StopBeforePrepareBuildActions {
172			return
173		}
174	}
175
176	extraDeps, errs = ctx.PrepareBuildActions(config)
177	if len(errs) > 0 {
178		fatalErrors(errs)
179	}
180	deps = append(deps, extraDeps...)
181
182	const outFilePermissions = 0666
183	var out io.Writer
184	var f *os.File
185	var buf *bufio.Writer
186
187	if emptyNinjaFile {
188		if err := ioutil.WriteFile(absolutePath(outFile), []byte(nil), outFilePermissions); err != nil {
189			fatalf("error writing empty Ninja file: %s", err)
190		}
191	}
192
193	if stage != StageMain || !emptyNinjaFile {
194		f, err = os.OpenFile(absolutePath(outFile), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, outFilePermissions)
195		if err != nil {
196			fatalf("error opening Ninja file: %s", err)
197		}
198		buf = bufio.NewWriter(f)
199		out = buf
200	} else {
201		out = ioutil.Discard
202	}
203
204	if globFile != "" {
205		buffer, errs := generateGlobNinjaFile(ctx.Globs)
206		if len(errs) > 0 {
207			fatalErrors(errs)
208		}
209
210		err = ioutil.WriteFile(absolutePath(globFile), buffer, outFilePermissions)
211		if err != nil {
212			fatalf("error writing %s: %s", globFile, err)
213		}
214	}
215
216	if depFile != "" {
217		err := deptools.WriteDepFile(absolutePath(depFile), outFile, deps)
218		if err != nil {
219			fatalf("error writing depfile: %s", err)
220		}
221	}
222
223	err = ctx.WriteBuildFile(out)
224	if err != nil {
225		fatalf("error writing Ninja file contents: %s", err)
226	}
227
228	if buf != nil {
229		err = buf.Flush()
230		if err != nil {
231			fatalf("error flushing Ninja file contents: %s", err)
232		}
233	}
234
235	if f != nil {
236		err = f.Close()
237		if err != nil {
238			fatalf("error closing Ninja file: %s", err)
239		}
240	}
241
242	if c, ok := config.(ConfigRemoveAbandonedFilesUnder); ok {
243		under, except := c.RemoveAbandonedFilesUnder()
244		err := removeAbandonedFilesUnder(ctx, bootstrapConfig, SrcDir, under, except)
245		if err != nil {
246			fatalf("error removing abandoned files: %s", err)
247		}
248	}
249
250	if memprofile != "" {
251		f, err := os.Create(absolutePath(memprofile))
252		if err != nil {
253			fatalf("error opening memprofile: %s", err)
254		}
255		defer f.Close()
256		pprof.WriteHeapProfile(f)
257	}
258}
259
260func fatalf(format string, args ...interface{}) {
261	fmt.Printf(format, args...)
262	fmt.Print("\n")
263	os.Exit(1)
264}
265
266func fatalErrors(errs []error) {
267	red := "\x1b[31m"
268	unred := "\x1b[0m"
269
270	for _, err := range errs {
271		switch err := err.(type) {
272		case *blueprint.BlueprintError,
273			*blueprint.ModuleError,
274			*blueprint.PropertyError:
275			fmt.Printf("%serror:%s %s\n", red, unred, err.Error())
276		default:
277			fmt.Printf("%sinternal error:%s %s\n", red, unred, err)
278		}
279	}
280	os.Exit(1)
281}
282
283func absolutePath(path string) string {
284	if filepath.IsAbs(path) {
285		return path
286	}
287	return filepath.Join(absSrcDir, path)
288}
289