<|start_filename|>Makefile<|end_filename|>
build: kang
	./$^ build
	
kang: .kang/kang-bootstrap
	$^ build

.kang/kang-bootstrap: .kang/bootstrap/github.com/constabulary/kang/cmd/kang.a
	go tool link -o $@ -L .kang/bootstrap -w -extld=gcc -buildmode=exe $^

.kang/bootstrap/github.com/constabulary/kang/cmd/kang.a: .kang/bootstrap/github.com/constabulary/kang.a cmd/kang/main.go cmd/kang/kangfile.go cmd/kang/stdlib.go
	mkdir -p .kang/bootstrap/github.com/constabulary/kang/cmd/
	go tool compile -o $@ -p github.com/constabular/cmd/kang -complete -I .kang/bootstrap -pack cmd/kang/main.go cmd/kang/kangfile.go cmd/kang/stdlib.go

.kang/bootstrap/github.com/constabulary/kang.a: kang.go
	mkdir -p .kang/bootstrap/github.com/constabulary
	go tool compile -o $@ -p github.com/constabulary/kang -complete -I .kang/bootstrap -pack $^

clean:
	rm -rf .kang/ kang


<|start_filename|>kang.go<|end_filename|>
package kang

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"time"
)

// Context contains all build specific values.
type Context struct {
	GOOS, GOARCH string
	Workdir      string
	Pkgdir       string
	Bindir       string
	force        bool     // always force build, even if not stale
	race         bool     // build a -race enabled binary
	gcflags      []string // -gcflags
	ldflags      []string // -ldflags
	buildtags    []string
}

func (c *Context) isCrossCompile() bool { return false }

func (c *Context) searchPaths() []string {
	return []string{
		c.Workdir,
		c.Pkgdir,
	}
}

// ctxString returns a string representation of the unique properties
// of the context.
func (c *Context) ctxString() string {
	v := []string{
		c.GOOS,
		c.GOARCH,
	}
	v = append(v, c.buildtags...)
	return strings.Join(v, "-")
}

// Package describes a set of Go files to be compiled.
type Package struct {
	*Context
	ImportPath string
	Dir        string
	GoFiles    []string
	Imports    []*Package
	standard   bool // is this part of the stdlib
	testScope  bool // is a test scoped packge
	Main       bool // this is a command
	NotStale   bool // this package _and_ all its dependencies are not stale
}

const debug = true

func debugf(format string, args ...interface{}) {
	if !debug {
		return
	}
	fmt.Fprintf(os.Stderr, format+"\n", args...)
}

// IsStale returns true if the source pkg is considered to be stale with
// respect to its installed version.
func (pkg *Package) IsStale() bool {
	switch pkg.ImportPath {
	case "C", "unsafe":
		// synthetic packages are never stale
		return false
	}

	if !pkg.standard && pkg.force {
		return true
	}

	// tests are always stale, they are never installed
	if pkg.testScope {
		return true
	}

	// Package is stale if completely unbuilt.
	var built time.Time
	if fi, err := os.Stat(pkg.pkgpath()); err == nil {
		built = fi.ModTime()
	}

	if built.IsZero() {
		debugf("%s is missing", pkg.pkgpath())
		return true
	}

	olderThan := func(file string) bool {
		fi, err := os.Stat(file)
		return err != nil || fi.ModTime().After(built)
	}

	newerThan := func(file string) bool {
		fi, err := os.Stat(file)
		return err != nil || fi.ModTime().Before(built)
	}

	if pkg.standard && !pkg.isCrossCompile() {
		// if this is a standard lib package, and we are not cross compiling
		// then assume the package is up to date. This also works around
		// golang/go#13769.
		return false
	}

	// Package is stale if a dependency is newer.
	for _, p := range pkg.Imports {
		if p.ImportPath == "C" || p.ImportPath == "unsafe" {
			continue // ignore stale imports of synthetic packages
		}
		if olderThan(p.pkgpath()) {
			debugf("%s is older than %s", pkg.pkgpath(), p.pkgpath())
			return true
		}
	}

	// if the main package is up to date but _newer_ than the binary (which
	// could have been removed), then consider it stale.
	if pkg.Main && newerThan(pkg.Binfile()) {
		debugf("%s is newer than %s", pkg.pkgpath(), pkg.Binfile())
		return true
	}

	for _, src := range pkg.files() {
		if olderThan(filepath.Join(pkg.Dir, src)) {
			debugf("%s is older than %s", pkg.pkgpath(), filepath.Join(pkg.Dir, src))
			return true
		}
	}

	return false
}

// files returns all source files in scope
func (p *Package) files() []string {
	return stringList(p.GoFiles)
}

