• 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	"fmt"
19	"go/build"
20	"io/ioutil"
21	"os"
22	"path/filepath"
23	"runtime"
24	"strings"
25
26	"github.com/google/blueprint"
27	"github.com/google/blueprint/pathtools"
28)
29
30const bootstrapSubDir = ".bootstrap"
31const miniBootstrapSubDir = ".minibootstrap"
32
33var (
34	pctx = blueprint.NewPackageContext("github.com/google/blueprint/bootstrap")
35
36	goTestMainCmd   = pctx.StaticVariable("goTestMainCmd", filepath.Join(bootstrapDir, "bin", "gotestmain"))
37	goTestRunnerCmd = pctx.StaticVariable("goTestRunnerCmd", filepath.Join(bootstrapDir, "bin", "gotestrunner"))
38	pluginGenSrcCmd = pctx.StaticVariable("pluginGenSrcCmd", filepath.Join(bootstrapDir, "bin", "loadplugins"))
39
40	parallelCompile = pctx.StaticVariable("parallelCompile", func() string {
41		// Parallel compilation is only supported on >= go1.9
42		for _, r := range build.Default.ReleaseTags {
43			if r == "go1.9" {
44				numCpu := runtime.NumCPU()
45				// This will cause us to recompile all go programs if the
46				// number of cpus changes. We don't get a lot of benefit from
47				// higher values, so cap this to make it cheaper to move trees
48				// between machines.
49				if numCpu > 8 {
50					numCpu = 8
51				}
52				return fmt.Sprintf("-c %d", numCpu)
53			}
54		}
55		return ""
56	}())
57
58	compile = pctx.StaticRule("compile",
59		blueprint.RuleParams{
60			Command: "GOROOT='$goRoot' $compileCmd $parallelCompile -o $out.tmp " +
61				"-p $pkgPath -complete $incFlags -pack $in && " +
62				"if cmp --quiet $out.tmp $out; then rm $out.tmp; else mv -f $out.tmp $out; fi",
63			CommandDeps: []string{"$compileCmd"},
64			Description: "compile $out",
65			Restat:      true,
66		},
67		"pkgPath", "incFlags")
68
69	link = pctx.StaticRule("link",
70		blueprint.RuleParams{
71			Command: "GOROOT='$goRoot' $linkCmd -o $out.tmp $libDirFlags $in && " +
72				"if cmp --quiet $out.tmp $out; then rm $out.tmp; else mv -f $out.tmp $out; fi",
73			CommandDeps: []string{"$linkCmd"},
74			Description: "link $out",
75			Restat:      true,
76		},
77		"libDirFlags")
78
79	goTestMain = pctx.StaticRule("gotestmain",
80		blueprint.RuleParams{
81			Command:     "$goTestMainCmd -o $out -pkg $pkg $in",
82			CommandDeps: []string{"$goTestMainCmd"},
83			Description: "gotestmain $out",
84		},
85		"pkg")
86
87	pluginGenSrc = pctx.StaticRule("pluginGenSrc",
88		blueprint.RuleParams{
89			Command:     "$pluginGenSrcCmd -o $out -p $pkg $plugins",
90			CommandDeps: []string{"$pluginGenSrcCmd"},
91			Description: "create $out",
92		},
93		"pkg", "plugins")
94
95	test = pctx.StaticRule("test",
96		blueprint.RuleParams{
97			Command:     "$goTestRunnerCmd -p $pkgSrcDir -f $out -- $in -test.short",
98			CommandDeps: []string{"$goTestRunnerCmd"},
99			Description: "test $pkg",
100		},
101		"pkg", "pkgSrcDir")
102
103	cp = pctx.StaticRule("cp",
104		blueprint.RuleParams{
105			Command:     "cp $in $out",
106			Description: "cp $out",
107		},
108		"generator")
109
110	bootstrap = pctx.StaticRule("bootstrap",
111		blueprint.RuleParams{
112			Command:     "BUILDDIR=$buildDir $bootstrapCmd -i $in",
113			CommandDeps: []string{"$bootstrapCmd"},
114			Description: "bootstrap $in",
115			Generator:   true,
116		})
117
118	touch = pctx.StaticRule("touch",
119		blueprint.RuleParams{
120			Command:     "touch $out",
121			Description: "touch $out",
122		},
123		"depfile", "generator")
124
125	generateBuildNinja = pctx.StaticRule("build.ninja",
126		blueprint.RuleParams{
127			Command:     "$builder $extra -b $buildDir -n $ninjaBuildDir -d $out.d -globFile $globFile -o $out $in",
128			CommandDeps: []string{"$builder"},
129			Description: "$builder $out",
130			Deps:        blueprint.DepsGCC,
131			Depfile:     "$out.d",
132			Restat:      true,
133		},
134		"builder", "extra", "generator", "globFile")
135
136	// Work around a Ninja issue.  See https://github.com/martine/ninja/pull/634
137	phony = pctx.StaticRule("phony",
138		blueprint.RuleParams{
139			Command:     "# phony $out",
140			Description: "phony $out",
141			Generator:   true,
142		},
143		"depfile")
144
145	_ = pctx.VariableFunc("BinDir", func(config interface{}) (string, error) {
146		return binDir(), nil
147	})
148
149	_ = pctx.VariableFunc("ToolDir", func(config interface{}) (string, error) {
150		return toolDir(config), nil
151	})
152
153	docsDir = filepath.Join(bootstrapDir, "docs")
154
155	bootstrapDir     = filepath.Join("$buildDir", bootstrapSubDir)
156	miniBootstrapDir = filepath.Join("$buildDir", miniBootstrapSubDir)
157
158	minibpFile = filepath.Join(miniBootstrapDir, "minibp")
159)
160
161type GoBinaryTool interface {
162	InstallPath() string
163
164	// So that other packages can't implement this interface
165	isGoBinary()
166}
167
168func binDir() string {
169	return filepath.Join(BuildDir, bootstrapSubDir, "bin")
170}
171
172func toolDir(config interface{}) string {
173	if c, ok := config.(ConfigBlueprintToolLocation); ok {
174		return filepath.Join(c.BlueprintToolLocation())
175	}
176	return filepath.Join(BuildDir, "bin")
177}
178
179func pluginDeps(ctx blueprint.BottomUpMutatorContext) {
180	if pkg, ok := ctx.Module().(*goPackage); ok {
181		for _, plugin := range pkg.properties.PluginFor {
182			ctx.AddReverseDependency(ctx.Module(), nil, plugin)
183		}
184	}
185}
186
187type goPackageProducer interface {
188	GoPkgRoot() string
189	GoPackageTarget() string
190	GoTestTargets() []string
191}
192
193func isGoPackageProducer(module blueprint.Module) bool {
194	_, ok := module.(goPackageProducer)
195	return ok
196}
197
198type goPluginProvider interface {
199	GoPkgPath() string
200	IsPluginFor(string) bool
201}
202
203func isGoPluginFor(name string) func(blueprint.Module) bool {
204	return func(module blueprint.Module) bool {
205		if plugin, ok := module.(goPluginProvider); ok {
206			return plugin.IsPluginFor(name)
207		}
208		return false
209	}
210}
211
212func isBootstrapModule(module blueprint.Module) bool {
213	_, isPackage := module.(*goPackage)
214	_, isBinary := module.(*goBinary)
215	return isPackage || isBinary
216}
217
218func isBootstrapBinaryModule(module blueprint.Module) bool {
219	_, isBinary := module.(*goBinary)
220	return isBinary
221}
222
223// A goPackage is a module for building Go packages.
224type goPackage struct {
225	blueprint.SimpleName
226	properties struct {
227		Deps      []string
228		PkgPath   string
229		Srcs      []string
230		TestSrcs  []string
231		PluginFor []string
232
233		Darwin struct {
234			Srcs     []string
235			TestSrcs []string
236		}
237		Linux struct {
238			Srcs     []string
239			TestSrcs []string
240		}
241	}
242
243	// The root dir in which the package .a file is located.  The full .a file
244	// path will be "packageRoot/PkgPath.a"
245	pkgRoot string
246
247	// The path of the .a file that is to be built.
248	archiveFile string
249
250	// The path of the test result file.
251	testResultFile []string
252
253	// The bootstrap Config
254	config *Config
255}
256
257var _ goPackageProducer = (*goPackage)(nil)
258
259func newGoPackageModuleFactory(config *Config) func() (blueprint.Module, []interface{}) {
260	return func() (blueprint.Module, []interface{}) {
261		module := &goPackage{
262			config: config,
263		}
264		return module, []interface{}{&module.properties, &module.SimpleName.Properties}
265	}
266}
267
268func (g *goPackage) DynamicDependencies(ctx blueprint.DynamicDependerModuleContext) []string {
269	return g.properties.Deps
270}
271
272func (g *goPackage) GoPkgPath() string {
273	return g.properties.PkgPath
274}
275
276func (g *goPackage) GoPkgRoot() string {
277	return g.pkgRoot
278}
279
280func (g *goPackage) GoPackageTarget() string {
281	return g.archiveFile
282}
283
284func (g *goPackage) GoTestTargets() []string {
285	return g.testResultFile
286}
287
288func (g *goPackage) IsPluginFor(name string) bool {
289	for _, plugin := range g.properties.PluginFor {
290		if plugin == name {
291			return true
292		}
293	}
294	return false
295}
296
297func (g *goPackage) GenerateBuildActions(ctx blueprint.ModuleContext) {
298	var (
299		name       = ctx.ModuleName()
300		hasPlugins = false
301		pluginSrc  = ""
302		genSrcs    = []string{}
303	)
304
305	if g.properties.PkgPath == "" {
306		ctx.ModuleErrorf("module %s did not specify a valid pkgPath", name)
307		return
308	}
309
310	g.pkgRoot = packageRoot(ctx)
311	g.archiveFile = filepath.Join(g.pkgRoot,
312		filepath.FromSlash(g.properties.PkgPath)+".a")
313
314	ctx.VisitDepsDepthFirstIf(isGoPluginFor(name),
315		func(module blueprint.Module) { hasPlugins = true })
316	if hasPlugins {
317		pluginSrc = filepath.Join(moduleGenSrcDir(ctx), "plugin.go")
318		genSrcs = append(genSrcs, pluginSrc)
319	}
320
321	if hasPlugins && !buildGoPluginLoader(ctx, g.properties.PkgPath, pluginSrc) {
322		return
323	}
324
325	var srcs, testSrcs []string
326	if runtime.GOOS == "darwin" {
327		srcs = append(g.properties.Srcs, g.properties.Darwin.Srcs...)
328		testSrcs = append(g.properties.TestSrcs, g.properties.Darwin.TestSrcs...)
329	} else if runtime.GOOS == "linux" {
330		srcs = append(g.properties.Srcs, g.properties.Linux.Srcs...)
331		testSrcs = append(g.properties.TestSrcs, g.properties.Linux.TestSrcs...)
332	}
333
334	if g.config.runGoTests {
335		testArchiveFile := filepath.Join(testRoot(ctx),
336			filepath.FromSlash(g.properties.PkgPath)+".a")
337		g.testResultFile = buildGoTest(ctx, testRoot(ctx), testArchiveFile,
338			g.properties.PkgPath, srcs, genSrcs,
339			testSrcs)
340	}
341
342	buildGoPackage(ctx, g.pkgRoot, g.properties.PkgPath, g.archiveFile,
343		srcs, genSrcs)
344}
345
346// A goBinary is a module for building executable binaries from Go sources.
347type goBinary struct {
348	blueprint.SimpleName
349	properties struct {
350		Deps           []string
351		Srcs           []string
352		TestSrcs       []string
353		PrimaryBuilder bool
354		Default        bool
355
356		Darwin struct {
357			Srcs     []string
358			TestSrcs []string
359		}
360		Linux struct {
361			Srcs     []string
362			TestSrcs []string
363		}
364
365		Tool_dir bool `blueprint:"mutated"`
366	}
367
368	installPath string
369
370	// The bootstrap Config
371	config *Config
372}
373
374var _ GoBinaryTool = (*goBinary)(nil)
375
376func newGoBinaryModuleFactory(config *Config, tooldir bool) func() (blueprint.Module, []interface{}) {
377	return func() (blueprint.Module, []interface{}) {
378		module := &goBinary{
379			config: config,
380		}
381		module.properties.Tool_dir = tooldir
382		return module, []interface{}{&module.properties, &module.SimpleName.Properties}
383	}
384}
385
386func (g *goBinary) DynamicDependencies(ctx blueprint.DynamicDependerModuleContext) []string {
387	return g.properties.Deps
388}
389
390func (g *goBinary) isGoBinary() {}
391func (g *goBinary) InstallPath() string {
392	return g.installPath
393}
394
395func (g *goBinary) GenerateBuildActions(ctx blueprint.ModuleContext) {
396	var (
397		name            = ctx.ModuleName()
398		objDir          = moduleObjDir(ctx)
399		archiveFile     = filepath.Join(objDir, name+".a")
400		testArchiveFile = filepath.Join(testRoot(ctx), name+".a")
401		aoutFile        = filepath.Join(objDir, "a.out")
402		hasPlugins      = false
403		pluginSrc       = ""
404		genSrcs         = []string{}
405	)
406
407	if g.properties.Tool_dir {
408		g.installPath = filepath.Join(toolDir(ctx.Config()), name)
409	} else {
410		g.installPath = filepath.Join(binDir(), name)
411	}
412
413	ctx.VisitDepsDepthFirstIf(isGoPluginFor(name),
414		func(module blueprint.Module) { hasPlugins = true })
415	if hasPlugins {
416		pluginSrc = filepath.Join(moduleGenSrcDir(ctx), "plugin.go")
417		genSrcs = append(genSrcs, pluginSrc)
418	}
419
420	var deps []string
421
422	if hasPlugins && !buildGoPluginLoader(ctx, "main", pluginSrc) {
423		return
424	}
425
426	var srcs, testSrcs []string
427	if runtime.GOOS == "darwin" {
428		srcs = append(g.properties.Srcs, g.properties.Darwin.Srcs...)
429		testSrcs = append(g.properties.TestSrcs, g.properties.Darwin.TestSrcs...)
430	} else if runtime.GOOS == "linux" {
431		srcs = append(g.properties.Srcs, g.properties.Linux.Srcs...)
432		testSrcs = append(g.properties.TestSrcs, g.properties.Linux.TestSrcs...)
433	}
434
435	if g.config.runGoTests {
436		deps = buildGoTest(ctx, testRoot(ctx), testArchiveFile,
437			name, srcs, genSrcs, testSrcs)
438	}
439
440	buildGoPackage(ctx, objDir, name, archiveFile, srcs, genSrcs)
441
442	var linkDeps []string
443	var libDirFlags []string
444	ctx.VisitDepsDepthFirstIf(isGoPackageProducer,
445		func(module blueprint.Module) {
446			dep := module.(goPackageProducer)
447			linkDeps = append(linkDeps, dep.GoPackageTarget())
448			libDir := dep.GoPkgRoot()
449			libDirFlags = append(libDirFlags, "-L "+libDir)
450			deps = append(deps, dep.GoTestTargets()...)
451		})
452
453	linkArgs := map[string]string{}
454	if len(libDirFlags) > 0 {
455		linkArgs["libDirFlags"] = strings.Join(libDirFlags, " ")
456	}
457
458	ctx.Build(pctx, blueprint.BuildParams{
459		Rule:      link,
460		Outputs:   []string{aoutFile},
461		Inputs:    []string{archiveFile},
462		Implicits: linkDeps,
463		Args:      linkArgs,
464		Optional:  true,
465	})
466
467	ctx.Build(pctx, blueprint.BuildParams{
468		Rule:      cp,
469		Outputs:   []string{g.installPath},
470		Inputs:    []string{aoutFile},
471		OrderOnly: deps,
472		Optional:  !g.properties.Default,
473	})
474}
475
476func buildGoPluginLoader(ctx blueprint.ModuleContext, pkgPath, pluginSrc string) bool {
477	ret := true
478	name := ctx.ModuleName()
479
480	var pluginPaths []string
481	ctx.VisitDepsDepthFirstIf(isGoPluginFor(name),
482		func(module blueprint.Module) {
483			plugin := module.(goPluginProvider)
484			pluginPaths = append(pluginPaths, plugin.GoPkgPath())
485		})
486
487	ctx.Build(pctx, blueprint.BuildParams{
488		Rule:    pluginGenSrc,
489		Outputs: []string{pluginSrc},
490		Args: map[string]string{
491			"pkg":     pkgPath,
492			"plugins": strings.Join(pluginPaths, " "),
493		},
494		Optional: true,
495	})
496
497	return ret
498}
499
500func buildGoPackage(ctx blueprint.ModuleContext, pkgRoot string,
501	pkgPath string, archiveFile string, srcs []string, genSrcs []string) {
502
503	srcDir := moduleSrcDir(ctx)
504	srcFiles := pathtools.PrefixPaths(srcs, srcDir)
505	srcFiles = append(srcFiles, genSrcs...)
506
507	var incFlags []string
508	var deps []string
509	ctx.VisitDepsDepthFirstIf(isGoPackageProducer,
510		func(module blueprint.Module) {
511			dep := module.(goPackageProducer)
512			incDir := dep.GoPkgRoot()
513			target := dep.GoPackageTarget()
514			incFlags = append(incFlags, "-I "+incDir)
515			deps = append(deps, target)
516		})
517
518	compileArgs := map[string]string{
519		"pkgPath": pkgPath,
520	}
521
522	if len(incFlags) > 0 {
523		compileArgs["incFlags"] = strings.Join(incFlags, " ")
524	}
525
526	ctx.Build(pctx, blueprint.BuildParams{
527		Rule:      compile,
528		Outputs:   []string{archiveFile},
529		Inputs:    srcFiles,
530		Implicits: deps,
531		Args:      compileArgs,
532		Optional:  true,
533	})
534}
535
536func buildGoTest(ctx blueprint.ModuleContext, testRoot, testPkgArchive,
537	pkgPath string, srcs, genSrcs, testSrcs []string) []string {
538
539	if len(testSrcs) == 0 {
540		return nil
541	}
542
543	srcDir := moduleSrcDir(ctx)
544	testFiles := pathtools.PrefixPaths(testSrcs, srcDir)
545
546	mainFile := filepath.Join(testRoot, "test.go")
547	testArchive := filepath.Join(testRoot, "test.a")
548	testFile := filepath.Join(testRoot, "test")
549	testPassed := filepath.Join(testRoot, "test.passed")
550
551	buildGoPackage(ctx, testRoot, pkgPath, testPkgArchive,
552		append(srcs, testSrcs...), genSrcs)
553
554	ctx.Build(pctx, blueprint.BuildParams{
555		Rule:    goTestMain,
556		Outputs: []string{mainFile},
557		Inputs:  testFiles,
558		Args: map[string]string{
559			"pkg": pkgPath,
560		},
561		Optional: true,
562	})
563
564	linkDeps := []string{testPkgArchive}
565	libDirFlags := []string{"-L " + testRoot}
566	testDeps := []string{}
567	ctx.VisitDepsDepthFirstIf(isGoPackageProducer,
568		func(module blueprint.Module) {
569			dep := module.(goPackageProducer)
570			linkDeps = append(linkDeps, dep.GoPackageTarget())
571			libDir := dep.GoPkgRoot()
572			libDirFlags = append(libDirFlags, "-L "+libDir)
573			testDeps = append(testDeps, dep.GoTestTargets()...)
574		})
575
576	ctx.Build(pctx, blueprint.BuildParams{
577		Rule:      compile,
578		Outputs:   []string{testArchive},
579		Inputs:    []string{mainFile},
580		Implicits: []string{testPkgArchive},
581		Args: map[string]string{
582			"pkgPath":  "main",
583			"incFlags": "-I " + testRoot,
584		},
585		Optional: true,
586	})
587
588	ctx.Build(pctx, blueprint.BuildParams{
589		Rule:      link,
590		Outputs:   []string{testFile},
591		Inputs:    []string{testArchive},
592		Implicits: linkDeps,
593		Args: map[string]string{
594			"libDirFlags": strings.Join(libDirFlags, " "),
595		},
596		Optional: true,
597	})
598
599	ctx.Build(pctx, blueprint.BuildParams{
600		Rule:      test,
601		Outputs:   []string{testPassed},
602		Inputs:    []string{testFile},
603		OrderOnly: testDeps,
604		Args: map[string]string{
605			"pkg":       pkgPath,
606			"pkgSrcDir": filepath.Dir(testFiles[0]),
607		},
608		Optional: true,
609	})
610
611	return []string{testPassed}
612}
613
614type singleton struct {
615	// The bootstrap Config
616	config *Config
617}
618
619func newSingletonFactory(config *Config) func() blueprint.Singleton {
620	return func() blueprint.Singleton {
621		return &singleton{
622			config: config,
623		}
624	}
625}
626
627func (s *singleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
628	// Find the module that's marked as the "primary builder", which means it's
629	// creating the binary that we'll use to generate the non-bootstrap
630	// build.ninja file.
631	var primaryBuilders []*goBinary
632	// blueprintTools contains blueprint go binaries that will be built in StageMain
633	var blueprintTools []string
634	ctx.VisitAllModulesIf(isBootstrapBinaryModule,
635		func(module blueprint.Module) {
636			binaryModule := module.(*goBinary)
637
638			if binaryModule.properties.Tool_dir {
639				blueprintTools = append(blueprintTools, binaryModule.InstallPath())
640			}
641			if binaryModule.properties.PrimaryBuilder {
642				primaryBuilders = append(primaryBuilders, binaryModule)
643			}
644		})
645
646	var extraSharedFlagArray []string
647	if s.config.runGoTests {
648		extraSharedFlagArray = append(extraSharedFlagArray, "-t")
649	}
650	if s.config.moduleListFile != "" {
651		extraSharedFlagArray = append(extraSharedFlagArray, "-l", s.config.moduleListFile)
652	}
653	if s.config.emptyNinjaFile {
654		extraSharedFlagArray = append(extraSharedFlagArray, "--empty-ninja-file")
655	}
656	extraSharedFlagString := strings.Join(extraSharedFlagArray, " ")
657
658	var primaryBuilderName, primaryBuilderExtraFlags string
659	switch len(primaryBuilders) {
660	case 0:
661		// If there's no primary builder module then that means we'll use minibp
662		// as the primary builder.  We can trigger its primary builder mode with
663		// the -p flag.
664		primaryBuilderName = "minibp"
665		primaryBuilderExtraFlags = "-p " + extraSharedFlagString
666
667	case 1:
668		primaryBuilderName = ctx.ModuleName(primaryBuilders[0])
669		primaryBuilderExtraFlags = extraSharedFlagString
670
671	default:
672		ctx.Errorf("multiple primary builder modules present:")
673		for _, primaryBuilder := range primaryBuilders {
674			ctx.ModuleErrorf(primaryBuilder, "<-- module %s",
675				ctx.ModuleName(primaryBuilder))
676		}
677		return
678	}
679
680	primaryBuilderFile := filepath.Join("$BinDir", primaryBuilderName)
681
682	// Get the filename of the top-level Blueprints file to pass to minibp.
683	topLevelBlueprints := filepath.Join("$srcDir",
684		filepath.Base(s.config.topLevelBlueprintsFile))
685
686	ctx.SetNinjaBuildDir(pctx, "${ninjaBuildDir}")
687
688	if s.config.stage == StagePrimary {
689		mainNinjaFile := filepath.Join("$buildDir", "build.ninja")
690		primaryBuilderNinjaGlobFile := filepath.Join(BuildDir, bootstrapSubDir, "build-globs.ninja")
691
692		if _, err := os.Stat(primaryBuilderNinjaGlobFile); os.IsNotExist(err) {
693			err = ioutil.WriteFile(primaryBuilderNinjaGlobFile, nil, 0666)
694			if err != nil {
695				ctx.Errorf("Failed to create empty ninja file: %s", err)
696			}
697		}
698
699		ctx.AddSubninja(primaryBuilderNinjaGlobFile)
700
701		// Build the main build.ninja
702		ctx.Build(pctx, blueprint.BuildParams{
703			Rule:    generateBuildNinja,
704			Outputs: []string{mainNinjaFile},
705			Inputs:  []string{topLevelBlueprints},
706			Args: map[string]string{
707				"builder":  primaryBuilderFile,
708				"extra":    primaryBuilderExtraFlags,
709				"globFile": primaryBuilderNinjaGlobFile,
710			},
711		})
712	}
713
714	if s.config.stage == StageMain {
715		if primaryBuilderName == "minibp" {
716			// This is a standalone Blueprint build, so we copy the minibp
717			// binary to the "bin" directory to make it easier to find.
718			finalMinibp := filepath.Join("$buildDir", "bin", primaryBuilderName)
719			ctx.Build(pctx, blueprint.BuildParams{
720				Rule:    cp,
721				Inputs:  []string{primaryBuilderFile},
722				Outputs: []string{finalMinibp},
723			})
724		}
725
726		// Generate build system docs for the primary builder.  Generating docs reads the source
727		// files used to build the primary builder, but that dependency will be picked up through
728		// the dependency on the primary builder itself.  There are no dependencies on the
729		// Blueprints files, as any relevant changes to the Blueprints files would have caused
730		// a rebuild of the primary builder.
731		docsFile := filepath.Join(docsDir, primaryBuilderName+".html")
732		bigbpDocs := ctx.Rule(pctx, "bigbpDocs",
733			blueprint.RuleParams{
734				Command: fmt.Sprintf("%s %s -b $buildDir --docs $out %s", primaryBuilderFile,
735					primaryBuilderExtraFlags, topLevelBlueprints),
736				CommandDeps: []string{primaryBuilderFile},
737				Description: fmt.Sprintf("%s docs $out", primaryBuilderName),
738			})
739
740		ctx.Build(pctx, blueprint.BuildParams{
741			Rule:    bigbpDocs,
742			Outputs: []string{docsFile},
743		})
744
745		// Add a phony target for building the documentation
746		ctx.Build(pctx, blueprint.BuildParams{
747			Rule:    blueprint.Phony,
748			Outputs: []string{"blueprint_docs"},
749			Inputs:  []string{docsFile},
750		})
751
752		// Add a phony target for building various tools that are part of blueprint
753		ctx.Build(pctx, blueprint.BuildParams{
754			Rule:    blueprint.Phony,
755			Outputs: []string{"blueprint_tools"},
756			Inputs:  blueprintTools,
757		})
758	}
759}
760
761// packageRoot returns the module-specific package root directory path.  This
762// directory is where the final package .a files are output and where dependant
763// modules search for this package via -I arguments.
764func packageRoot(ctx blueprint.ModuleContext) string {
765	return filepath.Join(bootstrapDir, ctx.ModuleName(), "pkg")
766}
767
768// testRoot returns the module-specific package root directory path used for
769// building tests. The .a files generated here will include everything from
770// packageRoot, plus the test-only code.
771func testRoot(ctx blueprint.ModuleContext) string {
772	return filepath.Join(bootstrapDir, ctx.ModuleName(), "test")
773}
774
775// moduleSrcDir returns the path of the directory that all source file paths are
776// specified relative to.
777func moduleSrcDir(ctx blueprint.ModuleContext) string {
778	return filepath.Join("$srcDir", ctx.ModuleDir())
779}
780
781// moduleObjDir returns the module-specific object directory path.
782func moduleObjDir(ctx blueprint.ModuleContext) string {
783	return filepath.Join(bootstrapDir, ctx.ModuleName(), "obj")
784}
785
786// moduleGenSrcDir returns the module-specific generated sources path.
787func moduleGenSrcDir(ctx blueprint.ModuleContext) string {
788	return filepath.Join(bootstrapDir, ctx.ModuleName(), "gen")
789}
790