xref: /aosp_15_r20/build/blueprint/bootstrap/bootstrap.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	"encoding/json"
19	"fmt"
20	"path/filepath"
21	"runtime"
22	"strings"
23
24	"github.com/google/blueprint"
25	"github.com/google/blueprint/pathtools"
26	"github.com/google/blueprint/proptools"
27)
28
29var (
30	pctx = blueprint.NewPackageContext("github.com/google/blueprint/bootstrap")
31
32	goTestMainCmd   = pctx.StaticVariable("goTestMainCmd", filepath.Join("$ToolDir", "gotestmain"))
33	goTestRunnerCmd = pctx.StaticVariable("goTestRunnerCmd", filepath.Join("$ToolDir", "gotestrunner"))
34	pluginGenSrcCmd = pctx.StaticVariable("pluginGenSrcCmd", filepath.Join("$ToolDir", "loadplugins"))
35
36	parallelCompile = pctx.StaticVariable("parallelCompile", func() string {
37		numCpu := runtime.NumCPU()
38		// This will cause us to recompile all go programs if the
39		// number of cpus changes. We don't get a lot of benefit from
40		// higher values, so cap this to make it cheaper to move trees
41		// between machines.
42		if numCpu > 8 {
43			numCpu = 8
44		}
45		return fmt.Sprintf("-c %d", numCpu)
46	}())
47
48	compile = pctx.StaticRule("compile",
49		blueprint.RuleParams{
50			Command: "GOROOT='$goRoot' $compileCmd $parallelCompile -o $out.tmp " +
51				"$debugFlags -p $pkgPath -complete $incFlags $embedFlags -pack $in && " +
52				"if cmp --quiet $out.tmp $out; then rm $out.tmp; else mv -f $out.tmp $out; fi",
53			CommandDeps: []string{"$compileCmd"},
54			Description: "compile $out",
55			Restat:      true,
56		},
57		"pkgPath", "incFlags", "embedFlags")
58
59	link = pctx.StaticRule("link",
60		blueprint.RuleParams{
61			Command: "GOROOT='$goRoot' $linkCmd -o $out.tmp $libDirFlags $in && " +
62				"if cmp --quiet $out.tmp $out; then rm $out.tmp; else mv -f $out.tmp $out; fi",
63			CommandDeps: []string{"$linkCmd"},
64			Description: "link $out",
65			Restat:      true,
66		},
67		"libDirFlags")
68
69	goTestMain = pctx.StaticRule("gotestmain",
70		blueprint.RuleParams{
71			Command:     "$goTestMainCmd -o $out -pkg $pkg $in",
72			CommandDeps: []string{"$goTestMainCmd"},
73			Description: "gotestmain $out",
74		},
75		"pkg")
76
77	pluginGenSrc = pctx.StaticRule("pluginGenSrc",
78		blueprint.RuleParams{
79			Command:     "$pluginGenSrcCmd -o $out -p $pkg $plugins",
80			CommandDeps: []string{"$pluginGenSrcCmd"},
81			Description: "create $out",
82		},
83		"pkg", "plugins")
84
85	test = pctx.StaticRule("test",
86		blueprint.RuleParams{
87			Command:     "$goTestRunnerCmd -p $pkgSrcDir -f $out -- $in -test.short",
88			CommandDeps: []string{"$goTestRunnerCmd"},
89			Description: "test $pkg",
90		},
91		"pkg", "pkgSrcDir")
92
93	cp = pctx.StaticRule("cp",
94		blueprint.RuleParams{
95			Command:     "cp $in $out",
96			Description: "cp $out",
97		},
98		"generator")
99
100	touch = pctx.StaticRule("touch",
101		blueprint.RuleParams{
102			Command:     "touch $out",
103			Description: "touch $out",
104		},
105		"depfile", "generator")
106
107	cat = pctx.StaticRule("Cat",
108		blueprint.RuleParams{
109			Command:     "rm -f $out && cat $in > $out",
110			Description: "concatenate files to $out",
111		})
112
113	// ubuntu 14.04 offcially use dash for /bin/sh, and its builtin echo command
114	// doesn't support -e option. Therefore we force to use /bin/bash when writing out
115	// content to file.
116	writeFile = pctx.StaticRule("writeFile",
117		blueprint.RuleParams{
118			Command:     `rm -f $out && /bin/bash -c 'echo -e -n "$$0" > $out' $content`,
119			Description: "writing file $out",
120		},
121		"content")
122
123	generateBuildNinja = pctx.StaticRule("build.ninja",
124		blueprint.RuleParams{
125			// TODO: it's kinda ugly that some parameters are computed from
126			// environment variables and some from Ninja parameters, but it's probably
127			// better to not to touch that while Blueprint and Soong are separate
128			// NOTE: The spaces at EOL are important because otherwise Ninja would
129			// omit all spaces between the different options.
130			Command: `cd "$$(dirname "$builder")" && ` +
131				`BUILDER="$$PWD/$$(basename "$builder")" && ` +
132				`cd / && ` +
133				`env -i $env "$$BUILDER" ` +
134				`    --top "$$TOP" ` +
135				`    --soong_out "$soongOutDir" ` +
136				`    --out "$outDir" ` +
137				`    $extra`,
138			CommandDeps: []string{"$builder"},
139			Description: "$builder $out",
140			Deps:        blueprint.DepsGCC,
141			Depfile:     "$out.d",
142			Restat:      true,
143		},
144		"builder", "env", "extra", "pool")
145
146	// Work around a Ninja issue.  See https://github.com/martine/ninja/pull/634
147	phony = pctx.StaticRule("phony",
148		blueprint.RuleParams{
149			Command:     "# phony $out",
150			Description: "phony $out",
151			Generator:   true,
152		},
153		"depfile")
154
155	_ = pctx.VariableFunc("ToolDir", func(ctx blueprint.VariableFuncContext, config interface{}) (string, error) {
156		return config.(BootstrapConfig).HostToolDir(), nil
157	})
158)
159
160var (
161	// echoEscaper escapes a string such that passing it to "echo -e" will produce the input value.
162	echoEscaper = strings.NewReplacer(
163		`\`, `\\`, // First escape existing backslashes so they aren't interpreted by `echo -e`.
164		"\n", `\n`, // Then replace newlines with \n
165	)
166)
167
168// shardString takes a string and returns a slice of strings where the length of each one is
169// at most shardSize.
170func shardString(s string, shardSize int) []string {
171	if len(s) == 0 {
172		return nil
173	}
174	ret := make([]string, 0, (len(s)+shardSize-1)/shardSize)
175	for len(s) > shardSize {
176		ret = append(ret, s[0:shardSize])
177		s = s[shardSize:]
178	}
179	if len(s) > 0 {
180		ret = append(ret, s)
181	}
182	return ret
183}
184
185// writeFileRule creates a ninja rule to write contents to a file.  The contents will be
186// escaped so that the file contains exactly the contents passed to the function.
187func writeFileRule(ctx blueprint.ModuleContext, outputFile string, content string) {
188	// This is MAX_ARG_STRLEN subtracted with some safety to account for shell escapes
189	const SHARD_SIZE = 131072 - 10000
190
191	buildWriteFileRule := func(outputFile string, content string) {
192		content = echoEscaper.Replace(content)
193		content = proptools.NinjaEscape(proptools.ShellEscapeIncludingSpaces(content))
194		if content == "" {
195			content = "''"
196		}
197		ctx.Build(pctx, blueprint.BuildParams{
198			Rule:        writeFile,
199			Outputs:     []string{outputFile},
200			Description: "write " + outputFile,
201			Args: map[string]string{
202				"content": content,
203			},
204		})
205	}
206
207	if len(content) > SHARD_SIZE {
208		var chunks []string
209		for i, c := range shardString(content, SHARD_SIZE) {
210			tempPath := fmt.Sprintf("%s.%d", outputFile, i)
211			buildWriteFileRule(tempPath, c)
212			chunks = append(chunks, tempPath)
213		}
214		ctx.Build(pctx, blueprint.BuildParams{
215			Rule:        cat,
216			Inputs:      chunks,
217			Outputs:     []string{outputFile},
218			Description: "Merging to " + outputFile,
219		})
220		return
221	}
222	buildWriteFileRule(outputFile, content)
223}
224
225type pluginDependencyTag struct {
226	blueprint.BaseDependencyTag
227}
228
229type bootstrapDependencies interface {
230	bootstrapDeps(ctx blueprint.BottomUpMutatorContext)
231}
232
233var pluginDepTag = pluginDependencyTag{}
234
235func BootstrapDeps(ctx blueprint.BottomUpMutatorContext) {
236	if pkg, ok := ctx.Module().(bootstrapDependencies); ok {
237		pkg.bootstrapDeps(ctx)
238	}
239}
240
241type PackageInfo struct {
242	PkgPath       string
243	PkgRoot       string
244	PackageTarget string
245	TestTargets   []string
246}
247
248var PackageProvider = blueprint.NewProvider[*PackageInfo]()
249
250type BinaryInfo struct {
251	IntermediatePath string
252	InstallPath      string
253	TestTargets      []string
254}
255
256var BinaryProvider = blueprint.NewProvider[*BinaryInfo]()
257
258type DocsPackageInfo struct {
259	PkgPath string
260	Srcs    []string
261}
262
263var DocsPackageProvider = blueprint.NewMutatorProvider[*DocsPackageInfo]("bootstrap_deps")
264
265// A GoPackage is a module for building Go packages.
266type GoPackage struct {
267	blueprint.SimpleName
268	properties struct {
269		Deps      []string
270		PkgPath   string
271		Srcs      []string
272		TestSrcs  []string
273		TestData  []string
274		PluginFor []string
275		EmbedSrcs []string
276		// The visibility property is unused in blueprint, but exists so that soong
277		// can add one and not have the bp files fail to parse during the bootstrap build.
278		Visibility []string
279
280		Darwin struct {
281			Srcs     []string
282			TestSrcs []string
283		}
284		Linux struct {
285			Srcs     []string
286			TestSrcs []string
287		}
288	}
289}
290
291func newGoPackageModuleFactory() func() (blueprint.Module, []interface{}) {
292	return func() (blueprint.Module, []interface{}) {
293		module := &GoPackage{}
294		return module, []interface{}{&module.properties, &module.SimpleName.Properties}
295	}
296}
297
298// Properties returns the list of property structs to be used for registering a wrapped module type.
299func (g *GoPackage) Properties() []interface{} {
300	return []interface{}{&g.properties}
301}
302
303func (g *GoPackage) DynamicDependencies(ctx blueprint.DynamicDependerModuleContext) []string {
304	return g.properties.Deps
305}
306
307func (g *GoPackage) bootstrapDeps(ctx blueprint.BottomUpMutatorContext) {
308	for _, plugin := range g.properties.PluginFor {
309		ctx.AddReverseDependency(ctx.Module(), pluginDepTag, plugin)
310	}
311	blueprint.SetProvider(ctx, DocsPackageProvider, &DocsPackageInfo{
312		PkgPath: g.properties.PkgPath,
313		Srcs:    g.properties.Srcs,
314	})
315}
316
317func (g *GoPackage) GenerateBuildActions(ctx blueprint.ModuleContext) {
318	var (
319		name       = ctx.ModuleName()
320		hasPlugins = false
321		pluginSrc  = ""
322		genSrcs    = []string{}
323	)
324
325	if g.properties.PkgPath == "" {
326		ctx.ModuleErrorf("module %s did not specify a valid pkgPath", name)
327		return
328	}
329
330	pkgRoot := packageRoot(ctx)
331	archiveFile := filepath.Join(pkgRoot,
332		filepath.FromSlash(g.properties.PkgPath)+".a")
333
334	ctx.VisitDepsDepthFirst(func(module blueprint.Module) {
335		if ctx.OtherModuleDependencyTag(module) == pluginDepTag {
336			hasPlugins = true
337		}
338	})
339	if hasPlugins {
340		pluginSrc = filepath.Join(moduleGenSrcDir(ctx), "plugin.go")
341		genSrcs = append(genSrcs, pluginSrc)
342	}
343
344	if hasPlugins && !buildGoPluginLoader(ctx, g.properties.PkgPath, pluginSrc) {
345		return
346	}
347
348	var srcs, testSrcs []string
349	if runtime.GOOS == "darwin" {
350		srcs = append(g.properties.Srcs, g.properties.Darwin.Srcs...)
351		testSrcs = append(g.properties.TestSrcs, g.properties.Darwin.TestSrcs...)
352	} else if runtime.GOOS == "linux" {
353		srcs = append(g.properties.Srcs, g.properties.Linux.Srcs...)
354		testSrcs = append(g.properties.TestSrcs, g.properties.Linux.TestSrcs...)
355	}
356
357	testArchiveFile := filepath.Join(testRoot(ctx),
358		filepath.FromSlash(g.properties.PkgPath)+".a")
359	testResultFile := buildGoTest(ctx, testRoot(ctx), testArchiveFile,
360		g.properties.PkgPath, srcs, genSrcs, testSrcs, g.properties.EmbedSrcs)
361
362	// Don't build for test-only packages
363	if len(srcs) == 0 && len(genSrcs) == 0 {
364		ctx.Build(pctx, blueprint.BuildParams{
365			Rule:     touch,
366			Outputs:  []string{archiveFile},
367			Optional: true,
368		})
369		return
370	}
371
372	buildGoPackage(ctx, pkgRoot, g.properties.PkgPath, archiveFile,
373		srcs, genSrcs, g.properties.EmbedSrcs)
374	blueprint.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: srcs})
375	blueprint.SetProvider(ctx, PackageProvider, &PackageInfo{
376		PkgPath:       g.properties.PkgPath,
377		PkgRoot:       pkgRoot,
378		PackageTarget: archiveFile,
379		TestTargets:   testResultFile,
380	})
381}
382
383// A GoBinary is a module for building executable binaries from Go sources.
384type GoBinary struct {
385	blueprint.SimpleName
386	properties struct {
387		Deps           []string
388		Srcs           []string
389		TestSrcs       []string
390		TestData       []string
391		EmbedSrcs      []string
392		PrimaryBuilder bool
393		Default        bool
394		// The visibility property is unused in blueprint, but exists so that soong
395		// can add one and not have the bp files fail to parse during the bootstrap build.
396		Visibility []string
397
398		Darwin struct {
399			Srcs     []string
400			TestSrcs []string
401		}
402		Linux struct {
403			Srcs     []string
404			TestSrcs []string
405		}
406	}
407
408	installPath string
409
410	// skipInstall can be set to true by a module type that wraps GoBinary to skip the install rule,
411	// allowing the wrapping module type to create the install rule itself.
412	skipInstall bool
413
414	// outputFile is set to the path to the intermediate output file.
415	outputFile string
416}
417
418func newGoBinaryModuleFactory() func() (blueprint.Module, []interface{}) {
419	return func() (blueprint.Module, []interface{}) {
420		module := &GoBinary{}
421		return module, []interface{}{&module.properties, &module.SimpleName.Properties}
422	}
423}
424
425func (g *GoBinary) DynamicDependencies(ctx blueprint.DynamicDependerModuleContext) []string {
426	return g.properties.Deps
427}
428
429func (g *GoBinary) bootstrapDeps(ctx blueprint.BottomUpMutatorContext) {
430	if g.properties.PrimaryBuilder {
431		blueprint.SetProvider(ctx, PrimaryBuilderProvider, PrimaryBuilderInfo{})
432	}
433}
434
435// IntermediateFile returns the path to the final linked intermedate file.
436func (g *GoBinary) IntermediateFile() string {
437	return g.outputFile
438}
439
440// SetSkipInstall is called by module types that wrap GoBinary to skip the install rule,
441// allowing the wrapping module type to create the install rule itself.
442func (g *GoBinary) SetSkipInstall() {
443	g.skipInstall = true
444}
445
446// Properties returns the list of property structs to be used for registering a wrapped module type.
447func (g *GoBinary) Properties() []interface{} {
448	return []interface{}{&g.properties}
449}
450
451func (g *GoBinary) GenerateBuildActions(ctx blueprint.ModuleContext) {
452	var (
453		name            = ctx.ModuleName()
454		objDir          = moduleObjDir(ctx)
455		archiveFile     = filepath.Join(objDir, name+".a")
456		testArchiveFile = filepath.Join(testRoot(ctx), name+".a")
457		aoutFile        = filepath.Join(objDir, name)
458		hasPlugins      = false
459		pluginSrc       = ""
460		genSrcs         = []string{}
461	)
462
463	if !g.skipInstall {
464		g.installPath = filepath.Join(ctx.Config().(BootstrapConfig).HostToolDir(), name)
465	}
466
467	ctx.VisitDirectDeps(func(module blueprint.Module) {
468		if ctx.OtherModuleDependencyTag(module) == pluginDepTag {
469			hasPlugins = true
470		}
471	})
472	if hasPlugins {
473		pluginSrc = filepath.Join(moduleGenSrcDir(ctx), "plugin.go")
474		genSrcs = append(genSrcs, pluginSrc)
475	}
476
477	var testDeps []string
478
479	if hasPlugins && !buildGoPluginLoader(ctx, "main", pluginSrc) {
480		return
481	}
482
483	var srcs, testSrcs []string
484	if runtime.GOOS == "darwin" {
485		srcs = append(g.properties.Srcs, g.properties.Darwin.Srcs...)
486		testSrcs = append(g.properties.TestSrcs, g.properties.Darwin.TestSrcs...)
487	} else if runtime.GOOS == "linux" {
488		srcs = append(g.properties.Srcs, g.properties.Linux.Srcs...)
489		testSrcs = append(g.properties.TestSrcs, g.properties.Linux.TestSrcs...)
490	}
491
492	testResultFile := buildGoTest(ctx, testRoot(ctx), testArchiveFile,
493		name, srcs, genSrcs, testSrcs, g.properties.EmbedSrcs)
494	testDeps = append(testDeps, testResultFile...)
495
496	buildGoPackage(ctx, objDir, "main", archiveFile, srcs, genSrcs, g.properties.EmbedSrcs)
497
498	var linkDeps []string
499	var libDirFlags []string
500	ctx.VisitDepsDepthFirst(func(module blueprint.Module) {
501		if info, ok := blueprint.OtherModuleProvider(ctx, module, PackageProvider); ok {
502			linkDeps = append(linkDeps, info.PackageTarget)
503			libDir := info.PkgRoot
504			libDirFlags = append(libDirFlags, "-L "+libDir)
505			testDeps = append(testDeps, info.TestTargets...)
506		}
507	})
508
509	linkArgs := map[string]string{}
510	if len(libDirFlags) > 0 {
511		linkArgs["libDirFlags"] = strings.Join(libDirFlags, " ")
512	}
513
514	ctx.Build(pctx, blueprint.BuildParams{
515		Rule:      link,
516		Outputs:   []string{aoutFile},
517		Inputs:    []string{archiveFile},
518		Implicits: linkDeps,
519		Args:      linkArgs,
520		Optional:  true,
521	})
522
523	g.outputFile = aoutFile
524
525	var validations []string
526	if ctx.Config().(BootstrapConfig).RunGoTests() {
527		validations = testDeps
528	}
529
530	if !g.skipInstall {
531		ctx.Build(pctx, blueprint.BuildParams{
532			Rule:        cp,
533			Outputs:     []string{g.installPath},
534			Inputs:      []string{aoutFile},
535			Validations: validations,
536			Optional:    !g.properties.Default,
537		})
538	}
539
540	blueprint.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: srcs})
541	blueprint.SetProvider(ctx, BinaryProvider, &BinaryInfo{
542		IntermediatePath: g.outputFile,
543		InstallPath:      g.installPath,
544		TestTargets:      testResultFile,
545	})
546}
547
548func buildGoPluginLoader(ctx blueprint.ModuleContext, pkgPath, pluginSrc string) bool {
549	ret := true
550
551	var pluginPaths []string
552	ctx.VisitDirectDeps(func(module blueprint.Module) {
553		if ctx.OtherModuleDependencyTag(module) == pluginDepTag {
554			if info, ok := blueprint.OtherModuleProvider(ctx, module, PackageProvider); ok {
555				pluginPaths = append(pluginPaths, info.PkgPath)
556			}
557		}
558	})
559
560	ctx.Build(pctx, blueprint.BuildParams{
561		Rule:    pluginGenSrc,
562		Outputs: []string{pluginSrc},
563		Args: map[string]string{
564			"pkg":     pkgPath,
565			"plugins": strings.Join(pluginPaths, " "),
566		},
567		Optional: true,
568	})
569
570	return ret
571}
572
573func generateEmbedcfgFile(ctx blueprint.ModuleContext, files []string, srcDir string, embedcfgFile string) {
574	embedcfg := struct {
575		Patterns map[string][]string
576		Files    map[string]string
577	}{
578		make(map[string][]string, len(files)),
579		make(map[string]string, len(files)),
580	}
581
582	for _, file := range files {
583		embedcfg.Patterns[file] = []string{file}
584		embedcfg.Files[file] = filepath.Join(srcDir, file)
585	}
586
587	embedcfgData, err := json.Marshal(&embedcfg)
588	if err != nil {
589		ctx.ModuleErrorf("Failed to marshal embedcfg data: %s", err.Error())
590	}
591
592	writeFileRule(ctx, embedcfgFile, string(embedcfgData))
593}
594
595func buildGoPackage(ctx blueprint.ModuleContext, pkgRoot string,
596	pkgPath string, archiveFile string, srcs []string, genSrcs []string, embedSrcs []string) {
597
598	srcDir := moduleSrcDir(ctx)
599	srcFiles := pathtools.PrefixPaths(srcs, srcDir)
600	srcFiles = append(srcFiles, genSrcs...)
601
602	var incFlags []string
603	var deps []string
604	ctx.VisitDepsDepthFirst(func(module blueprint.Module) {
605		if info, ok := blueprint.OtherModuleProvider(ctx, module, PackageProvider); ok {
606			incDir := info.PkgRoot
607			target := info.PackageTarget
608			incFlags = append(incFlags, "-I "+incDir)
609			deps = append(deps, target)
610		}
611	})
612
613	compileArgs := map[string]string{
614		"pkgPath": pkgPath,
615	}
616
617	if len(incFlags) > 0 {
618		compileArgs["incFlags"] = strings.Join(incFlags, " ")
619	}
620
621	if len(embedSrcs) > 0 {
622		embedcfgFile := archiveFile + ".embedcfg"
623		generateEmbedcfgFile(ctx, embedSrcs, srcDir, embedcfgFile)
624		compileArgs["embedFlags"] = "-embedcfg " + embedcfgFile
625		deps = append(deps, embedcfgFile)
626	}
627
628	ctx.Build(pctx, blueprint.BuildParams{
629		Rule:      compile,
630		Outputs:   []string{archiveFile},
631		Inputs:    srcFiles,
632		Implicits: deps,
633		Args:      compileArgs,
634		Optional:  true,
635	})
636}
637
638func buildGoTest(ctx blueprint.ModuleContext, testRoot, testPkgArchive,
639	pkgPath string, srcs, genSrcs, testSrcs []string, embedSrcs []string) []string {
640
641	if len(testSrcs) == 0 {
642		return nil
643	}
644
645	srcDir := moduleSrcDir(ctx)
646	testFiles := pathtools.PrefixPaths(testSrcs, srcDir)
647
648	mainFile := filepath.Join(testRoot, "test.go")
649	testArchive := filepath.Join(testRoot, "test.a")
650	testFile := filepath.Join(testRoot, "test")
651	testPassed := filepath.Join(testRoot, "test.passed")
652
653	buildGoPackage(ctx, testRoot, pkgPath, testPkgArchive,
654		append(srcs, testSrcs...), genSrcs, embedSrcs)
655
656	ctx.Build(pctx, blueprint.BuildParams{
657		Rule:    goTestMain,
658		Outputs: []string{mainFile},
659		Inputs:  testFiles,
660		Args: map[string]string{
661			"pkg": pkgPath,
662		},
663		Optional: true,
664	})
665
666	linkDeps := []string{testPkgArchive}
667	libDirFlags := []string{"-L " + testRoot}
668	testDeps := []string{}
669	ctx.VisitDepsDepthFirst(func(module blueprint.Module) {
670		if info, ok := blueprint.OtherModuleProvider(ctx, module, PackageProvider); ok {
671			linkDeps = append(linkDeps, info.PackageTarget)
672			libDir := info.PkgRoot
673			libDirFlags = append(libDirFlags, "-L "+libDir)
674			testDeps = append(testDeps, info.TestTargets...)
675		}
676	})
677
678	ctx.Build(pctx, blueprint.BuildParams{
679		Rule:      compile,
680		Outputs:   []string{testArchive},
681		Inputs:    []string{mainFile},
682		Implicits: []string{testPkgArchive},
683		Args: map[string]string{
684			"pkgPath":  "main",
685			"incFlags": "-I " + testRoot,
686		},
687		Optional: true,
688	})
689
690	ctx.Build(pctx, blueprint.BuildParams{
691		Rule:      link,
692		Outputs:   []string{testFile},
693		Inputs:    []string{testArchive},
694		Implicits: linkDeps,
695		Args: map[string]string{
696			"libDirFlags": strings.Join(libDirFlags, " "),
697		},
698		Optional: true,
699	})
700
701	ctx.Build(pctx, blueprint.BuildParams{
702		Rule:        test,
703		Outputs:     []string{testPassed},
704		Inputs:      []string{testFile},
705		Validations: testDeps,
706		Args: map[string]string{
707			"pkg":       pkgPath,
708			"pkgSrcDir": filepath.Dir(testFiles[0]),
709		},
710		Optional: true,
711	})
712
713	return []string{testPassed}
714}
715
716var PrimaryBuilderProvider = blueprint.NewMutatorProvider[PrimaryBuilderInfo]("bootstrap_deps")
717
718type PrimaryBuilderInfo struct{}
719
720type singleton struct {
721}
722
723func newSingletonFactory() func() blueprint.Singleton {
724	return func() blueprint.Singleton {
725		return &singleton{}
726	}
727}
728
729func (s *singleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
730	// Find the module that's marked as the "primary builder", which means it's
731	// creating the binary that we'll use to generate the non-bootstrap
732	// build.ninja file.
733	var primaryBuilders []string
734	// blueprintTools contains blueprint go binaries that will be built in StageMain
735	var blueprintTools []string
736	// blueprintTools contains the test outputs of go tests that can be run in StageMain
737	var blueprintTests []string
738	// blueprintGoPackages contains all blueprint go packages that can be built in StageMain
739	var blueprintGoPackages []string
740	ctx.VisitAllModules(func(module blueprint.Module) {
741		if ctx.PrimaryModule(module) == module {
742			if binaryInfo, ok := blueprint.SingletonModuleProvider(ctx, module, BinaryProvider); ok {
743				if binaryInfo.InstallPath != "" {
744					blueprintTools = append(blueprintTools, binaryInfo.InstallPath)
745				}
746				blueprintTests = append(blueprintTests, binaryInfo.TestTargets...)
747				if _, ok := blueprint.SingletonModuleProvider(ctx, module, PrimaryBuilderProvider); ok {
748					primaryBuilders = append(primaryBuilders, binaryInfo.InstallPath)
749				}
750			}
751
752			if packageInfo, ok := blueprint.SingletonModuleProvider(ctx, module, PackageProvider); ok {
753				blueprintGoPackages = append(blueprintGoPackages, packageInfo.PackageTarget)
754				blueprintTests = append(blueprintTests, packageInfo.TestTargets...)
755			}
756		}
757	})
758
759	var primaryBuilderCmdlinePrefix []string
760	var primaryBuilderFile string
761
762	if len(primaryBuilders) == 0 {
763		ctx.Errorf("no primary builder module present")
764		return
765	} else if len(primaryBuilders) > 1 {
766		ctx.Errorf("multiple primary builder modules present: %q", primaryBuilders)
767		return
768	} else {
769		primaryBuilderFile = primaryBuilders[0]
770	}
771
772	ctx.SetOutDir(pctx, "${outDir}")
773
774	for _, subninja := range ctx.Config().(BootstrapConfig).Subninjas() {
775		ctx.AddSubninja(subninja)
776	}
777
778	for _, i := range ctx.Config().(BootstrapConfig).PrimaryBuilderInvocations() {
779		flags := make([]string, 0)
780		flags = append(flags, primaryBuilderCmdlinePrefix...)
781		flags = append(flags, i.Args...)
782
783		pool := ""
784		if i.Console {
785			pool = "console"
786		}
787
788		envAssignments := ""
789		for k, v := range i.Env {
790			// NB: This is rife with quoting issues but we don't care because we trust
791			// soong_ui to not abuse this facility too much
792			envAssignments += k + "=" + v + " "
793		}
794
795		// Build the main build.ninja
796		ctx.Build(pctx, blueprint.BuildParams{
797			Rule:      generateBuildNinja,
798			Outputs:   i.Outputs,
799			Inputs:    i.Inputs,
800			Implicits: i.Implicits,
801			OrderOnly: i.OrderOnlyInputs,
802			Args: map[string]string{
803				"builder": primaryBuilderFile,
804				"env":     envAssignments,
805				"extra":   strings.Join(flags, " "),
806				"pool":    pool,
807			},
808			// soong_ui explicitly requests what it wants to be build. This is
809			// because the same Ninja file contains instructions to run
810			// soong_build, run bp2build and to generate the JSON module graph.
811			Optional:    true,
812			Description: i.Description,
813		})
814	}
815
816	// Add a phony target for building various tools that are part of blueprint
817	if len(blueprintTools) > 0 {
818		ctx.Build(pctx, blueprint.BuildParams{
819			Rule:    blueprint.Phony,
820			Outputs: []string{"blueprint_tools"},
821			Inputs:  blueprintTools,
822		})
823	}
824
825	// Add a phony target for running various tests that are part of blueprint
826	ctx.Build(pctx, blueprint.BuildParams{
827		Rule:    blueprint.Phony,
828		Outputs: []string{"blueprint_tests"},
829		Inputs:  blueprintTests,
830	})
831
832	// Add a phony target for running go tests
833	ctx.Build(pctx, blueprint.BuildParams{
834		Rule:     blueprint.Phony,
835		Outputs:  []string{"blueprint_go_packages"},
836		Inputs:   blueprintGoPackages,
837		Optional: true,
838	})
839}
840
841// packageRoot returns the module-specific package root directory path.  This
842// directory is where the final package .a files are output and where dependant
843// modules search for this package via -I arguments.
844func packageRoot(ctx blueprint.ModuleContext) string {
845	toolDir := ctx.Config().(BootstrapConfig).HostToolDir()
846	return filepath.Join(toolDir, "go", ctx.ModuleName(), ctx.ModuleSubDir(), "pkg")
847}
848
849// testRoot returns the module-specific package root directory path used for
850// building tests. The .a files generated here will include everything from
851// packageRoot, plus the test-only code.
852func testRoot(ctx blueprint.ModuleContext) string {
853	toolDir := ctx.Config().(BootstrapConfig).HostToolDir()
854	return filepath.Join(toolDir, "go", ctx.ModuleName(), ctx.ModuleSubDir(), "test")
855}
856
857// moduleSrcDir returns the path of the directory that all source file paths are
858// specified relative to.
859func moduleSrcDir(ctx blueprint.ModuleContext) string {
860	return ctx.ModuleDir()
861}
862
863// moduleObjDir returns the module-specific object directory path.
864func moduleObjDir(ctx blueprint.ModuleContext) string {
865	toolDir := ctx.Config().(BootstrapConfig).HostToolDir()
866	return filepath.Join(toolDir, "go", ctx.ModuleName(), ctx.ModuleSubDir(), "obj")
867}
868
869// moduleGenSrcDir returns the module-specific generated sources path.
870func moduleGenSrcDir(ctx blueprint.ModuleContext) string {
871	toolDir := ctx.Config().(BootstrapConfig).HostToolDir()
872	return filepath.Join(toolDir, "go", ctx.ModuleName(), ctx.ModuleSubDir(), "gen")
873}
874