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}
47
48// RegisterGoModuleTypes adds module types to build tools written in golang
49func RegisterGoModuleTypes(ctx *blueprint.Context) {
50	ctx.RegisterModuleType("bootstrap_go_package", newGoPackageModuleFactory())
51	ctx.RegisterModuleType("blueprint_go_binary", newGoBinaryModuleFactory())
52}
53
54// RunBlueprint emits `args.OutFile` (a Ninja file) and returns the list of
55// its dependencies. These can be written to a `${args.OutFile}.d` file
56// so that it is correctly rebuilt when needed in case Blueprint is itself
57// invoked from Ninja
58func RunBlueprint(args Args, stopBefore StopBefore, ctx *blueprint.Context, config interface{}) ([]string, error) {
59	runtime.GOMAXPROCS(runtime.NumCPU())
60
61	if args.NoGC {
62		debug.SetGCPercent(-1)
63	}
64
65	if args.Cpuprofile != "" {
66		f, err := os.Create(blueprint.JoinPath(ctx.SrcDir(), args.Cpuprofile))
67		if err != nil {
68			return nil, fmt.Errorf("error opening cpuprofile: %s", err)
69		}
70		pprof.StartCPUProfile(f)
71		defer f.Close()
72		defer pprof.StopCPUProfile()
73	}
74
75	if args.TraceFile != "" {
76		f, err := os.Create(blueprint.JoinPath(ctx.SrcDir(), args.TraceFile))
77		if err != nil {
78			return nil, fmt.Errorf("error opening trace: %s", err)
79		}
80		trace.Start(f)
81		defer f.Close()
82		defer trace.Stop()
83	}
84
85	if args.ModuleListFile == "" {
86		return nil, fmt.Errorf("-l <moduleListFile> is required and must be nonempty")
87	}
88	ctx.SetModuleListFile(args.ModuleListFile)
89
90	var ninjaDeps []string
91	ninjaDeps = append(ninjaDeps, args.ModuleListFile)
92
93	ctx.BeginEvent("list_modules")
94	var filesToParse []string
95	if f, err := ctx.ListModulePaths("."); err != nil {
96		return nil, fmt.Errorf("could not enumerate files: %v\n", err.Error())
97	} else {
98		filesToParse = f
99	}
100	ctx.EndEvent("list_modules")
101
102	ctx.RegisterBottomUpMutator("bootstrap_plugin_deps", pluginDeps)
103	ctx.RegisterSingletonType("bootstrap", newSingletonFactory(), false)
104	RegisterGoModuleTypes(ctx)
105
106	ctx.BeginEvent("parse_bp")
107	if blueprintFiles, errs := ctx.ParseFileList(".", filesToParse, config); len(errs) > 0 {
108		return nil, fatalErrors(errs)
109	} else {
110		ctx.EndEvent("parse_bp")
111		ninjaDeps = append(ninjaDeps, blueprintFiles...)
112	}
113
114	if resolvedDeps, errs := ctx.ResolveDependencies(config); len(errs) > 0 {
115		return nil, fatalErrors(errs)
116	} else {
117		ninjaDeps = append(ninjaDeps, resolvedDeps...)
118	}
119
120	if stopBefore == StopBeforePrepareBuildActions {
121		return ninjaDeps, nil
122	}
123
124	if ctx.BeforePrepareBuildActionsHook != nil {
125		if err := ctx.BeforePrepareBuildActionsHook(); err != nil {
126			return nil, fatalErrors([]error{err})
127		}
128	}
129
130	if buildActionsDeps, errs := ctx.PrepareBuildActions(config); len(errs) > 0 {
131		return nil, fatalErrors(errs)
132	} else {
133		ninjaDeps = append(ninjaDeps, buildActionsDeps...)
134	}
135
136	if args.ModuleDebugFile != "" {
137		ctx.GenerateModuleDebugInfo(args.ModuleDebugFile)
138	}
139
140	if stopBefore == StopBeforeWriteNinja {
141		return ninjaDeps, nil
142	}
143
144	providersValidationChan := make(chan []error, 1)
145	if ctx.GetVerifyProvidersAreUnchanged() {
146		go func() {
147			providersValidationChan <- ctx.VerifyProvidersWereUnchanged()
148		}()
149	} else {
150		providersValidationChan <- nil
151	}
152
153	var out blueprint.StringWriterWriter
154	var f *os.File
155	var buf *bufio.Writer
156
157	ctx.BeginEvent("write_files")
158	defer ctx.EndEvent("write_files")
159	if args.EmptyNinjaFile {
160		if err := os.WriteFile(blueprint.JoinPath(ctx.SrcDir(), args.OutFile), []byte(nil), blueprint.OutFilePermissions); err != nil {
161			return nil, fmt.Errorf("error writing empty Ninja file: %s", err)
162		}
163		out = io.Discard.(blueprint.StringWriterWriter)
164	} else {
165		f, err := os.OpenFile(blueprint.JoinPath(ctx.SrcDir(), args.OutFile), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, blueprint.OutFilePermissions)
166		if err != nil {
167			return nil, fmt.Errorf("error opening Ninja file: %s", err)
168		}
169		defer f.Close()
170		buf = bufio.NewWriterSize(f, 16*1024*1024)
171		out = buf
172	}
173
174	if err := ctx.WriteBuildFile(out, !strings.Contains(args.OutFile, "bootstrap.ninja") && !args.EmptyNinjaFile, args.OutFile); err != nil {
175		return nil, fmt.Errorf("error writing Ninja file contents: %s", err)
176	}
177
178	if buf != nil {
179		if err := buf.Flush(); err != nil {
180			return nil, fmt.Errorf("error flushing Ninja file contents: %s", err)
181		}
182	}
183
184	if f != nil {
185		if err := f.Close(); err != nil {
186			return nil, fmt.Errorf("error closing Ninja file: %s", err)
187		}
188	}
189
190	providerValidationErrors := <-providersValidationChan
191	if providerValidationErrors != nil {
192		return nil, proptools.MergeErrors(providerValidationErrors)
193	}
194
195	if args.Memprofile != "" {
196		f, err := os.Create(blueprint.JoinPath(ctx.SrcDir(), args.Memprofile))
197		if err != nil {
198			return nil, fmt.Errorf("error opening memprofile: %s", err)
199		}
200		defer f.Close()
201		pprof.WriteHeapProfile(f)
202	}
203
204	return ninjaDeps, nil
205}
206
207func fatalErrors(errs []error) error {
208	red := "\x1b[31m"
209	unred := "\x1b[0m"
210
211	for _, err := range errs {
212		switch err := err.(type) {
213		case *blueprint.BlueprintError,
214			*blueprint.ModuleError,
215			*blueprint.PropertyError:
216			fmt.Printf("%serror:%s %s\n", red, unred, err.Error())
217		default:
218			fmt.Printf("%sinternal error:%s %s\n", red, unred, err)
219		}
220	}
221
222	return errors.New("fatal errors encountered")
223}
224