Stop determining package names from the call stack.

This change replaces the automatic caller package divination with a
PackageContext object that must be explicitly passed in by callers.

Change-Id: I139be29ecf75a7cf8488b3958dee5e44363acc22
This commit is contained in:
Jamie Gennis 2014-10-03 02:49:58 -07:00 committed by Colin Cross
parent 0c35b2db92
commit 2fb2095caa
11 changed files with 306 additions and 283 deletions

View file

@ -3,13 +3,13 @@ bootstrap_go_package {
deps: ["blueprint-parser"],
pkgPath: "blueprint",
srcs: ["blueprint/context.go",
"blueprint/globals.go",
"blueprint/live_tracker.go",
"blueprint/mangle.go",
"blueprint/module_ctx.go",
"blueprint/ninja_defs.go",
"blueprint/ninja_strings.go",
"blueprint/ninja_writer.go",
"blueprint/package_ctx.go",
"blueprint/scope.go",
"blueprint/singleton_ctx.go",
"blueprint/unpack.go"],

View file

@ -11,11 +11,13 @@ import (
const bootstrapDir = ".bootstrap"
var (
gcCmd = blueprint.StaticVariable("gcCmd", "$goToolDir/${GoChar}g")
packCmd = blueprint.StaticVariable("packCmd", "$goToolDir/pack")
linkCmd = blueprint.StaticVariable("linkCmd", "$goToolDir/${GoChar}l")
pctx = blueprint.NewPackageContext("blueprint/bootstrap")
gc = blueprint.StaticRule("gc",
gcCmd = pctx.StaticVariable("gcCmd", "$goToolDir/${GoChar}g")
packCmd = pctx.StaticVariable("packCmd", "$goToolDir/pack")
linkCmd = pctx.StaticVariable("linkCmd", "$goToolDir/${GoChar}l")
gc = pctx.StaticRule("gc",
blueprint.RuleParams{
Command: "GOROOT='$GoRoot' $gcCmd -o $out -p $pkgPath -complete " +
"$incFlags $in",
@ -23,35 +25,35 @@ var (
},
"pkgPath", "incFlags")
pack = blueprint.StaticRule("pack",
pack = pctx.StaticRule("pack",
blueprint.RuleParams{
Command: "GOROOT='$GoRoot' $packCmd grcP $prefix $out $in",
Description: "pack $out",
},
"prefix")
link = blueprint.StaticRule("link",
link = pctx.StaticRule("link",
blueprint.RuleParams{
Command: "GOROOT='$GoRoot' $linkCmd -o $out $libDirFlags $in",
Description: "${GoChar}l $out",
},
"libDirFlags")
cp = blueprint.StaticRule("cp",
cp = pctx.StaticRule("cp",
blueprint.RuleParams{
Command: "cp $in $out",
Description: "cp $out",
},
"generator")
bootstrap = blueprint.StaticRule("bootstrap",
bootstrap = pctx.StaticRule("bootstrap",
blueprint.RuleParams{
Command: "$Bootstrap -i $in",
Description: "bootstrap $in",
Generator: true,
})
rebootstrap = blueprint.StaticRule("rebootstrap",
rebootstrap = pctx.StaticRule("rebootstrap",
blueprint.RuleParams{
// Ninja only re-invokes itself once when it regenerates a .ninja
// file. For the re-bootstrap process we need that to happen twice,
@ -70,7 +72,7 @@ var (
})
// Work around a Ninja issue. See https://github.com/martine/ninja/pull/634
phony = blueprint.StaticRule("phony",
phony = pctx.StaticRule("phony",
blueprint.RuleParams{
Command: "# phony $out",
Description: "phony $out",
@ -209,14 +211,14 @@ func (g *goBinary) GenerateBuildActions(ctx blueprint.ModuleContext) {
linkArgs["libDirFlags"] = strings.Join(libDirFlags, " ")
}
ctx.Build(blueprint.BuildParams{
ctx.Build(pctx, blueprint.BuildParams{
Rule: link,
Outputs: []string{aoutFile},
Inputs: []string{archiveFile},
Args: linkArgs,
})
ctx.Build(blueprint.BuildParams{
ctx.Build(pctx, blueprint.BuildParams{
Rule: cp,
Outputs: []string{binaryFile},
Inputs: []string{aoutFile},
@ -254,7 +256,7 @@ func buildGoPackage(ctx blueprint.ModuleContext, pkgRoot string,
gcArgs["incFlags"] = strings.Join(incFlags, " ")
}
ctx.Build(blueprint.BuildParams{
ctx.Build(pctx, blueprint.BuildParams{
Rule: gc,
Outputs: []string{objFile},
Inputs: srcFiles,
@ -262,7 +264,7 @@ func buildGoPackage(ctx blueprint.ModuleContext, pkgRoot string,
Args: gcArgs,
})
ctx.Build(blueprint.BuildParams{
ctx.Build(pctx, blueprint.BuildParams{
Rule: pack,
Outputs: []string{archiveFile},
Inputs: []string{objFile},
@ -284,7 +286,7 @@ func phonyGoTarget(ctx blueprint.ModuleContext, target string, srcs []string) {
moduleDir := ctx.ModuleDir()
srcs = pathtools.PrefixPaths(srcs, filepath.Join("$SrcDir", moduleDir))
ctx.Build(blueprint.BuildParams{
ctx.Build(pctx, blueprint.BuildParams{
Rule: phony,
Outputs: []string{target},
Inputs: srcs,
@ -297,7 +299,7 @@ func phonyGoTarget(ctx blueprint.ModuleContext, target string, srcs []string) {
// for each source file, which will cause Ninja to treat it as dirty if its
// missing.
for _, src := range srcs {
ctx.Build(blueprint.BuildParams{
ctx.Build(pctx, blueprint.BuildParams{
Rule: blueprint.Phony,
Outputs: []string{src},
})
@ -364,7 +366,7 @@ func (s *singleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
// file. Otherwise we occasionally get "warning: bad deps log signature
// or version; starting over" messages from Ninja, presumably because
// two Ninja processes try to write to the same log concurrently.
ctx.SetBuildDir(bootstrapDir)
ctx.SetBuildDir(pctx, bootstrapDir)
// We generate the depfile here that includes the dependencies for all
// the Blueprints files that contribute to generating the big build
@ -373,7 +375,7 @@ func (s *singleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
// bootstrap. Because the re-bootstrap rule's output is "build.ninja"
// we need to force the depfile to have that as its "make target"
// (recall that depfiles use a subset of the Makefile syntax).
bigbp := ctx.Rule("bigbp",
bigbp := ctx.Rule(pctx, "bigbp",
blueprint.RuleParams{
Command: fmt.Sprintf("%s %s -d %s -o $out $in",
primaryBuilderFile, primaryBuilderExtraFlags,
@ -382,7 +384,7 @@ func (s *singleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
Depfile: mainNinjaDepFile,
})
ctx.Build(blueprint.BuildParams{
ctx.Build(pctx, blueprint.BuildParams{
Rule: bigbp,
Outputs: []string{mainNinjaFile},
Inputs: []string{topLevelBlueprints},
@ -397,12 +399,12 @@ func (s *singleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
// We also need to add an implicit dependency on bootstrapNinjaFile so
// that it gets generated as part of the bootstrap process.
notAFile := filepath.Join(bootstrapDir, "notAFile")
ctx.Build(blueprint.BuildParams{
ctx.Build(pctx, blueprint.BuildParams{
Rule: blueprint.Phony,
Outputs: []string{notAFile},
})
ctx.Build(blueprint.BuildParams{
ctx.Build(pctx, blueprint.BuildParams{
Rule: bootstrap,
Outputs: []string{"build.ninja"},
Inputs: []string{mainNinjaFile},
@ -415,7 +417,7 @@ func (s *singleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
// file's mtime to match that of the current one. If they're different
// then the new file will have a newer timestamp than the current one
// and it will trigger a reboostrap by the non-boostrap build manifest.
minibp := ctx.Rule("minibp",
minibp := ctx.Rule(pctx, "minibp",
blueprint.RuleParams{
Command: fmt.Sprintf("%s -c $checkFile -d $out.d -o $out $in",
minibpFile),
@ -425,7 +427,7 @@ func (s *singleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
},
"checkFile")
ctx.Build(blueprint.BuildParams{
ctx.Build(pctx, blueprint.BuildParams{
Rule: minibp,
Outputs: []string{bootstrapNinjaFile},
Inputs: []string{topLevelBlueprints},
@ -445,7 +447,7 @@ func (s *singleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
// On top of that we need to use the depfile generated by the bigbp
// rule. We do this by depending on that file and then setting up a
// phony rule to generate it that uses the depfile.
ctx.Build(blueprint.BuildParams{
ctx.Build(pctx, blueprint.BuildParams{
Rule: rebootstrap,
Outputs: []string{"build.ninja"},
Inputs: []string{"$BootstrapManifest"},
@ -456,7 +458,7 @@ func (s *singleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
},
})
ctx.Build(blueprint.BuildParams{
ctx.Build(pctx, blueprint.BuildParams{
Rule: phony,
Outputs: []string{mainNinjaFile},
Inputs: []string{topLevelBlueprints},
@ -470,7 +472,7 @@ func (s *singleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
// the current bootstrap manifest. We enable the Ninja "generator"
// behavior so that Ninja doesn't invoke this build just because it's
// missing a command line log entry for the bootstrap manifest.
ctx.Build(blueprint.BuildParams{
ctx.Build(pctx, blueprint.BuildParams{
Rule: cp,
Outputs: []string{"$BootstrapManifest"},
Inputs: []string{bootstrapNinjaFile},
@ -483,7 +485,7 @@ func (s *singleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
// This is a standalone Blueprint build, so we copy the minibp
// binary to the "bin" directory to make it easier to find.
finalMinibp := filepath.Join("bin", primaryBuilderName)
ctx.Build(blueprint.BuildParams{
ctx.Build(pctx, blueprint.BuildParams{
Rule: cp,
Inputs: []string{primaryBuilderFile},
Outputs: []string{finalMinibp},

View file

@ -1,23 +1,19 @@
package bootstrap
import (
"blueprint"
)
var (
// These variables are the only configuration needed by the boostrap
// modules. They are always set to the variable name enclosed in "@@" so
// that their values can be easily replaced in the generated Ninja file.
SrcDir = blueprint.StaticVariable("SrcDir", "@@SrcDir@@")
GoRoot = blueprint.StaticVariable("GoRoot", "@@GoRoot@@")
GoOS = blueprint.StaticVariable("GoOS", "@@GoOS@@")
GoArch = blueprint.StaticVariable("GoArch", "@@GoArch@@")
GoChar = blueprint.StaticVariable("GoChar", "@@GoChar@@")
Bootstrap = blueprint.StaticVariable("Bootstrap", "@@Bootstrap@@")
BootstrapManifest = blueprint.StaticVariable("BootstrapManifest",
SrcDir = pctx.StaticVariable("SrcDir", "@@SrcDir@@")
GoRoot = pctx.StaticVariable("GoRoot", "@@GoRoot@@")
GoOS = pctx.StaticVariable("GoOS", "@@GoOS@@")
GoArch = pctx.StaticVariable("GoArch", "@@GoArch@@")
GoChar = pctx.StaticVariable("GoChar", "@@GoChar@@")
Bootstrap = pctx.StaticVariable("Bootstrap", "@@Bootstrap@@")
BootstrapManifest = pctx.StaticVariable("BootstrapManifest",
"@@BootstrapManifest@@")
goToolDir = blueprint.StaticVariable("goToolDir",
goToolDir = pctx.StaticVariable("goToolDir",
"$GoRoot/pkg/tool/${GoOS}_$GoArch")
)

View file

@ -58,7 +58,7 @@ type Context struct {
ignoreUnknownModuleTypes bool
// set during PrepareBuildActions
pkgNames map[*pkg]string
pkgNames map[*PackageContext]string
globalVariables map[Variable]*ninjaString
globalPools map[Pool]*poolDef
globalRules map[Rule]*ruleDef
@ -1107,59 +1107,59 @@ func (c *Context) setBuildDir(value *ninjaString) {
}
func (c *Context) makeUniquePackageNames(
liveGlobals *liveTracker) map[*pkg]string {
liveGlobals *liveTracker) map[*PackageContext]string {
pkgs := make(map[string]*pkg)
pkgNames := make(map[*pkg]string)
longPkgNames := make(map[*pkg]bool)
pkgs := make(map[string]*PackageContext)
pkgNames := make(map[*PackageContext]string)
longPkgNames := make(map[*PackageContext]bool)
processPackage := func(pkg *pkg) {
if pkg == nil {
processPackage := func(pctx *PackageContext) {
if pctx == nil {
// This is a built-in rule and has no package.
return
}
if _, ok := pkgNames[pkg]; ok {
if _, ok := pkgNames[pctx]; ok {
// We've already processed this package.
return
}
otherPkg, present := pkgs[pkg.shortName]
otherPkg, present := pkgs[pctx.shortName]
if present {
// Short name collision. Both this package and the one that's
// already there need to use their full names. We leave the short
// name in pkgNames for now so future collisions still get caught.
longPkgNames[pkg] = true
longPkgNames[pctx] = true
longPkgNames[otherPkg] = true
} else {
// No collision so far. Tentatively set the package's name to be
// its short name.
pkgNames[pkg] = pkg.shortName
pkgNames[pctx] = pctx.shortName
}
}
// We try to give all packages their short name, but when we get collisions
// we need to use the full unique package name.
for v, _ := range liveGlobals.variables {
processPackage(v.pkg())
processPackage(v.packageContext())
}
for p, _ := range liveGlobals.pools {
processPackage(p.pkg())
processPackage(p.packageContext())
}
for r, _ := range liveGlobals.rules {
processPackage(r.pkg())
processPackage(r.packageContext())
}
// Add the packages that had collisions using their full unique names. This
// will overwrite any short names that were added in the previous step.
for pkg := range longPkgNames {
pkgNames[pkg] = pkg.fullName
for pctx := range longPkgNames {
pkgNames[pctx] = pctx.fullName
}
return pkgNames
}
func (c *Context) checkForVariableReferenceCycles(
variables map[Variable]*ninjaString, pkgNames map[*pkg]string) {
variables map[Variable]*ninjaString, pkgNames map[*PackageContext]string) {
visited := make(map[Variable]bool) // variables that were already checked
checking := make(map[Variable]bool) // variables actively being checked
@ -1375,11 +1375,11 @@ func (c *Context) writeBuildDir(nw *ninjaWriter) error {
}
type globalEntity interface {
fullName(pkgNames map[*pkg]string) string
fullName(pkgNames map[*PackageContext]string) string
}
type globalEntitySorter struct {
pkgNames map[*pkg]string
pkgNames map[*PackageContext]string
entities []globalEntity
}

View file

@ -96,9 +96,9 @@ type ModuleContext interface {
OtherModuleName(m Module) string
OtherModuleErrorf(m Module, fmt string, args ...interface{})
Variable(name, value string)
Rule(name string, params RuleParams, argNames ...string) Rule
Build(params BuildParams)
Variable(pctx *PackageContext, name, value string)
Rule(pctx *PackageContext, name string, params RuleParams, argNames ...string) Rule
Build(pctx *PackageContext, params BuildParams)
VisitDepsDepthFirst(visit func(Module))
VisitDepsDepthFirstIf(pred func(Module) bool, visit func(Module))
@ -188,9 +188,8 @@ func (m *moduleContext) OtherModuleErrorf(module Module, format string,
})
}
func (m *moduleContext) Variable(name, value string) {
const skip = 2
m.scope.ReparentToCallerPackage(skip)
func (m *moduleContext) Variable(pctx *PackageContext, name, value string) {
m.scope.ReparentTo(pctx)
v, err := m.scope.AddLocalVariable(name, value)
if err != nil {
@ -200,11 +199,10 @@ func (m *moduleContext) Variable(name, value string) {
m.actionDefs.variables = append(m.actionDefs.variables, v)
}
func (m *moduleContext) Rule(name string, params RuleParams,
argNames ...string) Rule {
func (m *moduleContext) Rule(pctx *PackageContext, name string,
params RuleParams, argNames ...string) Rule {
const skip = 2
m.scope.ReparentToCallerPackage(skip)
m.scope.ReparentTo(pctx)
r, err := m.scope.AddLocalRule(name, &params, argNames...)
if err != nil {
@ -216,9 +214,8 @@ func (m *moduleContext) Rule(name string, params RuleParams,
return r
}
func (m *moduleContext) Build(params BuildParams) {
const skip = 2
m.scope.ReparentToCallerPackage(skip)
func (m *moduleContext) Build(pctx *PackageContext, params BuildParams) {
m.scope.ReparentTo(pctx)
def, err := parseBuildParams(m.scope, &params)
if err != nil {

View file

@ -181,7 +181,7 @@ func parseRuleParams(scope scope, params *RuleParams) (*ruleDef,
}
func (r *ruleDef) WriteTo(nw *ninjaWriter, name string,
pkgNames map[*pkg]string) error {
pkgNames map[*PackageContext]string) error {
if r.Comment != "" {
err := nw.Comment(r.Comment)
@ -288,7 +288,7 @@ func parseBuildParams(scope scope, params *BuildParams) (*buildDef,
return b, nil
}
func (b *buildDef) WriteTo(nw *ninjaWriter, pkgNames map[*pkg]string) error {
func (b *buildDef) WriteTo(nw *ninjaWriter, pkgNames map[*PackageContext]string) error {
var (
rule = b.Rule.fullName(pkgNames)
outputs = valueList(b.Outputs, pkgNames, outputEscaper)
@ -312,7 +312,7 @@ func (b *buildDef) WriteTo(nw *ninjaWriter, pkgNames map[*pkg]string) error {
return nil
}
func valueList(list []*ninjaString, pkgNames map[*pkg]string,
func valueList(list []*ninjaString, pkgNames map[*PackageContext]string,
escaper *strings.Replacer) []string {
result := make([]string, len(list))

View file

@ -213,11 +213,11 @@ func parseNinjaStrings(scope scope, strs []string) ([]*ninjaString,
return result, nil
}
func (n *ninjaString) Value(pkgNames map[*pkg]string) string {
func (n *ninjaString) Value(pkgNames map[*PackageContext]string) string {
return n.ValueWithEscaper(pkgNames, defaultEscaper)
}
func (n *ninjaString) ValueWithEscaper(pkgNames map[*pkg]string,
func (n *ninjaString) ValueWithEscaper(pkgNames map[*PackageContext]string,
escaper *strings.Replacer) string {
str := escaper.Replace(n.strings[0])

View file

@ -8,14 +8,76 @@ import (
"strings"
)
type pkg struct {
// A PackageContext provides a way to create package-scoped Ninja pools,
// rules, and variables. A Go package should create a single unexported
// package-scoped PackageContext variable that it uses to create all package-
// scoped Ninja object definitions. This PackageContext object should then be
// passed to all calls to define module- or singleton-specific Ninja
// definitions. For example:
//
// package blah
//
// import (
// "blueprint"
// )
//
// var (
// pctx = NewPackageContext("path/to/blah")
//
// myPrivateVar = pctx.StaticVariable("myPrivateVar", "abcdef")
// MyExportedVar = pctx.StaticVariable("MyExportedVar", "$myPrivateVar 123456!")
//
// SomeRule = pctx.StaticRule(...)
// )
//
// // ...
//
// func (m *MyModule) GenerateBuildActions(ctx blueprint.Module) {
// ctx.Build(pctx, blueprint.BuildParams{
// Rule: SomeRule,
// Outputs: []string{"$myPrivateVar"},
// })
// }
type PackageContext struct {
fullName string
shortName string
pkgPath string
scope *basicScope
}
var pkgs = map[string]*pkg{}
var packageContexts = map[string]*PackageContext{}
// NewPackageContext creates a PackageContext object for a given package. The
// pkgPath argument should always be set to the full path used to import the
// package. This function may only be called from a Go package's init()
// function or as part of a package-scoped variable initialization.
func NewPackageContext(pkgPath string) *PackageContext {
checkCalledFromInit()
if _, present := packageContexts[pkgPath]; present {
panic(fmt.Errorf("package %q already has a package context"))
}
pkgName := pkgPathToName(pkgPath)
err := validateNinjaName(pkgName)
if err != nil {
panic(err)
}
i := strings.LastIndex(pkgPath, "/")
shortName := pkgPath[i+1:]
p := &PackageContext{
fullName: pkgName,
shortName: shortName,
pkgPath: pkgPath,
scope: newScope(nil),
}
packageContexts[pkgPath] = p
return p
}
var Phony Rule = &builtinRule{
name_: "phony",
@ -24,24 +86,29 @@ var Phony Rule = &builtinRule{
var errRuleIsBuiltin = errors.New("the rule is a built-in")
var errVariableIsArg = errors.New("argument variables have no value")
// We make a Ninja-friendly name out of a Go package name by replaceing all the
// '/' characters with '.'. We assume the results are unique, though this is
// not 100% guaranteed for Go package names that already contain '.' characters.
// Disallowing package names with '.' isn't reasonable since many package names
// contain the name of the hosting site (e.g. "code.google.com"). In practice
// this probably isn't really a problem.
func pkgPathToName(pkgPath string) string {
return strings.Replace(pkgPath, "/", ".", -1)
// checkCalledFromInit panics if a Go package's init function is not on the
// call stack.
func checkCalledFromInit() {
for skip := 3; ; skip++ {
_, funcName, ok := callerName(skip)
if !ok {
panic("not called from an init func")
}
if funcName == "init" || strings.HasPrefix(funcName, "init·") {
return
}
}
}
// callerName returns the package path and function name of the calling
// function. The skip argument has the same meaning as the skip argument of
// runtime.Callers.
func callerName(skip int) (pkgPath, funcName string) {
func callerName(skip int) (pkgPath, funcName string, ok bool) {
var pc [1]uintptr
n := runtime.Callers(skip+1, pc[:])
if n != 1 {
panic("unable to get caller pc")
return "", "", false
}
f := runtime.FuncForPC(pc[0])
@ -61,70 +128,46 @@ func callerName(skip int) (pkgPath, funcName string) {
pkgPath = fullName[:lastDotIndex]
funcName = fullName[lastDotIndex+1:]
ok = true
return
}
// callerPackage returns the pkg of the function that called the caller of
// callerPackage. The caller of callerPackage must have been called from an
// init function of the package or callerPackage will panic.
//
// Looking for the package's init function on the call stack and using that to
// determine its package name is unfortunately dependent upon Go runtime
// implementation details. However, it allows us to ensure that it's easy to
// determine where a definition in a .ninja file came from.
func callerPackage() *pkg {
pkgPath, funcName := callerName(3)
if funcName != "init" && !strings.HasPrefix(funcName, "init·") {
panic("not called from an init func")
// pkgPathToName makes a Ninja-friendly name out of a Go package name by
// replaceing all the '/' characters with '.'. We assume the results are
// unique, though this is not 100% guaranteed for Go package names that
// already contain '.' characters. Disallowing package names with '.' isn't
// reasonable since many package names contain the name of the hosting site
// (e.g. "code.google.com"). In practice this probably isn't really a
// problem.
func pkgPathToName(pkgPath string) string {
return strings.Replace(pkgPath, "/", ".", -1)
}
pkgName := pkgPathToName(pkgPath)
err := validateNinjaName(pkgName)
if err != nil {
panic(err)
}
i := strings.LastIndex(pkgPath, "/")
shortName := pkgPath[i+1:]
p, ok := pkgs[pkgPath]
if !ok {
p = &pkg{
fullName: pkgName,
shortName: shortName,
pkgPath: pkgPath,
scope: newScope(nil),
}
pkgs[pkgPath] = p
}
return p
}
// Import enables access to the exported Ninja pools, rules, and variables that
// are defined at the package scope of another Go package. Go's visibility
// rules apply to these references - capitalized names indicate that something
// is exported. It may only be called from a Go package's init() function. The
// Go package path passed to Import must have already been imported into the Go
// package using a Go import statement. The imported variables may then be
// accessed from Ninja strings as "${pkg.Variable}", while the imported rules
// can simply be accessed as exported Go variables from the package. For
// example:
// Import enables access to the exported Ninja pools, rules, and variables
// that are defined at the package scope of another Go package. Go's
// visibility rules apply to these references - capitalized names indicate
// that something is exported. It may only be called from a Go package's
// init() function. The Go package path passed to Import must have already
// been imported into the Go package using a Go import statement. The
// imported variables may then be accessed from Ninja strings as
// "${pkg.Variable}", while the imported rules can simply be accessed as
// exported Go variables from the package. For example:
//
// import (
// "blueprint"
// "foo/bar"
// )
//
// var pctx = NewPackagePath("blah")
//
// func init() {
// blueprint.Import("foo/bar")
// pctx.Import("foo/bar")
// }
//
// ...
//
// func (m *MyModule) GenerateBuildActions(ctx blueprint.Module) {
// ctx.Build(blueprint.BuildParams{
// ctx.Build(pctx, blueprint.BuildParams{
// Rule: bar.SomeRule,
// Outputs: []string{"${bar.SomeVariable}"},
// })
@ -135,15 +178,14 @@ func callerPackage() *pkg {
// from Go's import declaration, which derives the local name from the package
// clause in the imported package. By convention these names are made to match,
// but this is not required.
func Import(pkgPath string) {
callerPkg := callerPackage()
importPkg, ok := pkgs[pkgPath]
func (p *PackageContext) Import(pkgPath string) {
checkCalledFromInit()
importPkg, ok := packageContexts[pkgPath]
if !ok {
panic(fmt.Errorf("package %q has no Blueprints definitions", pkgPath))
panic(fmt.Errorf("package %q has no context", pkgPath))
}
err := callerPkg.scope.AddImport(importPkg.shortName, importPkg.scope)
err := p.scope.AddImport(importPkg.shortName, importPkg.scope)
if err != nil {
panic(err)
}
@ -152,12 +194,11 @@ func Import(pkgPath string) {
// ImportAs provides the same functionality as Import, but it allows the local
// name that will be used to refer to the package to be specified explicitly.
// It may only be called from a Go package's init() function.
func ImportAs(as, pkgPath string) {
callerPkg := callerPackage()
importPkg, ok := pkgs[pkgPath]
func (p *PackageContext) ImportAs(as, pkgPath string) {
checkCalledFromInit()
importPkg, ok := packageContexts[pkgPath]
if !ok {
panic(fmt.Errorf("package %q has no Blueprints definitions", pkgPath))
panic(fmt.Errorf("package %q has no context", pkgPath))
}
err := validateNinjaName(as)
@ -165,14 +206,14 @@ func ImportAs(as, pkgPath string) {
panic(err)
}
err = callerPkg.scope.AddImport(as, importPkg.scope)
err = p.scope.AddImport(as, importPkg.scope)
if err != nil {
panic(err)
}
}
type staticVariable struct {
pkg_ *pkg
pctx *PackageContext
name_ string
value_ string
}
@ -186,16 +227,15 @@ type staticVariable struct {
// represents a Ninja variable that will be output. The name argument should
// exactly match the Go variable name, and the value string may reference other
// Ninja variables that are visible within the calling Go package.
func StaticVariable(name, value string) Variable {
func (p *PackageContext) StaticVariable(name, value string) Variable {
checkCalledFromInit()
err := validateNinjaName(name)
if err != nil {
panic(err)
}
pkg := callerPackage()
v := &staticVariable{pkg, name, value}
err = pkg.scope.AddVariable(v)
v := &staticVariable{p, name, value}
err = p.scope.AddVariable(v)
if err != nil {
panic(err)
}
@ -203,20 +243,20 @@ func StaticVariable(name, value string) Variable {
return v
}
func (v *staticVariable) pkg() *pkg {
return v.pkg_
func (v *staticVariable) packageContext() *PackageContext {
return v.pctx
}
func (v *staticVariable) name() string {
return v.name_
}
func (v *staticVariable) fullName(pkgNames map[*pkg]string) string {
return packageNamespacePrefix(pkgNames[v.pkg_]) + v.name_
func (v *staticVariable) fullName(pkgNames map[*PackageContext]string) string {
return packageNamespacePrefix(pkgNames[v.pctx]) + v.name_
}
func (v *staticVariable) value(interface{}) (*ninjaString, error) {
ninjaStr, err := parseNinjaString(v.pkg_.scope, v.value_)
ninjaStr, err := parseNinjaString(v.pctx.scope, v.value_)
if err != nil {
err = fmt.Errorf("error parsing variable %s value: %s", v, err)
panic(err)
@ -225,11 +265,11 @@ func (v *staticVariable) value(interface{}) (*ninjaString, error) {
}
func (v *staticVariable) String() string {
return v.pkg_.pkgPath + "." + v.name_
return v.pctx.pkgPath + "." + v.name_
}
type variableFunc struct {
pkg_ *pkg
pctx *PackageContext
name_ string
value_ func(interface{}) (string, error)
}
@ -245,18 +285,18 @@ type variableFunc struct {
// exactly match the Go variable name, and the value string returned by f may
// reference other Ninja variables that are visible within the calling Go
// package.
func VariableFunc(name string, f func(config interface{}) (string,
error)) Variable {
func (p *PackageContext) VariableFunc(name string,
f func(config interface{}) (string, error)) Variable {
checkCalledFromInit()
err := validateNinjaName(name)
if err != nil {
panic(err)
}
pkg := callerPackage()
v := &variableFunc{pkg, name, f}
err = pkg.scope.AddVariable(v)
v := &variableFunc{p, name, f}
err = p.scope.AddVariable(v)
if err != nil {
panic(err)
}
@ -275,14 +315,16 @@ func VariableFunc(name string, f func(config interface{}) (string,
// exactly match the Go variable name, and the value string returned by method
// may reference other Ninja variables that are visible within the calling Go
// package.
func VariableConfigMethod(name string, method interface{}) Variable {
func (p *PackageContext) VariableConfigMethod(name string,
method interface{}) Variable {
checkCalledFromInit()
err := validateNinjaName(name)
if err != nil {
panic(err)
}
pkg := callerPackage()
methodValue := reflect.ValueOf(method)
validateVariableMethod(name, methodValue)
@ -292,8 +334,8 @@ func VariableConfigMethod(name string, method interface{}) Variable {
return resultStr, nil
}
v := &variableFunc{pkg, name, fun}
err = pkg.scope.AddVariable(v)
v := &variableFunc{p, name, fun}
err = p.scope.AddVariable(v)
if err != nil {
panic(err)
}
@ -301,16 +343,16 @@ func VariableConfigMethod(name string, method interface{}) Variable {
return v
}
func (v *variableFunc) pkg() *pkg {
return v.pkg_
func (v *variableFunc) packageContext() *PackageContext {
return v.pctx
}
func (v *variableFunc) name() string {
return v.name_
}
func (v *variableFunc) fullName(pkgNames map[*pkg]string) string {
return packageNamespacePrefix(pkgNames[v.pkg_]) + v.name_
func (v *variableFunc) fullName(pkgNames map[*PackageContext]string) string {
return packageNamespacePrefix(pkgNames[v.pctx]) + v.name_
}
func (v *variableFunc) value(config interface{}) (*ninjaString, error) {
@ -319,7 +361,7 @@ func (v *variableFunc) value(config interface{}) (*ninjaString, error) {
return nil, err
}
ninjaStr, err := parseNinjaString(v.pkg_.scope, value)
ninjaStr, err := parseNinjaString(v.pctx.scope, value)
if err != nil {
err = fmt.Errorf("error parsing variable %s value: %s", v, err)
panic(err)
@ -329,7 +371,7 @@ func (v *variableFunc) value(config interface{}) (*ninjaString, error) {
}
func (v *variableFunc) String() string {
return v.pkg_.pkgPath + "." + v.name_
return v.pctx.pkgPath + "." + v.name_
}
func validateVariableMethod(name string, methodValue reflect.Value) {
@ -361,7 +403,7 @@ type argVariable struct {
name_ string
}
func (v *argVariable) pkg() *pkg {
func (v *argVariable) packageContext() *PackageContext {
panic("this should not be called")
}
@ -369,7 +411,7 @@ func (v *argVariable) name() string {
return v.name_
}
func (v *argVariable) fullName(pkgNames map[*pkg]string) string {
func (v *argVariable) fullName(pkgNames map[*PackageContext]string) string {
return v.name_
}
@ -382,7 +424,7 @@ func (v *argVariable) String() string {
}
type staticPool struct {
pkg_ *pkg
pctx *PackageContext
name_ string
params PoolParams
}
@ -396,37 +438,37 @@ type staticPool struct {
// represents a Ninja pool that will be output. The name argument should
// exactly match the Go variable name, and the params fields may reference other
// Ninja variables that are visible within the calling Go package.
func StaticPool(name string, params PoolParams) Pool {
func (p *PackageContext) StaticPool(name string, params PoolParams) Pool {
checkCalledFromInit()
err := validateNinjaName(name)
if err != nil {
panic(err)
}
pkg := callerPackage()
p := &staticPool{pkg, name, params}
err = pkg.scope.AddPool(p)
pool := &staticPool{p, name, params}
err = p.scope.AddPool(pool)
if err != nil {
panic(err)
}
return p
return pool
}
func (p *staticPool) pkg() *pkg {
return p.pkg_
func (p *staticPool) packageContext() *PackageContext {
return p.pctx
}
func (p *staticPool) name() string {
return p.name_
}
func (p *staticPool) fullName(pkgNames map[*pkg]string) string {
return packageNamespacePrefix(pkgNames[p.pkg_]) + p.name_
func (p *staticPool) fullName(pkgNames map[*PackageContext]string) string {
return packageNamespacePrefix(pkgNames[p.pctx]) + p.name_
}
func (p *staticPool) def(config interface{}) (*poolDef, error) {
def, err := parsePoolParams(p.pkg_.scope, &p.params)
def, err := parsePoolParams(p.pctx.scope, &p.params)
if err != nil {
panic(fmt.Errorf("error parsing PoolParams for %s: %s", p, err))
}
@ -434,11 +476,11 @@ func (p *staticPool) def(config interface{}) (*poolDef, error) {
}
func (p *staticPool) String() string {
return p.pkg_.pkgPath + "." + p.name_
return p.pctx.pkgPath + "." + p.name_
}
type poolFunc struct {
pkg_ *pkg
pctx *PackageContext
name_ string
paramsFunc func(interface{}) (PoolParams, error)
}
@ -453,33 +495,35 @@ type poolFunc struct {
// exactly match the Go variable name, and the string fields of the PoolParams
// returned by f may reference other Ninja variables that are visible within the
// calling Go package.
func PoolFunc(name string, f func(interface{}) (PoolParams, error)) Pool {
func (p *PackageContext) PoolFunc(name string, f func(interface{}) (PoolParams,
error)) Pool {
checkCalledFromInit()
err := validateNinjaName(name)
if err != nil {
panic(err)
}
pkg := callerPackage()
p := &poolFunc{pkg, name, f}
err = pkg.scope.AddPool(p)
pool := &poolFunc{p, name, f}
err = p.scope.AddPool(pool)
if err != nil {
panic(err)
}
return p
return pool
}
func (p *poolFunc) pkg() *pkg {
return p.pkg_
func (p *poolFunc) packageContext() *PackageContext {
return p.pctx
}
func (p *poolFunc) name() string {
return p.name_
}
func (p *poolFunc) fullName(pkgNames map[*pkg]string) string {
return packageNamespacePrefix(pkgNames[p.pkg_]) + p.name_
func (p *poolFunc) fullName(pkgNames map[*PackageContext]string) string {
return packageNamespacePrefix(pkgNames[p.pctx]) + p.name_
}
func (p *poolFunc) def(config interface{}) (*poolDef, error) {
@ -487,7 +531,7 @@ func (p *poolFunc) def(config interface{}) (*poolDef, error) {
if err != nil {
return nil, err
}
def, err := parsePoolParams(p.pkg_.scope, &params)
def, err := parsePoolParams(p.pctx.scope, &params)
if err != nil {
panic(fmt.Errorf("error parsing PoolParams for %s: %s", p, err))
}
@ -495,11 +539,11 @@ func (p *poolFunc) def(config interface{}) (*poolDef, error) {
}
func (p *poolFunc) String() string {
return p.pkg_.pkgPath + "." + p.name_
return p.pctx.pkgPath + "." + p.name_
}
type staticRule struct {
pkg_ *pkg
pctx *PackageContext
name_ string
params RuleParams
argNames map[string]bool
@ -524,8 +568,10 @@ type staticRule struct {
// results in the package-scoped variable's value being used for build
// statements that do not override the argument. For argument names that do not
// shadow package-scoped variables the default value is an empty string.
func StaticRule(name string, params RuleParams, argNames ...string) Rule {
pkg := callerPackage()
func (p *PackageContext) StaticRule(name string, params RuleParams,
argNames ...string) Rule {
checkCalledFromInit()
err := validateNinjaName(name)
if err != nil {
@ -544,8 +590,8 @@ func StaticRule(name string, params RuleParams, argNames ...string) Rule {
ruleScope := (*basicScope)(nil) // This will get created lazily
r := &staticRule{pkg, name, params, argNamesSet, ruleScope}
err = pkg.scope.AddRule(r)
r := &staticRule{p, name, params, argNamesSet, ruleScope}
err = p.scope.AddRule(r)
if err != nil {
panic(err)
}
@ -553,16 +599,16 @@ func StaticRule(name string, params RuleParams, argNames ...string) Rule {
return r
}
func (r *staticRule) pkg() *pkg {
return r.pkg_
func (r *staticRule) packageContext() *PackageContext {
return r.pctx
}
func (r *staticRule) name() string {
return r.name_
}
func (r *staticRule) fullName(pkgNames map[*pkg]string) string {
return packageNamespacePrefix(pkgNames[r.pkg_]) + r.name_
func (r *staticRule) fullName(pkgNames map[*PackageContext]string) string {
return packageNamespacePrefix(pkgNames[r.pctx]) + r.name_
}
func (r *staticRule) def(interface{}) (*ruleDef, error) {
@ -578,7 +624,7 @@ func (r *staticRule) scope() *basicScope {
// declared before the args are created. Otherwise we could incorrectly
// shadow a package-scoped variable with an arg variable.
if r.scope_ == nil {
r.scope_ = makeRuleScope(r.pkg_.scope, r.argNames)
r.scope_ = makeRuleScope(r.pctx.scope, r.argNames)
}
return r.scope_
}
@ -588,11 +634,11 @@ func (r *staticRule) isArg(argName string) bool {
}
func (r *staticRule) String() string {
return r.pkg_.pkgPath + "." + r.name_
return r.pctx.pkgPath + "." + r.name_
}
type ruleFunc struct {
pkg_ *pkg
pctx *PackageContext
name_ string
paramsFunc func(interface{}) (RuleParams, error)
argNames map[string]bool
@ -618,10 +664,10 @@ type ruleFunc struct {
// scoped variable results in the package-scoped variable's value being used for
// build statements that do not override the argument. For argument names that
// do not shadow package-scoped variables the default value is an empty string.
func RuleFunc(name string, f func(interface{}) (RuleParams, error),
argNames ...string) Rule {
func (p *PackageContext) RuleFunc(name string, f func(interface{}) (RuleParams,
error), argNames ...string) Rule {
pkg := callerPackage()
checkCalledFromInit()
err := validateNinjaName(name)
if err != nil {
@ -640,25 +686,25 @@ func RuleFunc(name string, f func(interface{}) (RuleParams, error),
ruleScope := (*basicScope)(nil) // This will get created lazily
r := &ruleFunc{pkg, name, f, argNamesSet, ruleScope}
err = pkg.scope.AddRule(r)
rule := &ruleFunc{p, name, f, argNamesSet, ruleScope}
err = p.scope.AddRule(rule)
if err != nil {
panic(err)
}
return r
return rule
}
func (r *ruleFunc) pkg() *pkg {
return r.pkg_
func (r *ruleFunc) packageContext() *PackageContext {
return r.pctx
}
func (r *ruleFunc) name() string {
return r.name_
}
func (r *ruleFunc) fullName(pkgNames map[*pkg]string) string {
return packageNamespacePrefix(pkgNames[r.pkg_]) + r.name_
func (r *ruleFunc) fullName(pkgNames map[*PackageContext]string) string {
return packageNamespacePrefix(pkgNames[r.pctx]) + r.name_
}
func (r *ruleFunc) def(config interface{}) (*ruleDef, error) {
@ -678,7 +724,7 @@ func (r *ruleFunc) scope() *basicScope {
// before the args are created. Otherwise we could incorrectly shadow a
// global variable with an arg variable.
if r.scope_ == nil {
r.scope_ = makeRuleScope(r.pkg_.scope, r.argNames)
r.scope_ = makeRuleScope(r.pctx.scope, r.argNames)
}
return r.scope_
}
@ -688,7 +734,7 @@ func (r *ruleFunc) isArg(argName string) bool {
}
func (r *ruleFunc) String() string {
return r.pkg_.pkgPath + "." + r.name_
return r.pctx.pkgPath + "." + r.name_
}
type builtinRule struct {
@ -696,7 +742,7 @@ type builtinRule struct {
scope_ *basicScope
}
func (r *builtinRule) pkg() *pkg {
func (r *builtinRule) packageContext() *PackageContext {
return nil
}
@ -704,7 +750,7 @@ func (r *builtinRule) name() string {
return r.name_
}
func (r *builtinRule) fullName(pkgNames map[*pkg]string) string {
func (r *builtinRule) fullName(pkgNames map[*PackageContext]string) string {
return r.name_
}

View file

@ -11,9 +11,9 @@ import (
// to the output .ninja file. A variable may contain references to other global
// Ninja variables, but circular variable references are not allowed.
type Variable interface {
pkg() *pkg
packageContext() *PackageContext
name() string // "foo"
fullName(pkgNames map[*pkg]string) string // "pkg.foo" or "path.to.pkg.foo"
fullName(pkgNames map[*PackageContext]string) string // "pkg.foo" or "path.to.pkg.foo"
value(config interface{}) (*ninjaString, error)
String() string
}
@ -21,9 +21,9 @@ type Variable interface {
// A Pool represents a Ninja pool that will be written to the output .ninja
// file.
type Pool interface {
pkg() *pkg
packageContext() *PackageContext
name() string // "foo"
fullName(pkgNames map[*pkg]string) string // "pkg.foo" or "path.to.pkg.foo"
fullName(pkgNames map[*PackageContext]string) string // "pkg.foo" or "path.to.pkg.foo"
def(config interface{}) (*poolDef, error)
String() string
}
@ -31,9 +31,9 @@ type Pool interface {
// A Rule represents a Ninja build rule that will be written to the output
// .ninja file.
type Rule interface {
pkg() *pkg
packageContext() *PackageContext
name() string // "foo"
fullName(pkgNames map[*pkg]string) string // "pkg.foo" or "path.to.pkg.foo"
fullName(pkgNames map[*PackageContext]string) string // "pkg.foo" or "path.to.pkg.foo"
def(config interface{}) (*ruleDef, error)
scope() *basicScope
isArg(argName string) bool
@ -237,26 +237,12 @@ func newLocalScope(parent *basicScope, namePrefix string) *localScope {
}
}
// ReparentToCallerPackage sets the localScope's parent scope to the scope of
// the Go package of the caller. This allows a ModuleContext and
// SingletonContext to be passed to a function defined in a different Go package
// with that function having access to all of the package-scoped variables of
// its own package.
//
// The skip argument has the same meaning as the skip argument of
// runtime.Callers.
func (s *localScope) ReparentToCallerPackage(skip int) {
var pkgScope *basicScope
pkgPath, _ := callerName(skip + 1)
pkg, ok := pkgs[pkgPath]
if ok {
pkgScope = pkg.scope
} else {
pkgScope = newScope(nil)
}
s.scope.parent = pkgScope
// ReparentTo sets the localScope's parent scope to the scope of the given
// package context. This allows a ModuleContext and SingletonContext to call
// a function defined in a different Go package and have that function retain
// access to all of the package-scoped variables of its own package.
func (s *localScope) ReparentTo(pctx *PackageContext) {
s.scope.parent = pctx.scope
}
func (s *localScope) LookupVariable(name string) (Variable, error) {
@ -349,7 +335,7 @@ type localVariable struct {
value_ *ninjaString
}
func (l *localVariable) pkg() *pkg {
func (l *localVariable) packageContext() *PackageContext {
return nil
}
@ -357,7 +343,7 @@ func (l *localVariable) name() string {
return l.name_
}
func (l *localVariable) fullName(pkgNames map[*pkg]string) string {
func (l *localVariable) fullName(pkgNames map[*PackageContext]string) string {
return l.namePrefix + l.name_
}
@ -377,7 +363,7 @@ type localRule struct {
scope_ *basicScope
}
func (l *localRule) pkg() *pkg {
func (l *localRule) packageContext() *PackageContext {
return nil
}
@ -385,7 +371,7 @@ func (l *localRule) name() string {
return l.name_
}
func (l *localRule) fullName(pkgNames map[*pkg]string) string {
func (l *localRule) fullName(pkgNames map[*PackageContext]string) string {
return l.namePrefix + l.name_
}

View file

@ -19,16 +19,16 @@ type SingletonContext interface {
ModuleErrorf(module Module, format string, args ...interface{})
Errorf(format string, args ...interface{})
Variable(name, value string)
Rule(name string, params RuleParams, argNames ...string) Rule
Build(params BuildParams)
Variable(pctx *PackageContext, name, value string)
Rule(pctx *PackageContext, name string, params RuleParams, argNames ...string) Rule
Build(pctx *PackageContext, params BuildParams)
RequireNinjaVersion(major, minor, micro int)
// SetBuildDir sets the value of the top-level "builddir" Ninja variable
// that controls where Ninja stores its build log files. This value can be
// set at most one time for a single build. Setting it multiple times (even
// across different singletons) will result in a panic.
SetBuildDir(value string)
SetBuildDir(pctx *PackageContext, value string)
VisitAllModules(visit func(Module))
VisitAllModulesIf(pred func(Module) bool, visit func(Module))
@ -86,9 +86,8 @@ func (s *singletonContext) Errorf(format string, args ...interface{}) {
s.errs = append(s.errs, fmt.Errorf(format, args...))
}
func (s *singletonContext) Variable(name, value string) {
const skip = 2
s.scope.ReparentToCallerPackage(skip)
func (s *singletonContext) Variable(pctx *PackageContext, name, value string) {
s.scope.ReparentTo(pctx)
v, err := s.scope.AddLocalVariable(name, value)
if err != nil {
@ -98,11 +97,10 @@ func (s *singletonContext) Variable(name, value string) {
s.actionDefs.variables = append(s.actionDefs.variables, v)
}
func (s *singletonContext) Rule(name string, params RuleParams,
argNames ...string) Rule {
func (s *singletonContext) Rule(pctx *PackageContext, name string,
params RuleParams, argNames ...string) Rule {
const skip = 2
s.scope.ReparentToCallerPackage(skip)
s.scope.ReparentTo(pctx)
r, err := s.scope.AddLocalRule(name, &params, argNames...)
if err != nil {
@ -114,9 +112,8 @@ func (s *singletonContext) Rule(name string, params RuleParams,
return r
}
func (s *singletonContext) Build(params BuildParams) {
const skip = 2
s.scope.ReparentToCallerPackage(skip)
func (s *singletonContext) Build(pctx *PackageContext, params BuildParams) {
s.scope.ReparentTo(pctx)
def, err := parseBuildParams(s.scope, &params)
if err != nil {
@ -130,9 +127,8 @@ func (s *singletonContext) RequireNinjaVersion(major, minor, micro int) {
s.context.requireNinjaVersion(major, minor, micro)
}
func (s *singletonContext) SetBuildDir(value string) {
const skip = 2
s.scope.ReparentToCallerPackage(skip)
func (s *singletonContext) SetBuildDir(pctx *PackageContext, value string) {
s.scope.ReparentTo(pctx)
ninjaValue, err := parseNinjaString(s.scope, value)
if err != nil {

View file

@ -62,13 +62,13 @@ rule g.bootstrap.pack
build .bootstrap/blueprint/obj/_go_.${g.bootstrap.GoChar}: g.bootstrap.gc $
${g.bootstrap.SrcDir}/blueprint/context.go $
${g.bootstrap.SrcDir}/blueprint/globals.go $
${g.bootstrap.SrcDir}/blueprint/live_tracker.go $
${g.bootstrap.SrcDir}/blueprint/mangle.go $
${g.bootstrap.SrcDir}/blueprint/module_ctx.go $
${g.bootstrap.SrcDir}/blueprint/ninja_defs.go $
${g.bootstrap.SrcDir}/blueprint/ninja_strings.go $
${g.bootstrap.SrcDir}/blueprint/ninja_writer.go $
${g.bootstrap.SrcDir}/blueprint/package_ctx.go $
${g.bootstrap.SrcDir}/blueprint/scope.go $
${g.bootstrap.SrcDir}/blueprint/singleton_ctx.go $
${g.bootstrap.SrcDir}/blueprint/unpack.go | $