Initial Blueprint commit.

Blueprint is a build system component that reads Blueprints files defining
modules to be built, and generates a Ninja build manifest that can be used to
perform all the build actions.  It does not dictate or implement much build
policy itself, but rather provides a framework to ease the process of defining
build logic in Go.

The "blueprint" and "blueprint/parser" Go packages contain the functionality
for reading Blueprint files and invoking build logic functions defined in other
Go packages.

The "blueprint/bootstrap" Go package contains just enough build logic to build
a binary that includes Blueprint and any pure-Go (i.e. no cgo) build logic
defined in external Go packages.  This can be used to create a minimal Ninja
file that's capable of bootstrapping a Blueprint-based build system from
source.

The "blueprint/bootstrap/minibp" Go package contains code for a minimal binary
that includes the build logic defined in the "blueprint/bootstrap" package.
This binary can then create the Ninja file for the bootstrapping process.

Change-Id: I8d8390042372a72d225785cda738525001b009f1
This commit is contained in:
Jamie Gennis 2014-05-27 16:34:41 -07:00
parent 2ec08c8555
commit 1bc967ed43
25 changed files with 5629 additions and 0 deletions

38
Blueprints Normal file
View file

@ -0,0 +1,38 @@
bootstrap_go_package {
name: "blueprint",
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/scope.go",
"blueprint/singleton_ctx.go",
"blueprint/unpack.go"],
}
bootstrap_go_package {
name: "blueprint-parser",
pkgPath: "blueprint/parser",
srcs: ["blueprint/parser/parser.go"],
}
bootstrap_go_package {
name: "blueprint-bootstrap",
deps: ["blueprint"],
pkgPath: "blueprint/bootstrap",
srcs: ["blueprint/bootstrap/bootstrap.go",
"blueprint/bootstrap/command.go",
"blueprint/bootstrap/config.go",
"blueprint/bootstrap/doc.go"],
}
bootstrap_go_binary {
name: "minibp",
deps: ["blueprint", "blueprint-bootstrap"],
srcs: ["blueprint/bootstrap/minibp/main.go"],
}

View file

@ -0,0 +1,490 @@
package bootstrap
import (
"blueprint"
"fmt"
"path/filepath"
"strings"
)
var (
gcCmd = blueprint.StaticVariable("gcCmd", "$goToolDir/${GoChar}g")
packCmd = blueprint.StaticVariable("packCmd", "$goToolDir/pack")
linkCmd = blueprint.StaticVariable("linkCmd", "$goToolDir/${GoChar}l")
gc = blueprint.StaticRule("gc",
blueprint.RuleParams{
Command: "GOROOT='$GoRoot' $gcCmd -o $out -p $pkgPath -complete " +
"$incFlags $in",
Description: "${GoChar}g $out",
},
"pkgPath", "incFlags")
pack = blueprint.StaticRule("pack",
blueprint.RuleParams{
Command: "GOROOT='$GoRoot' $packCmd grcP $prefix $out $in",
Description: "pack $out",
},
"prefix")
link = blueprint.StaticRule("link",
blueprint.RuleParams{
Command: "GOROOT='$GoRoot' $linkCmd -o $out $libDirFlags $in",
Description: "${GoChar}l $out",
},
"libDirFlags")
cp = blueprint.StaticRule("cp",
blueprint.RuleParams{
Command: "cp $in $out",
Description: "cp $out",
})
bootstrap = blueprint.StaticRule("bootstrap",
blueprint.RuleParams{
Command: "$Bootstrap $in $BootstrapManifest",
Description: "bootstrap $in",
Generator: true,
})
rebootstrap = blueprint.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,
// so we invoke ninja ourselves once from this. Unfortunately this
// seems to cause "warning: bad deps log signature or version;
// starting over" messages from Ninja. This warning can be avoided
// by having the bootstrap and non-bootstrap build manifests have a
// different builddir (so they use different log files).
//
// This workaround can be avoided entirely by making a simple change
// to Ninja that would allow it to rebuild the manifest twice rather
// than just once.
Command: "$Bootstrap $in && ninja",
Description: "re-bootstrap $in",
Generator: true,
})
minibp = blueprint.StaticRule("minibp",
blueprint.RuleParams{
Command: fmt.Sprintf("%s -d %s -o $out $in",
minibpFile, minibpDepFile),
Description: "minibp $out",
Generator: true,
Restat: true,
Depfile: minibpDepFile,
Deps: blueprint.DepsGCC,
})
// Work around a Ninja issue. See https://github.com/martine/ninja/pull/634
phony = blueprint.StaticRule("phony",
blueprint.RuleParams{
Command: "# phony $out",
Description: "phony $out",
Generator: true,
},
"depfile")
goPackageModule = blueprint.MakeModuleType("goPackageModule", newGoPackage)
goBinaryModule = blueprint.MakeModuleType("goBinaryModule", newGoBinary)
binDir = filepath.Join("bootstrap", "bin")
minibpFile = filepath.Join(binDir, "minibp")
minibpDepFile = filepath.Join("bootstrap", "bootstrap_manifest.d")
)
type goPackageProducer interface {
GoPkgRoot() string
GoPackageTarget() string
}
func isGoPackageProducer(module blueprint.Module) bool {
_, ok := module.(goPackageProducer)
return ok
}
func isBootstrapModule(module blueprint.Module) bool {
_, isPackage := module.(*goPackage)
_, isBinary := module.(*goBinary)
return isPackage || isBinary
}
func isBootstrapBinaryModule(module blueprint.Module) bool {
_, isBinary := module.(*goBinary)
return isBinary
}
func generatingBootstrapper(config blueprint.Config) bool {
bootstrapConfig, ok := config.(Config)
if ok {
return bootstrapConfig.GeneratingBootstrapper()
}
return false
}
// A goPackage is a module for building Go packages.
type goPackage struct {
properties struct {
PkgPath string
Srcs []string
}
// The root dir in which the package .a file is located. The full .a file
// path will be "packageRoot/PkgPath.a"
pkgRoot string
// The path of the .a file that is to be built.
archiveFile string
}
var _ goPackageProducer = (*goPackage)(nil)
func newGoPackage() (blueprint.Module, interface{}) {
module := &goPackage{}
return module, &module.properties
}
func (g *goPackage) GoPkgRoot() string {
return g.pkgRoot
}
func (g *goPackage) GoPackageTarget() string {
return g.archiveFile
}
func (g *goPackage) GenerateBuildActions(ctx blueprint.ModuleContext) {
name := ctx.ModuleName()
if g.properties.PkgPath == "" {
ctx.ModuleErrorf("module %s did not specify a valid pkgPath", name)
return
}
g.pkgRoot = packageRoot(ctx)
g.archiveFile = filepath.Clean(filepath.Join(g.pkgRoot,
filepath.FromSlash(g.properties.PkgPath)+".a"))
// We only actually want to build the builder modules if we're running as
// minibp (i.e. we're generating a bootstrap Ninja file). This is to break
// the circular dependence that occurs when the builder requires a new Ninja
// file to be built, but building a new ninja file requires the builder to
// be built.
if generatingBootstrapper(ctx.Config()) {
buildGoPackage(ctx, g.pkgRoot, g.properties.PkgPath, g.archiveFile,
g.properties.Srcs)
} else {
phonyGoTarget(ctx, g.archiveFile, g.properties.Srcs)
}
}
// A goBinary is a module for building executable binaries from Go sources.
type goBinary struct {
properties struct {
Srcs []string
PrimaryBuilder bool
}
}
func newGoBinary() (blueprint.Module, interface{}) {
module := &goBinary{}
return module, &module.properties
}
func (g *goBinary) GenerateBuildActions(ctx blueprint.ModuleContext) {
var (
name = ctx.ModuleName()
objDir = objDir(ctx)
archiveFile = filepath.Join(objDir, name+".a")
aoutFile = filepath.Join(objDir, "a.out")
binaryFile = filepath.Join(binDir, name)
)
// We only actually want to build the builder modules if we're running as
// minibp (i.e. we're generating a bootstrap Ninja file). This is to break
// the circular dependence that occurs when the builder requires a new Ninja
// file to be built, but building a new ninja file requires the builder to
// be built.
if generatingBootstrapper(ctx.Config()) {
buildGoPackage(ctx, objDir, name, archiveFile, g.properties.Srcs)
var libDirFlags []string
ctx.VisitDepsDepthFirstIf(isGoPackageProducer,
func(module blueprint.Module) {
dep := module.(goPackageProducer)
libDir := dep.GoPkgRoot()
libDirFlags = append(libDirFlags, "-L "+libDir)
})
linkArgs := map[string]string{}
if len(libDirFlags) > 0 {
linkArgs["libDirFlags"] = strings.Join(libDirFlags, " ")
}
ctx.Build(blueprint.BuildParams{
Rule: link,
Outputs: []string{aoutFile},
Inputs: []string{archiveFile},
Args: linkArgs,
})
ctx.Build(blueprint.BuildParams{
Rule: cp,
Outputs: []string{binaryFile},
Inputs: []string{aoutFile},
})
} else {
phonyGoTarget(ctx, binaryFile, g.properties.Srcs)
}
}
func buildGoPackage(ctx blueprint.ModuleContext, pkgRoot string,
pkgPath string, archiveFile string, srcs []string) {
srcDir := srcDir(ctx)
srcFiles := PrefixPaths(srcs, srcDir)
objDir := objDir(ctx)
objFile := filepath.Join(objDir, "_go_.$GoChar")
var incFlags []string
var depTargets []string
ctx.VisitDepsDepthFirstIf(isGoPackageProducer,
func(module blueprint.Module) {
dep := module.(goPackageProducer)
incDir := dep.GoPkgRoot()
target := dep.GoPackageTarget()
incFlags = append(incFlags, "-I "+incDir)
depTargets = append(depTargets, target)
})
gcArgs := map[string]string{
"pkgPath": pkgPath,
}
if len(incFlags) > 0 {
gcArgs["incFlags"] = strings.Join(incFlags, " ")
}
ctx.Build(blueprint.BuildParams{
Rule: gc,
Outputs: []string{objFile},
Inputs: srcFiles,
Implicits: depTargets,
Args: gcArgs,
})
ctx.Build(blueprint.BuildParams{
Rule: pack,
Outputs: []string{archiveFile},
Inputs: []string{objFile},
Args: map[string]string{
"prefix": pkgRoot,
},
})
}
func phonyGoTarget(ctx blueprint.ModuleContext, target string, srcs []string) {
var depTargets []string
ctx.VisitDepsDepthFirstIf(isGoPackageProducer,
func(module blueprint.Module) {
dep := module.(goPackageProducer)
target := dep.GoPackageTarget()
depTargets = append(depTargets, target)
})
moduleDir := ctx.ModuleDir()
srcs = PrefixPaths(srcs, filepath.Join("$SrcDir", moduleDir))
ctx.Build(blueprint.BuildParams{
Rule: phony,
Outputs: []string{target},
Inputs: srcs,
Implicits: depTargets,
})
// If one of the source files gets deleted or renamed that will prevent the
// re-bootstrapping happening because it depends on the missing source file.
// To get around this we add a build statement using the built-in phony rule
// for each source file, which will cause Ninja to treat it as dirty if its
// missing.
for _, src := range srcs {
ctx.Build(blueprint.BuildParams{
Rule: blueprint.Phony,
Outputs: []string{src},
})
}
}
type singleton struct{}
func newSingleton() *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 []*goBinary
ctx.VisitAllModulesIf(isBootstrapBinaryModule,
func(module blueprint.Module) {
binaryModule := module.(*goBinary)
if binaryModule.properties.PrimaryBuilder {
primaryBuilders = append(primaryBuilders, binaryModule)
}
})
var primaryBuilderName, primaryBuilderExtraFlags string
switch len(primaryBuilders) {
case 0:
// If there's no primary builder module then that means we'll use minibp
// as the primary builder. We can trigger its primary builder mode with
// the -p flag.
primaryBuilderName = "minibp"
primaryBuilderExtraFlags = "-p"
case 1:
primaryBuilderName = ctx.ModuleName(primaryBuilders[0])
default:
ctx.Errorf("multiple primary builder modules present:")
for _, primaryBuilder := range primaryBuilders {
ctx.ModuleErrorf(primaryBuilder, "<-- module %s",
ctx.ModuleName(primaryBuilder))
}
return
}
primaryBuilderFile := filepath.Join(binDir, primaryBuilderName)
// Get the filename of the top-level Blueprints file to pass to minibp.
// This comes stored in a global variable that's set by Main.
topLevelBlueprints := filepath.Join("$SrcDir",
filepath.Base(topLevelBlueprintsFile))
tmpNinjaFile := filepath.Join("bootstrap", "build.ninja.in")
tmpNinjaDepFile := tmpNinjaFile + ".d"
if generatingBootstrapper(ctx.Config()) {
// We're generating a bootstrapper Ninja file, so we need to set things
// up to rebuild the build.ninja file using the primary builder.
// We generate the depfile here that includes the dependencies for all
// the Blueprints files that contribute to generating the big build
// manifest (build.ninja file). This depfile will be used by the non-
// bootstrap build manifest to determine whether it should trigger a re-
// 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",
blueprint.RuleParams{
Command: fmt.Sprintf("%s %s -d %s -o $out $in",
primaryBuilderFile, primaryBuilderExtraFlags,
tmpNinjaDepFile),
Description: fmt.Sprintf("%s $out", primaryBuilderName),
Depfile: tmpNinjaDepFile,
})
ctx.Build(blueprint.BuildParams{
Rule: bigbp,
Outputs: []string{tmpNinjaFile},
Inputs: []string{topLevelBlueprints},
Implicits: []string{primaryBuilderFile},
})
// When the current build.ninja file is a bootstrapper, we always want
// to have it replace itself with a non-bootstrapper build.ninja. To
// accomplish that we depend on a file that should never exist and
// "build" it using Ninja's built-in phony rule.
//
// We also need to add an implicit dependency on the minibp binary so
// that it actually gets built. Nothing in the bootstrap build.ninja
// file actually requires minibp, but the non-bootstrap build.ninja
// requires that it have been built during the bootstrapping.
notAFile := filepath.Join("bootstrap", "notAFile")
ctx.Build(blueprint.BuildParams{
Rule: blueprint.Phony,
Outputs: []string{notAFile},
})
ctx.Build(blueprint.BuildParams{
Rule: bootstrap,
Outputs: []string{"build.ninja"},
Inputs: []string{tmpNinjaFile},
Implicits: []string{"$Bootstrap", notAFile, minibpFile},
})
// Because the non-bootstrap build.ninja file manually re-invokes Ninja,
// its builddir must be different than that of the bootstrap build.ninja
// 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("bootstrap")
} else {
// We're generating a non-bootstrapper Ninja file, so we need to set it
// up to depend on the bootstrapper Ninja file. The build.ninja target
// also has an implicit dependency on the primary builder, which will
// have a phony dependency on all its sources. This will cause any
// changes to the primary builder's sources to trigger a re-bootstrap
// operation, which will rebuild the primary builder.
//
// 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{
Rule: rebootstrap,
Outputs: []string{"build.ninja"},
Inputs: []string{"$BootstrapManifest"},
Implicits: []string{"$Bootstrap", primaryBuilderFile, tmpNinjaFile},
})
ctx.Build(blueprint.BuildParams{
Rule: phony,
Outputs: []string{tmpNinjaFile},
Inputs: []string{topLevelBlueprints},
Args: map[string]string{
"depfile": tmpNinjaDepFile,
},
})
// Rebuild the bootstrap Ninja file using minibp, passing it all the
// Blueprint files that define a bootstrap_* module.
ctx.Build(blueprint.BuildParams{
Rule: minibp,
Outputs: []string{"$BootstrapManifest"},
Inputs: []string{topLevelBlueprints},
Implicits: []string{minibpFile},
})
}
}
// 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 {
return filepath.Join("bootstrap", ctx.ModuleName(), "pkg")
}
// srcDir returns the path of the directory that all source file paths are
// specified relative to.
func srcDir(ctx blueprint.ModuleContext) string {
return filepath.Join("$SrcDir", ctx.ModuleDir())
}
// objDir returns the module-specific object directory path.
func objDir(ctx blueprint.ModuleContext) string {
return filepath.Join("bootstrap", ctx.ModuleName(), "obj")
}
// PrefixPaths returns a list of paths consisting of prefix joined with each
// element of paths. The resulting paths are "clean" in the filepath.Clean
// sense.
//
// TODO: This should probably go in a utility package.
func PrefixPaths(paths []string, prefix string) []string {
result := make([]string, len(paths))
for i, path := range paths {
result[i] = filepath.Clean(filepath.Join(prefix, path))
}
return result
}

