• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 buf *bufio.Writer
170
171	ctx.BeginEvent("write_files")
172	defer ctx.EndEvent("write_files")
173	if args.EmptyNinjaFile {
174		if err := os.WriteFile(blueprint.JoinPath(ctx.SrcDir(), args.OutFile), []byte(nil), blueprint.OutFilePermissions); err != nil {
175			return nil, fmt.Errorf("error writing empty Ninja file: %s", err)
176		}
177		out = io.Discard.(blueprint.StringWriterWriter)
178	} else {
179		f, err := os.OpenFile(blueprint.JoinPath(ctx.SrcDir(), args.OutFile), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, blueprint.OutFilePermissions)
180		if err != nil {
181			return nil, fmt.Errorf("error opening Ninja file: %s", err)
182		}
183		defer f.Close()
184		buf = bufio.NewWriterSize(f, 16*1024*1024)
185		out = buf
186	}
187
188	if err := ctx.WriteBuildFile(out, !strings.Contains(args.OutFile, "bootstrap.ninja") && !args.EmptyNinjaFile, args.OutFile); err != nil {
189		return nil, fmt.Errorf("error writing Ninja file contents: %s", err)
190	}
191
192	if buf != nil {
193		if err := buf.Flush(); err != nil {
194			return nil, fmt.Errorf("error flushing Ninja file contents: %s", err)
195		}
196	}
197
198	// TODO(b/357140398): parallelize this with other ninja file writing work.
199	if ctx.GetIncrementalEnabled() {
200		if err := ctx.CacheAllBuildActions(config.(BootstrapConfig).SoongOutDir()); err != nil {
201			return nil, fmt.Errorf("error cache build actions: %s", err)
202		}
203	}
204
205	providerValidationErrors := <-providersValidationChan
206	if providerValidationErrors != nil {
207		return nil, proptools.MergeErrors(providerValidationErrors)
208	}
209
210	if args.Memprofile != "" {
211		f, err := os.Create(blueprint.JoinPath(ctx.SrcDir(), args.Memprofile))
212		if err != nil {
213			return nil, fmt.Errorf("error opening memprofile: %s", err)
214		}
215		defer f.Close()
216		pprof.WriteHeapProfile(f)
217	}
218
219	return ninjaDeps, nil
220}
221
222func colorizeErrs(errs []error) error {
223	red := "\x1b[31m"
224	unred := "\x1b[0m"
225
226	var colorizedErrs []error
227	for _, err := range errs {
228		switch err := err.(type) {
229		case *blueprint.BlueprintError,
230			*blueprint.ModuleError,
231			*blueprint.PropertyError:
232			colorizedErrs = append(colorizedErrs, fmt.Errorf("%serror:%s %w", red, unred, err))
233		default:
234			colorizedErrs = append(colorizedErrs, fmt.Errorf("%sinternal error:%s %s", red, unred, err))
235		}
236	}
237
238	return errors.Join(colorizedErrs...)
239}
240