// pkgpath returns the destination for object cached for this Package.
func (pkg *Package) pkgpath() string {
	importpath := filepath.FromSlash(pkg.ImportPath) + ".a"
	switch {
	case pkg.isCrossCompile():
		return filepath.Join(pkg.Pkgdir, importpath)
	case pkg.standard && pkg.race:
		// race enabled standard lib
		return filepath.Join(runtime.GOROOT(), "pkg", pkg.GOOS+"_"+pkg.GOARCH+"_race", importpath)
	case pkg.standard:
		// standard lib
		return filepath.Join(runtime.GOROOT(), "pkg", pkg.GOOS+"_"+pkg.GOARCH, importpath)
	default:
		return filepath.Join(pkg.Pkgdir, importpath)
	}
}

// Binfile returns the destination of the compiled target of this command.
func (pkg *Package) Binfile() string {
	// TODO(dfc) should have a check for package main, or should be merged in to objfile.
	target := filepath.Join(pkg.Bindir, pkg.binname())
	if pkg.testScope {
		target = filepath.Join(pkg.Workdir, filepath.FromSlash(pkg.ImportPath), "_test", pkg.binname())
	}

	// if this is a cross compile or GOOS/GOARCH are both defined or there are build tags, add ctxString.
	if pkg.isCrossCompile() || (os.Getenv("GOOS") != "" && os.Getenv("GOARCH") != "") {
		target += "-" + pkg.ctxString()
	} else if len(pkg.buildtags) > 0 {
		target += "-" + strings.Join(pkg.buildtags, "-")
	}

	if pkg.GOOS == "windows" {
		target += ".exe"
	}
	return target
}

func (pkg *Package) binname() string {
	switch {
	case pkg.testScope:
		return pkg.name() + ".test"
	case pkg.Main:
		return filepath.Base(filepath.FromSlash(pkg.ImportPath))
	default:
		panic("binname called with non main package: " + pkg.ImportPath)
	}
}

func (p *Package) complete() bool {
	return true // no cgo or runtime code
}

func (p *Package) name() string { return filepath.FromSlash(p.ImportPath) }

func stringList(args ...[]string) []string {
	var l []string
	for _, arg := range args {
		l = append(l, arg...)
	}
	return l
}