View file

@ -0,0 +1,147 @@
package bootstrap
import (
"blueprint"
"bytes"
"flag"
"fmt"
"io/ioutil"
"os"
"strings"
)
var outFile string
var depFile string
var depTarget string
// topLevelBlueprintsFile is set by Main as a way to pass this information on to
// the bootstrap build manifest generators. This information was not passed via
// the Config object so as to allow the caller of Main to use whatever Config
// object it wants.
var topLevelBlueprintsFile string
func init() {
flag.StringVar(&outFile, "o", "build.ninja.in", "the Ninja file to output")
flag.StringVar(&depFile, "d", "", "the dependency file to output")
flag.StringVar(&depTarget, "t", "", "the target name for the dependency "+
"file")
}
func Main(ctx *blueprint.Context, config blueprint.Config) {
if !flag.Parsed() {
flag.Parse()
}
ctx.RegisterModuleType("bootstrap_go_package", goPackageModule)
ctx.RegisterModuleType("bootstrap_go_binary", goBinaryModule)
ctx.RegisterSingleton("bootstrap", newSingleton())
if flag.NArg() != 1 {
fatalf("no Blueprints file specified")
}
topLevelBlueprintsFile = flag.Arg(0)
deps, errs := ctx.ParseBlueprintsFiles(topLevelBlueprintsFile)
if len(errs) > 0 {
fatalErrors(errs)
}
errs = ctx.PrepareBuildActions(config)
if len(errs) > 0 {
fatalErrors(errs)
}
buf := bytes.NewBuffer(nil)
err := ctx.WriteBuildFile(buf)
if err != nil {
fatalf("error generating Ninja file contents: %s", err)
}
err = writeFileIfChanged(outFile, buf.Bytes(), 0666)
if err != nil {
fatalf("error writing %s: %s", outFile, err)
}
if depFile != "" {
f, err := os.Create(depFile)
if err != nil {
fatalf("error creating depfile: %s", err)
}
target := depTarget
if target == "" {
target = outFile
}
_, err = fmt.Fprintf(f, "%s: \\\n %s\n", target,
strings.Join(deps, " \\\n "))
if err != nil {
fatalf("error writing depfile: %s", err)
}
f.Close()
}
os.Exit(0)
}
func fatalf(format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, format, args...)
os.Exit(1)
}
func fatalErrors(errs []error) {
for _, err := range errs {
switch err.(type) {
case *blueprint.Error:
_, _ = fmt.Fprintf(os.Stderr, "%s\n", err.Error())
default:
_, _ = fmt.Fprintf(os.Stderr, "internal error: %s\n", err)
}
}
os.Exit(1)
}
func writeFileIfChanged(filename string, data []byte, perm os.FileMode) error {
var isChanged bool
info, err := os.Stat(filename)
if err != nil {
if os.IsNotExist(err) {
// The file does not exist yet.
isChanged = true
} else {
return err
}
} else {
if info.Size() != int64(len(data)) {
isChanged = true
} else {
oldData, err := ioutil.ReadFile(filename)
if err != nil {
return err
}
if len(oldData) != len(data) {
isChanged = true
} else {
for i := range data {
if oldData[i] != data[i] {
isChanged = true
break
}
}
}
}
}
if isChanged {
err = ioutil.WriteFile(filename, data, perm)
if err != nil {
return err
}
}
return nil
}

View file

@ -0,0 +1,29 @@
package bootstrap
import (
"blueprint"
)
var (
// These variables are the only 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",
"@@BootstrapManifest@@")
goToolDir = blueprint.StaticVariable("goToolDir",
"$GoRoot/pkg/tool/${GoOS}_$GoArch")
)
type Config interface {
// GeneratingBootstrapper should return true if this build invocation is
// creating a build.ninja.in file to be used in a build bootstrapping
// sequence.
GeneratingBootstrapper() bool
}

View file

@ -0,0 +1,29 @@
/*
The Blueprint bootstrapping mechanism is intended to enable building a source
tree using a Blueprint-based build system that is embedded (as source) in that
source tree. The only prerequisites for performing such a build are:
1. A Ninja binary
2. A script interpreter (e.g. Bash or Python)
3. A Go toolchain
The bootstrapping process is intended to be customized for the source tree in
which it is embedded. As an initial example, we'll examine the bootstrapping
system used to build the core Blueprint packages. Bootstrapping the core
Blueprint packages involves two files:
bootstrap.bash: When this script is run it initializes the current
working directory as a build output directory. It does
this by first automatically determining the root source
directory and Go build environment. It then uses those
values to do a simple string replacement over the
build.ninja.in file contents, and places the result into
the current working directory.
build.ninja.in: This file is generated by passing all the Blueprint
files needed to construct the primary builder
*/
package bootstrap

View file

@ -0,0 +1,32 @@
package main
import (
"blueprint"
"blueprint/bootstrap"
"flag"
)
var runAsPrimaryBuilder bool
func init() {
flag.BoolVar(&runAsPrimaryBuilder, "p", false, "run as a primary builder")
}
type Config bool
func (c Config) GeneratingBootstrapper() bool {
return bool(c)
}
func main() {
flag.Parse()
ctx := blueprint.NewContext()
if !runAsPrimaryBuilder {
ctx.SetIgnoreUnknownModuleTypes(true)
}
config := Config(!runAsPrimaryBuilder)
bootstrap.Main(ctx, config)
}

1363
blueprint/context.go Normal file

File diff suppressed because it is too large Load diff

91
blueprint/context_test.go Normal file
View file

@ -0,0 +1,91 @@
package blueprint
import (
"bytes"
"testing"
)
type fooModule struct {
properties struct {
Foo string
}
}
var FooModule = MakeModuleType("FooModule", newFooModule)
func newFooModule() (Module, interface{}) {
m := &fooModule{}
return m, &m.properties
}
func (f *fooModule) GenerateBuildActions(ModuleContext) {
}
func (f *fooModule) Foo() string {
return f.properties.Foo
}
type barModule struct {
properties struct {
Bar bool
}
}
var BarModule = MakeModuleType("BarModule", newBarModule)
func newBarModule() (Module, interface{}) {
m := &barModule{}
return m, &m.properties
}
func (b *barModule) GenerateBuildActions(ModuleContext) {
}
func (b *barModule) Bar() bool {
return b.properties.Bar
}
func TestContextParse(t *testing.T) {
ctx := NewContext()
ctx.RegisterModuleType("foo_module", FooModule)
ctx.RegisterModuleType("bar_module", BarModule)
r := bytes.NewBufferString(`
foo_module {
name: "MyFooModule",
deps: ["MyBarModule"],
}
bar_module {
name: "MyBarModule",
}
`)
_, errs := ctx.Parse(".", "Blueprint", r)
if len(errs) > 0 {
t.Errorf("unexpected parse errors:")
for _, err := range errs {
t.Errorf(" %s", err)
}
t.FailNow()
}
errs = ctx.resolveDependencies()
if len(errs) > 0 {
t.Errorf("unexpected dep errors:")
for _, err := range errs {
t.Errorf(" %s", err)
}
t.FailNow()
}
errs = ctx.checkForDependencyCycles()
if len(errs) > 0 {
t.Errorf("unexpected dep cycle errors:")
for _, err := range errs {
t.Errorf(" %s", err)
}
t.FailNow()
}
}

533
blueprint/globals.go Normal file
View file

