xref: /aosp_15_r20/build/blueprint/bootstrap/command.go (revision 1fa6dee971e1612fa5cc0aa5ca2d35a22e2c34a3)
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