• 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	"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		})
368		return
369	}
370
371	buildGoPackage(ctx, pkgRoot, g.properties.PkgPath, archiveFile,
372		srcs, genSrcs, g.properties.EmbedSrcs)
373	blueprint.SetProvider(ctx, PackageProvider, &PackageInfo{
374		PkgPath:       g.properties.PkgPath,
375		PkgRoot:       pkgRoot,
376		PackageTarget: archiveFile,
377		TestTargets:   testResultFile,
378	})
379}
380
381// A GoBinary is a module for building executable binaries from Go sources.
382type GoBinary struct {
383	blueprint.SimpleName
384	properties struct {
385		Deps           []string
386		Srcs           []string
387		TestSrcs       []string
388		TestData       []string
389		EmbedSrcs      []string
390		PrimaryBuilder bool
391		Default        bool
392		// The visibility property is unused in blueprint, but exists so that soong
393		// can add one and not have the bp files fail to parse during the bootstrap build.
394		Visibility []string
395
396		Darwin struct {
397			Srcs     []string
398			TestSrcs []string
399		}
400		Linux struct {
401			Srcs     []string
402			TestSrcs []string
403		}
404	}
405
406	installPath string
407
408	// skipInstall can be set to true by a module type that wraps GoBinary to skip the install rule,
409	// allowing the wrapping module type to create the install rule itself.
410	skipInstall bool
411
412	// outputFile is set to the path to the intermediate output file.
413	outputFile string
414}
415
416func newGoBinaryModuleFactory() func() (blueprint.Module, []interface{}) {
417	return func() (blueprint.Module, []interface{}) {
418		module := &GoBinary{}
419		return module, []interface{}{&module.properties, &module.SimpleName.Properties}
420	}
421}
422
423func (g *GoBinary) DynamicDependencies(ctx blueprint.DynamicDependerModuleContext) []string {
424	return g.properties.Deps
425}
426
427func (g *GoBinary) bootstrapDeps(ctx blueprint.BottomUpMutatorContext) {
428	if g.properties.PrimaryBuilder {
429		blueprint.SetProvider(ctx, PrimaryBuilderProvider, PrimaryBuilderInfo{})
430	}
431}
432
433// IntermediateFile returns the path to the final linked intermedate file.
434func (g *GoBinary) IntermediateFile() string {
435	return g.outputFile
436}
437
438// SetSkipInstall is called by module types that wrap GoBinary to skip the install rule,
439// allowing the wrapping module type to create the install rule itself.
440func (g *GoBinary) SetSkipInstall() {
441	g.skipInstall = true
442}
443
444// Properties returns the list of property structs to be used for registering a wrapped module type.
445func (g *GoBinary) Properties() []interface{} {
446	return []interface{}{&g.properties}
447}
448
449func (g *GoBinary) GenerateBuildActions(ctx blueprint.ModuleContext) {
450	var (
451		name            = ctx.ModuleName()
452		objDir          = moduleObjDir(ctx)
453		archiveFile     = filepath.Join(objDir, name+".a")
454		testArchiveFile = filepath.Join(testRoot(ctx), name+".a")
455		aoutFile        = filepath.Join(objDir, name)
456		hasPlugins      = false
457		pluginSrc       = ""
458		genSrcs         = []string{}
459	)
460
461	if !g.skipInstall {
462		g.installPath = filepath.Join(ctx.Config().(BootstrapConfig).HostToolDir(), name)
463	}
464
465	ctx.VisitDirectDeps(func(module blueprint.Module) {
466		if ctx.OtherModuleDependencyTag(module) == pluginDepTag {
467			hasPlugins = true
468		}
469	})
470	if hasPlugins {
471		pluginSrc = filepath.Join(moduleGenSrcDir(ctx), "plugin.go")
472		genSrcs = append(genSrcs, pluginSrc)
473	}
474
475	var testDeps []string
476
477	if hasPlugins && !buildGoPluginLoader(ctx, "main", pluginSrc) {
478		return
479	}
480
481	var srcs, testSrcs []string
482	if runtime.GOOS == "darwin" {
483		srcs = append(g.properties.Srcs, g.properties.Darwin.Srcs...)
484		testSrcs = append(g.properties.TestSrcs, g.properties.Darwin.TestSrcs...)
485	} else if runtime.GOOS == "linux" {
486		srcs = append(g.properties.Srcs, g.properties.Linux.Srcs...)
487		testSrcs = append(g.properties.TestSrcs, g.properties.Linux.TestSrcs...)
488	}
489
490	testResultFile := buildGoTest(ctx, testRoot(ctx), testArchiveFile,
491		name, srcs, genSrcs, testSrcs, g.properties.EmbedSrcs)
492	testDeps = append(testDeps, testResultFile...)
493
494	buildGoPackage(ctx, objDir, "main", archiveFile, srcs, genSrcs, g.properties.EmbedSrcs)
495
496	var linkDeps []string
497	var libDirFlags []string
498	ctx.VisitDepsDepthFirst(func(module blueprint.Module) {
499		if info, ok := blueprint.OtherModuleProvider(ctx, module, PackageProvider); ok {
500			linkDeps = append(linkDeps, info.PackageTarget)
501			libDir := info.PkgRoot
502			libDirFlags = append(libDirFlags, "-L "+libDir)
503			testDeps = append(testDeps, info.TestTargets...)
504		}
505	})
506
507	linkArgs := map[string]string{}
508	if len(libDirFlags) > 0 {
509		linkArgs["libDirFlags"] = strings.Join(libDirFlags, " ")
510	}
511
512	ctx.Build(pctx, blueprint.BuildParams{
513		Rule:      link,
514		Outputs:   []string{aoutFile},
515		Inputs:    []string{archiveFile},
516		Implicits: linkDeps,
517		Args:      linkArgs,
518	})
519
520	g.outputFile = aoutFile
521
522	var validations []string
523	if ctx.Config().(BootstrapConfig).RunGoTests() {
524		validations = testDeps
525	}
526
527	if !g.skipInstall {
528		ctx.Build(pctx, blueprint.BuildParams{
529			Rule:        cp,
530			Outputs:     []string{g.installPath},
531			Inputs:      []string{aoutFile},
532			Validations: validations,
533			Default:     g.properties.Default,
534		})
535	}
536
537	blueprint.SetProvider(ctx, BinaryProvider, &BinaryInfo{
538		IntermediatePath: g.outputFile,
539		InstallPath:      g.installPath,
540		TestTargets:      testResultFile,
541	})
542}
543
544func buildGoPluginLoader(ctx blueprint.ModuleContext, pkgPath, pluginSrc string) bool {
545	ret := true
546
547	var pluginPaths []string
548	ctx.VisitDirectDeps(func(module blueprint.Module) {
549		if ctx.OtherModuleDependencyTag(module) == pluginDepTag {
550			if info, ok := blueprint.OtherModuleProvider(ctx, module, PackageProvider); ok {
551				pluginPaths = append(pluginPaths, info.PkgPath)
552			}
553		}
554	})
555
556	ctx.Build(pctx, blueprint.BuildParams{
557		Rule:    pluginGenSrc,
558		Outputs: []string{pluginSrc},
559		Args: map[string]string{
560			"pkg":     pkgPath,
561			"plugins": strings.Join(pluginPaths, " "),
562		},
563	})
564
565	return ret
566}
567
568func generateEmbedcfgFile(ctx blueprint.ModuleContext, files []string, srcDir string, embedcfgFile string) {
569	embedcfg := struct {
570		Patterns map[string][]string
571		Files    map[string]string
572	}{
573		make(map[string][]string, len(files)),
574		make(map[string]string, len(files)),
575	}
576
577	for _, file := range files {
578		embedcfg.Patterns[file] = []string{file}
579		embedcfg.Files[file] = filepath.Join(srcDir, file)
580	}
581
582	embedcfgData, err := json.Marshal(&embedcfg)
583	if err != nil {
584		ctx.ModuleErrorf("Failed to marshal embedcfg data: %s", err.Error())
585	}
586
587	writeFileRule(ctx, embedcfgFile, string(embedcfgData))
588}
589
590func buildGoPackage(ctx blueprint.ModuleContext, pkgRoot string,
591	pkgPath string, archiveFile string, srcs []string, genSrcs []string, embedSrcs []string) {
592
593	srcDir := moduleSrcDir(ctx)
594	srcFiles := pathtools.PrefixPaths(srcs, srcDir)
595	srcFiles = append(srcFiles, genSrcs...)
596
597	var incFlags []string
598	var deps []string
599	ctx.VisitDepsDepthFirst(func(module blueprint.Module) {
600		if info, ok := blueprint.OtherModuleProvider(ctx, module, PackageProvider); ok {
601			incDir := info.PkgRoot
602			target := info.PackageTarget
603			incFlags = append(incFlags, "-I "+incDir)
604			deps = append(deps, target)
605		}
606	})
607
608	compileArgs := map[string]string{
609		"pkgPath": pkgPath,
610	}
611
612	if len(incFlags) > 0 {
613		compileArgs["incFlags"] = strings.Join(incFlags, " ")
614	}
615
616	if len(embedSrcs) > 0 {
617		embedcfgFile := archiveFile + ".embedcfg"
618		generateEmbedcfgFile(ctx, embedSrcs, srcDir, embedcfgFile)
619		compileArgs["embedFlags"] = "-embedcfg " + embedcfgFile
620		deps = append(deps, embedcfgFile)
621	}
622
623	ctx.Build(pctx, blueprint.BuildParams{
624		Rule:      compile,
625		Outputs:   []string{archiveFile},
626		Inputs:    srcFiles,
627		Implicits: deps,
628		Args:      compileArgs,
629	})
630}
631
632func buildGoTest(ctx blueprint.ModuleContext, testRoot, testPkgArchive,
633	pkgPath string, srcs, genSrcs, testSrcs []string, embedSrcs []string) []string {
634
635	if len(testSrcs) == 0 {
636		return nil
637	}
638
639	srcDir := moduleSrcDir(ctx)
640	testFiles := pathtools.PrefixPaths(testSrcs, srcDir)
641
642	mainFile := filepath.Join(testRoot, "test.go")
643	testArchive := filepath.Join(testRoot, "test.a")
644	testFile := filepath.Join(testRoot, "test")
645	testPassed := filepath.Join(testRoot, "test.passed")
646
647	buildGoPackage(ctx, testRoot, pkgPath, testPkgArchive,
648		append(srcs, testSrcs...), genSrcs, embedSrcs)
649
650	ctx.Build(pctx, blueprint.BuildParams{
651		Rule:    goTestMain,
652		Outputs: []string{mainFile},
653		Inputs:  testFiles,
654		Args: map[string]string{
655			"pkg": pkgPath,
656		},
657	})
658
659	linkDeps := []string{testPkgArchive}
660	libDirFlags := []string{"-L " + testRoot}
661	testDeps := []string{}
662	ctx.VisitDepsDepthFirst(func(module blueprint.Module) {
663		if info, ok := blueprint.OtherModuleProvider(ctx, module, PackageProvider); ok {
664			linkDeps = append(linkDeps, info.PackageTarget)
665			libDir := info.PkgRoot
666			libDirFlags = append(libDirFlags, "-L "+libDir)
667			testDeps = append(testDeps, info.TestTargets...)
668		}
669	})
670
671	ctx.Build(pctx, blueprint.BuildParams{
672		Rule:      compile,
673		Outputs:   []string{testArchive},
674		Inputs:    []string{mainFile},
675		Implicits: []string{testPkgArchive},
676		Args: map[string]string{
677			"pkgPath":  "main",
678			"incFlags": "-I " + testRoot,
679		},
680	})
681
682	ctx.Build(pctx, blueprint.BuildParams{
683		Rule:      link,
684		Outputs:   []string{testFile},
685		Inputs:    []string{testArchive},
686		Implicits: linkDeps,
687		Args: map[string]string{
688			"libDirFlags": strings.Join(libDirFlags, " "),
689		},
690	})
691
692	ctx.Build(pctx, blueprint.BuildParams{
693		Rule:        test,
694		Outputs:     []string{testPassed},
695		Inputs:      []string{testFile},
696		Validations: testDeps,
697		Args: map[string]string{
698			"pkg":       pkgPath,
699			"pkgSrcDir": filepath.Dir(testFiles[0]),
700		},
701	})
702
703	return []string{testPassed}
704}
705
706var PrimaryBuilderProvider = blueprint.NewMutatorProvider[PrimaryBuilderInfo]("bootstrap_deps")
707
708type PrimaryBuilderInfo struct{}
709
710type singleton struct {
711}
712
713func newSingletonFactory() func() blueprint.Singleton {
714	return func() blueprint.Singleton {
715		return &singleton{}
716	}
717}
718
719func (s *singleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
720	// Find the module that's marked as the "primary builder", which means it's
721	// creating the binary that we'll use to generate the non-bootstrap
722	// build.ninja file.
723	var primaryBuilders []string
724	// blueprintTools contains blueprint go binaries that will be built in StageMain
725	var blueprintTools []string
726	// blueprintTools contains the test outputs of go tests that can be run in StageMain
727	var blueprintTests []string
728	// blueprintGoPackages contains all blueprint go packages that can be built in StageMain
729	var blueprintGoPackages []string
730	ctx.VisitAllModules(func(module blueprint.Module) {
731		if ctx.PrimaryModule(module) == module {
732			if binaryInfo, ok := blueprint.SingletonModuleProvider(ctx, module, BinaryProvider); ok {
733				if binaryInfo.InstallPath != "" {
734					blueprintTools = append(blueprintTools, binaryInfo.InstallPath)
735				}
736				blueprintTests = append(blueprintTests, binaryInfo.TestTargets...)
737				if _, ok := blueprint.SingletonModuleProvider(ctx, module, PrimaryBuilderProvider); ok {
738					primaryBuilders = append(primaryBuilders, binaryInfo.InstallPath)
739				}
740			}
741
742			if packageInfo, ok := blueprint.SingletonModuleProvider(ctx, module, PackageProvider); ok {
743				blueprintGoPackages = append(blueprintGoPackages, packageInfo.PackageTarget)
744				blueprintTests = append(blueprintTests, packageInfo.TestTargets...)
745			}
746		}
747	})
748
749	var primaryBuilderCmdlinePrefix []string
750	var primaryBuilderFile string
751
752	if len(primaryBuilders) == 0 {
753		ctx.Errorf("no primary builder module present")
754		return
755	} else if len(primaryBuilders) > 1 {
756		ctx.Errorf("multiple primary builder modules present: %q", primaryBuilders)
757		return
758	} else {
759		primaryBuilderFile = primaryBuilders[0]
760	}
761
762	ctx.SetOutDir(pctx, "${outDir}")
763
764	for _, subninja := range ctx.Config().(BootstrapConfig).Subninjas() {
765		ctx.AddSubninja(subninja)
766	}
767
768	for _, i := range ctx.Config().(BootstrapConfig).PrimaryBuilderInvocations() {
769		flags := make([]string, 0)
770		flags = append(flags, primaryBuilderCmdlinePrefix...)
771		flags = append(flags, i.Args...)
772
773		pool := ""
774		if i.Console {
775			pool = "console"
776		}
777
778		envAssignments := ""
779		for k, v := range i.Env {
780			// NB: This is rife with quoting issues but we don't care because we trust
781			// soong_ui to not abuse this facility too much
782			envAssignments += k + "=" + v + " "
783		}
784
785		// Build the main build.ninja
786		ctx.Build(pctx, blueprint.BuildParams{
787			Rule:      generateBuildNinja,
788			Outputs:   i.Outputs,
789			Inputs:    i.Inputs,
790			Implicits: i.Implicits,
791			OrderOnly: i.OrderOnlyInputs,
792			Args: map[string]string{
793				"builder": primaryBuilderFile,
794				"env":     envAssignments,
795				"extra":   strings.Join(flags, " "),
796				"pool":    pool,
797			},
798			Description: i.Description,
799		})
800	}
801
802	// Add a phony target for building various tools that are part of blueprint
803	if len(blueprintTools) > 0 {
804		ctx.Build(pctx, blueprint.BuildParams{
805			Rule:    blueprint.Phony,
806			Outputs: []string{"blueprint_tools"},
807			Inputs:  blueprintTools,
808			Default: true,
809		})
810	}
811
812	// Add a phony target for running various tests that are part of blueprint
813	ctx.Build(pctx, blueprint.BuildParams{
814		Rule:    blueprint.Phony,
815		Outputs: []string{"blueprint_tests"},
816		Inputs:  blueprintTests,
817		Default: true,
818	})
819
820	// Add a phony target for running go tests
821	ctx.Build(pctx, blueprint.BuildParams{
822		Rule:    blueprint.Phony,
823		Outputs: []string{"blueprint_go_packages"},
824		Inputs:  blueprintGoPackages,
825	})
826}
827
828// packageRoot returns the module-specific package root directory path.  This
829// directory is where the final package .a files are output and where dependant
830// modules search for this package via -I arguments.
831func packageRoot(ctx blueprint.ModuleContext) string {
832	toolDir := ctx.Config().(BootstrapConfig).HostToolDir()
833	return filepath.Join(toolDir, "go", ctx.ModuleName(), ctx.ModuleSubDir(), "pkg")
834}
835
836// testRoot returns the module-specific package root directory path used for
837// building tests. The .a files generated here will include everything from
838// packageRoot, plus the test-only code.
839func testRoot(ctx blueprint.ModuleContext) string {
840	toolDir := ctx.Config().(BootstrapConfig).HostToolDir()
841	return filepath.Join(toolDir, "go", ctx.ModuleName(), ctx.ModuleSubDir(), "test")
842}
843
844// moduleSrcDir returns the path of the directory that all source file paths are
845// specified relative to.
846func moduleSrcDir(ctx blueprint.ModuleContext) string {
847	return ctx.ModuleDir()
848}
849
850// moduleObjDir returns the module-specific object directory path.
851func moduleObjDir(ctx blueprint.ModuleContext) string {
852	toolDir := ctx.Config().(BootstrapConfig).HostToolDir()
853	return filepath.Join(toolDir, "go", ctx.ModuleName(), ctx.ModuleSubDir(), "obj")
854}
855
856// moduleGenSrcDir returns the module-specific generated sources path.
857func moduleGenSrcDir(ctx blueprint.ModuleContext) string {
858	toolDir := ctx.Config().(BootstrapConfig).HostToolDir()
859	return filepath.Join(toolDir, "go", ctx.ModuleName(), ctx.ModuleSubDir(), "gen")
860}
861