@ -0,0 +1,533 @@
package blueprint
import (
"errors"
"fmt"
"regexp"
"runtime"
"strings"
)
type pkg struct {
fullName string
shortName string
pkgPath string
scope *scope
}
var pkgs = map[string]*pkg{}
var pkgRegexp = regexp.MustCompile(`(.*)\.init(·[0-9]+)?`)
var Phony Rule = &builtinRule{
name_: "phony",
}
var errRuleIsBuiltin = errors.New("the rule is a built-in")
// 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)
}
// 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 {
var pc [1]uintptr
n := runtime.Callers(3, pc[:])
if n != 1 {
panic("unable to get caller pc")
}
f := runtime.FuncForPC(pc[0])
callerName := f.Name()
submatches := pkgRegexp.FindSubmatch([]byte(callerName))
if submatches == nil {
println(callerName)
panic("not called from an init func")
}
pkgPath := string(submatches[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
}
func Import(pkgPath string) {
callerPkg := callerPackage()
importPkg, ok := pkgs[pkgPath]
if !ok {
panic(fmt.Errorf("package %q has no Blueprints definitions", pkgPath))
}
err := callerPkg.scope.AddImport(importPkg.shortName, importPkg.scope)
if err != nil {
panic(err)
}
}
func ImportAs(as, pkgPath string) {
callerPkg := callerPackage()
importPkg, ok := pkgs[pkgPath]
if !ok {
panic(fmt.Errorf("package %q has no Blueprints definitions", pkgPath))
}
err := validateNinjaName(as)
if err != nil {
panic(err)
}
err = callerPkg.scope.AddImport(as, importPkg.scope)
if err != nil {
panic(err)
}
}
type staticVariable struct {
pkg_ *pkg
name_ string
value_ string
}
// StaticVariable returns a Variable that does not depend on any configuration
// information.
func StaticVariable(name, value string) Variable {
err := validateNinjaName(name)
if err != nil {
panic(err)
}
pkg := callerPackage()
v := &staticVariable{pkg, name, value}
err = pkg.scope.AddVariable(v)
if err != nil {
panic(err)
}
return v
}
func (v *staticVariable) pkg() *pkg {
return v.pkg_
}
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) value(Config) (*ninjaString, error) {
return parseNinjaString(v.pkg_.scope, v.value_)
}
type variableFunc struct {
pkg_ *pkg
name_ string
value_ func(Config) (string, error)
}
// VariableFunc returns a Variable whose value is determined by a function that
// takes a Config object as input and returns either the variable value or an
// error.
func VariableFunc(name string, f func(Config) (string, error)) Variable {
err := validateNinjaName(name)
if err != nil {
panic(err)
}
pkg := callerPackage()
v := &variableFunc{pkg, name, f}
err = pkg.scope.AddVariable(v)
if err != nil {
panic(err)
}
return v
}
func (v *variableFunc) pkg() *pkg {
return v.pkg_
}
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) value(config Config) (*ninjaString, error) {
value, err := v.value_(config)
if err != nil {
return nil, err
}
return parseNinjaString(v.pkg_.scope, value)
}
// An argVariable is a Variable that exists only when it is set by a build
// statement to pass a value to the rule being invoked. It has no value, so it
// can never be used to create a Ninja assignment statement. It is inserted
// into the rule's scope, which is used for name lookups within the rule and
// when assigning argument values as part of a build statement.
type argVariable struct {
name_ string
}
func (v *argVariable) pkg() *pkg {
panic("this should not be called")
}
func (v *argVariable) name() string {
return v.name_
}
func (v *argVariable) fullName(pkgNames map[*pkg]string) string {
return v.name_
}
var errVariableIsArg = errors.New("argument variables have no value")
func (v *argVariable) value(config Config) (*ninjaString, error) {
return nil, errVariableIsArg
}
type staticPool struct {
pkg_ *pkg
name_ string
params PoolParams
}
func StaticPool(name string, params PoolParams) Pool {
err := validateNinjaName(name)
if err != nil {
panic(err)
}
pkg := callerPackage()
p := &staticPool{pkg, name, params}
err = pkg.scope.AddPool(p)
if err != nil {
panic(err)
}
return p
}
func (p *staticPool) pkg() *pkg {
return p.pkg_
}
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) def(config Config) (*poolDef, error) {
def, err := parsePoolParams(p.pkg_.scope, &p.params)
if err != nil {
panic(fmt.Errorf("error parsing PoolParams for %s: %s", p.name_, err))
}
return def, nil
}
type poolFunc struct {
pkg_ *pkg
name_ string
paramsFunc func(Config) (PoolParams, error)
}
func PoolFunc(name string, f func(Config) (PoolParams, error)) Pool {
err := validateNinjaName(name)
if err != nil {
panic(err)
}
pkg := callerPackage()
p := &poolFunc{pkg, name, f}
err = pkg.scope.AddPool(p)
if err != nil {
panic(err)
}
return p
}
func (p *poolFunc) pkg() *pkg {
return p.pkg_
}
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) def(config Config) (*poolDef, error) {
params, err := p.paramsFunc(config)
if err != nil {
return nil, err
}
def, err := parsePoolParams(p.pkg_.scope, &params)
if err != nil {
panic(fmt.Errorf("error parsing PoolParams for %s: %s", p.name_, err))
}
return def, nil
}
type staticRule struct {
pkg_ *pkg
name_ string
params RuleParams
argNames map[string]bool
scope_ *scope
}
func StaticRule(name string, params RuleParams, argNames ...string) Rule {
pkg := callerPackage()
err := validateNinjaName(name)
if err != nil {
panic(err)
}
err = validateArgNames(argNames)
if err != nil {
panic(fmt.Errorf("invalid argument name: %s", err))
}
argNamesSet := make(map[string]bool)
for _, argName := range argNames {
argNamesSet[argName] = true
}
ruleScope := (*scope)(nil) // This will get created lazily
r := &staticRule{pkg, name, params, argNamesSet, ruleScope}
err = pkg.scope.AddRule(r)
if err != nil {
panic(err)
}
return r
}
func (r *staticRule) pkg() *pkg {
return r.pkg_
}
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) def(Config) (*ruleDef, error) {
def, err := parseRuleParams(r.scope(), &r.params)
if err != nil {
panic(fmt.Errorf("error parsing RuleParams for %s: %s", r.name_, err))
}
return def, nil
}
func (r *staticRule) scope() *scope {
// We lazily create the scope so that all the global variables get declared
// 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)
}
return r.scope_
}
func (r *staticRule) isArg(argName string) bool {
return r.argNames[argName]
}
type ruleFunc struct {
pkg_ *pkg
name_ string
paramsFunc func(Config) (RuleParams, error)
argNames map[string]bool
scope_ *scope
}
func RuleFunc(name string, f func(Config) (RuleParams, error),
argNames ...string) Rule {
pkg := callerPackage()
err := validateNinjaName(name)
if err != nil {
panic(err)
}
err = validateArgNames(argNames)
if err != nil {
panic(fmt.Errorf("invalid argument name: %s", err))
}
argNamesSet := make(map[string]bool)
for _, argName := range argNames {
argNamesSet[argName] = true
}
ruleScope := (*scope)(nil) // This will get created lazily
r := &ruleFunc{pkg, name, f, argNamesSet, ruleScope}
err = pkg.scope.AddRule(r)
if err != nil {
panic(err)
}
return r
}
func (r *ruleFunc) pkg() *pkg {
return r.pkg_
}
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) def(config Config) (*ruleDef, error) {
params, err := r.paramsFunc(config)
if err != nil {
return nil, err
}
def, err := parseRuleParams(r.scope(), &params)
if err != nil {
panic(fmt.Errorf("error parsing RuleParams for %s: %s", r.name_, err))
}
return def, nil
}
func (r *ruleFunc) scope() *scope {
// We lazily create the scope so that all the global variables get declared
// 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)
}
return r.scope_
}
func (r *ruleFunc) isArg(argName string) bool {
return r.argNames[argName]
}
type builtinRule struct {
name_ string
scope_ *scope
}
func (r *builtinRule) pkg() *pkg {
return nil
}
func (r *builtinRule) name() string {
return r.name_
}
func (r *builtinRule) fullName(pkgNames map[*pkg]string) string {
return r.name_
}
func (r *builtinRule) def(config Config) (*ruleDef, error) {
return nil, errRuleIsBuiltin
}
func (r *builtinRule) scope() *scope {
if r.scope_ == nil {
r.scope_ = makeRuleScope(nil, nil)
}
return r.scope_
}
func (r *builtinRule) isArg(argName string) bool {
return false
}
type ModuleType interface {
pkg() *pkg
name() string
new() (m Module, properties interface{})
}
type moduleTypeFunc struct {
pkg_ *pkg
name_ string
new_ func() (Module, interface{})
}
func MakeModuleType(name string,
new func() (m Module, properties interface{})) ModuleType {
pkg := callerPackage()
return &moduleTypeFunc{pkg, name, new}
}
func (m *moduleTypeFunc) pkg() *pkg {
return m.pkg_
}
func (m *moduleTypeFunc) name() string {
return m.pkg_.pkgPath + "." + m.name_
}
func (m *moduleTypeFunc) new() (Module, interface{}) {
return m.new_()
}

149
blueprint/live_tracker.go Normal file
View file

@ -0,0 +1,149 @@
package blueprint
// A liveTracker tracks the values of live variables, rules, and pools. An
// entity is made "live" when it is referenced directly or indirectly by a build
// definition. When an entity is made live its value is computed based on the
// configuration.
type liveTracker struct {
config Config // Used to evaluate variable, rule, and pool values.
variables map[Variable]*ninjaString
pools map[Pool]*poolDef
rules map[Rule]*ruleDef
}
func newLiveTracker(config Config) *liveTracker {
return &liveTracker{
config: config,
variables: make(map[Variable]*ninjaString),
pools: make(map[Pool]*poolDef),
rules: make(map[Rule]*ruleDef),
}
}
func (l *liveTracker) AddBuildDefDeps(def *buildDef) error {
err := l.addRule(def.Rule)
if err != nil {
return err
}
err = l.addNinjaStringListDeps(def.Outputs)
if err != nil {
return err
}
err = l.addNinjaStringListDeps(def.Inputs)
if err != nil {
return err
}
err = l.addNinjaStringListDeps(def.Implicits)
if err != nil {
return err
}
err = l.addNinjaStringListDeps(def.OrderOnly)
if err != nil {
return err
}
for _, value := range def.Args {
err = l.addNinjaStringDeps(value)
if err != nil {
return err
}
}
return nil
}
func (l *liveTracker) addRule(r Rule) error {
_, ok := l.rules[r]
if !ok {
def, err := r.def(l.config)
if err == errRuleIsBuiltin {
// No need to do anything for built-in rules.
return nil
}
if err != nil {
return err
}
if def.Pool != nil {
err = l.addPool(def.Pool)
if err != nil {
return err
}
}
for _, value := range def.Variables {
err = l.addNinjaStringDeps(value)
if err != nil {
return err
}
}
l.rules[r] = def
}
return nil
}
func (l *liveTracker) addPool(p Pool) error {
_, ok := l.pools[p]
if !ok {
def, err := p.def(l.config)
if err != nil {
return err
}
l.pools[p] = def
}
return nil
}
func (l *liveTracker) addVariable(v Variable) error {
_, ok := l.variables[v]
if !ok {
value, err := v.value(l.config)
if err == errVariableIsArg {
// This variable is a placeholder for an argument that can be passed
// to a rule. It has no value and thus doesn't reference any other
// variables.
return nil
}
if err != nil {
return err
}
l.variables[v] = value
err = l.addNinjaStringDeps(value)
if err != nil {
return err
}
}
return nil
}
func (l *liveTracker) addNinjaStringListDeps(list []*ninjaString) error {
for _, str := range list {
err := l.addNinjaStringDeps(str)
if err != nil {
return err
}
}
return nil
}
func (l *liveTracker) addNinjaStringDeps(str *ninjaString) error {
for _, v := range str.variables {
err := l.addVariable(v)
if err != nil {
return err
}
}
return nil
}

13
blueprint/mangle.go Normal file
View file

@ -0,0 +1,13 @@
package blueprint
func packageNamespacePrefix(packageName string) string {
return "g." + packageName + "."
}
func moduleNamespacePrefix(moduleName string) string {
return "m." + moduleName + "."
}
func singletonNamespacePrefix(singletonName string) string {
return "s." + singletonName + "."
}

116
blueprint/module_ctx.go Normal file
View file

