Fix scoping logic to respect the caller's package.
This change makes the ModuleContext and SingletonContext methods respect the caller's Go package when performing name lookups. It does this by re-parenting the module or singleton's scope to the scope of the caller's package, thus allowing a Module or Singleton to pass its context to a function defined in another Go package and have the called function access variables and rules defined in its own Go package. Change-Id: Ifdec87ba3095a453b36fb00e38c0bb3a928a2b9b
This commit is contained in:
parent
3159cb7673
commit
0ed63eff29
4 changed files with 72 additions and 16 deletions
|
@ -4,7 +4,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
@ -18,8 +17,6 @@ type pkg struct {
|
|||
|
||||
var pkgs = map[string]*pkg{}
|
||||
|
||||
var pkgRegexp = regexp.MustCompile(`(.*)\.init(·[0-9]+)?`)
|
||||
|
||||
var Phony Rule = &builtinRule{
|
||||
name_: "phony",
|
||||
}
|
||||
|
@ -37,6 +34,36 @@ func pkgPathToName(pkgPath string) string {
|
|||
return strings.Replace(pkgPath, "/", ".", -1)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
var pc [1]uintptr
|
||||
n := runtime.Callers(skip+1, pc[:])
|
||||
if n != 1 {
|
||||
panic("unable to get caller pc")
|
||||
}
|
||||
|
||||
f := runtime.FuncForPC(pc[0])
|
||||
fullName := f.Name()
|
||||
|
||||
lastDotIndex := strings.LastIndex(fullName, ".")
|
||||
if lastDotIndex == -1 {
|
||||
panic("unable to distinguish function name from package")
|
||||
}
|
||||
|
||||
if fullName[lastDotIndex-1] == ')' {
|
||||
// The caller is a method on some type, so it's name looks like
|
||||
// "pkg/path.(type).method". We need to go back one dot farther to get
|
||||
// to the package name.
|
||||
lastDotIndex = strings.LastIndex(fullName[:lastDotIndex], ".")
|
||||
}
|
||||
|
||||
pkgPath = fullName[:lastDotIndex]
|
||||
funcName = fullName[lastDotIndex+1:]
|
||||
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.
|
||||
|
@ -46,23 +73,12 @@ func pkgPathToName(pkgPath string) string {
|
|||
// 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 {
|
||||
var pc [1]uintptr
|
||||
n := runtime.Callers(3, pc[:])
|
||||
if n != 1 {
|
||||
panic("unable to get caller pc")
|
||||
}
|
||||
pkgPath, funcName := callerName(3)
|
||||
|
||||
f := runtime.FuncForPC(pc[0])
|
||||
callerName := f.Name()
|
||||
|
||||
submatches := pkgRegexp.FindSubmatch([]byte(callerName))
|
||||
if submatches == nil {
|
||||
println(callerName)
|
||||
if funcName != "init" && !strings.HasPrefix(funcName, "init·") {
|
||||
panic("not called from an init func")
|
||||
}
|
||||
|
||||
pkgPath := string(submatches[1])
|
||||
|
||||
pkgName := pkgPathToName(pkgPath)
|
||||
err := validateNinjaName(pkgName)
|
||||
if err != nil {
|
||||
|
|
|
@ -93,6 +93,9 @@ func (m *moduleContext) OtherModuleErrorf(module Module, format string,
|
|||
}
|
||||
|
||||
func (m *moduleContext) Variable(name, value string) {
|
||||
const skip = 2
|
||||
m.scope.ReparentToCallerPackage(skip)
|
||||
|
||||
v, err := m.scope.AddLocalVariable(name, value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -104,6 +107,9 @@ func (m *moduleContext) Variable(name, value string) {
|
|||
func (m *moduleContext) Rule(name string, params RuleParams,
|
||||
argNames ...string) Rule {
|
||||
|
||||
const skip = 2
|
||||
m.scope.ReparentToCallerPackage(skip)
|
||||
|
||||
r, err := m.scope.AddLocalRule(name, ¶ms, argNames...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -115,6 +121,9 @@ func (m *moduleContext) Rule(name string, params RuleParams,
|
|||
}
|
||||
|
||||
func (m *moduleContext) Build(params BuildParams) {
|
||||
const skip = 2
|
||||
m.scope.ReparentToCallerPackage(skip)
|
||||
|
||||
def, err := parseBuildParams(m.scope, ¶ms)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
|
@ -237,6 +237,28 @@ 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
|
||||
}
|
||||
|
||||
func (s *localScope) LookupVariable(name string) (Variable, error) {
|
||||
return s.scope.LookupVariable(name)
|
||||
}
|
||||
|
|
|
@ -87,6 +87,9 @@ func (s *singletonContext) Errorf(format string, args ...interface{}) {
|
|||
}
|
||||
|
||||
func (s *singletonContext) Variable(name, value string) {
|
||||
const skip = 2
|
||||
s.scope.ReparentToCallerPackage(skip)
|
||||
|
||||
v, err := s.scope.AddLocalVariable(name, value)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -98,6 +101,9 @@ func (s *singletonContext) Variable(name, value string) {
|
|||
func (s *singletonContext) Rule(name string, params RuleParams,
|
||||
argNames ...string) Rule {
|
||||
|
||||
const skip = 2
|
||||
s.scope.ReparentToCallerPackage(skip)
|
||||
|
||||
r, err := s.scope.AddLocalRule(name, ¶ms, argNames...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -109,6 +115,9 @@ func (s *singletonContext) Rule(name string, params RuleParams,
|
|||
}
|
||||
|
||||
func (s *singletonContext) Build(params BuildParams) {
|
||||
const skip = 2
|
||||
s.scope.ReparentToCallerPackage(skip)
|
||||
|
||||
def, err := parseBuildParams(s.scope, ¶ms)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
|
Loading…
Reference in a new issue