func (pkg *Package) Compile() error {
	args := append(pkg.gcflags, "-p", pkg.ImportPath, "-pack")
	args = append(args, "-o", pkg.pkgpath())
	for _, d := range pkg.searchPaths() {
		args = append(args, "-I", d)
	}
	if pkg.standard && pkg.ImportPath == "runtime" {
		// runtime compiles with a special gc flag to emit
		// additional reflect type data.
		args = append(args, "-+")
	}

	switch {
	case pkg.complete():
		args = append(args, "-complete")
	}

	args = append(args, pkg.GoFiles...)
	if err := mkdir(filepath.Dir(pkg.pkgpath())); err != nil {
		return err
	}
	cmd := exec.Command(filepath.Join(runtime.GOROOT(), "pkg", "tool", runtime.GOOS+"_"+runtime.GOARCH, "compile"), args...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = pkg.Dir
	fmt.Fprintf(os.Stderr, "+ %s\n", strings.Join(cmd.Args, " "))
	return cmd.Run()
}

func (pkg *Package) Link() error {
	// to ensure we don't write a partial binary, link the binary to a temporary file in
	// in the target directory, then rename.
	tmp, err := ioutil.TempFile(filepath.Dir(pkg.Binfile()), ".kang-link")
	if err != nil {
		return err
	}
	tmp.Close()

	args := append(pkg.ldflags, "-o", tmp.Name())
	for _, d := range pkg.searchPaths() {
		args = append(args, "-L", d)
	}
	args = append(args, "-buildmode", "exe")
	args = append(args, pkg.pkgpath())

	cmd := exec.Command(filepath.Join(runtime.GOROOT(), "pkg", "tool", runtime.GOOS+"_"+runtime.GOARCH, "link"), args...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Dir = pkg.Workdir
	fmt.Fprintf(os.Stderr, "+ %s\n", strings.Join(cmd.Args, " "))
	if err := cmd.Run(); err != nil {
		os.Remove(tmp.Name()) // remove partial file
		return err
	}
	if err := mkdir(filepath.Dir(pkg.Binfile())); err != nil {
		os.Remove(tmp.Name()) // remove partial file
		return err
	}

	return rename(tmp.Name(), pkg.Binfile())
}

func mkdir(path string) error {
	return os.MkdirAll(path, 0755)
}

func rename(from, to string) error {
	fmt.Fprintf(os.Stderr, "+ mv %s %s\n", from, to)
	return os.Rename(from, to)
}


<|start_filename|>cmd/kang/main.go<|end_filename|>
package main

import (
	"crypto/sha1"
	"flag"
	"fmt"
	"go/build"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/constabulary/kang"
)

func check(err error) {
	if err != nil {
		fatal(err)
		os.Exit(1)
	}
}

func fatal(arg interface{}, args ...interface{}) {
	fmt.Fprint(os.Stderr, "fatal: ", arg)
	fmt.Fprintln(os.Stderr, args...)
	os.Exit(1)
}

func main() {
	flag.Parse()
	f, err := findkangfile(cwd())
	check(err)

	f, err = filepath.Abs(f)
	check(err)

	fmt.Println("Using", f)

	kf, err := ParseFile(f)
	check(err)

	prefix, ok := kf["project"]["prefix"]
	if prefix == "" || !ok {
		fatal("project prefix missing from .kangfile")
	}

	workdir, err := ioutil.TempDir("", "kang")
	check(err)

	rootdir := filepath.Dir(f)
	pkgdir := filepath.Join(rootdir, ".kang", "pkg")

	ctx := &kang.Context{
		GOOS:    runtime.GOOS,
		GOARCH:  runtime.GOARCH,
		Workdir: workdir,
		Pkgdir:  pkgdir,
		Bindir:  rootdir,
	}

	action := "build"

	switch action {
	case "build":
		srcs := loadSources(prefix, rootdir)
		for _, src := range srcs {
			fmt.Printf("loaded %s (%s)\n", src.ImportPath, src.Name)
		}

		srcs = loadDependencies(rootdir, kf, srcs...)

		pkgs := transform(ctx, srcs...)
		computeStale(pkgs...)

		targets := make(map[string]func() error)
		fn, err := buildPackages(targets, pkgs...)
		check(err)
		check(fn())
	default:
		fatal("unknown action:", action)
	}
}

func cwd() string {
	wd, err := os.Getwd()
	check(err)
	return wd
}

// transform takes a slice of go/build.Package and returns the
// corresponding slice of kang.Packages.
func transform(ctx *kang.Context, v ...*build.Package) []*kang.Package {
	srcs := make(map[string]*build.Package)
	for _, pkg := range v {
		srcs[pkg.ImportPath] = pkg
	}

	var pkgs []*kang.Package
	seen := make(map[string]bool)

	var walk func(src *build.Package)
	walk = func(src *build.Package) {
		if seen[src.ImportPath] {
			return
		}
		seen[src.ImportPath] = true

		for _, i := range src.Imports {
			if stdlib[i] {
				// skip stdlib package
				continue
			}
			pkg, ok := srcs[i]
			if !ok {
				fatal("transform: pkg ", i, "is not loaded")
			}
			walk(pkg)
		}

		pkgs = append(pkgs, &kang.Package{
			Context:    ctx,
			ImportPath: src.ImportPath,
			Dir:        src.Dir,
			GoFiles:    src.GoFiles,
			Main:       src.Name == "main",
		})
	}
	for _, p := range v {
		walk(p)
	}
	return pkgs
}

// computeStale sets the UpToDate flag on a set of package roots.
func computeStale(roots ...*kang.Package) {
	seen := make(map[*kang.Package]bool)

	var walk func(pkg *kang.Package) bool
	walk = func(pkg *kang.Package) bool {
		if seen[pkg] {
			return pkg.NotStale
		}
		seen[pkg] = true

		for _, i := range pkg.Imports {
			if !walk(i) {
				// a dep is stale so we are stale
				return false
			}
		}

		stale := pkg.IsStale()
		pkg.NotStale = !stale
		return !stale
	}

	for _, root := range roots {
		walk(root)
	}
}

// findkangfile returns the location of the closest .kangfile
// relative to the dir provided. If no .kangfile is found, an
// error is returned.
func findkangfile(dir string) (string, error) {
	orig := dir
	for {
		path := filepath.Join(dir, ".kangfile")
		fi, err := os.Stat(path)
		if err == nil && !fi.IsDir() {
			return path, nil
		}
		if err != nil && !os.IsNotExist(err) {
			check(err)
		}
		d := filepath.Dir(dir)
		if d == dir {
			// got to the root directory without
			return "", fmt.Errorf("could not locate .kangfile in %s", orig)
		}
		dir = d
	}
}

func buildPackages(targets map[string]func() error, pkgs ...*kang.Package) (func() error, error) {
	var deps []func() error
	for _, pkg := range pkgs {
		fn, err := buildPackage(targets, pkg)
		check(err)
		deps = append(deps, fn)
	}
	return func() error {
		for _, fn := range deps {
			if err := fn(); err != nil {
				return err
			}
		}
		return nil
	}, nil
}

func buildPackage(targets map[string]func() error, pkg *kang.Package) (func() error, error) {

	// if this action is already present in the map, return it
	// rather than creating a new action.
	if fn, ok := targets[pkg.ImportPath]; ok {
		return fn, nil
	}

	// step 0. are we stale ?
	// if this package is not stale, then by definition none of its
	// dependencies are stale, so ignore this whole tree.
	if pkg.NotStale {
		return func() error {
			fmt.Println(pkg.ImportPath, "is up to date")
			return nil
		}, nil
	}

	// step 1. build dependencies
	var deps []func() error
	for _, pkg := range pkg.Imports {
		fn, err := buildPackage(targets, pkg)
		if err != nil {
			return nil, err
		}
		deps = append(deps, fn)
	}

	// step 2. build this package
	build := func() error {
		for _, dep := range deps {
			if err := dep(); err != nil {
				return err
			}
		}
		if err := pkg.Compile(); err != nil {
			return err
		}
		if !pkg.Main {
			return nil // we're done
		}
		return pkg.Link()
	}

	// record the final action as the action that represents
	// building this package.
	targets[pkg.ImportPath] = build

	return build, nil
}

func loadSources(prefix string, dir string) []*build.Package {
	f, err := os.Open(dir)
	check(err)
	files, err := f.Readdir(-1)
	check(err)
	f.Close()

	var srcs []*build.Package
	for _, fi := range files {
		name := fi.Name()
		if strings.HasPrefix(name, "_") || strings.HasPrefix(name, ".") || name == "testdata" || name == "vendor" {
			// ignore it
			continue
		}
		if fi.IsDir() {
			srcs = append(srcs, loadSources(path.Join(prefix, name), filepath.Join(dir, name))...)
		}
	}

	pkg, err := build.ImportDir(dir, 0)
	switch err := err.(type) {
	case nil:
		// ImportDir does not know the import path for this package
		// but we know the prefix, so fix it.
		pkg.ImportPath = prefix
		srcs = append(srcs, pkg)
	case (*build.NoGoError):
		// do nothing
	default:
		check(err)
	}

	return srcs
}

func loadDependencies(rootdir string, m map[string]map[string]string, srcs ...*build.Package) []*build.Package {
	load := func(path string) *build.Package { fatal("cannot resolve path ", path); return nil }
	for prefix, d := range m {
		if prefix == "project" {
			// skip kang metadata
			continue
		}
		if version, ok := d["version"]; ok {
			load = register(rootdir, prefix, "version", version, load)
			continue
		}
		if tag, ok := d["tag"]; ok {
			load = register(rootdir, prefix, "tag", tag, load)
			continue
		}
		if commit, ok := d["commit"]; ok {
			load = register(rootdir, prefix, "commit", commit, load)
			continue
		}
		fatal("unknoww dependency", d)
	}

	seen := make(map[string]bool)
	var walk func(string)
	walk = func(path string) {
		if stdlib[path] {
			return
		}
		if seen[path] {
			return
		}
		seen[path] = true
		pkg := load(path)
		srcs = append(srcs, pkg)
		for _, i := range pkg.Imports {
			walk(i)
		}
	}
	for _, src := range srcs {
		seen[src.ImportPath] = true
	}
	for _, src := range srcs[:] {
		for _, i := range src.Imports {
			walk(i)
		}
	}
	return srcs
}

func register(rootdir, prefix, kind, arg string, next func(string) *build.Package) func(string) *build.Package {
	dir := cacheDir(rootdir, prefix+kind+"="+arg)
	fmt.Println("registered:", prefix, "@", arg)
	return func(path string) *build.Package {
		if !strings.HasPrefix(path, prefix) {
			return next(path)
		}
		fmt.Println("searching", path, "in", prefix, "@", arg)
		dir := filepath.Join(dir, path)
		_, err := os.Stat(dir)
		if os.IsNotExist(err) {
			check(err)
		}
		return importPath(path, dir)
	}
}

func importPath(path, dir string) *build.Package {
	pkg, err := build.ImportDir(dir, 0)
	check(err)
	// ImportDir does not know the import path for this package
	// but we know the prefix, so fix it.
	pkg.ImportPath = path
	return pkg
}

func cacheDir(rootdir, key string) string {
	hash := sha1.Sum([]byte(key))
	return filepath.Join(rootdir, ".kang", "cache", fmt.Sprintf("%x", hash[0:1]), fmt.Sprintf("%x", hash[1:]))
}