@ -0,0 +1,116 @@
package blueprint
import (
"fmt"
"path/filepath"
)
type Module interface {
GenerateBuildActions(ModuleContext)
}
type ModuleContext interface {
ModuleName() string
ModuleDir() string
Config() Config
ModuleErrorf(fmt string, args ...interface{})
PropertyErrorf(property, fmt string, args ...interface{})
Variable(name, value string)
Rule(name string, params RuleParams) Rule
Build(params BuildParams)
VisitDepsDepthFirst(visit func(Module))
VisitDepsDepthFirstIf(pred func(Module) bool, visit func(Module))
}
var _ ModuleContext = (*moduleContext)(nil)
type moduleContext struct {
context *Context
config Config
module Module
scope *localScope
info *moduleInfo
errs []error
actionDefs localBuildActions
}
func (m *moduleContext) ModuleName() string {
return m.info.properties.Name
}
func (m *moduleContext) ModuleDir() string {
return filepath.Dir(m.info.relBlueprintFile)
}
func (m *moduleContext) Config() Config {
return m.config
}
func (m *moduleContext) ModuleErrorf(format string, args ...interface{}) {
m.errs = append(m.errs, &Error{
Err: fmt.Errorf(format, args...),
Pos: m.info.pos,
})
}
func (m *moduleContext) PropertyErrorf(property, format string,
args ...interface{}) {
pos, ok := m.info.propertyPos[property]
if !ok {
panic(fmt.Errorf("property %q was not set for this module", property))
}
m.errs = append(m.errs, &Error{
Err: fmt.Errorf(format, args...),
Pos: pos,
})
}
func (m *moduleContext) Variable(name, value string) {
v, err := m.scope.AddLocalVariable(name, value)
if err != nil {
panic(err)
}
m.actionDefs.variables = append(m.actionDefs.variables, v)
}
func (m *moduleContext) Rule(name string, params RuleParams) Rule {
// TODO: Verify that params.Pool is accessible in this module's scope.
r, err := m.scope.AddLocalRule(name, &params)
if err != nil {
panic(err)
}
m.actionDefs.rules = append(m.actionDefs.rules, r)
return r
}
func (m *moduleContext) Build(params BuildParams) {
// TODO: Verify that params.Rule is accessible in this module's scope.
def, err := parseBuildParams(m.scope, &params)
if err != nil {
panic(err)
}
m.actionDefs.buildDefs = append(m.actionDefs.buildDefs, def)
}
func (m *moduleContext) VisitDepsDepthFirst(visit func(Module)) {
m.context.visitDepsDepthFirst(m.module, visit)
}
func (m *moduleContext) VisitDepsDepthFirstIf(pred func(Module) bool,
visit func(Module)) {
m.context.visitDepsDepthFirstIf(m.module, pred, visit)
}

299
blueprint/ninja_defs.go Normal file
View file

@ -0,0 +1,299 @@
package blueprint
import (
"fmt"
"strconv"
"strings"
)
type Deps int
const (
DepsNone Deps = iota
DepsGCC
DepsMSVC
)
func (d Deps) String() string {
switch d {
case DepsNone:
return "none"
case DepsGCC:
return "gcc"
case DepsMSVC:
return "msvc"
default:
panic(fmt.Sprintf("unknown deps value: %d", d))
}
}
type PoolParams struct {
Comment string
Depth int
}
type RuleParams struct {
Comment string
Command string
Depfile string
Deps Deps
Description string
Generator bool
Pool Pool
Restat bool
Rspfile string
RspfileContent string
}
type BuildParams struct {
Rule Rule
Outputs []string
Inputs []string
Implicits []string
OrderOnly []string
Args map[string]string
}
// A poolDef describes a pool definition. It does not include the name of the
// pool.
type poolDef struct {
Comment string
Depth int
}
func parsePoolParams(scope variableLookup, params *PoolParams) (*poolDef,
error) {
def := &poolDef{
Comment: params.Comment,
Depth: params.Depth,
}
return def, nil
}
func (p *poolDef) WriteTo(nw *ninjaWriter, name string) error {
if p.Comment != "" {
err := nw.Comment(p.Comment)
if err != nil {
return err
}
}
err := nw.Pool(name)
if err != nil {
return err
}
return nw.ScopedAssign("depth", strconv.Itoa(p.Depth))
}
// A ruleDef describes a rule definition. It does not include the name of the
// rule.
type ruleDef struct {
Comment string
Pool Pool
Variables map[string]*ninjaString
}
func parseRuleParams(scope variableLookup, params *RuleParams) (*ruleDef,
error) {
r := &ruleDef{
Comment: params.Comment,
Pool: params.Pool,
Variables: make(map[string]*ninjaString),
}
if params.Command == "" {
return nil, fmt.Errorf("encountered rule params with no command " +
"specified")
}
value, err := parseNinjaString(scope, params.Command)
if err != nil {
return nil, fmt.Errorf("error parsing Command param: %s", err)
}
r.Variables["command"] = value
if params.Depfile != "" {
value, err = parseNinjaString(scope, params.Depfile)
if err != nil {
return nil, fmt.Errorf("error parsing Depfile param: %s", err)
}
r.Variables["depfile"] = value
}
if params.Deps != DepsNone {
r.Variables["deps"] = simpleNinjaString(params.Deps.String())
}
if params.Description != "" {
value, err = parseNinjaString(scope, params.Description)
if err != nil {
return nil, fmt.Errorf("error parsing Description param: %s", err)
}
r.Variables["description"] = value
}
if params.Generator {
r.Variables["generator"] = simpleNinjaString("true")
}
if params.Restat {
r.Variables["restat"] = simpleNinjaString("true")
}
if params.Rspfile != "" {
value, err = parseNinjaString(scope, params.Rspfile)
if err != nil {
return nil, fmt.Errorf("error parsing Rspfile param: %s", err)
}
r.Variables["rspfile"] = value
}
if params.RspfileContent != "" {
value, err = parseNinjaString(scope, params.RspfileContent)
if err != nil {
return nil, fmt.Errorf("error parsing RspfileContent param: %s",
err)
}
r.Variables["rspfile_content"] = value
}
return r, nil
}
func (r *ruleDef) WriteTo(nw *ninjaWriter, name string,
pkgNames map[*pkg]string) error {
if r.Comment != "" {
err := nw.Comment(r.Comment)
if err != nil {
return err
}
}
err := nw.Rule(name)
if err != nil {
return err
}
if r.Pool != nil {
err = nw.ScopedAssign("pool", r.Pool.fullName(pkgNames))
if err != nil {
return err
}
}
for name, value := range r.Variables {
err = nw.ScopedAssign(name, value.Value(pkgNames))
if err != nil {
return err
}
}
return nil
}
// A buildDef describes a build target definition.
type buildDef struct {
Rule Rule
Outputs []*ninjaString
Inputs []*ninjaString
Implicits []*ninjaString
OrderOnly []*ninjaString
Args map[Variable]*ninjaString
}
func parseBuildParams(scope variableLookup, params *BuildParams) (*buildDef,
error) {
rule := params.Rule
b := &buildDef{
Rule: rule,
}
var err error
b.Outputs, err = parseNinjaStrings(scope, params.Outputs)
if err != nil {
return nil, fmt.Errorf("error parsing Outputs param: %s", err)
}
b.Inputs, err = parseNinjaStrings(scope, params.Inputs)
if err != nil {
return nil, fmt.Errorf("error parsing Inputs param: %s", err)
}
b.Implicits, err = parseNinjaStrings(scope, params.Implicits)
if err != nil {
return nil, fmt.Errorf("error parsing Implicits param: %s", err)
}
b.OrderOnly, err = parseNinjaStrings(scope, params.OrderOnly)
if err != nil {
return nil, fmt.Errorf("error parsing OrderOnly param: %s", err)
}
argNameScope := rule.scope()
if len(params.Args) > 0 {
b.Args = make(map[Variable]*ninjaString)
for name, value := range params.Args {
if !rule.isArg(name) {
return nil, fmt.Errorf("unknown argument %q", name)
}
argVar, err := argNameScope.LookupVariable(name)
if err != nil {
// This shouldn't happen.
return nil, fmt.Errorf("argument lookup error: %s", err)
}
ninjaValue, err := parseNinjaString(scope, value)
if err != nil {
return nil, fmt.Errorf("error parsing variable %q: %s", name,
err)
}
b.Args[argVar] = ninjaValue
}
}
return b, nil
}
func (b *buildDef) WriteTo(nw *ninjaWriter, pkgNames map[*pkg]string) error {
var (
rule = b.Rule.fullName(pkgNames)
outputs = valueList(b.Outputs, pkgNames, outputEscaper)
explicitDeps = valueList(b.Inputs, pkgNames, inputEscaper)
implicitDeps = valueList(b.Implicits, pkgNames, inputEscaper)
orderOnlyDeps = valueList(b.OrderOnly, pkgNames, inputEscaper)
)
err := nw.Build(rule, outputs, explicitDeps, implicitDeps, orderOnlyDeps)
if err != nil {
return err
}
for argVar, value := range b.Args {
name := argVar.fullName(pkgNames)
err = nw.ScopedAssign(name, value.Value(pkgNames))
if err != nil {
return err
}
}
return nil
}
func valueList(list []*ninjaString, pkgNames map[*pkg]string,
escaper *strings.Replacer) []string {
result := make([]string, len(list))
for i, ninjaStr := range list {
result[i] = ninjaStr.ValueWithEscaper(pkgNames, escaper)
}
return result
}

281
blueprint/ninja_strings.go Normal file
View file

@ -0,0 +1,281 @@
package blueprint
import (
"fmt"
"strings"
)
const EOF = -1
var (
defaultEscaper = strings.NewReplacer(
"\n", "$\n")
inputEscaper = strings.NewReplacer(
"\n", "$\n",
" ", "$ ")
outputEscaper = strings.NewReplacer(
"\n", "$\n",
" ", "$ ",
":", "$:")
)
type ninjaString struct {
strings []string
variables []Variable
}
type variableLookup interface {
LookupVariable(name string) (Variable, error)
}
func simpleNinjaString(str string) *ninjaString {
return &ninjaString{
strings: []string{str},
}
}
// parseNinjaString parses an unescaped ninja string (i.e. all $<something>
// occurrences are expected to be variables or $$) and returns a list of the
// variable names that the string references.
func parseNinjaString(scope variableLookup, str string) (*ninjaString, error) {
type stateFunc func(int, rune) (stateFunc, error)
var (
stringState stateFunc
dollarStartState stateFunc
dollarState stateFunc
bracketsState stateFunc
)
var stringStart, varStart int
var result ninjaString
stringState = func(i int, r rune) (stateFunc, error) {
switch {
case r == '$':
varStart = i + 1
return dollarStartState, nil
case r == EOF:
result.strings = append(result.strings, str[stringStart:i])
return nil, nil
default:
return stringState, nil
}
}
dollarStartState = func(i int, r rune) (stateFunc, error) {
switch {
case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z',
r >= '0' && r <= '9', r == '_', r == '-':
// The beginning of a of the variable name. Output the string and
// keep going.
result.strings = append(result.strings, str[stringStart:i-1])
return dollarState, nil
case r == '$':
// Just a "$$". Go back to stringState without changing
// stringStart.
return stringState, nil
case r == '{':
// This is a bracketted variable name (e.g. "${blah.blah}"). Output
// the string and keep going.
result.strings = append(result.strings, str[stringStart:i-1])
varStart = i + 1
return bracketsState, nil
case r == EOF:
return nil, fmt.Errorf("unexpected end of string after '$'")
default:
// This was some arbitrary character following a dollar sign,
// which is not allowed.
return nil, fmt.Errorf("invalid character after '$' at byte "+
"offset %d", i)
}
}
dollarState = func(i int, r rune) (stateFunc, error) {
switch {
case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z',
r >= '0' && r <= '9', r == '_', r == '-':
// A part of the variable name. Keep going.
return dollarState, nil
case r == '$':
// A dollar after the variable name (e.g. "$blah$"). Output the
// variable we have and start a new one.
v, err := scope.LookupVariable(str[varStart:i])
if err != nil {
return nil, err
}
result.variables = append(result.variables, v)
varStart = i + 1
return dollarState, nil
case r == EOF:
// This is the end of the variable name.
v, err := scope.LookupVariable(str[varStart:i])
if err != nil {
return nil, err
}
result.variables = append(result.variables, v)
// We always end with a string, even if it's an empty one.
result.strings = append(result.strings, "")
return nil, nil
default:
// We've just gone past the end of the variable name, so record what
// we have.
v, err := scope.LookupVariable(str[varStart:i])
if err != nil {
return nil, err
}
result.variables = append(result.variables, v)
stringStart = i
return stringState, nil
}
}
bracketsState = func(i int, r rune) (stateFunc, error) {
switch {
case r >= 'a' && r <= 'z', r >= 'A' && r <= 'Z',
r >= '0' && r <= '9', r == '_', r == '-', r == '.':
// A part of the variable name. Keep going.
return bracketsState, nil
case r == '}':
if varStart == i {
// The brackets were immediately closed. That's no good.
return nil, fmt.Errorf("empty variable name at byte offset %d",
i)
}
// This is the end of the variable name.
v, err := scope.LookupVariable(str[varStart:i])
if err != nil {
return nil, err
}
result.variables = append(result.variables, v)
stringStart = i + 1
return stringState, nil
case r == EOF:
return nil, fmt.Errorf("unexpected end of string in variable name")
default:
// This character isn't allowed in a variable name.
return nil, fmt.Errorf("invalid character in variable name at "+
"byte offset %d", i)
}
}
state := stringState
var err error
for i, r := range str {
state, err = state(i, r)
if err != nil {
return nil, err
}
}
_, err = state(len(str), EOF)
if err != nil {
return nil, err
}
return &result, nil
}
func parseNinjaStrings(scope variableLookup, strs []string) ([]*ninjaString,
error) {
if len(strs) == 0 {
return nil, nil
}
result := make([]*ninjaString, len(strs))
for i, str := range strs {
ninjaStr, err := parseNinjaString(scope, str)
if err != nil {
return nil, fmt.Errorf("error parsing element %d: %s", i, err)
}
result[i] = ninjaStr
}
return result, nil
}
func (n *ninjaString) Value(pkgNames map[*pkg]string) string {
return n.ValueWithEscaper(pkgNames, defaultEscaper)
}
func (n *ninjaString) ValueWithEscaper(pkgNames map[*pkg]string,
escaper *strings.Replacer) string {
str := escaper.Replace(n.strings[0])
for i, v := range n.variables {
str += "${" + v.fullName(pkgNames) + "}"
str += escaper.Replace(n.strings[i+1])
}
return str
}
func validateNinjaName(name string) error {
for i, r := range name {
valid := (r >= 'a' && r <= 'z') ||
(r >= 'A' && r <= 'Z') ||
(r >= '0' && r <= '9') ||
(r == '_') ||
(r == '-') ||
(r == '.')
if !valid {
return fmt.Errorf("%q contains an invalid Ninja name character "+
"at byte offset %d", name, i)
}
}
return nil
}
var builtinRuleArgs = []string{"out", "in"}
func validateArgName(argName string) error {
err := validateNinjaName(argName)
if err != nil {
return err
}
// We only allow globals within the rule's package to be used as rule
// arguments. A global in another package can always be mirrored into
// the rule's package by defining a new variable, so this doesn't limit
// what's possible. This limitation prevents situations where a Build
// invocation in another package must use the rule-defining package's
// import name for a 3rd package in order to set the rule's arguments.
if strings.ContainsRune(argName, '.') {
return fmt.Errorf("%q contains a '.' character", argName)
}
for _, builtin := range builtinRuleArgs {
if argName == builtin {
return fmt.Errorf("%q conflicts with Ninja built-in", argName)
}
}
return nil
}
func validateArgNames(argNames []string) error {
for _, argName := range argNames {
err := validateArgName(argName)
if err != nil {
return err
}
}
return nil
}

