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 Optional: true, 368 }) 369 return 370 } 371 372 buildGoPackage(ctx, pkgRoot, g.properties.PkgPath, archiveFile, 373 srcs, genSrcs, g.properties.EmbedSrcs) 374 blueprint.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: srcs}) 375 blueprint.SetProvider(ctx, PackageProvider, &PackageInfo{ 376 PkgPath: g.properties.PkgPath, 377 PkgRoot: pkgRoot, 378 PackageTarget: archiveFile, 379 TestTargets: testResultFile, 380 }) 381} 382 383// A GoBinary is a module for building executable binaries from Go sources. 384type GoBinary struct { 385 blueprint.SimpleName 386 properties struct { 387 Deps []string 388 Srcs []string 389 TestSrcs []string 390 TestData []string 391 EmbedSrcs []string 392 PrimaryBuilder bool 393 Default bool 394 // The visibility property is unused in blueprint, but exists so that soong 395 // can add one and not have the bp files fail to parse during the bootstrap build. 396 Visibility []string 397 398 Darwin struct { 399 Srcs []string 400 TestSrcs []string 401 } 402 Linux struct { 403 Srcs []string 404 TestSrcs []string 405 } 406 } 407 408 installPath string 409 410 // skipInstall can be set to true by a module type that wraps GoBinary to skip the install rule, 411 // allowing the wrapping module type to create the install rule itself. 412 skipInstall bool 413 414 // outputFile is set to the path to the intermediate output file. 415 outputFile string 416} 417 418func newGoBinaryModuleFactory() func() (blueprint.Module, []interface{}) { 419 return func() (blueprint.Module, []interface{}) { 420 module := &GoBinary{} 421 return module, []interface{}{&module.properties, &module.SimpleName.Properties} 422 } 423} 424 425func (g *GoBinary) DynamicDependencies(ctx blueprint.DynamicDependerModuleContext) []string { 426 return g.properties.Deps 427} 428 429func (g *GoBinary) bootstrapDeps(ctx blueprint.BottomUpMutatorContext) { 430 if g.properties.PrimaryBuilder { 431 blueprint.SetProvider(ctx, PrimaryBuilderProvider, PrimaryBuilderInfo{}) 432 } 433} 434 435// IntermediateFile returns the path to the final linked intermedate file. 436func (g *GoBinary) IntermediateFile() string { 437 return g.outputFile 438} 439 440// SetSkipInstall is called by module types that wrap GoBinary to skip the install rule, 441// allowing the wrapping module type to create the install rule itself. 442func (g *GoBinary) SetSkipInstall() { 443 g.skipInstall = true 444} 445 446// Properties returns the list of property structs to be used for registering a wrapped module type. 447func (g *GoBinary) Properties() []interface{} { 448 return []interface{}{&g.properties} 449} 450 451func (g *GoBinary) GenerateBuildActions(ctx blueprint.ModuleContext) { 452 var ( 453 name = ctx.ModuleName() 454 objDir = moduleObjDir(ctx) 455 archiveFile = filepath.Join(objDir, name+".a") 456 testArchiveFile = filepath.Join(testRoot(ctx), name+".a") 457 aoutFile = filepath.Join(objDir, name) 458 hasPlugins = false 459 pluginSrc = "" 460 genSrcs = []string{} 461 ) 462 463 if !g.skipInstall { 464 g.installPath = filepath.Join(ctx.Config().(BootstrapConfig).HostToolDir(), name) 465 } 466 467 ctx.VisitDirectDeps(func(module blueprint.Module) { 468 if ctx.OtherModuleDependencyTag(module) == pluginDepTag { 469 hasPlugins = true 470 } 471 }) 472 if hasPlugins { 473 pluginSrc = filepath.Join(moduleGenSrcDir(ctx), "plugin.go") 474 genSrcs = append(genSrcs, pluginSrc) 475 } 476 477 var testDeps []string 478 479 if hasPlugins && !buildGoPluginLoader(ctx, "main", pluginSrc) { 480 return 481 } 482 483 var srcs, testSrcs []string 484 if runtime.GOOS == "darwin" { 485 srcs = append(g.properties.Srcs, g.properties.Darwin.Srcs...) 486 testSrcs = append(g.properties.TestSrcs, g.properties.Darwin.TestSrcs...) 487 } else if runtime.GOOS == "linux" { 488 srcs = append(g.properties.Srcs, g.properties.Linux.Srcs...) 489 testSrcs = append(g.properties.TestSrcs, g.properties.Linux.TestSrcs...) 490 } 491 492 testResultFile := buildGoTest(ctx, testRoot(ctx), testArchiveFile, 493 name, srcs, genSrcs, testSrcs, g.properties.EmbedSrcs) 494 testDeps = append(testDeps, testResultFile...) 495 496 buildGoPackage(ctx, objDir, "main", archiveFile, srcs, genSrcs, g.properties.EmbedSrcs) 497 498 var linkDeps []string 499 var libDirFlags []string 500 ctx.VisitDepsDepthFirst(func(module blueprint.Module) { 501 if info, ok := blueprint.OtherModuleProvider(ctx, module, PackageProvider); ok { 502 linkDeps = append(linkDeps, info.PackageTarget) 503 libDir := info.PkgRoot 504 libDirFlags = append(libDirFlags, "-L "+libDir) 505 testDeps = append(testDeps, info.TestTargets...) 506 } 507 }) 508 509 linkArgs := map[string]string{} 510 if len(libDirFlags) > 0 { 511 linkArgs["libDirFlags"] = strings.Join(libDirFlags, " ") 512 } 513 514 ctx.Build(pctx, blueprint.BuildParams{ 515 Rule: link, 516 Outputs: []string{aoutFile}, 517 Inputs: []string{archiveFile}, 518 Implicits: linkDeps, 519 Args: linkArgs, 520 Optional: true, 521 }) 522 523 g.outputFile = aoutFile 524 525 var validations []string 526 if ctx.Config().(BootstrapConfig).RunGoTests() { 527 validations = testDeps 528 } 529 530 if !g.skipInstall { 531 ctx.Build(pctx, blueprint.BuildParams{ 532 Rule: cp, 533 Outputs: []string{g.installPath}, 534 Inputs: []string{aoutFile}, 535 Validations: validations, 536 Optional: !g.properties.Default, 537 }) 538 } 539 540 blueprint.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: srcs}) 541 blueprint.SetProvider(ctx, BinaryProvider, &BinaryInfo{ 542 IntermediatePath: g.outputFile, 543 InstallPath: g.installPath, 544 TestTargets: testResultFile, 545 }) 546} 547 548func buildGoPluginLoader(ctx blueprint.ModuleContext, pkgPath, pluginSrc string) bool { 549 ret := true 550 551 var pluginPaths []string 552 ctx.VisitDirectDeps(func(module blueprint.Module) { 553 if ctx.OtherModuleDependencyTag(module) == pluginDepTag { 554 if info, ok := blueprint.OtherModuleProvider(ctx, module, PackageProvider); ok { 555 pluginPaths = append(pluginPaths, info.PkgPath) 556 } 557 } 558 }) 559 560 ctx.Build(pctx, blueprint.BuildParams{ 561 Rule: pluginGenSrc, 562 Outputs: []string{pluginSrc}, 563 Args: map[string]string{ 564 "pkg": pkgPath, 565 "plugins": strings.Join(pluginPaths, " "), 566 }, 567 Optional: true, 568 }) 569 570 return ret 571} 572 573func generateEmbedcfgFile(ctx blueprint.ModuleContext, files []string, srcDir string, embedcfgFile string) { 574 embedcfg := struct { 575 Patterns map[string][]string 576 Files map[string]string 577 }{ 578 make(map[string][]string, len(files)), 579 make(map[string]string, len(files)), 580 } 581 582 for _, file := range files { 583 embedcfg.Patterns[file] = []string{file} 584 embedcfg.Files[file] = filepath.Join(srcDir, file) 585 } 586 587 embedcfgData, err := json.Marshal(&embedcfg) 588 if err != nil { 589 ctx.ModuleErrorf("Failed to marshal embedcfg data: %s", err.Error()) 590 } 591 592 writeFileRule(ctx, embedcfgFile, string(embedcfgData)) 593} 594 595func buildGoPackage(ctx blueprint.ModuleContext, pkgRoot string, 596 pkgPath string, archiveFile string, srcs []string, genSrcs []string, embedSrcs []string) { 597 598 srcDir := moduleSrcDir(ctx) 599 srcFiles := pathtools.PrefixPaths(srcs, srcDir) 600 srcFiles = append(srcFiles, genSrcs...) 601 602 var incFlags []string 603 var deps []string 604 ctx.VisitDepsDepthFirst(func(module blueprint.Module) { 605 if info, ok := blueprint.OtherModuleProvider(ctx, module, PackageProvider); ok { 606 incDir := info.PkgRoot 607 target := info.PackageTarget 608 incFlags = append(incFlags, "-I "+incDir) 609 deps = append(deps, target) 610 } 611 }) 612 613 compileArgs := map[string]string{ 614 "pkgPath": pkgPath, 615 } 616 617 if len(incFlags) > 0 { 618 compileArgs["incFlags"] = strings.Join(incFlags, " ") 619 } 620 621 if len(embedSrcs) > 0 { 622 embedcfgFile := archiveFile + ".embedcfg" 623 generateEmbedcfgFile(ctx, embedSrcs, srcDir, embedcfgFile) 624 compileArgs["embedFlags"] = "-embedcfg " + embedcfgFile 625 deps = append(deps, embedcfgFile) 626 } 627 628 ctx.Build(pctx, blueprint.BuildParams{ 629 Rule: compile, 630 Outputs: []string{archiveFile}, 631 Inputs: srcFiles, 632 Implicits: deps, 633 Args: compileArgs, 634 Optional: true, 635 }) 636} 637 638func buildGoTest(ctx blueprint.ModuleContext, testRoot, testPkgArchive, 639 pkgPath string, srcs, genSrcs, testSrcs []string, embedSrcs []string) []string { 640 641 if len(testSrcs) == 0 { 642 return nil 643 } 644 645 srcDir := moduleSrcDir(ctx) 646 testFiles := pathtools.PrefixPaths(testSrcs, srcDir) 647 648 mainFile := filepath.Join(testRoot, "test.go") 649 testArchive := filepath.Join(testRoot, "test.a") 650 testFile := filepath.Join(testRoot, "test") 651 testPassed := filepath.Join(testRoot, "test.passed") 652 653 buildGoPackage(ctx, testRoot, pkgPath, testPkgArchive, 654 append(srcs, testSrcs...), genSrcs, embedSrcs) 655 656 ctx.Build(pctx, blueprint.BuildParams{ 657 Rule: goTestMain, 658 Outputs: []string{mainFile}, 659 Inputs: testFiles, 660 Args: map[string]string{ 661 "pkg": pkgPath, 662 }, 663 Optional: true, 664 }) 665 666 linkDeps := []string{testPkgArchive} 667 libDirFlags := []string{"-L " + testRoot} 668 testDeps := []string{} 669 ctx.VisitDepsDepthFirst(func(module blueprint.Module) { 670 if info, ok := blueprint.OtherModuleProvider(ctx, module, PackageProvider); ok { 671 linkDeps = append(linkDeps, info.PackageTarget) 672 libDir := info.PkgRoot 673 libDirFlags = append(libDirFlags, "-L "+libDir) 674 testDeps = append(testDeps, info.TestTargets...) 675 } 676 }) 677 678 ctx.Build(pctx, blueprint.BuildParams{ 679 Rule: compile, 680 Outputs: []string{testArchive}, 681 Inputs: []string{mainFile}, 682 Implicits: []string{testPkgArchive}, 683 Args: map[string]string{ 684 "pkgPath": "main", 685 "incFlags": "-I " + testRoot, 686 }, 687 Optional: true, 688 }) 689 690 ctx.Build(pctx, blueprint.BuildParams{ 691 Rule: link, 692 Outputs: []string{testFile}, 693 Inputs: []string{testArchive}, 694 Implicits: linkDeps, 695 Args: map[string]string{ 696 "libDirFlags": strings.Join(libDirFlags, " "), 697 }, 698 Optional: true, 699 }) 700 701 ctx.Build(pctx, blueprint.BuildParams{ 702 Rule: test, 703 Outputs: []string{testPassed}, 704 Inputs: []string{testFile}, 705 Validations: testDeps, 706 Args: map[string]string{ 707 "pkg": pkgPath, 708 "pkgSrcDir": filepath.Dir(testFiles[0]), 709 }, 710 Optional: true, 711 }) 712 713 return []string{testPassed} 714} 715 716var PrimaryBuilderProvider = blueprint.NewMutatorProvider[PrimaryBuilderInfo]("bootstrap_deps") 717 718type PrimaryBuilderInfo struct{} 719 720type singleton struct { 721} 722 723func newSingletonFactory() func() blueprint.Singleton { 724 return func() blueprint.Singleton { 725 return &singleton{} 726 } 727} 728 729func (s *singleton) GenerateBuildActions(ctx blueprint.SingletonContext) { 730 // Find the module that's marked as the "primary builder", which means it's 731 // creating the binary that we'll use to generate the non-bootstrap 732 // build.ninja file. 733 var primaryBuilders []string 734 // blueprintTools contains blueprint go binaries that will be built in StageMain 735 var blueprintTools []string 736 // blueprintTools contains the test outputs of go tests that can be run in StageMain 737 var blueprintTests []string 738 // blueprintGoPackages contains all blueprint go packages that can be built in StageMain 739 var blueprintGoPackages []string 740 ctx.VisitAllModules(func(module blueprint.Module) { 741 if ctx.PrimaryModule(module) == module { 742 if binaryInfo, ok := blueprint.SingletonModuleProvider(ctx, module, BinaryProvider); ok { 743 if binaryInfo.InstallPath != "" { 744 blueprintTools = append(blueprintTools, binaryInfo.InstallPath) 745 } 746 blueprintTests = append(blueprintTests, binaryInfo.TestTargets...) 747 if _, ok := blueprint.SingletonModuleProvider(ctx, module, PrimaryBuilderProvider); ok { 748 primaryBuilders = append(primaryBuilders, binaryInfo.InstallPath) 749 } 750 } 751 752 if packageInfo, ok := blueprint.SingletonModuleProvider(ctx, module, PackageProvider); ok { 753 blueprintGoPackages = append(blueprintGoPackages, packageInfo.PackageTarget) 754 blueprintTests = append(blueprintTests, packageInfo.TestTargets...) 755 } 756 } 757 }) 758 759 var primaryBuilderCmdlinePrefix []string 760 var primaryBuilderFile string 761 762 if len(primaryBuilders) == 0 { 763 ctx.Errorf("no primary builder module present") 764 return 765 } else if len(primaryBuilders) > 1 { 766 ctx.Errorf("multiple primary builder modules present: %q", primaryBuilders) 767 return 768 } else { 769 primaryBuilderFile = primaryBuilders[0] 770 } 771 772 ctx.SetOutDir(pctx, "${outDir}") 773 774 for _, subninja := range ctx.Config().(BootstrapConfig).Subninjas() { 775 ctx.AddSubninja(subninja) 776 } 777 778 for _, i := range ctx.Config().(BootstrapConfig).PrimaryBuilderInvocations() { 779 flags := make([]string, 0) 780 flags = append(flags, primaryBuilderCmdlinePrefix...) 781 flags = append(flags, i.Args...) 782 783 pool := "" 784 if i.Console { 785 pool = "console" 786 } 787 788 envAssignments := "" 789 for k, v := range i.Env { 790 // NB: This is rife with quoting issues but we don't care because we trust 791 // soong_ui to not abuse this facility too much 792 envAssignments += k + "=" + v + " " 793 } 794 795 // Build the main build.ninja 796 ctx.Build(pctx, blueprint.BuildParams{ 797 Rule: generateBuildNinja, 798 Outputs: i.Outputs, 799 Inputs: i.Inputs, 800 Implicits: i.Implicits, 801 OrderOnly: i.OrderOnlyInputs, 802 Args: map[string]string{ 803 "builder": primaryBuilderFile, 804 "env": envAssignments, 805 "extra": strings.Join(flags, " "), 806 "pool": pool, 807 }, 808 // soong_ui explicitly requests what it wants to be build. This is 809 // because the same Ninja file contains instructions to run 810 // soong_build, run bp2build and to generate the JSON module graph. 811 Optional: true, 812 Description: i.Description, 813 }) 814 } 815 816 // Add a phony target for building various tools that are part of blueprint 817 if len(blueprintTools) > 0 { 818 ctx.Build(pctx, blueprint.BuildParams{ 819 Rule: blueprint.Phony, 820 Outputs: []string{"blueprint_tools"}, 821 Inputs: blueprintTools, 822 }) 823 } 824 825 // Add a phony target for running various tests that are part of blueprint 826 ctx.Build(pctx, blueprint.BuildParams{ 827 Rule: blueprint.Phony, 828 Outputs: []string{"blueprint_tests"}, 829 Inputs: blueprintTests, 830 }) 831 832 // Add a phony target for running go tests 833 ctx.Build(pctx, blueprint.BuildParams{ 834 Rule: blueprint.Phony, 835 Outputs: []string{"blueprint_go_packages"}, 836 Inputs: blueprintGoPackages, 837 Optional: true, 838 }) 839} 840 841// packageRoot returns the module-specific package root directory path. This 842// directory is where the final package .a files are output and where dependant 843// modules search for this package via -I arguments. 844func packageRoot(ctx blueprint.ModuleContext) string { 845 toolDir := ctx.Config().(BootstrapConfig).HostToolDir() 846 return filepath.Join(toolDir, "go", ctx.ModuleName(), ctx.ModuleSubDir(), "pkg") 847} 848 849// testRoot returns the module-specific package root directory path used for 850// building tests. The .a files generated here will include everything from 851// packageRoot, plus the test-only code. 852func testRoot(ctx blueprint.ModuleContext) string { 853 toolDir := ctx.Config().(BootstrapConfig).HostToolDir() 854 return filepath.Join(toolDir, "go", ctx.ModuleName(), ctx.ModuleSubDir(), "test") 855} 856 857// moduleSrcDir returns the path of the directory that all source file paths are 858// specified relative to. 859func moduleSrcDir(ctx blueprint.ModuleContext) string { 860 return ctx.ModuleDir() 861} 862 863// moduleObjDir returns the module-specific object directory path. 864func moduleObjDir(ctx blueprint.ModuleContext) string { 865 toolDir := ctx.Config().(BootstrapConfig).HostToolDir() 866 return filepath.Join(toolDir, "go", ctx.ModuleName(), ctx.ModuleSubDir(), "obj") 867} 868 869// moduleGenSrcDir returns the module-specific generated sources path. 870func moduleGenSrcDir(ctx blueprint.ModuleContext) string { 871 toolDir := ctx.Config().(BootstrapConfig).HostToolDir() 872 return filepath.Join(toolDir, "go", ctx.ModuleName(), ctx.ModuleSubDir(), "gen") 873} 874