Blueprint based package build system

This is a simple package build system for Fuchsia based on
Blueprint and Ninja that can be used to drive other build systems.

Change-Id: I4215899c2c3c4f2e3624ce4d3d29f375c482460c
diff --git a/README.md b/README.md
index 1212b30..1897392 100644
--- a/README.md
+++ b/README.md
@@ -5,6 +5,61 @@
 responsible for invoking the build system of individual projects, handling
 dependencies across projects and packaging build artifacts.
 
+## Usage
+
+After checking out the source, you need to run `toyen` executable to generate
+the `build.ninja` file.
+
+```bash
+$ toyen -src . -out out packages/root.bp
+```
+
+This is only needed after checking out the source for the first time. The
+Ninja files have rules re-generating themselves when necessary.
+
+Therefore, a typical iteration loop is:
+
+1. edit your source
+2. run `ninja -C out`
+
+## Ninja and Blueprint
+
+Toyen build system is implemented on top of Blueprint and Ninja, so the reader
+should first understand how they work. This document provides a brief surface
+exploration of the Blueprint and Ninja.
+
+### Ninja
+
+Ninja provides a simple syntax for defining:
+1. file dependencies (e.g. foo.o depends on foo.c), and
+2. arbitrary commands to run for generating files when they're out of date.
+
+To keep things fast, there are very few other features. There's no branching
+logic, globbing, or decision making of any kind, really.  Ninja files can be
+written and read by humans, but that would be pointlessly tedious. Instead,
+a program is used to generate Ninja files.
+
+Check out the Ninja [documentation](http://martine.github.io/ninja/manual.html)
+and [source](https://github.com/martine/ninja) for more information.
+
+### Blueprint
+
+Unlike other build systems (even those that also generate Ninja files, like
+[gn](https://chromium.googlesource.com/chromium/src/tools/gn/)) Blueprint is not
+a stand-alone executable.  You don't *run Blueprint*.  Blueprint also doesn't
+come with any pre-defined build rules, except for the two that are necessary to
+build Blueprint itself.
+
+Blueprint is a set of Go packages that you use to implement whatever build rules
+your project finds useful. Through these packages, Blueprint provides high
+level constructs that are useful for generating Ninja files. Blueprint also
+provides a parser for reading Blueprint config files.
+
+To use Blueprint, you implement two things: your build modules, and a builder.
+Blueprint comes with basic versions of both of these things that are capable of
+building Blueprint itself. To learn more, read the extensive comments in the
+[Blueprint code](http://github.com/google/blueprint) for complete documentation.
+
 ## FAQ
 
 ### Why the name "Toyen"?
diff --git a/build.go b/build.go
new file mode 100644
index 0000000..05bc666
--- /dev/null
+++ b/build.go
@@ -0,0 +1,557 @@
+// Copyright 2016 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package main
+
+import (
+	"fmt"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"strings"
+
+	"github.com/google/blueprint"
+)
+
+var (
+	pctx = blueprint.NewPackageContext("fuchsia.googlesource.com/toyen")
+
+	cmakeCmd = pctx.StaticVariable("cmakeCmd", "cmake")
+	gnCmd    = pctx.StaticVariable("gnCmd", "gn")
+	makeCmd  = pctx.StaticVariable("makeCmd", "make")
+	ninjaCmd = pctx.StaticVariable("ninjaCmd", "ninja")
+
+	cmake = pctx.StaticRule("cmake",
+		blueprint.RuleParams{
+			Command: "cd $buildDir && $envVars $cmakeCmd -GNinja " +
+				"$cmakeOptions $cmakeDir",
+			Generator:   true,
+			Description: "cmake $cmakeDir",
+		},
+		"envVars", "cmakeOptions", "cmakeDir", "buildDir")
+
+	gn = pctx.StaticRule("gn",
+		blueprint.RuleParams{
+			Command: "$envVars $gnCmd gen $buildDir " +
+				"--root=$gnDir --script-executable=/usr/bin/env --args='$gnArgs'",
+			Generator: true,
+			Description: "gn $gnDir",
+		},
+		"envVars", "gnDir", "gnArgs", "buildDir")
+
+	_make = pctx.StaticRule("make",
+		blueprint.RuleParams{
+			Command: "$envVars $makeCmd -j $Jobs " +
+				"-C $makeDir -f $makeFile $targets",
+			Description: "make $makeDir",
+		},
+		"envVars", "targets", "makeFile", "makeDir")
+
+	ninja = pctx.StaticRule("ninja",
+		blueprint.RuleParams{
+			Command: "$envVars $ninjaCmd -j $Jobs " +
+				"-C $ninjaDir -f $ninjaFile $targets",
+			Description: "ninja $ninjaDir",
+		},
+		"envVars", "targets", "ninjaFile", "ninjaDir")
+
+	script = pctx.StaticRule("script",
+		blueprint.RuleParams{
+			Command:     "cd $workingDir && $envVars $scriptCmd $scriptArgs",
+			Description: "sh $in",
+		},
+		"envVars", "scriptCmd", "scriptArgs", "workingDir")
+
+	cp = pctx.StaticRule("cp",
+		blueprint.RuleParams{
+			Command:     "cp -vR $in $out",
+			Description: "cp $out",
+		})
+
+	install = pctx.StaticRule("install",
+		blueprint.RuleParams{
+			Command:     "install -c $in $out",
+			Description: "install $out",
+		})
+
+	mkdir = pctx.StaticRule("mkdir",
+		blueprint.RuleParams{
+			Command:     "mkdir -p $out",
+			Description: "mkdir $out",
+		})
+
+	stamp = pctx.StaticRule("stamp",
+		blueprint.RuleParams{
+			Command:     "touch $out",
+			Description: "stamp $out",
+		})
+)
+
+type BuilderModule interface {
+	TargetName() string
+}
+
+type builderModule struct {
+	targetName string
+}
+
+func (bm *builderModule) TargetName() string {
+	return bm.targetName
+}
+
+type Alias struct {
+	builderModule
+	properties struct{}
+	config     Config
+}
+
+func newAliasModuleFactory(config Config) func() (blueprint.Module, []interface{}) {
+	return func() (blueprint.Module, []interface{}) {
+		module := &Alias{
+			config: config,
+		}
+		return module, []interface{}{&module.properties}
+	}
+}
+
+func (a *Alias) GenerateBuildActions(ctx blueprint.ModuleContext) {
+	a.targetName = ctx.ModuleName()
+
+	ctx.Build(pctx, blueprint.BuildParams{
+		Rule:    blueprint.Phony,
+		Outputs: []string{ctx.ModuleName()},
+		Inputs:  getDirectDependencies(ctx),
+	})
+}
+
+type CMake struct {
+	builderModule
+	properties struct {
+		Env      []string
+		Options  []string
+		Src      string
+		BuildDir string
+	}
+	config Config
+}
+
+func newCMakeModuleFactory(config Config) func() (blueprint.Module, []interface{}) {
+	return func() (blueprint.Module, []interface{}) {
+		module := &CMake{
+			config: config,
+		}
+		return module, []interface{}{&module.properties}
+	}
+}
+
+func (c *CMake) GenerateBuildActions(ctx blueprint.ModuleContext) {
+	c.targetName = ctx.ModuleName()
+
+	options := make([]string, len(c.properties.Options))
+	for i := range c.properties.Options {
+		options[i] = fmt.Sprintf("-D%s", c.properties.Options[i])
+	}
+
+	// Add a rule for making the destination directory, in case it doesn't exist.
+	ctx.Build(pctx, blueprint.BuildParams{
+		Rule:     mkdir,
+		Outputs:  []string{c.properties.BuildDir},
+		Optional: true,
+	})
+
+	cmakeArgs := map[string]string{
+		"cmakeOptions": strings.Join(options, " "),
+		"cmakeDir":     c.properties.Src,
+		"envVars":      strings.Join(c.properties.Env, " "),
+		"buildDir":     c.properties.BuildDir,
+	}
+
+	ninjaFile := filepath.Join(c.properties.BuildDir, "build.ninja")
+
+	// Add a rule to generate the Ninja file.
+	ctx.Build(pctx, blueprint.BuildParams{
+		Rule:      cmake,
+		Outputs:   []string{ninjaFile},
+		Implicits: getDirectDependencies(ctx),
+		OrderOnly: []string{c.properties.BuildDir},
+		Args:      cmakeArgs,
+	})
+}
+
+type Copy struct {
+	builderModule
+	properties struct {
+		Sources     []string
+		Destination string
+	}
+	config Config
+}
+
+func newCopyModuleFactory(config Config) func() (blueprint.Module, []interface{}) {
+	return func() (blueprint.Module, []interface{}) {
+		module := &Copy{
+			config: config,
+		}
+		return module, []interface{}{&module.properties}
+	}
+}
+
+func (c *Copy) GenerateBuildActions(ctx blueprint.ModuleContext) {
+	c.targetName = ctx.ModuleName()
+
+	// If multiple source files, destination is a directory, otherwise, it's a file.
+	var destinationDir string
+	destinationFiles := make([]string, len(c.properties.Sources))
+	if len(c.properties.Sources) > 1 {
+		destinationDir = c.properties.Destination
+		for i, src := range c.properties.Sources {
+			destinationFiles[i] = filepath.Join(destinationDir, filepath.Base(src))
+		}
+	} else {
+		destinationDir = filepath.Dir(c.properties.Destination)
+		destinationFiles[0] = c.properties.Destination
+	}
+
+	// Add a rule for making the destination directory, in case it doesn't exist.
+	ctx.Build(pctx, blueprint.BuildParams{
+		Rule:     mkdir,
+		Outputs:  []string{destinationDir},
+		Optional: true,
+	})
+
+	// Add a rule for each source/destination pair.
+	for i, src := range c.properties.Sources {
+		ctx.Build(pctx, blueprint.BuildParams{
+			Rule:      cp,
+			Outputs:   []string{destinationFiles[i]},
+			Inputs:    []string{src},
+			Implicits: getDirectDependencies(ctx),
+			OrderOnly: []string{destinationDir},
+			Optional:  true,
+		})
+	}
+
+	// Add a phony rule to copy all the files with one rule.
+	ctx.Build(pctx, blueprint.BuildParams{
+		Rule:    blueprint.Phony,
+		Outputs: []string{ctx.ModuleName()},
+		Inputs:  destinationFiles,
+	})
+}
+
+type Gn struct {
+	builderModule
+	properties struct {
+		Env      []string
+		Args     []string
+		SrcDir   string
+		BuildDir string
+	}
+	config Config
+}
+
+func newGnModuleFactory(config Config) func() (blueprint.Module, []interface{}) {
+	return func() (blueprint.Module, []interface{}) {
+		module := &Gn{
+			config: config,
+		}
+		return module, []interface{}{&module.properties}
+	}
+}
+
+func (g *Gn) GenerateBuildActions(ctx blueprint.ModuleContext) {
+	g.targetName = ctx.ModuleName()
+
+	gnArgs := map[string]string{
+		"envVars":  strings.Join(g.properties.Env, " "),
+		"gnDir":    g.properties.SrcDir,
+		"gnArgs":   strings.Join(g.properties.Args, " "),
+		"buildDir": g.properties.BuildDir,
+	}
+
+	ninjaFile := filepath.Join(g.properties.BuildDir, "build.ninja")
+
+	// Add a rule to generate the Ninja file.
+	ctx.Build(pctx, blueprint.BuildParams{
+		Rule:      gn,
+		Outputs:   []string{ninjaFile},
+		Implicits: getDirectDependencies(ctx),
+		Args:      gnArgs,
+	})
+}
+
+type Install struct {
+	builderModule
+	properties struct {
+		Sources     []string
+		Destination string
+	}
+	config Config
+}
+
+func newInstallModuleFactory(config Config) func() (blueprint.Module, []interface{}) {
+	return func() (blueprint.Module, []interface{}) {
+		module := &Install{
+			config: config,
+		}
+		return module, []interface{}{&module.properties}
+	}
+}
+
+func (i *Install) GenerateBuildActions(ctx blueprint.ModuleContext) {
+	i.targetName = ctx.ModuleName()
+
+	destinationFiles := make([]string, len(i.properties.Sources))
+	for j, src := range i.properties.Sources {
+		destinationFiles[j] = filepath.Join(i.properties.Destination, filepath.Base(src))
+	}
+
+	// Add a rule for making the destination directory, in case it doesn't exist.
+	ctx.Build(pctx, blueprint.BuildParams{
+		Rule:     mkdir,
+		Outputs:  []string{i.properties.Destination},
+		Optional: true,
+	})
+
+	// Add a rule to install sources.
+	ctx.Build(pctx, blueprint.BuildParams{
+		Rule:      install,
+		Outputs:   destinationFiles,
+		Inputs:    i.properties.Sources,
+		Implicits: getDirectDependencies(ctx),
+		OrderOnly: []string{i.properties.Destination},
+	})
+}
+
+type Make struct {
+	builderModule
+	properties struct {
+		Env      []string
+		Makefile string
+		Targets  []string
+		Outputs  []string
+	}
+	config Config
+}
+
+func newMakeModuleFactory(config Config) func() (blueprint.Module, []interface{}) {
+	return func() (blueprint.Module, []interface{}) {
+		module := &Make{
+			config: config,
+		}
+		return module, []interface{}{&module.properties}
+	}
+}
+
+func (m *Make) GenerateBuildActions(ctx blueprint.ModuleContext) {
+	m.targetName = ctx.ModuleName()
+
+	makeArgs := map[string]string{
+		"envVars":  strings.Join(m.properties.Env, " "),
+		"makeFile": filepath.Base(m.properties.Makefile),
+		"makeDir":  filepath.Dir(m.properties.Makefile),
+	}
+	if len(m.properties.Targets) > 0 {
+		makeArgs["targets"] = strings.Join(m.properties.Targets, " ")
+	}
+
+	var outputs []string
+	if len(m.properties.Outputs) > 0 {
+		outputs = m.properties.Outputs
+	} else {
+		outputs = []string{ctx.ModuleName()}
+	}
+
+	// Add a single rule where our touchFile depends on our make rule.
+	ctx.Build(pctx, blueprint.BuildParams{
+		Rule:      _make,
+		Outputs:   outputs,
+		Implicits: append(getDirectDependencies(ctx), m.properties.Makefile),
+		Args:      makeArgs,
+		Optional:  true,
+	})
+}
+
+type Ninja struct {
+	builderModule
+	properties struct {
+		Env       []string
+		NinjaFile string
+		Targets   []string
+		Outputs   []string
+	}
+	config Config
+}
+
+func newNinjaModuleFactory(config Config) func() (blueprint.Module, []interface{}) {
+	return func() (blueprint.Module, []interface{}) {
+		module := &Ninja{
+			config: config,
+		}
+		return module, []interface{}{&module.properties}
+	}
+}
+
+func (n *Ninja) GenerateBuildActions(ctx blueprint.ModuleContext) {
+	n.targetName = ctx.ModuleName()
+
+	ninjaArgs := map[string]string{
+		"envVars":   strings.Join(n.properties.Env, " "),
+		"ninjaFile": filepath.Base(n.properties.NinjaFile),
+		"ninjaDir":  filepath.Dir(n.properties.NinjaFile),
+	}
+	if len(n.properties.Targets) != 0 {
+		ninjaArgs["targets"] = strings.Join(n.properties.Targets, " ")
+	}
+
+	var outputs []string
+	if len(n.properties.Outputs) > 0 {
+		outputs = n.properties.Outputs
+	} else {
+		outputs = []string{ctx.ModuleName()}
+	}
+
+	ctx.Build(pctx, blueprint.BuildParams{
+		Rule:      ninja,
+		Outputs:   outputs,
+		Implicits: append(getDirectDependencies(ctx), n.properties.NinjaFile),
+		Args:      ninjaArgs,
+		Optional:  true,
+	})
+}
+
+type Script struct {
+	builderModule
+	properties struct {
+		Script     string
+		Outputs    []string
+		Inputs     []string
+		Args       []string
+		WorkingDir string
+		Env        []string
+		GenFiles   []string
+	}
+	config Config
+}
+
+func newScriptModuleFactory(config Config) func() (blueprint.Module, []interface{}) {
+	return func() (blueprint.Module, []interface{}) {
+		module := &Script{
+			config: config,
+		}
+		return module, []interface{}{&module.properties}
+	}
+}
+
+func (s *Script) GenerateBuildActions(ctx blueprint.ModuleContext) {
+	s.targetName = ctx.ModuleName()
+
+	// Add a rule for making the destination directory, in case it doesn't exist.
+	ctx.Build(pctx, blueprint.BuildParams{
+		Rule:     mkdir,
+		Outputs:  []string{s.properties.WorkingDir},
+		Optional: true,
+	})
+
+	ruleArgs := map[string]string{
+		"envVars":    strings.Join(s.properties.Env, " "),
+		"scriptCmd":  s.properties.Script,
+		"scriptArgs": strings.Join(s.properties.Args, " "),
+		"workingDir": s.properties.WorkingDir,
+	}
+
+	implicits := append(
+		s.properties.Inputs,
+		append(getDirectDependencies(ctx), s.properties.Script)...,
+	)
+
+	outputs := append(s.properties.Outputs, s.properties.GenFiles...)
+
+	if len(outputs) != 0 {
+		// Add a rule to run the script to generate the outputs.
+		ctx.Build(pctx, blueprint.BuildParams{
+			Rule:      script,
+			Outputs:   outputs,
+			Inputs:    []string{s.properties.Script},
+			Args:      ruleArgs,
+			Implicits: implicits,
+			OrderOnly: []string{s.properties.WorkingDir},
+			Optional:  true,
+		})
+
+		// The only default rule should be one with the module name.
+		ctx.Build(pctx, blueprint.BuildParams{
+			Rule:    blueprint.Phony,
+			Outputs: []string{ctx.ModuleName()},
+			Inputs:  outputs,
+		})
+	} else {
+		// For scripts with no outputs, use a script rule with the module name.
+		ctx.Build(pctx, blueprint.BuildParams{
+			Rule:      script,
+			Outputs:   []string{ctx.ModuleName()},
+			Inputs:    []string{s.properties.Script},
+			Args:      ruleArgs,
+			Implicits: implicits,
+			OrderOnly: []string{s.properties.WorkingDir},
+		})
+	}
+}
+
+type Bootstrap struct {
+	config Config
+}
+
+func newBootstrapFactory(config Config) func() blueprint.Singleton {
+	return func() blueprint.Singleton {
+		return &Bootstrap{
+			config: config,
+		}
+	}
+}
+
+func (m *Bootstrap) GenerateBuildActions(ctx blueprint.SingletonContext) {
+	executable, _ := exec.LookPath(os.Args[0])
+
+	builder := ctx.Rule(pctx, "builder",
+		blueprint.RuleParams{
+			Command:     fmt.Sprintf("%s $rootBlueprintFile", executable),
+			Description: "Regenerating Ninja files",
+			Generator:   true,
+			Depfile:     depFile,
+		}, "rootBlueprintFile")
+
+	args := map[string]string{
+		"rootBlueprintFile": rootBlueprintsFile,
+	}
+
+	ctx.Build(pctx, blueprint.BuildParams{
+		Rule:    builder,
+		Outputs: []string{outFile},
+		Args:    args,
+	})
+}
+
+func isBuilderModule(m blueprint.Module) bool {
+	_, ok := m.(BuilderModule)
+	return ok
+}
+
+func getDirectDependencies(ctx blueprint.ModuleContext) []string {
+	var depTargets []string
+	ctx.VisitDirectDepsIf(isBuilderModule, func(m blueprint.Module) {
+		target := m.(BuilderModule)
+		depTargets = append(depTargets, target.TargetName())
+	})
+	return depTargets
+}
+
+func getAllDependencies(ctx blueprint.ModuleContext) []string {
+	var depTargets []string
+	ctx.VisitDepsDepthFirstIf(isBuilderModule, func(m blueprint.Module) {
+		target := m.(BuilderModule)
+		depTargets = append(depTargets, target.TargetName())
+	})
+	return depTargets
+}
diff --git a/config.go b/config.go
new file mode 100644
index 0000000..e36b299
--- /dev/null
+++ b/config.go
@@ -0,0 +1,94 @@
+// Copyright 2016 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package main
+
+import (
+	"flag"
+	"fmt"
+	"path/filepath"
+	"runtime"
+	"strings"
+)
+
+var (
+	OutDir       = pctx.VariableConfigMethod("OutDir", Config.OutDir)
+	SrcDir       = pctx.VariableConfigMethod("SrcDir", Config.SrcDir)
+	HostTriple   = pctx.VariableConfigMethod("HostTriple", Config.HostTriple)
+	TargetTriple = pctx.VariableConfigMethod("TargetTriple", Config.TargetTriple)
+
+	HostArch     = pctx.VariableConfigMethod("HostArch", Config.HostArch)
+	HostOS       = pctx.VariableConfigMethod("HostOS", Config.HostOS)
+	TargetArch   = pctx.VariableConfigMethod("TargetArch", Config.TargetArch)
+	TargetOS     = pctx.VariableConfigMethod("TargetOS", Config.TargetOS)
+
+	jobs int
+	Jobs = pctx.VariableConfigMethod("Jobs", func(c *buildConfig) string {
+		return fmt.Sprintf("%d", jobs)
+	})
+
+	ToolsDir = pctx.StaticVariable("ToolsDir", filepath.Join("root", "tools"))
+)
+
+func init() {
+	flag.IntVar(&jobs, "j", runtime.NumCPU(), "number of parallel jobs")
+}
+
+type Config interface {
+	OutDir() string
+	SrcDir() string
+	HostTriple() string
+	TargetTriple() string
+	HostArch() string
+	HostOS() string
+	TargetArch() string
+	TargetOS() string
+}
+
+type buildConfig struct {
+	srcDir       string
+	outDir       string
+	hostTriple   string
+	targetTriple string
+}
+
+func NewConfig(srcDir string, outDir string, hostTriple string, targetTriple string) *buildConfig {
+	return &buildConfig{srcDir, outDir, hostTriple, targetTriple}
+}
+
+func (c *buildConfig) OutDir() string {
+	return c.outDir
+}
+
+func (c *buildConfig) SrcDir() string {
+	return c.srcDir
+}
+
+func (c *buildConfig) HostTriple() string {
+	return c.hostTriple
+}
+
+func (c *buildConfig) TargetTriple() string {
+	return c.targetTriple
+}
+
+func (c *buildConfig) HostArch() string {
+	s := strings.Split(c.hostTriple, "-")
+	return s[0]
+}
+
+func (c *buildConfig) HostOS() string {
+	s := strings.Split(c.hostTriple, "-")
+	return strings.Title(s[len(s) - 1])
+}
+
+func (c *buildConfig) TargetArch() string {
+	s := strings.Split(c.targetTriple, "-")
+	return s[0]
+}
+
+func (c *buildConfig) TargetOS() string {
+	s := strings.SplitN(c.targetTriple, "-", 3)
+	return strings.Title(s[len(s) - 1])
+}
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..c46656d
--- /dev/null
+++ b/main.go
@@ -0,0 +1,147 @@
+// Copyright 2016 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package main
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"runtime"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/deptools"
+)
+
+var (
+	rootBlueprintsFile string
+	manifestFile       string
+
+	depFile      string
+	outFile      string
+	hostTriple   string
+	targetTriple string
+
+	srcDir string
+	outDir string
+)
+
+func init() {
+	flag.StringVar(&srcDir, "src", ".", "the source directory")
+	flag.StringVar(&outDir, "out", ".", "the build output directory")
+	flag.StringVar(&hostTriple, "host", triple(), "build tools to run on")
+	flag.StringVar(&targetTriple, "target", "", "target triple")
+
+	flag.Usage = func() {
+	    fmt.Printf("Usage: toyen [options] <Blueprint file>\n")
+	    flag.PrintDefaults()
+	}
+}
+
+func triple() string {
+	arches := map[string]string{
+		"386":   "i386",
+		"amd64": "x86_64",
+		"arm":   "armv7a",
+		"arm64": "aarch64",
+	}
+
+	var arch string
+	var ok bool
+	if arch, ok = arches[runtime.GOARCH]; !ok {
+		arch = "unknown"
+	}
+
+	return fmt.Sprintf("%s-%s", arch, runtime.GOOS)
+}
+
+func main() {
+	flag.Parse()
+
+	// The top-level Blueprints file is passed as the first argument.
+	if len(flag.Args()) == 0 {
+		flag.Usage()
+		os.Exit(1)
+	}
+
+	rootBlueprintsFile, _ = filepath.Abs(flag.Arg(0))
+
+	srcDir, _ = filepath.Abs(srcDir)
+	outDir, _ = filepath.Abs(outDir)
+
+	outFile = filepath.Join(outDir, "build.ninja")
+	depFile = outFile + ".d"
+
+	config := NewConfig(srcDir, outDir, hostTriple, targetTriple)
+
+	// Create the build context.
+	ctx := blueprint.NewContext()
+
+	// Register custom module types.
+	ctx.RegisterModuleType("alias", newAliasModuleFactory(config))
+	ctx.RegisterModuleType("cmake", newCMakeModuleFactory(config))
+	ctx.RegisterModuleType("copy", newCopyModuleFactory(config))
+	ctx.RegisterModuleType("gn", newGnModuleFactory(config))
+	ctx.RegisterModuleType("install", newInstallModuleFactory(config))
+	ctx.RegisterModuleType("make", newMakeModuleFactory(config))
+	ctx.RegisterModuleType("ninja", newNinjaModuleFactory(config))
+	ctx.RegisterModuleType("script", newScriptModuleFactory(config))
+
+	ctx.RegisterSingletonType("bootstrap", newBootstrapFactory(config))
+
+	deps, errs := ctx.ParseBlueprintsFiles(rootBlueprintsFile)
+	if len(errs) > 0 {
+		fatalErrors(errs)
+	}
+
+	errs = ctx.ResolveDependencies(config)
+	if len(errs) > 0 {
+		fatalErrors(errs)
+	}
+
+	extraDeps, errs := ctx.PrepareBuildActions(config)
+	if len(errs) > 0 {
+		fatalErrors(errs)
+	}
+	deps = append(deps, extraDeps...)
+
+	buf := bytes.NewBuffer(nil)
+	if err := ctx.WriteBuildFile(buf); err != nil {
+		fatalf("error generating Ninja file contents: %s", err)
+	}
+
+	const outFilePermissions = 0666
+
+	if err := ioutil.WriteFile(outFile, buf.Bytes(), outFilePermissions); err != nil {
+		fatalf("error writing %s: %s", outFile, err)
+	}
+
+	if err := deptools.WriteDepFile(depFile, outFile, deps); err != nil {
+		fatalf("error writing depfile: %s", err)
+	}
+}
+
+func fatalf(format string, args ...interface{}) {
+	fmt.Printf(format, args...)
+	fmt.Print("\n")
+	os.Exit(1)
+}
+
+func fatalErrors(errs []error) {
+	red := "\x1b[31m"
+	unred := "\x1b[0m"
+
+	for _, err := range errs {
+		switch err := err.(type) {
+		case *blueprint.Error:
+			fmt.Printf("%serror:%s %s\n", red, unred, err.Error())
+		default:
+			fmt.Printf("%sinternal error:%s %s\n", red, unred, err)
+		}
+	}
+	os.Exit(1)
+}