View file

@ -0,0 +1,114 @@
package blueprint
import (
"reflect"
"testing"
)
var ninjaParseTestCases = []struct {
input string
output []string
err string
}{
{
input: "abc def $ghi jkl",
output: []string{"ghi"},
},
{
input: "abc def $ghi$jkl",
output: []string{"ghi", "jkl"},
},
{
input: "foo $012_-345xyz_! bar",
output: []string{"012_-345xyz_"},
},
{
input: "foo ${012_-345xyz_} bar",
output: []string{"012_-345xyz_"},
},
{
input: "foo ${012_-345xyz_} bar",
output: []string{"012_-345xyz_"},
},
{
input: "foo $$ bar",
output: nil,
},
{
input: "foo $ bar",
err: "invalid character after '$' at byte offset 5",
},
{
input: "foo $",
err: "unexpected end of string after '$'",
},
{
input: "foo ${} bar",
err: "empty variable name at byte offset 6",
},
{
input: "foo ${abc!} bar",
err: "invalid character in variable name at byte offset 9",
},
{
input: "foo ${abc",
err: "unexpected end of string in variable name",
},
}
func TestParseNinjaString(t *testing.T) {
for _, testCase := range ninjaParseTestCases {
scope := newLocalScope(nil, "namespace")
var expectedVars []Variable
for _, varName := range testCase.output {
v, err := scope.LookupVariable(varName)
if err != nil {
v, err = scope.AddLocalVariable(varName, "")
if err != nil {
t.Fatalf("error creating scope: %s", err)
}
}
expectedVars = append(expectedVars, v)
}
output, err := parseNinjaString(scope, testCase.input)
if err == nil && !reflect.DeepEqual(output.variables, expectedVars) {
t.Errorf("incorrect output:")
t.Errorf(" input: %q", testCase.input)
t.Errorf(" expected: %#v", testCase.output)
t.Errorf(" got: %#v", output)
}
var errStr string
if err != nil {
errStr = err.Error()
}
if err != nil && err.Error() != testCase.err {
t.Errorf("unexpected error:")
t.Errorf(" input: %q", testCase.input)
t.Errorf(" expected: %q", testCase.err)
t.Errorf(" got: %q", errStr)
}
}
}
func TestParseNinjaStringWithImportedVar(t *testing.T) {
ImpVar := &staticVariable{name_: "ImpVar"}
impScope := newScope(nil)
impScope.AddVariable(ImpVar)
scope := newScope(nil)
scope.AddImport("impPkg", impScope)
input := "abc def ${impPkg.ImpVar} ghi"
output, err := parseNinjaString(scope, input)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
expect := []Variable{ImpVar}
if !reflect.DeepEqual(output.variables, expect) {
t.Errorf("incorrect output:")
t.Errorf(" input: %q", input)
t.Errorf(" expected: %#v", expect)
t.Errorf(" got: %#v", output)
}
}

244
blueprint/ninja_writer.go Normal file
View file

@ -0,0 +1,244 @@
package blueprint
import (
"fmt"
"io"
"strings"
"unicode"
)
const (
indentWidth = 4
lineWidth = 80
)
type ninjaWriter struct {
writer io.Writer
justDidBlankLine bool // true if the last operation was a BlankLine
}
func newNinjaWriter(writer io.Writer) *ninjaWriter {
return &ninjaWriter{
writer: writer,
}
}
func (n *ninjaWriter) Comment(comment string) error {
n.justDidBlankLine = false
const lineHeaderLen = len("# ")
const maxLineLen = lineWidth - lineHeaderLen
var lineStart, lastSplitPoint int
for i, r := range comment {
if unicode.IsSpace(r) {
// We know we can safely split the line here.
lastSplitPoint = i + 1
}
var line string
var writeLine bool
switch {
case r == '\n':
// Output the line without trimming the left so as to allow comments
// to contain their own indentation.
line = strings.TrimRightFunc(comment[lineStart:i], unicode.IsSpace)
writeLine = true
case (i-lineStart > maxLineLen) && (lastSplitPoint > lineStart):
// The line has grown too long and is splittable. Split it at the
// last split point.
line = strings.TrimSpace(comment[lineStart:lastSplitPoint])
writeLine = true
}
if writeLine {
line = strings.TrimSpace("# "+line) + "\n"
_, err := io.WriteString(n.writer, line)
if err != nil {
return err
}
lineStart = lastSplitPoint
}
}
if lineStart != len(comment) {
line := strings.TrimSpace(comment[lineStart:])
_, err := fmt.Fprintf(n.writer, "# %s\n", line)
if err != nil {
return err
}
}
return nil
}
func (n *ninjaWriter) Pool(name string) error {
n.justDidBlankLine = false
_, err := fmt.Fprintf(n.writer, "pool %s\n", name)
return err
}
func (n *ninjaWriter) Rule(name string) error {
n.justDidBlankLine = false
_, err := fmt.Fprintf(n.writer, "rule %s\n", name)
return err
}
func (n *ninjaWriter) Build(rule string, outputs, explicitDeps, implicitDeps,
orderOnlyDeps []string) error {
n.justDidBlankLine = false
const lineWrapLen = len(" $")
const maxLineLen = lineWidth - lineWrapLen
line := "build"
appendWithWrap := func(s string) (err error) {
if len(line)+len(s) > maxLineLen {
_, err = fmt.Fprintf(n.writer, "%s $\n", line)
line = strings.Repeat(" ", indentWidth*2)
s = strings.TrimLeftFunc(s, unicode.IsSpace)
}
line += s
return
}
for _, output := range outputs {
err := appendWithWrap(" " + output)
if err != nil {
return err
}
}
err := appendWithWrap(":")
if err != nil {
return err
}
err = appendWithWrap(" " + rule)
if err != nil {
return err
}
for _, dep := range explicitDeps {
err := appendWithWrap(" " + dep)
if err != nil {
return err
}
}
if len(implicitDeps) > 0 {
err := appendWithWrap(" |")
if err != nil {
return err
}
for _, dep := range implicitDeps {
err := appendWithWrap(" " + dep)
if err != nil {
return err
}
}
}
if len(orderOnlyDeps) > 0 {
err := appendWithWrap(" ||")
if err != nil {
return err
}
for _, dep := range orderOnlyDeps {
err := appendWithWrap(" " + dep)
if err != nil {
return err
}
}
}
_, err = fmt.Fprintln(n.writer, line)
return err
}
func (n *ninjaWriter) Assign(name, value string) error {
n.justDidBlankLine = false
_, err := fmt.Fprintf(n.writer, "%s = %s\n", name, value)
return err
}
func (n *ninjaWriter) ScopedAssign(name, value string) error {
n.justDidBlankLine = false
indent := strings.Repeat(" ", indentWidth)
_, err := fmt.Fprintf(n.writer, "%s%s = %s\n", indent, name, value)
return err
}
func (n *ninjaWriter) Default(targets ...string) error {
n.justDidBlankLine = false
const lineWrapLen = len(" $")
const maxLineLen = lineWidth - lineWrapLen
line := "default"
appendWithWrap := func(s string) (err error) {
if len(line)+len(s) > maxLineLen {
_, err = fmt.Fprintf(n.writer, "%s $\n", line)
line = strings.Repeat(" ", indentWidth*2)
s = strings.TrimLeftFunc(s, unicode.IsSpace)
}
line += s
return
}
for _, target := range targets {
err := appendWithWrap(" " + target)
if err != nil {
return err
}
}
_, err := fmt.Fprintln(n.writer, line)
return err
}
func (n *ninjaWriter) BlankLine() (err error) {
// We don't output multiple blank lines in a row.
if !n.justDidBlankLine {
n.justDidBlankLine = true
_, err = io.WriteString(n.writer, "\n")
}
return err
}
func writeAssignments(w io.Writer, indent int, assignments ...string) error {
var maxNameLen int
for i := 0; i < len(assignments); i += 2 {
name := assignments[i]
err := validateNinjaName(name)
if err != nil {
return err
}
if maxNameLen < len(name) {
maxNameLen = len(name)
}
}
indentStr := strings.Repeat(" ", indent*indentWidth)
extraIndentStr := strings.Repeat(" ", (indent+1)*indentWidth)
replacer := strings.NewReplacer("\n", "$\n"+extraIndentStr)
for i := 0; i < len(assignments); i += 2 {
name := assignments[i]
value := replacer.Replace(assignments[i+1])
_, err := fmt.Fprintf(w, "%s% *s = %s\n", indentStr, maxNameLen, name,
value)
if err != nil {
return err
}
}
return nil
}

View file

@ -0,0 +1,105 @@
package blueprint
import (
"bytes"
"testing"
)
func ck(err error) {
if err != nil {
panic(err)
}
}
var ninjaWriterTestCases = []struct {
input func(w *ninjaWriter)
output string
}{
{
input: func(w *ninjaWriter) {
ck(w.Comment("foo"))
},
output: "# foo\n",
},
{
input: func(w *ninjaWriter) {
ck(w.Pool("foo"))
},
output: "pool foo\n",
},
{
input: func(w *ninjaWriter) {
ck(w.Rule("foo"))
},
output: "rule foo\n",
},
{
input: func(w *ninjaWriter) {
ck(w.Build("foo", []string{"o1", "o2"}, []string{"e1", "e2"},
[]string{"i1", "i2"}, []string{"oo1", "oo2"}))
},
output: "build o1 o2: foo e1 e2 | i1 i2 || oo1 oo2\n",
},
{
input: func(w *ninjaWriter) {
ck(w.Default("foo"))
},
output: "default foo\n",
},
{
input: func(w *ninjaWriter) {
ck(w.Assign("foo", "bar"))
},
output: "foo = bar\n",
},
{
input: func(w *ninjaWriter) {
ck(w.ScopedAssign("foo", "bar"))
},
output: " foo = bar\n",
},
{
input: func(w *ninjaWriter) {
ck(w.BlankLine())
},
output: "\n",
},
{
input: func(w *ninjaWriter) {
ck(w.Pool("p"))
ck(w.ScopedAssign("depth", "3"))
ck(w.BlankLine())
ck(w.Comment("here comes a rule"))
ck(w.Rule("r"))
ck(w.ScopedAssign("command", "echo out: $out in: $in _arg: $_arg"))
ck(w.ScopedAssign("pool", "p"))
ck(w.BlankLine())
ck(w.Build("r", []string{"foo.o"}, []string{"foo.in"}, nil, nil))
ck(w.ScopedAssign("_arg", "arg value"))
},
output: `pool p
depth = 3
# here comes a rule
rule r
command = echo out: $out in: $in _arg: $_arg
pool = p
build foo.o: r foo.in
_arg = arg value
`,
},
}
func TestNinjaWriter(t *testing.T) {
for i, testCase := range ninjaWriterTestCases {
buf := bytes.NewBuffer(nil)
w := newNinjaWriter(buf)
testCase.input(w)
if buf.String() != testCase.output {
t.Errorf("incorrect output for test case %d", i)
t.Errorf(" expected: %q", testCase.output)
t.Errorf(" got: %q", buf.String())
}
}
}

