| // Copyright 2014 Google Inc. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package bootstrap |
| |
| import ( |
| "encoding/json" |
| "fmt" |
| "path/filepath" |
| "runtime" |
| "strings" |
| |
| "github.com/google/blueprint" |
| "github.com/google/blueprint/pathtools" |
| "github.com/google/blueprint/proptools" |
| ) |
| |
| var ( |
| pctx = blueprint.NewPackageContext("github.com/google/blueprint/bootstrap") |
| |
| goTestMainCmd = pctx.StaticVariable("goTestMainCmd", filepath.Join("$ToolDir", "gotestmain")) |
| goTestRunnerCmd = pctx.StaticVariable("goTestRunnerCmd", filepath.Join("$ToolDir", "gotestrunner")) |
| pluginGenSrcCmd = pctx.StaticVariable("pluginGenSrcCmd", filepath.Join("$ToolDir", "loadplugins")) |
| |
| parallelCompile = pctx.StaticVariable("parallelCompile", func() string { |
| numCpu := runtime.NumCPU() |
| // This will cause us to recompile all go programs if the |
| // number of cpus changes. We don't get a lot of benefit from |
| // higher values, so cap this to make it cheaper to move trees |
| // between machines. |
| if numCpu > 8 { |
| numCpu = 8 |
| } |
| return fmt.Sprintf("-c %d", numCpu) |
| }()) |
| |
| compile = pctx.StaticRule("compile", |
| blueprint.RuleParams{ |
| Command: "GOROOT='$goRoot' $compileCmd $parallelCompile -o $out.tmp " + |
| "$debugFlags -p $pkgPath -complete $incFlags $embedFlags -pack $in && " + |
| "if cmp --quiet $out.tmp $out; then rm $out.tmp; else mv -f $out.tmp $out; fi", |
| CommandDeps: []string{"$compileCmd"}, |
| Description: "compile $out", |
| Restat: true, |
| }, |
| "pkgPath", "incFlags", "embedFlags") |
| |
| link = pctx.StaticRule("link", |
| blueprint.RuleParams{ |
| Command: "GOROOT='$goRoot' $linkCmd -o $out.tmp $libDirFlags $in && " + |
| "if cmp --quiet $out.tmp $out; then rm $out.tmp; else mv -f $out.tmp $out; fi", |
| CommandDeps: []string{"$linkCmd"}, |
| Description: "link $out", |
| Restat: true, |
| }, |
| "libDirFlags") |
| |
| goTestMain = pctx.StaticRule("gotestmain", |
| blueprint.RuleParams{ |
| Command: "$goTestMainCmd -o $out -pkg $pkg $in", |
| CommandDeps: []string{"$goTestMainCmd"}, |
| Description: "gotestmain $out", |
| }, |
| "pkg") |
| |
| pluginGenSrc = pctx.StaticRule("pluginGenSrc", |
| blueprint.RuleParams{ |
| Command: "$pluginGenSrcCmd -o $out -p $pkg $plugins", |
| CommandDeps: []string{"$pluginGenSrcCmd"}, |
| Description: "create $out", |
| }, |
| "pkg", "plugins") |
| |
| test = pctx.StaticRule("test", |
| blueprint.RuleParams{ |
| Command: "$goTestRunnerCmd -p $pkgSrcDir -f $out -- $in -test.short", |
| CommandDeps: []string{"$goTestRunnerCmd"}, |
| Description: "test $pkg", |
| }, |
| "pkg", "pkgSrcDir") |
| |
| cp = pctx.StaticRule("cp", |
| blueprint.RuleParams{ |
| Command: "cp $in $out", |
| Description: "cp $out", |
| }, |
| "generator") |
| |
| touch = pctx.StaticRule("touch", |
| blueprint.RuleParams{ |
| Command: "touch $out", |
| Description: "touch $out", |
| }, |
| "depfile", "generator") |
| |
| cat = pctx.StaticRule("Cat", |
| blueprint.RuleParams{ |
| Command: "rm -f $out && cat $in > $out", |
| Description: "concatenate files to $out", |
| }) |
| |
| // ubuntu 14.04 offcially use dash for /bin/sh, and its builtin echo command |
| // doesn't support -e option. Therefore we force to use /bin/bash when writing out |
| // content to file. |
| writeFile = pctx.StaticRule("writeFile", |
| blueprint.RuleParams{ |
| Command: `rm -f $out && /bin/bash -c 'echo -e -n "$$0" > $out' $content`, |
| Description: "writing file $out", |
| }, |
| "content") |
| |
| generateBuildNinja = pctx.StaticRule("build.ninja", |
| blueprint.RuleParams{ |
| // TODO: it's kinda ugly that some parameters are computed from |
| // environment variables and some from Ninja parameters, but it's probably |
| // better to not to touch that while Blueprint and Soong are separate |
| // NOTE: The spaces at EOL are important because otherwise Ninja would |
| // omit all spaces between the different options. |
| Command: `cd "$$(dirname "$builder")" && ` + |
| `BUILDER="$$PWD/$$(basename "$builder")" && ` + |
| `cd / && ` + |
| `env -i $env "$$BUILDER" ` + |
| ` --top "$$TOP" ` + |
| ` --soong_out "$soongOutDir" ` + |
| ` --out "$outDir" ` + |
| ` $extra`, |
| CommandDeps: []string{"$builder"}, |
| Description: "$builder $out", |
| Deps: blueprint.DepsGCC, |
| Depfile: "$out.d", |
| Restat: true, |
| }, |
| "builder", "env", "extra", "pool") |
| |
| // Work around a Ninja issue. See https://github.com/martine/ninja/pull/634 |
| phony = pctx.StaticRule("phony", |
| blueprint.RuleParams{ |
| Command: "# phony $out", |
| Description: "phony $out", |
| Generator: true, |
| }, |
| "depfile") |
| |
| _ = pctx.VariableFunc("ToolDir", func(ctx blueprint.VariableFuncContext, config interface{}) (string, error) { |
| return config.(BootstrapConfig).HostToolDir(), nil |
| }) |
| ) |
| |
| var ( |
| // echoEscaper escapes a string such that passing it to "echo -e" will produce the input value. |
| echoEscaper = strings.NewReplacer( |
| `\`, `\\`, // First escape existing backslashes so they aren't interpreted by `echo -e`. |
| "\n", `\n`, // Then replace newlines with \n |
| ) |
| ) |
| |
| // shardString takes a string and returns a slice of strings where the length of each one is |
| // at most shardSize. |
| func shardString(s string, shardSize int) []string { |
| if len(s) == 0 { |
| return nil |
| } |
| ret := make([]string, 0, (len(s)+shardSize-1)/shardSize) |
| for len(s) > shardSize { |
| ret = append(ret, s[0:shardSize]) |
| s = s[shardSize:] |
| } |
| if len(s) > 0 { |
| ret = append(ret, s) |
| } |
| return ret |
| } |
| |
| // writeFileRule creates a ninja rule to write contents to a file. The contents will be |
| // escaped so that the file contains exactly the contents passed to the function. |
| func writeFileRule(ctx blueprint.ModuleContext, outputFile string, content string) { |
| // This is MAX_ARG_STRLEN subtracted with some safety to account for shell escapes |
| const SHARD_SIZE = 131072 - 10000 |
| |
| buildWriteFileRule := func(outputFile string, content string) { |
| content = echoEscaper.Replace(content) |
| content = proptools.NinjaEscape(proptools.ShellEscapeIncludingSpaces(content)) |
| if content == "" { |
| content = "''" |
| } |
| ctx.Build(pctx, blueprint.BuildParams{ |
| Rule: writeFile, |
| Outputs: []string{outputFile}, |
| Description: "write " + outputFile, |
| Args: map[string]string{ |
| "content": content, |
| }, |
| }) |
| } |
| |
| if len(content) > SHARD_SIZE { |
| var chunks []string |
| for i, c := range shardString(content, SHARD_SIZE) { |
| tempPath := fmt.Sprintf("%s.%d", outputFile, i) |
| buildWriteFileRule(tempPath, c) |
| chunks = append(chunks, tempPath) |
| } |
| ctx.Build(pctx, blueprint.BuildParams{ |
| Rule: cat, |
| Inputs: chunks, |
| Outputs: []string{outputFile}, |
| Description: "Merging to " + outputFile, |
| }) |
| return |
| } |
| buildWriteFileRule(outputFile, content) |
| } |
| |
| type pluginDependencyTag struct { |
| blueprint.BaseDependencyTag |
| } |
| |
| type bootstrapDependencies interface { |
| bootstrapDeps(ctx blueprint.BottomUpMutatorContext) |
| } |
| |
| var pluginDepTag = pluginDependencyTag{} |
| |
| func BootstrapDeps(ctx blueprint.BottomUpMutatorContext) { |
| if pkg, ok := ctx.Module().(bootstrapDependencies); ok { |
| pkg.bootstrapDeps(ctx) |
| } |
| } |
| |
| type PackageInfo struct { |
| PkgPath string |
| PkgRoot string |
| PackageTarget string |
| TestTargets []string |
| } |
| |
| var PackageProvider = blueprint.NewProvider[*PackageInfo]() |
| |
| type BinaryInfo struct { |
| IntermediatePath string |
| InstallPath string |
| TestTargets []string |
| } |
| |
| var BinaryProvider = blueprint.NewProvider[*BinaryInfo]() |
| |
| type DocsPackageInfo struct { |
| PkgPath string |
| Srcs []string |
| } |
| |
| var DocsPackageProvider = blueprint.NewMutatorProvider[*DocsPackageInfo]("bootstrap_deps") |
| |
| // A GoPackage is a module for building Go packages. |
| type GoPackage struct { |
| blueprint.SimpleName |
| properties struct { |
| Deps []string |
| PkgPath string |
| Srcs []string |
| TestSrcs []string |
| TestData []string |
| PluginFor []string |
| EmbedSrcs []string |
| // The visibility property is unused in blueprint, but exists so that soong |
| // can add one and not have the bp files fail to parse during the bootstrap build. |
| Visibility []string |
| |
| Darwin struct { |
| Srcs []string |
| TestSrcs []string |
| } |
| Linux struct { |
| Srcs []string |
| TestSrcs []string |
| } |
| } |
| } |
| |
| func newGoPackageModuleFactory() func() (blueprint.Module, []interface{}) { |
| return func() (blueprint.Module, []interface{}) { |
| module := &GoPackage{} |
| return module, []interface{}{&module.properties, &module.SimpleName.Properties} |
| } |
| } |
| |
| // Properties returns the list of property structs to be used for registering a wrapped module type. |
| func (g *GoPackage) Properties() []interface{} { |
| return []interface{}{&g.properties} |
| } |
| |
| func (g *GoPackage) DynamicDependencies(ctx blueprint.DynamicDependerModuleContext) []string { |
| return g.properties.Deps |
| } |
| |
| func (g *GoPackage) bootstrapDeps(ctx blueprint.BottomUpMutatorContext) { |
| for _, plugin := range g.properties.PluginFor { |
| ctx.AddReverseDependency(ctx.Module(), pluginDepTag, plugin) |
| } |
| blueprint.SetProvider(ctx, DocsPackageProvider, &DocsPackageInfo{ |
| PkgPath: g.properties.PkgPath, |
| Srcs: g.properties.Srcs, |
| }) |
| } |
| |
| func (g *GoPackage) GenerateBuildActions(ctx blueprint.ModuleContext) { |
| var ( |
| name = ctx.ModuleName() |
| hasPlugins = false |
| pluginSrc = "" |
| genSrcs = []string{} |
| ) |
| |
| if g.properties.PkgPath == "" { |
| ctx.ModuleErrorf("module %s did not specify a valid pkgPath", name) |
| return |
| } |
| |
| pkgRoot := packageRoot(ctx) |
| archiveFile := filepath.Join(pkgRoot, |
| filepath.FromSlash(g.properties.PkgPath)+".a") |
| |
| ctx.VisitDepsDepthFirst(func(module blueprint.Module) { |
| if ctx.OtherModuleDependencyTag(module) == pluginDepTag { |
| hasPlugins = true |
| } |
| }) |
| if hasPlugins { |
| pluginSrc = filepath.Join(moduleGenSrcDir(ctx), "plugin.go") |
| genSrcs = append(genSrcs, pluginSrc) |
| } |
| |
| if hasPlugins && !buildGoPluginLoader(ctx, g.properties.PkgPath, pluginSrc) { |
| return |
| } |
| |
| var srcs, testSrcs []string |
| if runtime.GOOS == "darwin" { |
| srcs = append(g.properties.Srcs, g.properties.Darwin.Srcs...) |
| testSrcs = append(g.properties.TestSrcs, g.properties.Darwin.TestSrcs...) |
| } else if runtime.GOOS == "linux" { |
| srcs = append(g.properties.Srcs, g.properties.Linux.Srcs...) |
| testSrcs = append(g.properties.TestSrcs, g.properties.Linux.TestSrcs...) |
| } |
| |
| testArchiveFile := filepath.Join(testRoot(ctx), |
| filepath.FromSlash(g.properties.PkgPath)+".a") |
| testResultFile := buildGoTest(ctx, testRoot(ctx), testArchiveFile, |
| g.properties.PkgPath, srcs, genSrcs, testSrcs, g.properties.EmbedSrcs) |
| |
| // Don't build for test-only packages |
| if len(srcs) == 0 && len(genSrcs) == 0 { |
| ctx.Build(pctx, blueprint.BuildParams{ |
| Rule: touch, |
| Outputs: []string{archiveFile}, |
| }) |
| return |
| } |
| |
| buildGoPackage(ctx, pkgRoot, g.properties.PkgPath, archiveFile, |
| srcs, genSrcs, g.properties.EmbedSrcs) |
| blueprint.SetProvider(ctx, PackageProvider, &PackageInfo{ |
| PkgPath: g.properties.PkgPath, |
| PkgRoot: pkgRoot, |
| PackageTarget: archiveFile, |
| TestTargets: testResultFile, |
| }) |
| } |
| |
| // A GoBinary is a module for building executable binaries from Go sources. |
| type GoBinary struct { |
| blueprint.SimpleName |
| properties struct { |
| Deps []string |
| Srcs []string |
| TestSrcs []string |
| TestData []string |
| EmbedSrcs []string |
| PrimaryBuilder bool |
| Default bool |
| // The visibility property is unused in blueprint, but exists so that soong |
| // can add one and not have the bp files fail to parse during the bootstrap build. |
| Visibility []string |
| |
| Darwin struct { |
| Srcs []string |
| TestSrcs []string |
| } |
| Linux struct { |
| Srcs []string |
| TestSrcs []string |
| } |
| } |
| |
| installPath string |
| |
| // skipInstall can be set to true by a module type that wraps GoBinary to skip the install rule, |
| // allowing the wrapping module type to create the install rule itself. |
| skipInstall bool |
| |
| // outputFile is set to the path to the intermediate output file. |
| outputFile string |
| } |
| |
| func newGoBinaryModuleFactory() func() (blueprint.Module, []interface{}) { |
| return func() (blueprint.Module, []interface{}) { |
| module := &GoBinary{} |
| return module, []interface{}{&module.properties, &module.SimpleName.Properties} |
| } |
| } |
| |
| func (g *GoBinary) DynamicDependencies(ctx blueprint.DynamicDependerModuleContext) []string { |
| return g.properties.Deps |
| } |
| |
| func (g *GoBinary) bootstrapDeps(ctx blueprint.BottomUpMutatorContext) { |
| if g.properties.PrimaryBuilder { |
| blueprint.SetProvider(ctx, PrimaryBuilderProvider, PrimaryBuilderInfo{}) |
| } |
| } |
| |
| // IntermediateFile returns the path to the final linked intermedate file. |
| func (g *GoBinary) IntermediateFile() string { |
| return g.outputFile |
| } |
| |
| // SetSkipInstall is called by module types that wrap GoBinary to skip the install rule, |
| // allowing the wrapping module type to create the install rule itself. |
| func (g *GoBinary) SetSkipInstall() { |
| g.skipInstall = true |
| } |
| |
| // Properties returns the list of property structs to be used for registering a wrapped module type. |
| func (g *GoBinary) Properties() []interface{} { |
| return []interface{}{&g.properties} |
| } |
| |
| func (g *GoBinary) GenerateBuildActions(ctx blueprint.ModuleContext) { |
| var ( |
| name = ctx.ModuleName() |
| objDir = moduleObjDir(ctx) |
| archiveFile = filepath.Join(objDir, name+".a") |
| testArchiveFile = filepath.Join(testRoot(ctx), name+".a") |
| aoutFile = filepath.Join(objDir, name) |
| hasPlugins = false |
| pluginSrc = "" |
| genSrcs = []string{} |
| ) |
| |
| if !g.skipInstall { |
| g.installPath = filepath.Join(ctx.Config().(BootstrapConfig).HostToolDir(), name) |
| } |
| |
| ctx.VisitDirectDeps(func(module blueprint.Module) { |
| if ctx.OtherModuleDependencyTag(module) == pluginDepTag { |
| hasPlugins = true |
| } |
| }) |
| if hasPlugins { |
| pluginSrc = filepath.Join(moduleGenSrcDir(ctx), "plugin.go") |
| genSrcs = append(genSrcs, pluginSrc) |
| } |
| |
| var testDeps []string |
| |
| if hasPlugins && !buildGoPluginLoader(ctx, "main", pluginSrc) { |
| return |
| } |
| |
| var srcs, testSrcs []string |
| if runtime.GOOS == "darwin" { |
| srcs = append(g.properties.Srcs, g.properties.Darwin.Srcs...) |
| testSrcs = append(g.properties.TestSrcs, g.properties.Darwin.TestSrcs...) |
| } else if runtime.GOOS == "linux" { |
| srcs = append(g.properties.Srcs, g.properties.Linux.Srcs...) |
| testSrcs = append(g.properties.TestSrcs, g.properties.Linux.TestSrcs...) |
| } |
| |
| testResultFile := buildGoTest(ctx, testRoot(ctx), testArchiveFile, |
| name, srcs, genSrcs, testSrcs, g.properties.EmbedSrcs) |
| testDeps = append(testDeps, testResultFile...) |
| |
| buildGoPackage(ctx, objDir, "main", archiveFile, srcs, genSrcs, g.properties.EmbedSrcs) |
| |
| var linkDeps []string |
| var libDirFlags []string |
| ctx.VisitDepsDepthFirst(func(module blueprint.Module) { |
| if info, ok := blueprint.OtherModuleProvider(ctx, module, PackageProvider); ok { |
| linkDeps = append(linkDeps, info.PackageTarget) |
| libDir := info.PkgRoot |
| libDirFlags = append(libDirFlags, "-L "+libDir) |
| testDeps = append(testDeps, info.TestTargets...) |
| } |
| }) |
| |
| linkArgs := map[string]string{} |
| if len(libDirFlags) > 0 { |
| linkArgs["libDirFlags"] = strings.Join(libDirFlags, " ") |
| } |
| |
| ctx.Build(pctx, blueprint.BuildParams{ |
| Rule: link, |
| Outputs: []string{aoutFile}, |
| Inputs: []string{archiveFile}, |
| Implicits: linkDeps, |
| Args: linkArgs, |
| }) |
| |
| g.outputFile = aoutFile |
| |
| var validations []string |
| if ctx.Config().(BootstrapConfig).RunGoTests() { |
| validations = testDeps |
| } |
| |
| if !g.skipInstall { |
| ctx.Build(pctx, blueprint.BuildParams{ |
| Rule: cp, |
| Outputs: []string{g.installPath}, |
| Inputs: []string{aoutFile}, |
| Validations: validations, |
| Default: g.properties.Default, |
| }) |
| } |
| |
| blueprint.SetProvider(ctx, BinaryProvider, &BinaryInfo{ |
| IntermediatePath: g.outputFile, |
| InstallPath: g.installPath, |
| TestTargets: testResultFile, |
| }) |
| } |
| |
| func buildGoPluginLoader(ctx blueprint.ModuleContext, pkgPath, pluginSrc string) bool { |
| ret := true |
| |
| var pluginPaths []string |
| ctx.VisitDirectDeps(func(module blueprint.Module) { |
| if ctx.OtherModuleDependencyTag(module) == pluginDepTag { |
| if info, ok := blueprint.OtherModuleProvider(ctx, module, PackageProvider); ok { |
| pluginPaths = append(pluginPaths, info.PkgPath) |
| } |
| } |
| }) |
| |
| ctx.Build(pctx, blueprint.BuildParams{ |
| Rule: pluginGenSrc, |
| Outputs: []string{pluginSrc}, |
| Args: map[string]string{ |
| "pkg": pkgPath, |
| "plugins": strings.Join(pluginPaths, " "), |
| }, |
| }) |
| |
| return ret |
| } |
| |
| func generateEmbedcfgFile(ctx blueprint.ModuleContext, files []string, srcDir string, embedcfgFile string) { |
| embedcfg := struct { |
| Patterns map[string][]string |
| Files map[string]string |
| }{ |
| make(map[string][]string, len(files)), |
| make(map[string]string, len(files)), |
| } |
| |
| for _, file := range files { |
| embedcfg.Patterns[file] = []string{file} |
| embedcfg.Files[file] = filepath.Join(srcDir, file) |
| } |
| |
| embedcfgData, err := json.Marshal(&embedcfg) |
| if err != nil { |
| ctx.ModuleErrorf("Failed to marshal embedcfg data: %s", err.Error()) |
| } |
| |
| writeFileRule(ctx, embedcfgFile, string(embedcfgData)) |
| } |
| |
| func buildGoPackage(ctx blueprint.ModuleContext, pkgRoot string, |
| pkgPath string, archiveFile string, srcs []string, genSrcs []string, embedSrcs []string) { |
| |
| srcDir := moduleSrcDir(ctx) |
| srcFiles := pathtools.PrefixPaths(srcs, srcDir) |
| srcFiles = append(srcFiles, genSrcs...) |
| |
| var incFlags []string |
| var deps []string |
| ctx.VisitDepsDepthFirst(func(module blueprint.Module) { |
| if info, ok := blueprint.OtherModuleProvider(ctx, module, PackageProvider); ok { |
| incDir := info.PkgRoot |
| target := info.PackageTarget |
| incFlags = append(incFlags, "-I "+incDir) |
| deps = append(deps, target) |
| } |
| }) |
| |
| compileArgs := map[string]string{ |
| "pkgPath": pkgPath, |
| } |
| |
| if len(incFlags) > 0 { |
| compileArgs["incFlags"] = strings.Join(incFlags, " ") |
| } |
| |
| if len(embedSrcs) > 0 { |
| embedcfgFile := archiveFile + ".embedcfg" |
| generateEmbedcfgFile(ctx, embedSrcs, srcDir, embedcfgFile) |
| compileArgs["embedFlags"] = "-embedcfg " + embedcfgFile |
| deps = append(deps, embedcfgFile) |
| } |
| |
| ctx.Build(pctx, blueprint.BuildParams{ |
| Rule: compile, |
| Outputs: []string{archiveFile}, |
| Inputs: srcFiles, |
| Implicits: deps, |
| Args: compileArgs, |
| }) |
| } |
| |
| func buildGoTest(ctx blueprint.ModuleContext, testRoot, testPkgArchive, |
| pkgPath string, srcs, genSrcs, testSrcs []string, embedSrcs []string) []string { |
| |
| if len(testSrcs) == 0 { |
| return nil |
| } |
| |
| srcDir := moduleSrcDir(ctx) |
| testFiles := pathtools.PrefixPaths(testSrcs, srcDir) |
| |
| mainFile := filepath.Join(testRoot, "test.go") |
| testArchive := filepath.Join(testRoot, "test.a") |
| testFile := filepath.Join(testRoot, "test") |
| testPassed := filepath.Join(testRoot, "test.passed") |
| |
| buildGoPackage(ctx, testRoot, pkgPath, testPkgArchive, |
| append(srcs, testSrcs...), genSrcs, embedSrcs) |
| |
| ctx.Build(pctx, blueprint.BuildParams{ |
| Rule: goTestMain, |
| Outputs: []string{mainFile}, |
| Inputs: testFiles, |
| Args: map[string]string{ |
| "pkg": pkgPath, |
| }, |
| }) |
| |
| linkDeps := []string{testPkgArchive} |
| libDirFlags := []string{"-L " + testRoot} |
| testDeps := []string{} |
| ctx.VisitDepsDepthFirst(func(module blueprint.Module) { |
| if info, ok := blueprint.OtherModuleProvider(ctx, module, PackageProvider); ok { |
| linkDeps = append(linkDeps, info.PackageTarget) |
| libDir := info.PkgRoot |
| libDirFlags = append(libDirFlags, "-L "+libDir) |
| testDeps = append(testDeps, info.TestTargets...) |
| } |
| }) |
| |
| ctx.Build(pctx, blueprint.BuildParams{ |
| Rule: compile, |
| Outputs: []string{testArchive}, |
| Inputs: []string{mainFile}, |
| Implicits: []string{testPkgArchive}, |
| Args: map[string]string{ |
| "pkgPath": "main", |
| "incFlags": "-I " + testRoot, |
| }, |
| }) |
| |
| ctx.Build(pctx, blueprint.BuildParams{ |
| Rule: link, |
| Outputs: []string{testFile}, |
| Inputs: []string{testArchive}, |
| Implicits: linkDeps, |
| Args: map[string]string{ |
| "libDirFlags": strings.Join(libDirFlags, " "), |
| }, |
| }) |
| |
| ctx.Build(pctx, blueprint.BuildParams{ |
| Rule: test, |
| Outputs: []string{testPassed}, |
| Inputs: []string{testFile}, |
| Validations: testDeps, |
| Args: map[string]string{ |
| "pkg": pkgPath, |
| "pkgSrcDir": filepath.Dir(testFiles[0]), |
| }, |
| }) |
| |
| return []string{testPassed} |
| } |
| |
| var PrimaryBuilderProvider = blueprint.NewMutatorProvider[PrimaryBuilderInfo]("bootstrap_deps") |
| |
| type PrimaryBuilderInfo struct{} |
| |
| type singleton struct { |
| } |
| |
| func newSingletonFactory() func() blueprint.Singleton { |
| return func() blueprint.Singleton { |
| return &singleton{} |
| } |
| } |
| |
| func (s *singleton) GenerateBuildActions(ctx blueprint.SingletonContext) { |
| // Find the module that's marked as the "primary builder", which means it's |
| // creating the binary that we'll use to generate the non-bootstrap |
| // build.ninja file. |
| var primaryBuilders []string |
| // blueprintTools contains blueprint go binaries that will be built in StageMain |
| var blueprintTools []string |
| // blueprintTools contains the test outputs of go tests that can be run in StageMain |
| var blueprintTests []string |
| // blueprintGoPackages contains all blueprint go packages that can be built in StageMain |
| var blueprintGoPackages []string |
| ctx.VisitAllModules(func(module blueprint.Module) { |
| if ctx.PrimaryModule(module) == module { |
| if binaryInfo, ok := blueprint.SingletonModuleProvider(ctx, module, BinaryProvider); ok { |
| if binaryInfo.InstallPath != "" { |
| blueprintTools = append(blueprintTools, binaryInfo.InstallPath) |
| } |
| blueprintTests = append(blueprintTests, binaryInfo.TestTargets...) |
| if _, ok := blueprint.SingletonModuleProvider(ctx, module, PrimaryBuilderProvider); ok { |
| primaryBuilders = append(primaryBuilders, binaryInfo.InstallPath) |
| } |
| } |
| |
| if packageInfo, ok := blueprint.SingletonModuleProvider(ctx, module, PackageProvider); ok { |
| blueprintGoPackages = append(blueprintGoPackages, packageInfo.PackageTarget) |
| blueprintTests = append(blueprintTests, packageInfo.TestTargets...) |
| } |
| } |
| }) |
| |
| var primaryBuilderCmdlinePrefix []string |
| var primaryBuilderFile string |
| |
| if len(primaryBuilders) == 0 { |
| ctx.Errorf("no primary builder module present") |
| return |
| } else if len(primaryBuilders) > 1 { |
| ctx.Errorf("multiple primary builder modules present: %q", primaryBuilders) |
| return |
| } else { |
| primaryBuilderFile = primaryBuilders[0] |
| } |
| |
| ctx.SetOutDir(pctx, "${outDir}") |
| |
| for _, subninja := range ctx.Config().(BootstrapConfig).Subninjas() { |
| ctx.AddSubninja(subninja) |
| } |
| |
| for _, i := range ctx.Config().(BootstrapConfig).PrimaryBuilderInvocations() { |
| flags := make([]string, 0) |
| flags = append(flags, primaryBuilderCmdlinePrefix...) |
| flags = append(flags, i.Args...) |
| |
| pool := "" |
| if i.Console { |
| pool = "console" |
| } |
| |
| envAssignments := "" |
| for k, v := range i.Env { |
| // NB: This is rife with quoting issues but we don't care because we trust |
| // soong_ui to not abuse this facility too much |
| envAssignments += k + "=" + v + " " |
| } |
| |
| // Build the main build.ninja |
| ctx.Build(pctx, blueprint.BuildParams{ |
| Rule: generateBuildNinja, |
| Outputs: i.Outputs, |
| Inputs: i.Inputs, |
| Implicits: i.Implicits, |
| OrderOnly: i.OrderOnlyInputs, |
| Args: map[string]string{ |
| "builder": primaryBuilderFile, |
| "env": envAssignments, |
| "extra": strings.Join(flags, " "), |
| "pool": pool, |
| }, |
| Description: i.Description, |
| }) |
| } |
| |
| // Add a phony target for building various tools that are part of blueprint |
| if len(blueprintTools) > 0 { |
| ctx.Build(pctx, blueprint.BuildParams{ |
| Rule: blueprint.Phony, |
| Outputs: []string{"blueprint_tools"}, |
| Inputs: blueprintTools, |
| Default: true, |
| }) |
| } |
| |
| // Add a phony target for running various tests that are part of blueprint |
| ctx.Build(pctx, blueprint.BuildParams{ |
| Rule: blueprint.Phony, |
| Outputs: []string{"blueprint_tests"}, |
| Inputs: blueprintTests, |
| Default: true, |
| }) |
| |
| // Add a phony target for running go tests |
| ctx.Build(pctx, blueprint.BuildParams{ |
| Rule: blueprint.Phony, |
| Outputs: []string{"blueprint_go_packages"}, |
| Inputs: blueprintGoPackages, |
| }) |
| } |
| |
| // packageRoot returns the module-specific package root directory path. This |
| // directory is where the final package .a files are output and where dependant |
| // modules search for this package via -I arguments. |
| func packageRoot(ctx blueprint.ModuleContext) string { |
| toolDir := ctx.Config().(BootstrapConfig).HostToolDir() |
| return filepath.Join(toolDir, "go", ctx.ModuleName(), ctx.ModuleSubDir(), "pkg") |
| } |
| |
| // testRoot returns the module-specific package root directory path used for |
| // building tests. The .a files generated here will include everything from |
| // packageRoot, plus the test-only code. |
| func testRoot(ctx blueprint.ModuleContext) string { |
| toolDir := ctx.Config().(BootstrapConfig).HostToolDir() |
| return filepath.Join(toolDir, "go", ctx.ModuleName(), ctx.ModuleSubDir(), "test") |
| } |
| |
| // moduleSrcDir returns the path of the directory that all source file paths are |
| // specified relative to. |
| func moduleSrcDir(ctx blueprint.ModuleContext) string { |
| return ctx.ModuleDir() |
| } |
| |
| // moduleObjDir returns the module-specific object directory path. |
| func moduleObjDir(ctx blueprint.ModuleContext) string { |
| toolDir := ctx.Config().(BootstrapConfig).HostToolDir() |
| return filepath.Join(toolDir, "go", ctx.ModuleName(), ctx.ModuleSubDir(), "obj") |
| } |
| |
| // moduleGenSrcDir returns the module-specific generated sources path. |
| func moduleGenSrcDir(ctx blueprint.ModuleContext) string { |
| toolDir := ctx.Config().(BootstrapConfig).HostToolDir() |
| return filepath.Join(toolDir, "go", ctx.ModuleName(), ctx.ModuleSubDir(), "gen") |
| } |