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