354
blueprint/parser/parser.go Normal file
View file

@ -0,0 +1,354 @@
package parser
import (
"errors"
"fmt"
"io"
"strconv"
"strings"
"text/scanner"
)
var errTooManyErrors = errors.New("too many errors")
const maxErrors = 1
type ParseError struct {
Err error
Pos scanner.Position
}
func (e *ParseError) Error() string {
return fmt.Sprintf("%s: %s", e.Pos, e.Err)
}
func Parse(filename string, r io.Reader) (defs []Definition, errs []error) {
p := newParser(r)
p.scanner.Filename = filename
defer func() {
if r := recover(); r != nil {
if r == errTooManyErrors {
defs = nil
errs = p.errors
return
}
panic(r)
}
}()
defs = p.parseDefinitions()
p.accept(scanner.EOF)
errs = p.errors
return
}
type parser struct {
scanner scanner.Scanner
tok rune
errors []error
}
func newParser(r io.Reader) *parser {
p := &parser{}
p.scanner.Init(r)
p.scanner.Error = func(sc *scanner.Scanner, msg string) {
p.errorf(msg)
}
p.scanner.Mode = scanner.ScanIdents | scanner.ScanStrings |
scanner.ScanComments | scanner.SkipComments
p.next()
return p
}
func (p *parser) errorf(format string, args ...interface{}) {
pos := p.scanner.Position
if !pos.IsValid() {
pos = p.scanner.Pos()
}
err := &ParseError{
Err: fmt.Errorf(format, args...),
Pos: pos,
}
p.errors = append(p.errors, err)
if len(p.errors) >= maxErrors {
panic(errTooManyErrors)
}
}
func (p *parser) accept(toks ...rune) bool {
for _, tok := range toks {
if p.tok != tok {
p.errorf("expected %s, found %s", scanner.TokenString(tok),
scanner.TokenString(p.tok))
return false
}
if p.tok != scanner.EOF {
p.tok = p.scanner.Scan()
}
}
return true
}
func (p *parser) next() {
if p.tok != scanner.EOF {
p.tok = p.scanner.Scan()
}
return
}
func (p *parser) parseDefinitions() (defs []Definition) {
for {
switch p.tok {
case scanner.Ident:
ident := p.scanner.TokenText()
pos := p.scanner.Position
p.accept(scanner.Ident)
switch p.tok {
case '=':
defs = append(defs, p.parseAssignment(ident, pos))
case '{':
defs = append(defs, p.parseModule(ident, pos))
default:
p.errorf("expected \"=\" or \"{\", found %s",
scanner.TokenString(p.tok))
}
case scanner.EOF:
return
default:
p.errorf("expected assignment or module definition, found %s",
scanner.TokenString(p.tok))
return
}
}
}
func (p *parser) parseAssignment(name string,
pos scanner.Position) (assignment *Assignment) {
assignment = new(Assignment)
if !p.accept('=') {
return
}
value := p.parseValue()
assignment.Name = name
assignment.Value = value
assignment.Pos = pos
return
}
func (p *parser) parseModule(typ string,
pos scanner.Position) (module *Module) {
module = new(Module)
if !p.accept('{') {
return
}
properties := p.parsePropertyList()
p.accept('}')
module.Type = typ
module.Properties = properties
module.Pos = pos
return
}
func (p *parser) parsePropertyList() (properties []*Property) {
for p.tok == scanner.Ident {
property := p.parseProperty()
properties = append(properties, property)
if p.tok != ',' {
// There was no comma, so the list is done.
break
}
p.accept(',')
}
return
}
func (p *parser) parseProperty() (property *Property) {
property = new(Property)
name := p.scanner.TokenText()
pos := p.scanner.Position
if !p.accept(scanner.Ident, ':') {
return
}
value := p.parseValue()
property.Name = name
property.Value = value
property.Pos = pos
return
}
func (p *parser) parseValue() (value Value) {
switch p.tok {
case scanner.Ident:
return p.parseBoolValue()
case scanner.String:
return p.parseStringValue()
case '[':
return p.parseListValue()
default:
p.errorf("expected bool, list, or string value; found %s",
scanner.TokenString(p.tok))
return
}
}
func (p *parser) parseBoolValue() (value Value) {
value.Type = Bool
value.Pos = p.scanner.Position
switch text := p.scanner.TokenText(); text {
case "true":
value.BoolValue = true
case "false":
value.BoolValue = false
default:
p.errorf("expected true or false; found %q", text)
return
}
p.accept(scanner.Ident)
return
}
func (p *parser) parseStringValue() (value Value) {
value.Type = String
value.Pos = p.scanner.Position
str, err := strconv.Unquote(p.scanner.TokenText())
if err != nil {
p.errorf("couldn't parse string: %s", err)
return
}
value.StringValue = str
p.accept(scanner.String)
return
}
func (p *parser) parseListValue() (value Value) {
value.Type = List
value.Pos = p.scanner.Position
if !p.accept('[') {
return
}
var elements []Value
for p.tok == scanner.String {
elements = append(elements, p.parseStringValue())
if p.tok != ',' {
// There was no comma, so the list is done.
break
}
p.accept(',')
}
value.ListValue = elements
p.accept(']')
return
}
type ValueType int
const (
Bool ValueType = iota
String
List
)
func (p ValueType) String() string {
switch p {
case Bool:
return "bool"
case String:
return "string"
case List:
return "list"
default:
panic(fmt.Errorf("unknown value type: %d", p))
}
}
type Definition interface {
String() string
definitionTag()
}
type Assignment struct {
Name string
Value Value
Pos scanner.Position
}
func (a *Assignment) String() string {
return fmt.Sprintf("%s@%d:%s: %s", a.Name, a.Pos.Offset, a.Pos, a.Value)
}
func (a *Assignment) definitionTag() {}
type Module struct {
Type string
Properties []*Property
Pos scanner.Position
}
func (m *Module) String() string {
propertyStrings := make([]string, len(m.Properties))
for i, property := range m.Properties {
propertyStrings[i] = property.String()
}
return fmt.Sprintf("%s@%d:%s{%s}", m.Type, m.Pos.Offset, m.Pos,
strings.Join(propertyStrings, ", "))
}
func (m *Module) definitionTag() {}
type Property struct {
Name string
Value Value
Pos scanner.Position
}
func (p *Property) String() string {
return fmt.Sprintf("%s@%d:%s: %s", p.Name, p.Pos.Offset, p.Pos, p.Value)
}
type Value struct {
Type ValueType
BoolValue bool
StringValue string
ListValue []Value
Pos scanner.Position
}
func (p Value) String() string {
switch p.Type {
case Bool:
return fmt.Sprintf("%t@%d:%s", p.BoolValue, p.Pos.Offset, p.Pos)
case String:
return fmt.Sprintf("%q@%d:%s", p.StringValue, p.Pos.Offset, p.Pos)
case List:
valueStrings := make([]string, len(p.ListValue))
for i, value := range p.ListValue {
valueStrings[i] = value.String()
}
return fmt.Sprintf("@%d:%s[%s]", p.Pos.Offset, p.Pos,
strings.Join(valueStrings, ", "))
default:
panic(fmt.Errorf("bad property type: %d", p.Type))
}
}

View file

@ -0,0 +1,234 @@
package parser
import (
"bytes"
"reflect"
"strings"
"testing"
"text/scanner"
)
func mkpos(offset, line, column int) scanner.Position {
return scanner.Position{
Offset: offset,
Line: line,
Column: column,
}
}
var validParseTestCases = []struct {
input string
output []Definition
}{
{`
foo {}
`,
[]Definition{
&Module{
Type: "foo",
Pos: mkpos(3, 2, 3),
},
},
},
{`
foo {
name: "abc",
}
`,
[]Definition{
&Module{
Type: "foo",
Pos: mkpos(3, 2, 3),
Properties: []*Property{
{
Name: "name",
Pos: mkpos(12, 3, 4),
Value: Value{
Type: String,
Pos: mkpos(18, 3, 10),
StringValue: "abc",
},
},
},
},
},
},
{`
foo {
isGood: true,
}
`,
[]Definition{
&Module{
Type: "foo",
Pos: mkpos(3, 2, 3),
Properties: []*Property{
{
Name: "isGood",
Pos: mkpos(12, 3, 4),
Value: Value{
Type: Bool,
Pos: mkpos(20, 3, 12),
BoolValue: true,
},
},
},
},
},
},
{`
foo {
stuff: ["asdf", "jkl;", "qwert",
"uiop", "bnm,"]
}
`,
[]Definition{
&Module{
Type: "foo",
Pos: mkpos(3, 2, 3),
Properties: []*Property{
{
Name: "stuff",
Pos: mkpos(12, 3, 4),
Value: Value{
Type: List,
Pos: mkpos(19, 3, 11),
ListValue: []Value{
Value{
Type: String,
Pos: mkpos(20, 3, 12),
StringValue: "asdf",
},
Value{
Type: String,
Pos: mkpos(28, 3, 20),
StringValue: "jkl;",
},
Value{
Type: String,
Pos: mkpos(36, 3, 28),
StringValue: "qwert",
},
Value{
Type: String,
Pos: mkpos(49, 4, 5),
StringValue: "uiop",
},
Value{
Type: String,
Pos: mkpos(57, 4, 13),
StringValue: "bnm,",
},
},
},
},
},
},
},
},
{`
// comment
foo {
// comment
isGood: true, // comment
}
`,
[]Definition{
&Module{
Type: "foo",
Pos: mkpos(16, 3, 3),
Properties: []*Property{
{
Name: "isGood",
Pos: mkpos(39, 5, 4),
Value: Value{
Type: Bool,
Pos: mkpos(47, 5, 12),
BoolValue: true,
},
},
},
},
},
},
{`
foo {
name: "abc",
}
bar {
name: "def",
}
`,
[]Definition{
&Module{
Type: "foo",
Pos: mkpos(3, 2, 3),
Properties: []*Property{
{
Name: "name",
Pos: mkpos(12, 3, 4),
Value: Value{
Type: String,
Pos: mkpos(18, 3, 10),
StringValue: "abc",
},
},
},
},
&Module{
Type: "bar",
Pos: mkpos(32, 6, 3),
Properties: []*Property{
{
Name: "name",
Pos: mkpos(41, 7, 4),
Value: Value{
Type: String,
Pos: mkpos(47, 7, 10),
StringValue: "def",
},
},
},
},
},
},
}
func defListString(defs []Definition) string {
defStrings := make([]string, len(defs))
for i, def := range defs {
defStrings[i] = def.String()
}
return strings.Join(defStrings, ", ")
}
func TestParseValidInput(t *testing.T) {
for _, testCase := range validParseTestCases {
r := bytes.NewBufferString(testCase.input)
defs, errs := Parse("", r)
if len(errs) != 0 {
t.Errorf("test case: %s", testCase.input)
t.Errorf("unexpected errors:")
for _, err := range errs {
t.Errorf(" %s", err)
}
t.FailNow()
}
if !reflect.DeepEqual(defs, testCase.output) {
t.Errorf("test case: %s", testCase.input)
t.Errorf("incorrect output:")
t.Errorf(" expected: %s", defListString(testCase.output))
t.Errorf(" got: %s", defListString(defs))
}
}
}
// TODO: Test error strings

320
blueprint/scope.go Normal file
View file

