1// Copyright 2014 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package bootstrap 16 17import ( 18 "encoding/json" 19 "fmt" 20 "path/filepath" 21 "runtime" 22 "strings" 23 24 "github.com/google/blueprint" 25 "github.com/google/blueprint/pathtools" 26 "github.com/google/blueprint/proptools" 27) 28 29var ( 30 pctx = blueprint.NewPackageContext("github.com/google/blueprint/bootstrap") 31 32 goTestMainCmd = pctx.StaticVariable("goTestMainCmd", filepath.Join("$ToolDir", "gotestmain")) 33 goTestRunnerCmd = pctx.StaticVariable("goTestRunnerCmd", filepath.Join("$ToolDir", "gotestrunner")) 34 pluginGenSrcCmd = pctx.StaticVariable("pluginGenSrcCmd", filepath.Join("$ToolDir", "loadplugins")) 35 36 parallelCompile = pctx.StaticVariable("parallelCompile", func() string { 37 numCpu := runtime.NumCPU() 38 // This will cause us to recompile all go programs if the 39 // number of cpus changes. We don't get a lot of benefit from 40 // higher values, so cap this to make it cheaper to move trees 41 // between machines. 42 if numCpu > 8 { 43 numCpu = 8 44 } 45 return fmt.Sprintf("-c %d", numCpu) 46 }()) 47 48 compile = pctx.StaticRule("compile", 49 blueprint.RuleParams{ 50 Command: "GOROOT='$goRoot' $compileCmd $parallelCompile -o $out.tmp " + 51 "$debugFlags -p $pkgPath -complete $incFlags $embedFlags -pack $in && " + 52 "if cmp --quiet $out.tmp $out; then rm $out.tmp; else mv -f $out.tmp $out; fi", 53 CommandDeps: []string{"$compileCmd"}, 54 Description: "compile $out", 55 Restat: true, 56 }, 57 "pkgPath", "incFlags", "embedFlags") 58 59 link = pctx.StaticRule("link", 60 blueprint.RuleParams{ 61 Command: "GOROOT='$goRoot' $linkCmd -o $out.tmp $libDirFlags $in && " + 62 "if cmp --quiet $out.tmp $out; then rm $out.tmp; else mv -f $out.tmp $out; fi", 63 CommandDeps: []string{"$linkCmd"}, 64 Description: "link $out", 65 Restat: true, 66 }, 67 "libDirFlags") 68 69 goTestMain = pctx.StaticRule("gotestmain", 70 blueprint.RuleParams{ 71 Command: "$goTestMainCmd -o $out -pkg $pkg $in", 72 CommandDeps: []string{"$goTestMainCmd"}, 73 Description: "gotestmain $out", 74 }, 75 "pkg") 76 77 pluginGenSrc = pctx.StaticRule("pluginGenSrc", 78 blueprint.RuleParams{ 79 Command: "$pluginGenSrcCmd -o $out -p $pkg $plugins", 80 CommandDeps: []string{"$pluginGenSrcCmd"}, 81 Description: "create $out", 82 }, 83 "pkg", "plugins") 84 85 test = pctx.StaticRule("test", 86 blueprint.RuleParams{ 87 Command: "$goTestRunnerCmd -p $pkgSrcDir -f $out -- $in -test.short", 88 CommandDeps: []string{"$goTestRunnerCmd"}, 89 Description: "test $pkg", 90 }, 91 "pkg", "pkgSrcDir") 92 93 cp = pctx.StaticRule("cp", 94 blueprint.RuleParams{ 95 Command: "cp $in $out", 96 Description: "cp $out", 97 }, 98 "generator") 99 100 touch = pctx.StaticRule("touch", 101 blueprint.RuleParams{ 102 Command: "touch $out", 103 Description: "touch $out", 104 }, 105 "depfile", "generator") 106 107 cat = pctx.StaticRule("Cat", 108 blueprint.RuleParams{ 109 Command: "rm -f $out && cat $in > $out", 110 Description: "concatenate files to $out", 111 }) 112 113 // ubuntu 14.04 offcially use dash for /bin/sh, and its builtin echo command 114 // doesn't support -e option. Therefore we force to use /bin/bash when writing out 115 // content to file. 116 writeFile = pctx.StaticRule("writeFile", 117 blueprint.RuleParams{ 118 Command: `rm -f $out && /bin/bash -c 'echo -e -n "$$0" > $out' $content`, 119 Description: "writing file $out", 120 }, 121 "content") 122 123 generateBuildNinja = pctx.StaticRule("build.ninja", 124 blueprint.RuleParams{ 125 // TODO: it's kinda ugly that some parameters are computed from 126 // environment variables and some from Ninja parameters, but it's probably 127 // better to not to touch that while Blueprint and Soong are separate 128 // NOTE: The spaces at EOL are important because otherwise Ninja would 129 // omit all spaces between the different options. 130 Command: `cd "$$(dirname "$builder")" && ` + 131 `BUILDER="$$PWD/$$(basename "$builder")" && ` + 132 `cd / && ` + 133 `env -i $env "$$BUILDER" ` + 134 ` --top "$$TOP" ` + 135 ` --soong_out "$soongOutDir" ` + 136 ` --out "$outDir" ` + 137 ` $extra`, 138 CommandDeps: []string{"$builder"}, 139 Description: "$builder $out", 140 Deps: blueprint.DepsGCC, 141 Depfile: "$out.d", 142 Restat: true, 143 }, 144 "builder", "env", "extra", "pool") 145 146 // Work around a Ninja issue. See https://github.com/martine/ninja/pull/634 147 phony = pctx.StaticRule("phony", 148 blueprint.RuleParams{ 149 Command: "# phony $out", 150 Description: "phony $out", 151 Generator: true, 152 }, 153 "depfile") 154 155 _ = pctx.VariableFunc("ToolDir", func(ctx blueprint.VariableFuncContext, config interface{}) (string, error) { 156 return config.(BootstrapConfig).HostToolDir(), nil 157 }) 158) 159 160var ( 161 // echoEscaper escapes a string such that passing it to "echo -e" will produce the input value. 162 echoEscaper = strings.NewReplacer( 163 `\`, `\\`, // First escape existing backslashes so they aren't interpreted by `echo -e`. 164 "\n", `\n`, // Then replace newlines with \n 165 ) 166) 167 168// shardString takes a string and returns a slice of strings where the length of each one is 169// at most shardSize. 170func shardString(s string, shardSize int) []string { 171 if len(s) == 0 { 172 return nil 173 } 174 ret := make([]string, 0, (len(s)+shardSize-1)/shardSize) 175 for len(s) > shardSize { 176 ret = append(ret, s[0:shardSize]) 177 s = s[shardSize:] 178 } 179 if len(s) > 0 { 180 ret = append(ret, s) 181 } 182 return ret 183} 184 185// writeFileRule creates a ninja rule to write contents to a file. The contents will be 186// escaped so that the file contains exactly the contents passed to the function. 187func writeFileRule(ctx blueprint.ModuleContext, outputFile string, content string) { 188 // This is MAX_ARG_STRLEN subtracted with some safety to account for shell escapes 189 const SHARD_SIZE = 131072 - 10000 190 191 buildWriteFileRule := func(outputFile string, content string) { 192 content = echoEscaper.Replace(content) 193 content = proptools.NinjaEscape(proptools.ShellEscapeIncludingSpaces(content)) 194 if content == "" { 195 content = "''" 196 } 197 ctx.Build(pctx, blueprint.BuildParams{ 198 Rule: writeFile, 199 Outputs: []string{outputFile}, 200 Description: "write " + outputFile, 201 Args: map[string]string{ 202 "content": content, 203 }, 204 }) 205 } 206 207 if len(content) > SHARD_SIZE { 208 var chunks []string 209 for i, c := range shardString(content, SHARD_SIZE) { 210 tempPath := fmt.Sprintf("%s.%d", outputFile, i) 211 buildWriteFileRule(tempPath, c) 212 chunks = append(chunks, tempPath) 213 } 214 ctx.Build(pctx, blueprint.BuildParams{ 215 Rule: cat, 216 Inputs: chunks, 217 Outputs: []string{outputFile}, 218 Description: "Merging to " + outputFile, 219 }) 220 return 221 } 222 buildWriteFileRule(outputFile, content) 223} 224 225type pluginDependencyTag struct { 226 blueprint.BaseDependencyTag 227} 228 229type bootstrapDependencies interface { 230 bootstrapDeps(ctx blueprint.BottomUpMutatorContext) 231} 232 233var pluginDepTag = pluginDependencyTag{} 234 235func BootstrapDeps(ctx blueprint.BottomUpMutatorContext) { 236 if pkg, ok := ctx.Module().(bootstrapDependencies); ok { 237 pkg.bootstrapDeps(ctx) 238 } 239} 240 241type PackageInfo struct { 242 PkgPath string 243 PkgRoot string 244 PackageTarget string 245 TestTargets []string 246} 247 248var PackageProvider = blueprint.NewProvider[*PackageInfo]() 249 250type BinaryInfo struct { 251 IntermediatePath string 252 InstallPath string 253 TestTargets []string 254} 255 256var BinaryProvider = blueprint.NewProvider[*BinaryInfo]() 257 258type DocsPackageInfo struct { 259 PkgPath string 260 Srcs []string 261} 262 263var DocsPackageProvider = blueprint.NewMutatorProvider[*DocsPackageInfo]("bootstrap_deps") 264 265// A GoPackage is a module for building Go packages. 266type GoPackage struct { 267 blueprint.SimpleName 268 properties struct { 269 Deps []string 270 PkgPath string 271 Srcs []string 272 TestSrcs []string 273 TestData []string 274 PluginFor []string 275 EmbedSrcs []string 276 // The visibility property is unused in blueprint, but exists so that soong 277 // can add one and not have the bp files fail to parse during the bootstrap build. 278 Visibility []string 279 280 Darwin struct { 281 Srcs []string 282 TestSrcs []string 283 } 284 Linux struct { 285 Srcs []string 286 TestSrcs []string 287 } 288 } 289} 290 291func newGoPackageModuleFactory() func() (blueprint.Module, []interface{}) { 292 return func() (blueprint.Module, []interface{}) { 293 module := &GoPackage{} 294 return module, []interface{}{&module.properties, &module.SimpleName.Properties} 295 } 296} 297 298// Properties returns the list of property structs to be used for registering a wrapped module type. 299func (g *GoPackage) Properties() []interface{} { 300 return []interface{}{&g.properties} 301} 302 303func (g *GoPackage) DynamicDependencies(ctx blueprint.DynamicDependerModuleContext) []string { 304 return g.properties.Deps 305} 306 307func (g *GoPackage) bootstrapDeps(ctx blueprint.BottomUpMutatorContext) { 308 for _, plugin := range g.properties.PluginFor { 309 ctx.AddReverseDependency(ctx.Module(), pluginDepTag, plugin) 310 } 311 blueprint.SetProvider(ctx, DocsPackageProvider, &DocsPackageInfo{ 312 PkgPath: g.properties.PkgPath, 313 Srcs: g.properties.Srcs, 314 }) 315} 316 317func (g *GoPackage) GenerateBuildActions(ctx blueprint.ModuleContext) { 318 var ( 319 name = ctx.ModuleName() 320 hasPlugins = false 321 pluginSrc = "" 322 genSrcs = []string{} 323 ) 324 325 if g.properties.PkgPath == "" { 326 ctx.ModuleErrorf("module %s did not specify a valid pkgPath", name) 327 return 328 } 329 330 pkgRoot := packageRoot(ctx) 331 archiveFile := filepath.Join(pkgRoot, 332 filepath.FromSlash(g.properties.PkgPath)+".a") 333 334 ctx.VisitDepsDepthFirst(func(module blueprint.Module) { 335 if ctx.OtherModuleDependencyTag(module) == pluginDepTag { 336 hasPlugins = true 337 } 338 }) 339 if hasPlugins { 340 pluginSrc = filepath.Join(moduleGenSrcDir(ctx), "plugin.go") 341 genSrcs = append(genSrcs, pluginSrc) 342 } 343 344 if hasPlugins && !buildGoPluginLoader(ctx, g.properties.PkgPath, pluginSrc) { 345 return 346 } 347 348 var srcs, testSrcs []string 349 if runtime.GOOS == "darwin" { 350 srcs = append(g.properties.Srcs, g.properties.Darwin.Srcs...) 351 testSrcs = append(g.properties.TestSrcs, g.properties.Darwin.TestSrcs...) 352 } else if runtime.GOOS == "linux" { 353 srcs = append(g.properties.Srcs, g.properties.Linux.Srcs...) 354 testSrcs = append(g.properties.TestSrcs, g.properties.Linux.TestSrcs...) 355 } 356 357 testArchiveFile := filepath.Join(testRoot(ctx), 358 filepath.FromSlash(g.properties.PkgPath)+".a") 359 testResultFile := buildGoTest(ctx, testRoot(ctx), testArchiveFile, 360 g.properties.PkgPath, srcs, genSrcs, testSrcs, g.properties.EmbedSrcs) 361 362 // Don't build for test-only packages 363 if len(srcs) == 0 && len(genSrcs) == 0 { 364 ctx.Build(pctx, blueprint.BuildParams{ 365 Rule: touch, 366 Outputs: []string{archiveFile}, 367 }) 368 return 369 } 370 371 buildGoPackage(ctx, pkgRoot, g.properties.PkgPath, archiveFile, 372 srcs, genSrcs, g.properties.EmbedSrcs) 373 blueprint.SetProvider(ctx, PackageProvider, &PackageInfo{ 374 PkgPath: g.properties.PkgPath, 375 PkgRoot: pkgRoot, 376 PackageTarget: archiveFile, 377 TestTargets: testResultFile, 378 }) 379} 380 381// A GoBinary is a module for building executable binaries from Go sources. 382type GoBinary struct { 383 blueprint.SimpleName 384 properties struct { 385 Deps []string 386 Srcs []string 387 TestSrcs []string 388 TestData []string 389 EmbedSrcs []string 390 PrimaryBuilder bool 391 Default bool 392 // The visibility property is unused in blueprint, but exists so that soong 393 // can add one and not have the bp files fail to parse during the bootstrap build. 394 Visibility []string 395 396 Darwin struct { 397 Srcs []string 398 TestSrcs []string 399 } 400 Linux struct { 401 Srcs []string 402 TestSrcs []string 403 } 404 } 405 406 installPath string 407 408 // skipInstall can be set to true by a module type that wraps GoBinary to skip the install rule, 409 // allowing the wrapping module type to create the install rule itself. 410 skipInstall bool 411 412 // outputFile is set to the path to the intermediate output file. 413 outputFile string 414} 415 416func newGoBinaryModuleFactory() func() (blueprint.Module, []interface{}) { 417 return func() (blueprint.Module, []interface{}) { 418 module := &GoBinary{} 419 return module, []interface{}{&module.properties, &module.SimpleName.Properties} 420 } 421} 422 423func (g *GoBinary) DynamicDependencies(ctx blueprint.DynamicDependerModuleContext) []string { 424 return g.properties.Deps 425} 426 427func (g *GoBinary) bootstrapDeps(ctx blueprint.BottomUpMutatorContext) { 428 if g.properties.PrimaryBuilder { 429 blueprint.SetProvider(ctx, PrimaryBuilderProvider, PrimaryBuilderInfo{}) 430 } 431} 432 433// IntermediateFile returns the path to the final linked intermedate file. 434func (g *GoBinary) IntermediateFile() string { 435 return g.outputFile 436} 437 438// SetSkipInstall is called by module types that wrap GoBinary to skip the install rule, 439// allowing the wrapping module type to create the install rule itself. 440func (g *GoBinary) SetSkipInstall() { 441 g.skipInstall = true 442} 443 444// Properties returns the list of property structs to be used for registering a wrapped module type. 445func (g *GoBinary) Properties() []interface{} { 446 return []interface{}{&g.properties} 447} 448 449func (g *GoBinary) GenerateBuildActions(ctx blueprint.ModuleContext) { 450 var ( 451 name = ctx.ModuleName() 452 objDir = moduleObjDir(ctx) 453 archiveFile = filepath.Join(objDir, name+".a") 454 testArchiveFile = filepath.Join(testRoot(ctx), name+".a") 455 aoutFile = filepath.Join(objDir, name) 456 hasPlugins = false 457 pluginSrc = "" 458 genSrcs = []string{} 459 ) 460 461 if !g.skipInstall { 462 g.installPath = filepath.Join(ctx.Config().(BootstrapConfig).HostToolDir(), name) 463 } 464 465 ctx.VisitDirectDeps(func(module blueprint.Module) { 466 if ctx.OtherModuleDependencyTag(module) == pluginDepTag { 467 hasPlugins = true 468 } 469 }) 470 if hasPlugins { 471 pluginSrc = filepath.Join(moduleGenSrcDir(ctx), "plugin.go") 472 genSrcs = append(genSrcs, pluginSrc) 473 } 474 475 var testDeps []string 476 477 if hasPlugins && !buildGoPluginLoader(ctx, "main", pluginSrc) { 478 return 479 } 480 481 var srcs, testSrcs []string 482 if runtime.GOOS == "darwin" { 483 srcs = append(g.properties.Srcs, g.properties.Darwin.Srcs...) 484 testSrcs = append(g.properties.TestSrcs, g.properties.Darwin.TestSrcs...) 485 } else if runtime.GOOS == "linux" { 486 srcs = append(g.properties.Srcs, g.properties.Linux.Srcs...) 487 testSrcs = append(g.properties.TestSrcs, g.properties.Linux.TestSrcs...) 488 } 489 490 testResultFile := buildGoTest(ctx, testRoot(ctx), testArchiveFile, 491 name, srcs, genSrcs, testSrcs, g.properties.EmbedSrcs) 492 testDeps = append(testDeps, testResultFile...) 493 494 buildGoPackage(ctx, objDir, "main", archiveFile, srcs, genSrcs, g.properties.EmbedSrcs) 495 496 var linkDeps []string 497 var libDirFlags []string 498 ctx.VisitDepsDepthFirst(func(module blueprint.Module) { 499 if info, ok := blueprint.OtherModuleProvider(ctx, module, PackageProvider); ok { 500 linkDeps = append(linkDeps, info.PackageTarget) 501 libDir := info.PkgRoot 502 libDirFlags = append(libDirFlags, "-L "+libDir) 503 testDeps = append(testDeps, info.TestTargets...) 504 } 505 }) 506 507 linkArgs := map[string]string{} 508 if len(libDirFlags) > 0 { 509 linkArgs["libDirFlags"] = strings.Join(libDirFlags, " ") 510 } 511 512 ctx.Build(pctx, blueprint.BuildParams{ 513 Rule: link, 514 Outputs: []string{aoutFile}, 515 Inputs: []string{archiveFile}, 516 Implicits: linkDeps, 517 Args: linkArgs, 518 }) 519 520 g.outputFile = aoutFile 521 522 var validations []string 523 if ctx.Config().(BootstrapConfig).RunGoTests() { 524 validations = testDeps 525 } 526 527 if !g.skipInstall { 528 ctx.Build(pctx, blueprint.BuildParams{ 529 Rule: cp, 530 Outputs: []string{g.installPath}, 531 Inputs: []string{aoutFile}, 532 Validations: validations, 533 Default: g.properties.Default, 534 }) 535 } 536 537 blueprint.SetProvider(ctx, BinaryProvider, &BinaryInfo{ 538 IntermediatePath: g.outputFile, 539 InstallPath: g.installPath, 540 TestTargets: testResultFile, 541 }) 542} 543 544func buildGoPluginLoader(ctx blueprint.ModuleContext, pkgPath, pluginSrc string) bool { 545 ret := true 546 547 var pluginPaths []string 548 ctx.VisitDirectDeps(func(module blueprint.Module) { 549 if ctx.OtherModuleDependencyTag(module) == pluginDepTag { 550 if info, ok := blueprint.OtherModuleProvider(ctx, module, PackageProvider); ok { 551 pluginPaths = append(pluginPaths, info.PkgPath) 552 } 553 } 554 }) 555 556 ctx.Build(pctx, blueprint.BuildParams{ 557 Rule: pluginGenSrc, 558 Outputs: []string{pluginSrc}, 559 Args: map[string]string{ 560 "pkg": pkgPath, 561 "plugins": strings.Join(pluginPaths, " "), 562 }, 563 }) 564 565 return ret 566} 567 568func generateEmbedcfgFile(ctx blueprint.ModuleContext, files []string, srcDir string, embedcfgFile string) { 569 embedcfg := struct { 570 Patterns map[string][]string 571 Files map[string]string 572 }{ 573 make(map[string][]string, len(files)), 574 make(map[string]string, len(files)), 575 } 576 577 for _, file := range files { 578 embedcfg.Patterns[file] = []string{file} 579 embedcfg.Files[file] = filepath.Join(srcDir, file) 580 } 581 582 embedcfgData, err := json.Marshal(&embedcfg) 583 if err != nil { 584 ctx.ModuleErrorf("Failed to marshal embedcfg data: %s", err.Error()) 585 } 586 587 writeFileRule(ctx, embedcfgFile, string(embedcfgData)) 588} 589 590func buildGoPackage(ctx blueprint.ModuleContext, pkgRoot string, 591 pkgPath string, archiveFile string, srcs []string, genSrcs []string, embedSrcs []string) { 592 593 srcDir := moduleSrcDir(ctx) 594 srcFiles := pathtools.PrefixPaths(srcs, srcDir) 595 srcFiles = append(srcFiles, genSrcs...) 596 597 var incFlags []string 598 var deps []string 599 ctx.VisitDepsDepthFirst(func(module blueprint.Module) { 600 if info, ok := blueprint.OtherModuleProvider(ctx, module, PackageProvider); ok { 601 incDir := info.PkgRoot 602 target := info.PackageTarget 603 incFlags = append(incFlags, "-I "+incDir) 604 deps = append(deps, target) 605 } 606 }) 607 608 compileArgs := map[string]string{ 609 "pkgPath": pkgPath, 610 } 611 612 if len(incFlags) > 0 { 613 compileArgs["incFlags"] = strings.Join(incFlags, " ") 614 } 615 616 if len(embedSrcs) > 0 { 617 embedcfgFile := archiveFile + ".embedcfg" 618 generateEmbedcfgFile(ctx, embedSrcs, srcDir, embedcfgFile) 619 compileArgs["embedFlags"] = "-embedcfg " + embedcfgFile 620 deps = append(deps, embedcfgFile) 621 } 622 623 ctx.Build(pctx, blueprint.BuildParams{ 624 Rule: compile, 625 Outputs: []string{archiveFile}, 626 Inputs: srcFiles, 627 Implicits: deps, 628 Args: compileArgs, 629 }) 630} 631 632func buildGoTest(ctx blueprint.ModuleContext, testRoot, testPkgArchive, 633 pkgPath string, srcs, genSrcs, testSrcs []string, embedSrcs []string) []string { 634 635 if len(testSrcs) == 0 { 636 return nil 637 } 638 639 srcDir := moduleSrcDir(ctx) 640 testFiles := pathtools.PrefixPaths(testSrcs, srcDir) 641 642 mainFile := filepath.Join(testRoot, "test.go") 643 testArchive := filepath.Join(testRoot, "test.a") 644 testFile := filepath.Join(testRoot, "test") 645 testPassed := filepath.Join(testRoot, "test.passed") 646 647 buildGoPackage(ctx, testRoot, pkgPath, testPkgArchive, 648 append(srcs, testSrcs...), genSrcs, embedSrcs) 649 650 ctx.Build(pctx, blueprint.BuildParams{ 651 Rule: goTestMain, 652 Outputs: []string{mainFile}, 653 Inputs: testFiles, 654 Args: map[string]string{ 655 "pkg": pkgPath, 656 }, 657 }) 658 659 linkDeps := []string{testPkgArchive} 660 libDirFlags := []string{"-L " + testRoot} 661 testDeps := []string{} 662 ctx.VisitDepsDepthFirst(func(module blueprint.Module) { 663 if info, ok := blueprint.OtherModuleProvider(ctx, module, PackageProvider); ok { 664 linkDeps = append(linkDeps, info.PackageTarget) 665 libDir := info.PkgRoot 666 libDirFlags = append(libDirFlags, "-L "+libDir) 667 testDeps = append(testDeps, info.TestTargets...) 668 } 669 }) 670 671 ctx.Build(pctx, blueprint.BuildParams{ 672 Rule: compile, 673 Outputs: []string{testArchive}, 674 Inputs: []string{mainFile}, 675 Implicits: []string{testPkgArchive}, 676 Args: map[string]string{ 677 "pkgPath": "main", 678 "incFlags": "-I " + testRoot, 679 }, 680 }) 681 682 ctx.Build(pctx, blueprint.BuildParams{ 683 Rule: link, 684 Outputs: []string{testFile}, 685 Inputs: []string{testArchive}, 686 Implicits: linkDeps, 687 Args: map[string]string{ 688 "libDirFlags": strings.Join(libDirFlags, " "), 689 }, 690 }) 691 692 ctx.Build(pctx, blueprint.BuildParams{ 693 Rule: test, 694 Outputs: []string{testPassed}, 695 Inputs: []string{testFile}, 696 Validations: testDeps, 697 Args: map[string]string{ 698 "pkg": pkgPath, 699 "pkgSrcDir": filepath.Dir(testFiles[0]), 700 }, 701 }) 702 703 return []string{testPassed} 704} 705 706var PrimaryBuilderProvider = blueprint.NewMutatorProvider[PrimaryBuilderInfo]("bootstrap_deps") 707 708type PrimaryBuilderInfo struct{} 709 710type singleton struct { 711} 712 713func newSingletonFactory() func() blueprint.Singleton { 714 return func() blueprint.Singleton { 715 return &singleton{} 716 } 717} 718 719func (s *singleton) GenerateBuildActions(ctx blueprint.SingletonContext) { 720 // Find the module that's marked as the "primary builder", which means it's 721 // creating the binary that we'll use to generate the non-bootstrap 722 // build.ninja file. 723 var primaryBuilders []string 724 // blueprintTools contains blueprint go binaries that will be built in StageMain 725 var blueprintTools []string 726 // blueprintTools contains the test outputs of go tests that can be run in StageMain 727 var blueprintTests []string 728 // blueprintGoPackages contains all blueprint go packages that can be built in StageMain 729 var blueprintGoPackages []string 730 ctx.VisitAllModules(func(module blueprint.Module) { 731 if ctx.PrimaryModule(module) == module { 732 if binaryInfo, ok := blueprint.SingletonModuleProvider(ctx, module, BinaryProvider); ok { 733 if binaryInfo.InstallPath != "" { 734 blueprintTools = append(blueprintTools, binaryInfo.InstallPath) 735 } 736 blueprintTests = append(blueprintTests, binaryInfo.TestTargets...) 737 if _, ok := blueprint.SingletonModuleProvider(ctx, module, PrimaryBuilderProvider); ok { 738 primaryBuilders = append(primaryBuilders, binaryInfo.InstallPath) 739 } 740 } 741 742 if packageInfo, ok := blueprint.SingletonModuleProvider(ctx, module, PackageProvider); ok { 743 blueprintGoPackages = append(blueprintGoPackages, packageInfo.PackageTarget) 744 blueprintTests = append(blueprintTests, packageInfo.TestTargets...) 745 } 746 } 747 }) 748 749 var primaryBuilderCmdlinePrefix []string 750 var primaryBuilderFile string 751 752 if len(primaryBuilders) == 0 { 753 ctx.Errorf("no primary builder module present") 754 return 755 } else if len(primaryBuilders) > 1 { 756 ctx.Errorf("multiple primary builder modules present: %q", primaryBuilders) 757 return 758 } else { 759 primaryBuilderFile = primaryBuilders[0] 760 } 761 762 ctx.SetOutDir(pctx, "${outDir}") 763 764 for _, subninja := range ctx.Config().(BootstrapConfig).Subninjas() { 765 ctx.AddSubninja(subninja) 766 } 767 768 for _, i := range ctx.Config().(BootstrapConfig).PrimaryBuilderInvocations() { 769 flags := make([]string, 0) 770 flags = append(flags, primaryBuilderCmdlinePrefix...) 771 flags = append(flags, i.Args...) 772 773 pool := "" 774 if i.Console { 775 pool = "console" 776 } 777 778 envAssignments := "" 779 for k, v := range i.Env { 780 // NB: This is rife with quoting issues but we don't care because we trust 781 // soong_ui to not abuse this facility too much 782 envAssignments += k + "=" + v + " " 783 } 784 785 // Build the main build.ninja 786 ctx.Build(pctx, blueprint.BuildParams{ 787 Rule: generateBuildNinja, 788 Outputs: i.Outputs, 789 Inputs: i.Inputs, 790 Implicits: i.Implicits, 791 OrderOnly: i.OrderOnlyInputs, 792 Args: map[string]string{ 793 "builder": primaryBuilderFile, 794 "env": envAssignments, 795 "extra": strings.Join(flags, " "), 796 "pool": pool, 797 }, 798 Description: i.Description, 799 }) 800 } 801 802 // Add a phony target for building various tools that are part of blueprint 803 if len(blueprintTools) > 0 { 804 ctx.Build(pctx, blueprint.BuildParams{ 805 Rule: blueprint.Phony, 806 Outputs: []string{"blueprint_tools"}, 807 Inputs: blueprintTools, 808 Default: true, 809 }) 810 } 811 812 // Add a phony target for running various tests that are part of blueprint 813 ctx.Build(pctx, blueprint.BuildParams{ 814 Rule: blueprint.Phony, 815 Outputs: []string{"blueprint_tests"}, 816 Inputs: blueprintTests, 817 Default: true, 818 }) 819 820 // Add a phony target for running go tests 821 ctx.Build(pctx, blueprint.BuildParams{ 822 Rule: blueprint.Phony, 823 Outputs: []string{"blueprint_go_packages"}, 824 Inputs: blueprintGoPackages, 825 }) 826} 827 828// packageRoot returns the module-specific package root directory path. This 829// directory is where the final package .a files are output and where dependant 830// modules search for this package via -I arguments. 831func packageRoot(ctx blueprint.ModuleContext) string { 832 toolDir := ctx.Config().(BootstrapConfig).HostToolDir() 833 return filepath.Join(toolDir, "go", ctx.ModuleName(), ctx.ModuleSubDir(), "pkg") 834} 835 836// testRoot returns the module-specific package root directory path used for 837// building tests. The .a files generated here will include everything from 838// packageRoot, plus the test-only code. 839func testRoot(ctx blueprint.ModuleContext) string { 840 toolDir := ctx.Config().(BootstrapConfig).HostToolDir() 841 return filepath.Join(toolDir, "go", ctx.ModuleName(), ctx.ModuleSubDir(), "test") 842} 843 844// moduleSrcDir returns the path of the directory that all source file paths are 845// specified relative to. 846func moduleSrcDir(ctx blueprint.ModuleContext) string { 847 return ctx.ModuleDir() 848} 849 850// moduleObjDir returns the module-specific object directory path. 851func moduleObjDir(ctx blueprint.ModuleContext) string { 852 toolDir := ctx.Config().(BootstrapConfig).HostToolDir() 853 return filepath.Join(toolDir, "go", ctx.ModuleName(), ctx.ModuleSubDir(), "obj") 854} 855 856// moduleGenSrcDir returns the module-specific generated sources path. 857func moduleGenSrcDir(ctx blueprint.ModuleContext) string { 858 toolDir := ctx.Config().(BootstrapConfig).HostToolDir() 859 return filepath.Join(toolDir, "go", ctx.ModuleName(), ctx.ModuleSubDir(), "gen") 860} 861