github.com/westcoastroms/westcoastroms-build@v0.0.0-20190928114312-2350e5a73030/build/blueprint/bootstrap/bootstrap.go (about)

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