@ -0,0 +1,320 @@
package blueprint
import (
"fmt"
"strings"
"unicode"
"unicode/utf8"
)
// A Variable represents a global Ninja variable definition that will be written
// 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
name() string // "foo"
fullName(pkgNames map[*pkg]string) string // "pkg.foo" or "path/to/pkg.foo"
value(config Config) (*ninjaString, error)
}
// A Pool represents a Ninja pool that will be written to the output .ninja
// file.
type Pool interface {
pkg() *pkg
name() string // "foo"
fullName(pkgNames map[*pkg]string) string // "pkg.foo" or "path/to/pkg.foo"
def(config Config) (*poolDef, error)
}
// A Rule represents a Ninja build rule that will be written to the output
// .ninja file.
type Rule interface {
pkg() *pkg
name() string // "foo"
fullName(pkgNames map[*pkg]string) string // "pkg.foo" or "path/to/pkg.foo"
def(config Config) (*ruleDef, error)
scope() *scope
isArg(argName string) bool
}
type scope struct {
parent *scope
variables map[string]Variable
pools map[string]Pool
rules map[string]Rule
imports map[string]*scope
}
func newScope(parent *scope) *scope {
return &scope{
parent: parent,
variables: make(map[string]Variable),
pools: make(map[string]Pool),
rules: make(map[string]Rule),
imports: make(map[string]*scope),
}
}
func makeRuleScope(parent *scope, argNames map[string]bool) *scope {
scope := newScope(parent)
for argName := range argNames {
_, err := scope.LookupVariable(argName)
if err != nil {
arg := &argVariable{argName}
err = scope.AddVariable(arg)
if err != nil {
// This should not happen. We should have already checked that
// the name is valid and that the scope doesn't have a variable
// with this name.
panic(err)
}
}
}
// We treat built-in variables like arguments for the purpose of this scope.
for _, builtin := range builtinRuleArgs {
arg := &argVariable{builtin}
err := scope.AddVariable(arg)
if err != nil {
panic(err)
}
}
return scope
}
func (s *scope) LookupVariable(name string) (Variable, error) {
dotIndex := strings.IndexRune(name, '.')
if dotIndex >= 0 {
// The variable name looks like "pkg.var"
if dotIndex+1 == len(name) {
return nil, fmt.Errorf("variable name %q ends with a '.'", name)
}
if strings.ContainsRune(name[dotIndex+1:], '.') {
return nil, fmt.Errorf("variable name %q contains multiple '.' "+
"characters", name)
}
pkgName := name[:dotIndex]
varName := name[dotIndex+1:]
first, _ := utf8.DecodeRuneInString(varName)
if !unicode.IsUpper(first) {
return nil, fmt.Errorf("cannot refer to unexported name %q", name)
}
importedScope, err := s.lookupImportedScope(pkgName)
if err != nil {
return nil, err
}
v, ok := importedScope.variables[varName]
if !ok {
return nil, fmt.Errorf("package %q does not contain variable %q",
pkgName, varName)
}
return v, nil
} else {
// The variable name has no package part; just "var"
for ; s != nil; s = s.parent {
v, ok := s.variables[name]
if ok {
return v, nil
}
}
return nil, fmt.Errorf("undefined variable %q", name)
}
}
func (s *scope) lookupImportedScope(pkgName string) (*scope, error) {
for ; s != nil; s = s.parent {
importedScope, ok := s.imports[pkgName]
if ok {
return importedScope, nil
}
}
return nil, fmt.Errorf("unknown imported package %q (missing call to "+
"blueprint.Import()?)", pkgName)
}
func (s *scope) AddImport(name string, importedScope *scope) error {
_, present := s.imports[name]
if present {
return fmt.Errorf("import %q is already defined in this scope", name)
}
s.imports[name] = importedScope
return nil
}
func (s *scope) AddVariable(v Variable) error {
name := v.name()
_, present := s.variables[name]
if present {
return fmt.Errorf("variable %q is already defined in this scope", name)
}
s.variables[name] = v
return nil
}
func (s *scope) AddPool(p Pool) error {
name := p.name()
_, present := s.pools[name]
if present {
return fmt.Errorf("pool %q is already defined in this scope", name)
}
s.pools[name] = p
return nil
}
func (s *scope) AddRule(r Rule) error {
name := r.name()
_, present := s.rules[name]
if present {
return fmt.Errorf("rule %q is already defined in this scope", name)
}
s.rules[name] = r
return nil
}
type localScope struct {
namePrefix string
scope *scope
}
func newLocalScope(parent *scope, namePrefix string) *localScope {
return &localScope{
namePrefix: namePrefix,
scope: newScope(parent),
}
}
func (s *localScope) LookupVariable(name string) (Variable, error) {
return s.scope.LookupVariable(name)
}
func (s *localScope) AddLocalVariable(name, value string) (*localVariable,
error) {
err := validateNinjaName(name)
if err != nil {
return nil, err
}
if strings.ContainsRune(name, '.') {
return nil, fmt.Errorf("local variable name %q contains '.'", name)
}
ninjaValue, err := parseNinjaString(s.scope, value)
if err != nil {
return nil, err
}
v := &localVariable{
namePrefix: s.namePrefix,
name_: name,
value_: ninjaValue,
}
err = s.scope.AddVariable(v)
if err != nil {
return nil, err
}
return v, nil
}
func (s *localScope) AddLocalRule(name string, params *RuleParams,
argNames ...string) (*localRule, error) {
err := validateNinjaName(name)
if err != nil {
return nil, err
}
err = validateArgNames(argNames)
if err != nil {
return nil, fmt.Errorf("invalid argument name: %s", err)
}
argNamesSet := make(map[string]bool)
for _, argName := range argNames {
argNamesSet[argName] = true
}
ruleScope := makeRuleScope(s.scope, argNamesSet)
def, err := parseRuleParams(ruleScope, params)
if err != nil {
return nil, err
}
r := &localRule{
namePrefix: s.namePrefix,
name_: name,
def_: def,
argNames: argNamesSet,
scope_: ruleScope,
}
err = s.scope.AddRule(r)
if err != nil {
return nil, err
}
return r, nil
}
type localVariable struct {
namePrefix string
name_ string
value_ *ninjaString
}
func (l *localVariable) pkg() *pkg {
return nil
}
func (l *localVariable) name() string {
return l.name_
}
func (l *localVariable) fullName(pkgNames map[*pkg]string) string {
return l.namePrefix + l.name_
}
func (l *localVariable) value(Config) (*ninjaString, error) {
return l.value_, nil
}
type localRule struct {
namePrefix string
name_ string
def_ *ruleDef
argNames map[string]bool
scope_ *scope
}
func (l *localRule) pkg() *pkg {
return nil
}
func (l *localRule) name() string {
return l.name_
}
func (l *localRule) fullName(pkgNames map[*pkg]string) string {
return l.namePrefix + l.name_
}
func (l *localRule) def(Config) (*ruleDef, error) {
return l.def_, nil
}
func (r *localRule) scope() *scope {
return r.scope_
}
func (r *localRule) isArg(argName string) bool {
return r.argNames[argName]
}

137
blueprint/singleton_ctx.go Normal file
View file

@ -0,0 +1,137 @@
package blueprint
import (
"fmt"
"path/filepath"
)
type Singleton interface {
GenerateBuildActions(SingletonContext)
}
type SingletonContext interface {
Config() Config
ModuleName(module Module) string
ModuleDir(module Module) string
BlueprintFile(module Module) string
ModuleErrorf(module Module, format string, args ...interface{})
Errorf(format string, args ...interface{})
Variable(name, value string)
Rule(name string, params RuleParams) Rule
Build(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)
VisitAllModules(visit func(Module))
VisitAllModulesIf(pred func(Module) bool, visit func(Module))
}
var _ SingletonContext = (*singletonContext)(nil)
type singletonContext struct {
context *Context
config Config
scope *localScope
errs []error
actionDefs localBuildActions
}
func (s *singletonContext) Config() Config {
return s.config
}
func (s *singletonContext) ModuleName(module Module) string {
info := s.context.moduleInfo[module]
return info.properties.Name
}
func (s *singletonContext) ModuleDir(module Module) string {
info := s.context.moduleInfo[module]
return filepath.Dir(info.relBlueprintFile)
}
func (s *singletonContext) BlueprintFile(module Module) string {
info := s.context.moduleInfo[module]
return info.relBlueprintFile
}
func (s *singletonContext) ModuleErrorf(module Module, format string,
args ...interface{}) {
info := s.context.moduleInfo[module]
s.errs = append(s.errs, &Error{
Err: fmt.Errorf(format, args...),
Pos: info.pos,
})
}
func (s *singletonContext) Errorf(format string, args ...interface{}) {
// TODO: Make this not result in the error being printed as "internal error"
s.errs = append(s.errs, fmt.Errorf(format, args...))
}
func (s *singletonContext) Variable(name, value string) {
v, err := s.scope.AddLocalVariable(name, value)
if err != nil {
panic(err)
}
s.actionDefs.variables = append(s.actionDefs.variables, v)
}
func (s *singletonContext) Rule(name string, params RuleParams) Rule {
// TODO: Verify that params.Pool is accessible in this module's scope.
r, err := s.scope.AddLocalRule(name, &params)
if err != nil {
panic(err)
}
s.actionDefs.rules = append(s.actionDefs.rules, r)
return r
}
func (s *singletonContext) Build(params BuildParams) {
// TODO: Verify that params.Rule is accessible in this module's scope.
def, err := parseBuildParams(s.scope, &params)
if err != nil {
panic(err)
}
s.actionDefs.buildDefs = append(s.actionDefs.buildDefs, def)
}
func (s *singletonContext) RequireNinjaVersion(major, minor, micro int) {
s.context.requireNinjaVersion(major, minor, micro)
}
func (s *singletonContext) SetBuildDir(value string) {
ninjaValue, err := parseNinjaString(s.scope, value)
if err != nil {
panic(err)
}
s.context.setBuildDir(ninjaValue)
}
func (s *singletonContext) VisitAllModules(visit func(Module)) {
s.context.visitAllModules(visit)
}
func (s *singletonContext) VisitAllModulesIf(pred func(Module) bool,
visit func(Module)) {
s.context.visitAllModulesIf(pred, visit)
}

196
blueprint/unpack.go Normal file
View file

@ -0,0 +1,196 @@
package blueprint
import (
"blueprint/parser"
"fmt"
"reflect"
"unicode"
"unicode/utf8"
)
type packedProperty struct {
property *parser.Property
unpacked bool
}
func unpackProperties(propertyDefs []*parser.Property,
propertiesStructs ...interface{}) (errs []error) {
propertyMap := make(map[string]*packedProperty)
for _, propertyDef := range propertyDefs {
name := propertyDef.Name
if first, present := propertyMap[name]; present {
errs = append(errs, &Error{
Err: fmt.Errorf("property %q already defined", name),
Pos: propertyDef.Pos,
})
errs = append(errs, &Error{
Err: fmt.Errorf("--> previous definition here"),
Pos: first.property.Pos,
})
if len(errs) >= maxErrors {
return errs
}
continue
}
propertyMap[name] = &packedProperty{
property: propertyDef,
unpacked: false,
}
}
for _, properties := range propertiesStructs {
propertiesValue := reflect.ValueOf(properties)
if propertiesValue.Kind() != reflect.Ptr {
panic("properties must be a pointer to a struct")
}
propertiesValue = propertiesValue.Elem()
if propertiesValue.Kind() != reflect.Struct {
panic("properties must be a pointer to a struct")
}
newErrs := unpackStruct(propertiesValue, propertyMap)
errs = append(errs, newErrs...)
if len(errs) >= maxErrors {
return errs
}
}
// Report any properties that didn't have corresponding struct fields as
// errors.
for name, packedProperty := range propertyMap {
if !packedProperty.unpacked {
err := &Error{
Err: fmt.Errorf("unrecognized property %q", name),
Pos: packedProperty.property.Pos,
}
errs = append(errs, err)
}
}
return errs
}
func unpackStruct(structValue reflect.Value,
propertyMap map[string]*packedProperty) []error {
structType := structValue.Type()
var errs []error
for i := 0; i < structValue.NumField(); i++ {
fieldValue := structValue.Field(i)
field := structType.Field(i)
if !fieldValue.CanSet() {
panic(fmt.Errorf("field %s is not settable", field.Name))
}
// To make testing easier we validate the struct field's type regardless
// of whether or not the property was specified in the parsed string.
switch kind := fieldValue.Kind(); kind {
case reflect.Bool, reflect.String:
// Do nothing
case reflect.Slice:
elemType := field.Type.Elem()
if elemType.Kind() != reflect.String {
panic(fmt.Errorf("field %s is a non-string slice", field.Name))
}
case reflect.Struct:
newErrs := unpackStruct(fieldValue, propertyMap)
errs = append(errs, newErrs...)
if len(errs) >= maxErrors {
return errs
}
continue // This field doesn't correspond to a specific property.
default:
panic(fmt.Errorf("unsupported kind for field %s: %s",
field.Name, kind))
}
// Get the property value if it was specified.
propertyName := propertyNameForField(field)
packedProperty, ok := propertyMap[propertyName]
if !ok {
// This property wasn't specified.
continue
}
packedProperty.unpacked = true
var newErrs []error
switch kind := fieldValue.Kind(); kind {
case reflect.Bool:
newErrs = unpackBool(fieldValue, packedProperty.property)
case reflect.String:
newErrs = unpackString(fieldValue, packedProperty.property)
case reflect.Slice:
newErrs = unpackSlice(fieldValue, packedProperty.property)
}
errs = append(errs, newErrs...)
if len(errs) >= maxErrors {
return errs
}
}
return errs
}
func unpackBool(boolValue reflect.Value, property *parser.Property) []error {
if property.Value.Type != parser.Bool {
return []error{
fmt.Errorf("%s: can't assign %s value to %s property %q",
property.Value.Pos, property.Value.Type, parser.Bool,
property.Name),
}
}
boolValue.SetBool(property.Value.BoolValue)
return nil
}
func unpackString(stringValue reflect.Value,
property *parser.Property) []error {
if property.Value.Type != parser.String {
return []error{
fmt.Errorf("%s: can't assign %s value to %s property %q",
property.Value.Pos, property.Value.Type, parser.String,
property.Name),
}
}
stringValue.SetString(property.Value.StringValue)
return nil
}
func unpackSlice(sliceValue reflect.Value, property *parser.Property) []error {
if property.Value.Type != parser.List {
return []error{
fmt.Errorf("%s: can't assign %s value to %s property %q",
property.Value.Pos, property.Value.Type, parser.List,
property.Name),
}
}
var list []string
for _, value := range property.Value.ListValue {
if value.Type != parser.String {
// The parser should not produce this.
panic("non-string value found in list")
}
list = append(list, value.StringValue)
}
sliceValue.Set(reflect.ValueOf(list))
return nil
}
func propertyNameForField(field reflect.StructField) string {
r, size := utf8.DecodeRuneInString(field.Name)
propertyName := string(unicode.ToLower(r))
if len(field.Name) > size {
propertyName += field.Name[size:]
}
return propertyName
}

