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	"os"
21	"path/filepath"
22	"runtime"
23	"strings"
24
25	"github.com/google/blueprint"
26	"github.com/google/blueprint/pathtools"
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	bootstrap = pctx.StaticRule("bootstrap",
101		blueprint.RuleParams{
102			Command:     "BUILDDIR=$soongOutDir $bootstrapCmd -i $in",
103			CommandDeps: []string{"$bootstrapCmd"},
104			Description: "bootstrap $in",
105			Generator:   true,
106		})
107
108	touch = pctx.StaticRule("touch",
109		blueprint.RuleParams{
110			Command:     "touch $out",
111			Description: "touch $out",
112		},
113		"depfile", "generator")
114
115	generateBuildNinja = pctx.StaticRule("build.ninja",
116		blueprint.RuleParams{
117			// TODO: it's kinda ugly that some parameters are computed from
118			// environment variables and some from Ninja parameters, but it's probably
119			// better to not to touch that while Blueprint and Soong are separate
120			// NOTE: The spaces at EOL are important because otherwise Ninja would
121			// omit all spaces between the different options.
122			Command: `cd "$$(dirname "$builder")" && ` +
123				`BUILDER="$$PWD/$$(basename "$builder")" && ` +
124				`cd / && ` +
125				`env -i $env "$$BUILDER" ` +
126				`    --top "$$TOP" ` +
127				`    --soong_out "$soongOutDir" ` +
128				`    --out "$outDir" ` +
129				`    $extra`,
130			CommandDeps: []string{"$builder"},
131			Description: "$builder $out",
132			Deps:        blueprint.DepsGCC,
133			Depfile:     "$out.d",
134			Restat:      true,
135		},
136		"builder", "env", "extra", "pool")
137
138	// Work around a Ninja issue.  See https://github.com/martine/ninja/pull/634
139	phony = pctx.StaticRule("phony",
140		blueprint.RuleParams{
141			Command:     "# phony $out",
142			Description: "phony $out",
143			Generator:   true,
144		},
145		"depfile")
146
147	_ = pctx.VariableFunc("ToolDir", func(ctx blueprint.VariableFuncContext, config interface{}) (string, error) {
148		return config.(BootstrapConfig).HostToolDir(), nil
149	})
150)
151
152type GoBinaryTool interface {
153	InstallPath() string
154
155	// So that other packages can't implement this interface
156	isGoBinary()
157}
158
159func pluginDeps(ctx blueprint.BottomUpMutatorContext) {
160	if pkg, ok := ctx.Module().(*GoPackage); ok {
161		if ctx.PrimaryModule() == ctx.Module() {
162			for _, plugin := range pkg.properties.PluginFor {
163				ctx.AddReverseDependency(ctx.Module(), nil, plugin)
164			}
165		}
166	}
167}
168
169type goPackageProducer interface {
170	GoPkgRoot() string
171	GoPackageTarget() string
172	GoTestTargets() []string
173}
174
175func isGoPackageProducer(module blueprint.Module) bool {
176	_, ok := module.(goPackageProducer)
177	return ok
178}
179
180type goPluginProvider interface {
181	GoPkgPath() string
182	IsPluginFor(string) bool
183}
184
185func isGoPluginFor(name string) func(blueprint.Module) bool {
186	return func(module blueprint.Module) bool {
187		if plugin, ok := module.(goPluginProvider); ok {
188			return plugin.IsPluginFor(name)
189		}
190		return false
191	}
192}
193
194func IsBootstrapModule(module blueprint.Module) bool {
195	_, isPackage := module.(*GoPackage)
196	_, isBinary := module.(*GoBinary)
197	return isPackage || isBinary
198}
199
200func isBootstrapBinaryModule(module blueprint.Module) bool {
201	_, isBinary := module.(*GoBinary)
202	return isBinary
203}
204
205// A GoPackage is a module for building Go packages.
206type GoPackage struct {
207	blueprint.SimpleName
208	properties struct {
209		Deps      []string
210		PkgPath   string
211		Srcs      []string
212		TestSrcs  []string
213		TestData  []string
214		PluginFor []string
215		EmbedSrcs []string
216
217		Darwin struct {
218			Srcs     []string
219			TestSrcs []string
220		}
221		Linux struct {
222			Srcs     []string
223			TestSrcs []string
224		}
225	}
226
227	// The root dir in which the package .a file is located.  The full .a file
228	// path will be "packageRoot/PkgPath.a"
229	pkgRoot string
230
231	// The path of the .a file that is to be built.
232	archiveFile string
233
234	// The path of the test result file.
235	testResultFile []string
236}
237
238var _ goPackageProducer = (*GoPackage)(nil)
239
240func newGoPackageModuleFactory() func() (blueprint.Module, []interface{}) {
241	return func() (blueprint.Module, []interface{}) {
242		module := &GoPackage{}
243		return module, []interface{}{&module.properties, &module.SimpleName.Properties}
244	}
245}
246
247func (g *GoPackage) DynamicDependencies(ctx blueprint.DynamicDependerModuleContext) []string {
248	if ctx.Module() != ctx.PrimaryModule() {
249		return nil
250	}
251	return g.properties.Deps
252}
253
254func (g *GoPackage) GoPkgPath() string {
255	return g.properties.PkgPath
256}
257
258func (g *GoPackage) GoPkgRoot() string {
259	return g.pkgRoot
260}
261
262func (g *GoPackage) GoPackageTarget() string {
263	return g.archiveFile
264}
265
266func (g *GoPackage) GoTestTargets() []string {
267	return g.testResultFile
268}
269
270func (g *GoPackage) IsPluginFor(name string) bool {
271	for _, plugin := range g.properties.PluginFor {
272		if plugin == name {
273			return true
274		}
275	}
276	return false
277}
278
279func (g *GoPackage) GenerateBuildActions(ctx blueprint.ModuleContext) {
280	// Allow the primary builder to create multiple variants.  Any variants after the first
281	// will copy outputs from the first.
282	if ctx.Module() != ctx.PrimaryModule() {
283		primary := ctx.PrimaryModule().(*GoPackage)
284		g.pkgRoot = primary.pkgRoot
285		g.archiveFile = primary.archiveFile
286		g.testResultFile = primary.testResultFile
287		return
288	}
289
290	var (
291		name       = ctx.ModuleName()
292		hasPlugins = false
293		pluginSrc  = ""
294		genSrcs    = []string{}
295	)
296
297	if g.properties.PkgPath == "" {
298		ctx.ModuleErrorf("module %s did not specify a valid pkgPath", name)
299		return
300	}
301
302	g.pkgRoot = packageRoot(ctx)
303	g.archiveFile = filepath.Join(g.pkgRoot,
304		filepath.FromSlash(g.properties.PkgPath)+".a")
305
306	ctx.VisitDepsDepthFirstIf(isGoPluginFor(name),
307		func(module blueprint.Module) { hasPlugins = true })
308	if hasPlugins {
309		pluginSrc = filepath.Join(moduleGenSrcDir(ctx), "plugin.go")
310		genSrcs = append(genSrcs, pluginSrc)
311	}
312
313	if hasPlugins && !buildGoPluginLoader(ctx, g.properties.PkgPath, pluginSrc) {
314		return
315	}
316
317	var srcs, testSrcs []string
318	if runtime.GOOS == "darwin" {
319		srcs = append(g.properties.Srcs, g.properties.Darwin.Srcs...)
320		testSrcs = append(g.properties.TestSrcs, g.properties.Darwin.TestSrcs...)
321	} else if runtime.GOOS == "linux" {
322		srcs = append(g.properties.Srcs, g.properties.Linux.Srcs...)
323		testSrcs = append(g.properties.TestSrcs, g.properties.Linux.TestSrcs...)
324	}
325
326	testArchiveFile := filepath.Join(testRoot(ctx),
327		filepath.FromSlash(g.properties.PkgPath)+".a")
328	g.testResultFile = buildGoTest(ctx, testRoot(ctx), testArchiveFile,
329		g.properties.PkgPath, srcs, genSrcs, testSrcs, g.properties.EmbedSrcs)
330
331	// Don't build for test-only packages
332	if len(srcs) == 0 && len(genSrcs) == 0 {
333		ctx.Build(pctx, blueprint.BuildParams{
334			Rule:     touch,
335			Outputs:  []string{g.archiveFile},
336			Optional: true,
337		})
338		return
339	}
340
341	buildGoPackage(ctx, g.pkgRoot, g.properties.PkgPath, g.archiveFile,
342		srcs, genSrcs, g.properties.EmbedSrcs)
343	blueprint.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: srcs})
344}
345
346func (g *GoPackage) Srcs() []string {
347	return g.properties.Srcs
348}
349
350func (g *GoPackage) LinuxSrcs() []string {
351	return g.properties.Linux.Srcs
352}
353
354func (g *GoPackage) DarwinSrcs() []string {
355	return g.properties.Darwin.Srcs
356}
357
358func (g *GoPackage) TestSrcs() []string {
359	return g.properties.TestSrcs
360}
361
362func (g *GoPackage) LinuxTestSrcs() []string {
363	return g.properties.Linux.TestSrcs
364}
365
366func (g *GoPackage) DarwinTestSrcs() []string {
367	return g.properties.Darwin.TestSrcs
368}
369
370func (g *GoPackage) Deps() []string {
371	return g.properties.Deps
372}
373
374func (g *GoPackage) TestData() []string {
375	return g.properties.TestData
376}
377
378// A GoBinary is a module for building executable binaries from Go sources.
379type GoBinary struct {
380	blueprint.SimpleName
381	properties struct {
382		Deps           []string
383		Srcs           []string
384		TestSrcs       []string
385		TestData       []string
386		EmbedSrcs      []string
387		PrimaryBuilder bool
388		Default        bool
389
390		Darwin struct {
391			Srcs     []string
392			TestSrcs []string
393		}
394		Linux struct {
395			Srcs     []string
396			TestSrcs []string
397		}
398	}
399
400	installPath string
401}
402
403var _ GoBinaryTool = (*GoBinary)(nil)
404
405func newGoBinaryModuleFactory() func() (blueprint.Module, []interface{}) {
406	return func() (blueprint.Module, []interface{}) {
407		module := &GoBinary{}
408		return module, []interface{}{&module.properties, &module.SimpleName.Properties}
409	}
410}
411
412func (g *GoBinary) DynamicDependencies(ctx blueprint.DynamicDependerModuleContext) []string {
413	if ctx.Module() != ctx.PrimaryModule() {
414		return nil
415	}
416	return g.properties.Deps
417}
418
419func (g *GoBinary) isGoBinary() {}
420func (g *GoBinary) InstallPath() string {
421	return g.installPath
422}
423
424func (g *GoBinary) Srcs() []string {
425	return g.properties.Srcs
426}
427
428func (g *GoBinary) LinuxSrcs() []string {
429	return g.properties.Linux.Srcs
430}
431
432func (g *GoBinary) DarwinSrcs() []string {
433	return g.properties.Darwin.Srcs
434}
435
436func (g *GoBinary) TestSrcs() []string {
437	return g.properties.TestSrcs
438}
439
440func (g *GoBinary) LinuxTestSrcs() []string {
441	return g.properties.Linux.TestSrcs
442}
443
444func (g *GoBinary) DarwinTestSrcs() []string {
445	return g.properties.Darwin.TestSrcs
446}
447
448func (g *GoBinary) Deps() []string {
449	return g.properties.Deps
450}
451
452func (g *GoBinary) TestData() []string {
453	return g.properties.TestData
454}
455
456func (g *GoBinary) GenerateBuildActions(ctx blueprint.ModuleContext) {
457	// Allow the primary builder to create multiple variants.  Any variants after the first
458	// will copy outputs from the first.
459	if ctx.Module() != ctx.PrimaryModule() {
460		primary := ctx.PrimaryModule().(*GoBinary)
461		g.installPath = primary.installPath
462		return
463	}
464
465	var (
466		name            = ctx.ModuleName()
467		objDir          = moduleObjDir(ctx)
468		archiveFile     = filepath.Join(objDir, name+".a")
469		testArchiveFile = filepath.Join(testRoot(ctx), name+".a")
470		aoutFile        = filepath.Join(objDir, "a.out")
471		hasPlugins      = false
472		pluginSrc       = ""
473		genSrcs         = []string{}
474	)
475
476	g.installPath = filepath.Join(ctx.Config().(BootstrapConfig).HostToolDir(), name)
477	ctx.VisitDepsDepthFirstIf(isGoPluginFor(name),
478		func(module blueprint.Module) { hasPlugins = true })
479	if hasPlugins {
480		pluginSrc = filepath.Join(moduleGenSrcDir(ctx), "plugin.go")
481		genSrcs = append(genSrcs, pluginSrc)
482	}
483
484	var testDeps []string
485
486	if hasPlugins && !buildGoPluginLoader(ctx, "main", pluginSrc) {
487		return
488	}
489
490	var srcs, testSrcs []string
491	if runtime.GOOS == "darwin" {
492		srcs = append(g.properties.Srcs, g.properties.Darwin.Srcs...)
493		testSrcs = append(g.properties.TestSrcs, g.properties.Darwin.TestSrcs...)
494	} else if runtime.GOOS == "linux" {
495		srcs = append(g.properties.Srcs, g.properties.Linux.Srcs...)
496		testSrcs = append(g.properties.TestSrcs, g.properties.Linux.TestSrcs...)
497	}
498
499	testDeps = buildGoTest(ctx, testRoot(ctx), testArchiveFile,
500		name, srcs, genSrcs, testSrcs, g.properties.EmbedSrcs)
501
502	buildGoPackage(ctx, objDir, "main", archiveFile, srcs, genSrcs, g.properties.EmbedSrcs)
503
504	var linkDeps []string
505	var libDirFlags []string
506	ctx.VisitDepsDepthFirstIf(isGoPackageProducer,
507		func(module blueprint.Module) {
508			dep := module.(goPackageProducer)
509			linkDeps = append(linkDeps, dep.GoPackageTarget())
510			libDir := dep.GoPkgRoot()
511			libDirFlags = append(libDirFlags, "-L "+libDir)
512			testDeps = append(testDeps, dep.GoTestTargets()...)
513		})
514
515	linkArgs := map[string]string{}
516	if len(libDirFlags) > 0 {
517		linkArgs["libDirFlags"] = strings.Join(libDirFlags, " ")
518	}
519
520	ctx.Build(pctx, blueprint.BuildParams{
521		Rule:      link,
522		Outputs:   []string{aoutFile},
523		Inputs:    []string{archiveFile},
524		Implicits: linkDeps,
525		Args:      linkArgs,
526		Optional:  true,
527	})
528
529	var validations []string
530	if ctx.Config().(BootstrapConfig).RunGoTests() {
531		validations = testDeps
532	}
533
534	ctx.Build(pctx, blueprint.BuildParams{
535		Rule:        cp,
536		Outputs:     []string{g.installPath},
537		Inputs:      []string{aoutFile},
538		Validations: validations,
539		Optional:    !g.properties.Default,
540	})
541	blueprint.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: srcs})
542}
543
544func buildGoPluginLoader(ctx blueprint.ModuleContext, pkgPath, pluginSrc string) bool {
545	ret := true
546	name := ctx.ModuleName()
547
548	var pluginPaths []string
549	ctx.VisitDepsDepthFirstIf(isGoPluginFor(name),
550		func(module blueprint.Module) {
551			plugin := module.(goPluginProvider)
552			pluginPaths = append(pluginPaths, plugin.GoPkgPath())
553		})
554
555	ctx.Build(pctx, blueprint.BuildParams{
556		Rule:    pluginGenSrc,
557		Outputs: []string{pluginSrc},
558		Args: map[string]string{
559			"pkg":     pkgPath,
560			"plugins": strings.Join(pluginPaths, " "),
561		},
562		Optional: true,
563	})
564
565	return ret
566}
567
568func generateEmbedcfgFile(files []string, srcDir string, embedcfgFile string) {
569	embedcfg := struct {
570		Patterns map[string][]string
571		Files    map[string]string
572	}{
573		map[string][]string{},
574		map[string]string{},
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		panic(err)
585	}
586
587	os.MkdirAll(filepath.Dir(embedcfgFile), os.ModePerm)
588	os.WriteFile(embedcfgFile, []byte(embedcfgData), 0644)
589}
590
591func buildGoPackage(ctx blueprint.ModuleContext, pkgRoot string,
592	pkgPath string, archiveFile string, srcs []string, genSrcs []string, embedSrcs []string) {
593
594	srcDir := moduleSrcDir(ctx)
595	srcFiles := pathtools.PrefixPaths(srcs, srcDir)
596	srcFiles = append(srcFiles, genSrcs...)
597
598	var incFlags []string
599	var deps []string
600	ctx.VisitDepsDepthFirstIf(isGoPackageProducer,
601		func(module blueprint.Module) {
602			dep := module.(goPackageProducer)
603			incDir := dep.GoPkgRoot()
604			target := dep.GoPackageTarget()
605			incFlags = append(incFlags, "-I "+incDir)
606			deps = append(deps, target)
607		})
608
609	compileArgs := map[string]string{
610		"pkgPath": pkgPath,
611	}
612
613	if len(incFlags) > 0 {
614		compileArgs["incFlags"] = strings.Join(incFlags, " ")
615	}
616
617	if len(embedSrcs) > 0 {
618		embedcfgFile := archiveFile + ".embedcfg"
619		generateEmbedcfgFile(embedSrcs, srcDir, embedcfgFile)
620		compileArgs["embedFlags"] = "-embedcfg " + 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		Optional:  true,
630	})
631}
632
633func buildGoTest(ctx blueprint.ModuleContext, testRoot, testPkgArchive,
634	pkgPath string, srcs, genSrcs, testSrcs []string, embedSrcs []string) []string {
635
636	if len(testSrcs) == 0 {
637		return nil
638	}
639
640	srcDir := moduleSrcDir(ctx)
641	testFiles := pathtools.PrefixPaths(testSrcs, srcDir)
642
643	mainFile := filepath.Join(testRoot, "test.go")
644	testArchive := filepath.Join(testRoot, "test.a")
645	testFile := filepath.Join(testRoot, "test")
646	testPassed := filepath.Join(testRoot, "test.passed")
647
648	buildGoPackage(ctx, testRoot, pkgPath, testPkgArchive,
649		append(srcs, testSrcs...), genSrcs, embedSrcs)
650
651	ctx.Build(pctx, blueprint.BuildParams{
652		Rule:    goTestMain,
653		Outputs: []string{mainFile},
654		Inputs:  testFiles,
655		Args: map[string]string{
656			"pkg": pkgPath,
657		},
658		Optional: true,
659	})
660
661	linkDeps := []string{testPkgArchive}
662	libDirFlags := []string{"-L " + testRoot}
663	testDeps := []string{}
664	ctx.VisitDepsDepthFirstIf(isGoPackageProducer,
665		func(module blueprint.Module) {
666			dep := module.(goPackageProducer)
667			linkDeps = append(linkDeps, dep.GoPackageTarget())
668			libDir := dep.GoPkgRoot()
669			libDirFlags = append(libDirFlags, "-L "+libDir)
670			testDeps = append(testDeps, dep.GoTestTargets()...)
671		})
672
673	ctx.Build(pctx, blueprint.BuildParams{
674		Rule:      compile,
675		Outputs:   []string{testArchive},
676		Inputs:    []string{mainFile},
677		Implicits: []string{testPkgArchive},
678		Args: map[string]string{
679			"pkgPath":  "main",
680			"incFlags": "-I " + testRoot,
681		},
682		Optional: true,
683	})
684
685	ctx.Build(pctx, blueprint.BuildParams{
686		Rule:      link,
687		Outputs:   []string{testFile},
688		Inputs:    []string{testArchive},
689		Implicits: linkDeps,
690		Args: map[string]string{
691			"libDirFlags": strings.Join(libDirFlags, " "),
692		},
693		Optional: true,
694	})
695
696	ctx.Build(pctx, blueprint.BuildParams{
697		Rule:        test,
698		Outputs:     []string{testPassed},
699		Inputs:      []string{testFile},
700		Validations: testDeps,
701		Args: map[string]string{
702			"pkg":       pkgPath,
703			"pkgSrcDir": filepath.Dir(testFiles[0]),
704		},
705		Optional: true,
706	})
707
708	return []string{testPassed}
709}
710
711type singleton struct {
712}
713
714func newSingletonFactory() func() blueprint.Singleton {
715	return func() blueprint.Singleton {
716		return &singleton{}
717	}
718}
719
720func (s *singleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
721	// Find the module that's marked as the "primary builder", which means it's
722	// creating the binary that we'll use to generate the non-bootstrap
723	// build.ninja file.
724	var primaryBuilders []*GoBinary
725	// blueprintTools contains blueprint go binaries that will be built in StageMain
726	var blueprintTools []string
727	// blueprintTools contains the test outputs of go tests that can be run in StageMain
728	var blueprintTests []string
729	// blueprintGoPackages contains all blueprint go packages that can be built in StageMain
730	var blueprintGoPackages []string
731	ctx.VisitAllModulesIf(IsBootstrapModule,
732		func(module blueprint.Module) {
733			if ctx.PrimaryModule(module) == module {
734				if binaryModule, ok := module.(*GoBinary); ok {
735					blueprintTools = append(blueprintTools, binaryModule.InstallPath())
736					if binaryModule.properties.PrimaryBuilder {
737						primaryBuilders = append(primaryBuilders, binaryModule)
738					}
739				}
740
741				if packageModule, ok := module.(*GoPackage); ok {
742					blueprintGoPackages = append(blueprintGoPackages,
743						packageModule.GoPackageTarget())
744					blueprintTests = append(blueprintTests,
745						packageModule.GoTestTargets()...)
746				}
747			}
748		})
749
750	var primaryBuilderCmdlinePrefix []string
751	var primaryBuilderName string
752
753	if len(primaryBuilders) == 0 {
754		ctx.Errorf("no primary builder module present")
755		return
756	} else if len(primaryBuilders) > 1 {
757		ctx.Errorf("multiple primary builder modules present:")
758		for _, primaryBuilder := range primaryBuilders {
759			ctx.ModuleErrorf(primaryBuilder, "<-- module %s",
760				ctx.ModuleName(primaryBuilder))
761		}
762		return
763	} else {
764		primaryBuilderName = ctx.ModuleName(primaryBuilders[0])
765	}
766
767	primaryBuilderFile := filepath.Join("$ToolDir", primaryBuilderName)
768	ctx.SetOutDir(pctx, "${outDir}")
769
770	for _, subninja := range ctx.Config().(BootstrapConfig).Subninjas() {
771		ctx.AddSubninja(subninja)
772	}
773
774	for _, i := range ctx.Config().(BootstrapConfig).PrimaryBuilderInvocations() {
775		flags := make([]string, 0)
776		flags = append(flags, primaryBuilderCmdlinePrefix...)
777		flags = append(flags, i.Args...)
778
779		pool := ""
780		if i.Console {
781			pool = "console"
782		}
783
784		envAssignments := ""
785		for k, v := range i.Env {
786			// NB: This is rife with quoting issues but we don't care because we trust
787			// soong_ui to not abuse this facility too much
788			envAssignments += k + "=" + v + " "
789		}
790
791		// Build the main build.ninja
792		ctx.Build(pctx, blueprint.BuildParams{
793			Rule:      generateBuildNinja,
794			Outputs:   i.Outputs,
795			Inputs:    i.Inputs,
796			Implicits: i.Implicits,
797			OrderOnly: i.OrderOnlyInputs,
798			Args: map[string]string{
799				"builder": primaryBuilderFile,
800				"env":     envAssignments,
801				"extra":   strings.Join(flags, " "),
802				"pool":    pool,
803			},
804			// soong_ui explicitly requests what it wants to be build. This is
805			// because the same Ninja file contains instructions to run
806			// soong_build, run bp2build and to generate the JSON module graph.
807			Optional:    true,
808			Description: i.Description,
809		})
810	}
811
812	// Add a phony target for building various tools that are part of blueprint
813	ctx.Build(pctx, blueprint.BuildParams{
814		Rule:    blueprint.Phony,
815		Outputs: []string{"blueprint_tools"},
816		Inputs:  blueprintTools,
817	})
818
819	// Add a phony target for running various tests that are part of blueprint
820	ctx.Build(pctx, blueprint.BuildParams{
821		Rule:    blueprint.Phony,
822		Outputs: []string{"blueprint_tests"},
823		Inputs:  blueprintTests,
824	})
825
826	// Add a phony target for running go tests
827	ctx.Build(pctx, blueprint.BuildParams{
828		Rule:     blueprint.Phony,
829		Outputs:  []string{"blueprint_go_packages"},
830		Inputs:   blueprintGoPackages,
831		Optional: true,
832	})
833}
834
835// packageRoot returns the module-specific package root directory path.  This
836// directory is where the final package .a files are output and where dependant
837// modules search for this package via -I arguments.
838func packageRoot(ctx blueprint.ModuleContext) string {
839	toolDir := ctx.Config().(BootstrapConfig).HostToolDir()
840	return filepath.Join(toolDir, "go", ctx.ModuleName(), "pkg")
841}
842
843// testRoot returns the module-specific package root directory path used for
844// building tests. The .a files generated here will include everything from
845// packageRoot, plus the test-only code.
846func testRoot(ctx blueprint.ModuleContext) string {
847	toolDir := ctx.Config().(BootstrapConfig).HostToolDir()
848	return filepath.Join(toolDir, "go", ctx.ModuleName(), "test")
849}
850
851// moduleSrcDir returns the path of the directory that all source file paths are
852// specified relative to.
853func moduleSrcDir(ctx blueprint.ModuleContext) string {
854	return ctx.ModuleDir()
855}
856
857// moduleObjDir returns the module-specific object directory path.
858func moduleObjDir(ctx blueprint.ModuleContext) string {
859	toolDir := ctx.Config().(BootstrapConfig).HostToolDir()
860	return filepath.Join(toolDir, "go", ctx.ModuleName(), "obj")
861}
862
863// moduleGenSrcDir returns the module-specific generated sources path.
864func moduleGenSrcDir(ctx blueprint.ModuleContext) string {
865	toolDir := ctx.Config().(BootstrapConfig).HostToolDir()
866	return filepath.Join(toolDir, "go", ctx.ModuleName(), "gen")
867}
868