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