124
blueprint/unpack_test.go Normal file
View file

@ -0,0 +1,124 @@
package blueprint
import (
"blueprint/parser"
"bytes"
"reflect"
"testing"
)
var validUnpackTestCases = []struct {
input string
output interface{}
}{
{`
m {
name: "abc",
}
`,
struct {
Name string
}{
Name: "abc",
},
},
{`
m {
isGood: true,
}
`,
struct {
IsGood bool
}{
IsGood: true,
},
},
{`
m {
stuff: ["asdf", "jkl;", "qwert",
"uiop", "bnm,"]
}
`,
struct {
Stuff []string
}{
Stuff: []string{"asdf", "jkl;", "qwert", "uiop", "bnm,"},
},
},
{`
m {
name: "abc",
}
`,
struct {
Nested struct {
Name string
}
}{
Nested: struct{ Name string }{
Name: "abc",
},
},
},
{`
m {
foo: "abc",
bar: false,
baz: ["def", "ghi"],
}
`,
struct {
Nested struct {
Foo string
}
Bar bool
Baz []string
}{
Nested: struct{ Foo string }{
Foo: "abc",
},
Bar: false,
Baz: []string{"def", "ghi"},
},
},
}
func TestUnpackProperties(t *testing.T) {
for _, testCase := range validUnpackTestCases {
r := bytes.NewBufferString(testCase.input)
defs, errs := parser.Parse("", r)
if len(errs) != 0 {
t.Errorf("test case: %s", testCase.input)
t.Errorf("unexpected parse errors:")
for _, err := range errs {
t.Errorf(" %s", err)
}
t.FailNow()
}
module := defs[0].(*parser.Module)
propertiesType := reflect.TypeOf(testCase.output)
properties := reflect.New(propertiesType)
errs = unpackProperties(module.Properties, properties.Interface())
if len(errs) != 0 {
t.Errorf("test case: %s", testCase.input)
t.Errorf("unexpected unpack errors:")
for _, err := range errs {
t.Errorf(" %s", err)
}
t.FailNow()
}
output := properties.Elem().Interface()
if !reflect.DeepEqual(output, testCase.output) {
t.Errorf("test case: %s", testCase.input)
t.Errorf("incorrect output:")
t.Errorf(" expected: %+v", testCase.output)
t.Errorf(" got: %+v", output)
}
}
}

35
bootstrap.bash Executable file
View file

@ -0,0 +1,35 @@
#!/bin/bash
# SRCDIR should be set to the path of the root source directory. It can be
# either an absolute path or a path relative to the build directory. Whether
# its an absolute or relative path determines whether the build directory can be
# moved relative to or along with the source directory without re-running the
# bootstrap script.
SRCDIR=`dirname "${BASH_SOURCE[0]}"`
# BOOTSTRAP should be set to the path of this script. It can be either an
# absolute path or one relative to the build directory (which of these is used
# should probably match what's used for SRCDIR).
BOOTSTRAP="${BASH_SOURCE[0]}"
# These variables should be set by auto-detecting or knowing a priori the Go
# toolchain properties.
GOROOT=`go env GOROOT`
GOOS=`go env GOHOSTOS`
GOARCH=`go env GOHOSTARCH`
GOCHAR=`go env GOCHAR`
case "$#" in
1) IN="$1";BOOTSTRAP_MANIFEST="$1";;
2) IN="$1";BOOTSTRAP_MANIFEST="$2";;
*) IN="${SRCDIR}/build.ninja.in";BOOTSTRAP_MANIFEST="$IN";;
esac
sed -e "s|@@SrcDir@@|$SRCDIR|g" \
-e "s|@@GoRoot@@|$GOROOT|g" \
-e "s|@@GoOS@@|$GOOS|g" \
-e "s|@@GoArch@@|$GOARCH|g" \
-e "s|@@GoChar@@|$GOCHAR|g" \
-e "s|@@Bootstrap@@|$BOOTSTRAP|g" \
-e "s|@@BootstrapManifest@@|$BOOTSTRAP_MANIFEST|g" \
$IN > build.ninja

156
build.ninja.in Normal file
View file

@ -0,0 +1,156 @@
# ******************************************************************************
# *** This file is generated and should not be edited ***
# ******************************************************************************
#
# This file contains variables, rules, and pools with name prefixes indicating
# they were generated by the following Go packages:
#
# bootstrap [from Go package blueprint/bootstrap]
#
ninja_required_version = 1.1.0
g.bootstrap.Bootstrap = @@Bootstrap@@
g.bootstrap.BootstrapManifest = @@BootstrapManifest@@
g.bootstrap.GoArch = @@GoArch@@
g.bootstrap.GoChar = @@GoChar@@
g.bootstrap.GoOS = @@GoOS@@
g.bootstrap.GoRoot = @@GoRoot@@
g.bootstrap.SrcDir = @@SrcDir@@
g.bootstrap.goToolDir = ${g.bootstrap.GoRoot}/pkg/tool/${g.bootstrap.GoOS}_${g.bootstrap.GoArch}
g.bootstrap.gcCmd = ${g.bootstrap.goToolDir}/${g.bootstrap.GoChar}g
g.bootstrap.linkCmd = ${g.bootstrap.goToolDir}/${g.bootstrap.GoChar}l
g.bootstrap.packCmd = ${g.bootstrap.goToolDir}/pack
builddir = bootstrap
rule g.bootstrap.gc
command = GOROOT='${g.bootstrap.GoRoot}' ${g.bootstrap.gcCmd} -o ${out} -p ${pkgPath} -complete ${incFlags} ${in}
description = ${g.bootstrap.GoChar}g ${out}
rule g.bootstrap.pack
command = GOROOT='${g.bootstrap.GoRoot}' ${g.bootstrap.packCmd} grcP ${prefix} ${out} ${in}
description = pack ${out}
rule g.bootstrap.link
command = GOROOT='${g.bootstrap.GoRoot}' ${g.bootstrap.linkCmd} -o ${out} ${libDirFlags} ${in}
description = ${g.bootstrap.GoChar}l ${out}
rule g.bootstrap.cp
command = cp ${in} ${out}
description = cp ${out}
rule g.bootstrap.bootstrap
command = ${g.bootstrap.Bootstrap} ${in} ${g.bootstrap.BootstrapManifest}
description = bootstrap ${in}
generator = true
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Module: blueprint
# Type: bootstrap_go_package
# GoType: blueprint/bootstrap.goPackageModule
# Defined: ../Blueprints:1:1
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/scope.go $
${g.bootstrap.SrcDir}/blueprint/singleton_ctx.go $
${g.bootstrap.SrcDir}/blueprint/unpack.go | $
bootstrap/blueprint-parser/pkg/blueprint/parser.a
pkgPath = blueprint
incFlags = -I bootstrap/blueprint-parser/pkg
build bootstrap/blueprint/pkg/blueprint.a: g.bootstrap.pack $
bootstrap/blueprint/obj/_go_.${g.bootstrap.GoChar}
prefix = bootstrap/blueprint/pkg
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Module: blueprint-parser
# Type: bootstrap_go_package
# GoType: blueprint/bootstrap.goPackageModule
# Defined: ../Blueprints:18:1
build bootstrap/blueprint-parser/obj/_go_.${g.bootstrap.GoChar}: $
g.bootstrap.gc ${g.bootstrap.SrcDir}/blueprint/parser/parser.go
pkgPath = blueprint/parser
build bootstrap/blueprint-parser/pkg/blueprint/parser.a: g.bootstrap.pack $
bootstrap/blueprint-parser/obj/_go_.${g.bootstrap.GoChar}
prefix = bootstrap/blueprint-parser/pkg
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Module: blueprint-bootstrap
# Type: bootstrap_go_package
# GoType: blueprint/bootstrap.goPackageModule
# Defined: ../Blueprints:24:1
build bootstrap/blueprint-bootstrap/obj/_go_.${g.bootstrap.GoChar}: $
g.bootstrap.gc ${g.bootstrap.SrcDir}/blueprint/bootstrap/bootstrap.go $
${g.bootstrap.SrcDir}/blueprint/bootstrap/command.go $
${g.bootstrap.SrcDir}/blueprint/bootstrap/config.go $
${g.bootstrap.SrcDir}/blueprint/bootstrap/doc.go | $
bootstrap/blueprint-parser/pkg/blueprint/parser.a $
bootstrap/blueprint/pkg/blueprint.a
pkgPath = blueprint/bootstrap
incFlags = -I bootstrap/blueprint-parser/pkg -I bootstrap/blueprint/pkg
build bootstrap/blueprint-bootstrap/pkg/blueprint/bootstrap.a: $
g.bootstrap.pack $
bootstrap/blueprint-bootstrap/obj/_go_.${g.bootstrap.GoChar}
prefix = bootstrap/blueprint-bootstrap/pkg
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Module: minibp
# Type: bootstrap_go_binary
# GoType: blueprint/bootstrap.goBinaryModule
# Defined: ../Blueprints:34:1
build bootstrap/minibp/obj/_go_.${g.bootstrap.GoChar}: g.bootstrap.gc $
${g.bootstrap.SrcDir}/blueprint/bootstrap/minibp/main.go | $
bootstrap/blueprint-parser/pkg/blueprint/parser.a $
bootstrap/blueprint/pkg/blueprint.a $
bootstrap/blueprint-bootstrap/pkg/blueprint/bootstrap.a
pkgPath = minibp
incFlags = -I bootstrap/blueprint-parser/pkg -I bootstrap/blueprint/pkg -I bootstrap/blueprint-bootstrap/pkg
build bootstrap/minibp/obj/minibp.a: g.bootstrap.pack $
bootstrap/minibp/obj/_go_.${g.bootstrap.GoChar}
prefix = bootstrap/minibp/obj
build bootstrap/minibp/obj/a.out: g.bootstrap.link $
bootstrap/minibp/obj/minibp.a
libDirFlags = -L bootstrap/blueprint-parser/pkg -L bootstrap/blueprint/pkg -L bootstrap/blueprint-bootstrap/pkg
build bootstrap/bin/minibp: g.bootstrap.cp bootstrap/minibp/obj/a.out
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Singleton: bootstrap
# GoType: blueprint/bootstrap.singleton
rule s.bootstrap.bigbp
command = bootstrap/bin/minibp -p -d bootstrap/build.ninja.in.d -o ${out} ${in}
depfile = bootstrap/build.ninja.in.d
description = minibp ${out}
build bootstrap/build.ninja.in: s.bootstrap.bigbp $
${g.bootstrap.SrcDir}/Blueprints | bootstrap/bin/minibp
build bootstrap/notAFile: phony
build build.ninja: g.bootstrap.bootstrap bootstrap/build.ninja.in | $
${g.bootstrap.Bootstrap} bootstrap/notAFile bootstrap/bin/minibp