aboutsummaryrefslogtreecommitdiff
path: root/maker
diff options
context:
space:
mode:
authorIan Cottrell <iancottrell@google.com>2015-05-05 19:09:36 +0100
committerIan Cottrell <iancottrell@google.com>2015-05-07 14:58:05 +0100
commitbf5b4e113443878dab37c276f2dc49e148e38560 (patch)
tree4d9b52c556f4f9ba5cecb2e58128557987e4f374 /maker
parent7f741334bb048724cea6cc42efde94f65c43ed0d (diff)
downloadgpu-bf5b4e113443878dab37c276f2dc49e148e38560.tar.gz
The new build system.
This works as a replacement for make.go, but has not yet subsumed the build package. Change-Id: I516bb4d1c9a021012d2c5ed7cef49472ca0ad319
Diffstat (limited to 'maker')
-rw-r--r--maker/command.go92
-rw-r--r--maker/config.go43
-rw-r--r--maker/copy.go67
-rw-r--r--maker/doc.go17
-rw-r--r--maker/entity.go103
-rw-r--r--maker/error.go68
-rw-r--r--maker/file.go155
-rw-r--r--maker/go.go74
-rw-r--r--maker/host_linux.go20
-rw-r--r--maker/host_osx.go20
-rw-r--r--maker/host_windows.go20
-rw-r--r--maker/run.go131
-rw-r--r--maker/step.go244
-rw-r--r--maker/util.go48
-rw-r--r--maker/virtual.go79
15 files changed, 1181 insertions, 0 deletions
diff --git a/maker/command.go b/maker/command.go
new file mode 100644
index 000000000..990f255fb
--- /dev/null
+++ b/maker/command.go
@@ -0,0 +1,92 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package maker
+
+import (
+ "bytes"
+ "fmt"
+ "log"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+)
+
+var (
+ // EnvVars holds the environment overrides used when spawning external commands.
+ EnvVars = map[string][]string{}
+)
+
+// Command builds and returns a new Step that runs the specified external binary
+// with the supplied arguments. The newly created Step will be made to depend on
+// the binary.
+func Command(binary Entity, args ...string) *Step {
+ return NewStep(func(step *Step) error {
+ cmd := exec.Command(binary.Name(), args...)
+ var output bytes.Buffer
+ if Config.Verbose > 1 {
+ cmd.Stdout = &output
+ cmd.Stderr = &output
+ } else {
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ }
+
+ cmd.Env = getEnvVars()
+ cmd.Dir = Config.RootPath
+ if Config.Verbose > 0 {
+ log.Printf("-> %s", strings.Join(cmd.Args, " "))
+ }
+ if Config.Verbose > 1 {
+ log.Printf("Working directory: %v", cmd.Dir)
+ log.Printf("Environment:")
+ for _, v := range cmd.Env {
+ log.Printf("• %s", v)
+ }
+ }
+ return cmd.Run()
+ }).DependsOn(binary)
+}
+
+type envVar struct {
+ key, value string
+}
+
+func getEnvVars() []string {
+ env := map[string]envVar{}
+ for _, pair := range os.Environ() {
+ v := strings.LastIndex(pair, "=")
+ key, value := pair[:v], pair[v+1:]
+ env[strings.ToUpper(key)] = envVar{key, value}
+ }
+
+ sep := fmt.Sprintf("%c", filepath.ListSeparator)
+
+ for key, values := range EnvVars {
+ keyUpper := strings.ToUpper(key)
+ if existing, found := env[keyUpper]; found {
+ values = append(values, existing.value)
+ env[keyUpper] = envVar{existing.key, strings.Join(values, sep)}
+ } else {
+ env[keyUpper] = envVar{key, strings.Join(values, sep)}
+ }
+ }
+
+ vars := []string{}
+ for _, v := range env {
+ vars = append(vars, v.key+"="+v.value)
+ }
+ return vars
+}
diff --git a/maker/config.go b/maker/config.go
new file mode 100644
index 000000000..47f3db94d
--- /dev/null
+++ b/maker/config.go
@@ -0,0 +1,43 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package maker
+
+import "path/filepath"
+
+var (
+ // Config holds the current configuration of the maker system.
+ Config struct {
+ // Verbose enables increased logging output.
+ Verbose int
+ // TargetArchitecture is the architecture to build binaries for.
+ TargetArchitecture string
+ // TargetOS is the OS to build for.
+ TargetOS string
+ // RootPath is the root directory to work in.
+ RootPath string
+ // DisableParallel turns of all parallel build support.
+ DisableParallel bool
+ }
+)
+
+// DepsPath joins the supplied path to the dependancy cache directory.
+func DepsPath(path string) string {
+ return filepath.Join(Config.RootPath, "deps", path)
+}
+
+// DataPath joins the supplied path to the application data directory.
+func DataPath(path string) string {
+ return filepath.Join(Config.RootPath, "data", path)
+}
diff --git a/maker/copy.go b/maker/copy.go
new file mode 100644
index 000000000..ed9fe8487
--- /dev/null
+++ b/maker/copy.go
@@ -0,0 +1,67 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package maker
+
+import (
+ "fmt"
+ "io"
+ "log"
+ "os"
+)
+
+// CopyFile builds a new Step that copies the file from src to dst, and returns
+// the dst entity.
+// The step will depend on the src, and the existance of the directory dst is
+// inside.
+func CopyFile(dst, src interface{}) Entity {
+ out := File(dst)
+ NewStep(copyFile).Creates(out).DependsOn(File(src), DirOf(out))
+ return out
+}
+
+func copyFile(s *Step) error {
+ if len(s.inputs) <= 1 {
+ return fmt.Errorf("copy needs inputs")
+ }
+ src := s.inputs[0]
+ if !IsFile(src) {
+ return fmt.Errorf("cannot copy from %s, not a file", src.Name())
+ }
+ if len(s.outputs) != 1 {
+ return fmt.Errorf("copy expects 1 output, got %d", len(s.outputs))
+ }
+ dst := s.outputs[0]
+ if !IsFile(dst) {
+ return fmt.Errorf("cannot copy to %s, not a file", dst.Name())
+ }
+ if Config.Verbose > 0 {
+ log.Printf("-> cp %v to %v", src, dst)
+ }
+ in, err := os.Open(src.Name())
+ if err != nil {
+ return err
+ }
+ defer in.Close()
+ out, err := os.Create(dst.Name())
+ if err != nil {
+ return err
+ }
+ defer out.Close()
+ _, err = io.Copy(out, in)
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/maker/doc.go b/maker/doc.go
new file mode 100644
index 000000000..4f7423e67
--- /dev/null
+++ b/maker/doc.go
@@ -0,0 +1,17 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Package maker provides a system for building and running a dependancy graph
+// of tasks.
+package maker
diff --git a/maker/entity.go b/maker/entity.go
new file mode 100644
index 000000000..6603f4f20
--- /dev/null
+++ b/maker/entity.go
@@ -0,0 +1,103 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package maker
+
+import (
+ "log"
+ "path/filepath"
+ "time"
+)
+
+// Entity represents an object in the build graph.
+type Entity interface {
+ // Name returns the unique name of this entity.
+ // If Name is the empty string, the entity does not appear in the main entity
+ // list and cannot be looked up.
+ Name() string
+ // Exists returns true if the entity currently exists.
+ Exists() bool
+ // Timestamp returns the last modified time of the entity, or the zero time if
+ // not available.
+ Timestamp() time.Time
+ // NeedsUpdate returns true if the entity requires it's generating step to run.
+ // The supplied timestamp is the newest timestamp of the entities this one
+ // depends on, and can be used to decide if an update is neccesary.
+ NeedsUpdate(t time.Time) bool
+ // Updated is called when a step that modifies this entity completes.
+ Updated()
+}
+
+var (
+ entities = map[string]Entity{}
+ entityHooks = []func(e Entity){}
+)
+
+// EntityHook registers a function that is invoked when a new entity is added
+// to the system.
+func EntityHook(f func(e Entity)) {
+ entityHooks = append(entityHooks, f)
+}
+
+// FindEntity tries to look up an entity by name, and returns nil if the entity
+// cannot be found.
+func FindEntity(name string) Entity {
+ if name == "" {
+ return nil
+ }
+ e, _ := entities[name]
+ return e
+}
+
+// EntityOf tries to find an entity for the supplied value.
+// If the value is an entity, it will be returned directly. If the value is a
+// string that matches an existing entity, that entity will be returned.
+// If it is a relative path that when resolved mathes an existing file entity,
+// then the file is returned.
+func EntityOf(v interface{}) Entity {
+ switch v := v.(type) {
+ case string:
+ if e := FindEntity(v); e != nil {
+ return e
+ }
+ if abs, err := filepath.Abs(v); err == nil {
+ if e := FindEntity(abs); e != nil {
+ return e
+ }
+ }
+ log.Fatalf("no such entity %s", v)
+ return nil
+ case Entity:
+ return v
+ default:
+ log.Fatalf("cannot get entity for %T", v)
+ return nil
+ }
+}
+
+// AddEntity adds a new entity into the system.
+// The entity must not have the same name as an already exixting entity.
+func AddEntity(e Entity) {
+ name := e.Name()
+ if name != "" {
+ _, found := entities[name]
+ if found {
+ log.Fatalf("Attempt to remap entity name %s", name)
+ }
+ entities[name] = e
+ }
+ for _, h := range entityHooks {
+ h(e)
+ }
+}
diff --git a/maker/error.go b/maker/error.go
new file mode 100644
index 000000000..c60635800
--- /dev/null
+++ b/maker/error.go
@@ -0,0 +1,68 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package maker
+
+import (
+ "fmt"
+ "log"
+ "sync"
+)
+
+type errorEntry struct {
+ step *Step
+ err error
+}
+
+type errors struct {
+ mu sync.Mutex
+ list []errorEntry
+}
+
+var (
+ // Errors is the main error list, it holds all errors that the system has
+ // encountered.
+ Errors errors
+)
+
+// Add a new error to the error list.
+// The method is concurrent safe.
+func (errs *errors) Add(s *Step, err error) {
+ errs.mu.Lock()
+ defer errs.mu.Unlock()
+ entry := errorEntry{s, err}
+ log.Printf("Error: %s", entry)
+ errs.list = append(errs.list, entry)
+ if s != nil && s.err == nil {
+ s.err = err
+ }
+}
+
+// Returns true if the system has a registered error, and is thus in a failure
+// state.
+func (errs *errors) Failed() bool {
+ return len(errs.list) > 0
+}
+
+// Returns the first error that was regisetered.
+// This method is concurrent safe.
+func (errs *errors) First() errorEntry {
+ errs.mu.Lock()
+ defer errs.mu.Unlock()
+ return errs.list[0]
+}
+
+func (e errorEntry) String() string {
+ return fmt.Sprintf("%s:%s", e.step, e.err)
+}
diff --git a/maker/file.go b/maker/file.go
new file mode 100644
index 000000000..1f8d0e209
--- /dev/null
+++ b/maker/file.go
@@ -0,0 +1,155 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package maker
+
+import (
+ "io/ioutil"
+ "log"
+ "os"
+ "path/filepath"
+ "time"
+)
+
+// File returns an Entity that represents a file.
+// The entities name will be the absolute path of the file.
+// If path is an entity already, it will be tested to make sure it is a file and
+// returned.
+// If it is a string, the the entity map will be checked for a matching file
+// entry and if one is not found, a new one will be added and returned.
+func File(path interface{}) *file {
+ switch path := path.(type) {
+ case string:
+ abs, err := filepath.Abs(path)
+ if err != nil {
+ log.Fatalf("%s", err)
+ }
+ e := FindEntity(abs)
+ if e != nil {
+ f, is := e.(*file)
+ if !is {
+ log.Fatalf("%s is not a file entity (%T)", abs, e)
+ }
+ return f
+ }
+ f := &file{abs: abs}
+ f.stat, _ = os.Stat(f.abs)
+ AddEntity(f)
+ return f
+ case *file:
+ return path
+ default:
+ log.Fatalf("cannot convert from %T to file entity", path)
+ return nil
+ }
+}
+
+// Dir returns an Entity that represents a directory.
+// The entities name will be the absolute path of the directory.
+// If path is an entity already, it will be tested to make sure it is a
+// directory and returned.
+// If it is a string, the the entity map will be checked for a matching directory
+// entry and if one is not found, a new one will be added and returned.
+// It also adds the rules to create the directory if needed.
+func Dir(path interface{}) *file {
+ d := File(path)
+ if Creator(d) == nil {
+ NewStep(makeDir).Creates(d)
+ }
+ return d
+}
+
+// DirOf attempts to make a Dir for the parent of the specified File.
+func DirOf(path interface{}) *file {
+ switch path := path.(type) {
+ case string:
+ return dirOf(path)
+ case *file:
+ return dirOf(path.Name())
+ default:
+ log.Fatalf("cannot get parent dir from %T", path)
+ return nil
+ }
+}
+
+// FilesOf reads the list of files in path, filters them with the supplied
+// filter and returns the set of file entities that matched.
+func FilesOf(path interface{}, filter func(os.FileInfo) bool) []*file {
+ dir := Dir(path)
+ infos, _ := ioutil.ReadDir(dir.Name())
+ files := []*file{}
+ for _, i := range infos {
+ if filter(i) {
+ files = append(files, File(filepath.Join(dir.Name(), i.Name())))
+ }
+ }
+ return files
+}
+
+// IsFile returns true if the supplied entity is of file type.
+func IsFile(e Entity) bool {
+ _, is := e.(*file)
+ return is
+}
+
+type file struct {
+ abs string
+ stat os.FileInfo
+}
+
+// Name returns the full absolute path to the file.
+func (f *file) Name() string { return f.abs }
+
+// String returns the Name of the file.
+func (f *file) String() string { return f.abs }
+
+// Exists returns true if the file could be statted.
+func (f *file) Exists() bool { return f.stat != nil }
+
+// Timestamp returns the last modified time reported by the file system.
+func (f *file) Timestamp() time.Time {
+ if f.stat == nil {
+ return time.Time{}
+ }
+ return f.stat.ModTime()
+}
+
+// NeedsUpdate returns true if the file does not exist, or is older than the
+// supplied timestamp.
+func (f *file) NeedsUpdate(t time.Time) bool {
+ if f.stat == nil {
+ return true
+ }
+ return f.stat.ModTime().Before(t)
+}
+
+// Updated refreshes the exists and timestamp information for the file.
+func (f *file) Updated() { f.stat, _ = os.Stat(f.abs) }
+
+func dirOf(name string) *file {
+ path := filepath.Dir(name)
+ if len(path) == 0 {
+ return nil
+ }
+ return Dir(path)
+}
+
+func makeDir(s *Step) error {
+ for _, out := range s.outputs {
+ if err := os.MkdirAll(out.Name(), os.ModePerm); err != nil {
+ return err
+ }
+ }
+ return nil
+}
diff --git a/maker/go.go b/maker/go.go
new file mode 100644
index 000000000..fd77f3b9b
--- /dev/null
+++ b/maker/go.go
@@ -0,0 +1,74 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package maker
+
+import (
+ "os"
+ "path/filepath"
+)
+
+var (
+ goTool Entity
+)
+
+var (
+ //GoPath is the GOPATH environment setting
+ GoPath string
+)
+
+func init() {
+ GoPath = os.Getenv("GOPATH")
+ goTool = FindTool("go")
+ Config.RootPath = GoPath
+ EnvVars["PATH"] = []string{filepath.Join(GoPath, "bin")}
+}
+
+// GoInstall builds a new Step that runs "go install" on the supplied module.
+// It will return the resulting binary entity.
+// The step will depend on the go tool, and will be set to always run if
+// depended on.
+func GoInstall(module string) Entity {
+ name := filepath.Base(module)
+ dst := File(GoBinPath(name + HostExecutableExtension))
+ if Creator(dst) == nil {
+ Command(goTool, "install", module).Creates(dst).AlwaysRun()
+ }
+ return dst
+}
+
+// GoTest creates a new Step that runs "go test" on the supplied module.
+// It returns a virtual entity that represents the test output.
+func GoTest(module string) Entity {
+ test := Virtual("")
+ Command(goTool, "test", module).Creates(test)
+ return test
+}
+
+// GoRun returns a Step that runs "go run" with the supplied go file
+// and arguments.
+func GoRun(gofile string, args ...string) *Step {
+ return Command(goTool, append([]string{"run", gofile}, args...)...)
+}
+
+// GoSrcPath returns the full path to a file or directory inside the GoPath.
+func GoSrcPath(path string) string {
+ return filepath.Join(GoPath, "src", path)
+}
+
+// GoBinPath returns the full path to a file or directory inside the go binary
+// directory.
+func GoBinPath(path string) string {
+ return filepath.Join(GoPath, "bin", path)
+}
diff --git a/maker/host_linux.go b/maker/host_linux.go
new file mode 100644
index 000000000..5b2647a38
--- /dev/null
+++ b/maker/host_linux.go
@@ -0,0 +1,20 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// +build linux
+
+package maker
+
+const HostOS = "linux"
+const HostExecutableExtension = ""
diff --git a/maker/host_osx.go b/maker/host_osx.go
new file mode 100644
index 000000000..2e3d88d04
--- /dev/null
+++ b/maker/host_osx.go
@@ -0,0 +1,20 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// +build darwin
+
+package maker
+
+const HostOS = "osx"
+const HostExecutableExtension = ""
diff --git a/maker/host_windows.go b/maker/host_windows.go
new file mode 100644
index 000000000..bad47812b
--- /dev/null
+++ b/maker/host_windows.go
@@ -0,0 +1,20 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// +build windows
+
+package maker
+
+const HostOS = "windows"
+const HostExecutableExtension = ".exe"
diff --git a/maker/run.go b/maker/run.go
new file mode 100644
index 000000000..e72f74e3c
--- /dev/null
+++ b/maker/run.go
@@ -0,0 +1,131 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package maker
+
+import (
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "runtime"
+ "sort"
+)
+
+const (
+ // Default is the name of the entity that is built if none are supplied on the
+ // command line.
+ Default = "default"
+)
+
+var (
+ prepares = []func(){}
+)
+
+// Register a new graph building function with the maker system.
+// The function will be invoked during Run to add entities and steps to the
+// build graph.
+func Register(f func()) {
+ prepares = append(prepares, f)
+}
+
+// Run should be invoked once from main.
+// It parses the command line, builds the graph, and then performs the required
+// action.
+func Run() {
+ // Get the configuration
+ verbose := flag.Int("v", 1, "Verbose mode")
+ targetArch := flag.String("arch", runtime.GOARCH, "Target architecture.")
+ targetOS := flag.String("os", runtime.GOOS, "Target OS.")
+ do := flag.String("do", "make", "The action to perform, one of make, show or clean.")
+ threads := flag.Int("threads", runtime.NumCPU(), "Set number of go routines to use. 0 disables parallel builds.")
+ flag.Parse()
+ if *threads > 0 {
+ runtime.GOMAXPROCS(*threads)
+ } else {
+ Config.DisableParallel = true
+ }
+ Config.Verbose = *verbose
+ Config.TargetArchitecture = *targetArch
+ Config.TargetOS = *targetOS
+ // Build the entity graph
+ for _, f := range prepares {
+ f()
+ }
+ // Prepare the active path
+ targets := flag.Args()
+ meta := List("")
+ for _, name := range targets {
+ meta.DependsOn(EntityOf(name))
+ }
+ // Perform the requested action
+ switch *do {
+ case "make":
+ if len(meta.inputs) == 0 {
+ meta.DependsOn(Default)
+ }
+ meta.start()
+ <-meta.done
+ if Errors.Failed() {
+ fmt.Printf("Failed:%s\n", Errors.First())
+ os.Exit(1)
+ } else {
+ fmt.Printf("Succeeded\n")
+ }
+ case "show":
+ if len(meta.inputs) == 0 {
+ fmt.Printf("targets available are:\n")
+ strings := sort.StringSlice{}
+ for _, e := range entities {
+ if IsVirtual(e) {
+ strings = append(strings, e.Name())
+ }
+ }
+ strings.Sort()
+ for _, n := range strings {
+ fmt.Printf(" %s\n", n)
+ }
+ } else {
+ fmt.Printf("active dependancy graph is:\n")
+ dumper{}.dump(meta, 1)
+ }
+ case "clean":
+ log.Fatalf("Clean not yet supported")
+ default:
+ log.Fatalf("Unknown action %q", *do)
+ }
+}
+
+type dumper map[*Step]struct{}
+
+func (d dumper) dump(s *Step, depth int) {
+ if s == nil {
+ fmt.Println()
+ return
+ }
+ fmt.Printf(" [%d]", len(s.inputs))
+ if _, done := d[s]; done {
+ fmt.Println(" - already seen")
+ return
+ }
+ fmt.Println()
+ d[s] = struct{}{}
+ for i, e := range s.inputs {
+ for i := 0; i < depth; i++ {
+ fmt.Print(" ")
+ }
+ fmt.Printf("(%d) %s", i+1, e)
+ d.dump(Creator(e), depth+1)
+ }
+}
diff --git a/maker/step.go b/maker/step.go
new file mode 100644
index 000000000..42e9516a5
--- /dev/null
+++ b/maker/step.go
@@ -0,0 +1,244 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package maker
+
+import (
+ "bufio"
+ "fmt"
+ "log"
+ "os"
+ "reflect"
+ "sync"
+ "time"
+)
+
+// Step represents an action that creates it's outputs from it's inputs.
+// It is the linking entity in the build graph.
+// Each entity can be built by only one step, but a step may build many entites,
+// and may depend on many entities.
+// It is an error to have a cycle in the build graph.
+type Step struct {
+ inputs []Entity
+ outputs []Entity
+ always bool
+ action func(*Step) error
+ once sync.Once
+ done chan struct{}
+ err error
+}
+
+var (
+ steps = map[Entity]*Step{}
+ stepHooks = []func(s *Step){}
+)
+
+// StepHook adds a function that is invoked each time a step is added or
+// modified.
+func StepHook(f func(s *Step)) {
+ stepHooks = append(stepHooks, f)
+}
+
+// Creator looks up the step that builds an entity.
+// The entity will be looked up using EntityOf.
+func Creator(of interface{}) *Step {
+ e := EntityOf(of)
+ s, _ := steps[e]
+ return s
+}
+
+// NewStep creates and returns a new step that runs the supplied action.
+func NewStep(a func(*Step) error) *Step {
+ s := &Step{
+ action: a,
+ done: make(chan struct{}),
+ }
+ for _, h := range stepHooks {
+ h(s)
+ }
+ return s
+}
+
+// Creates adds new outputs to the Step.
+// It will complain if any of the outputs already have a Creator.
+func (s *Step) Creates(out ...Entity) *Step {
+ for _, e := range out {
+ if Creator(e) != nil {
+ log.Fatalf("%s created by more than one step", e.Name())
+ }
+ steps[e] = s
+ }
+ s.outputs = append(s.outputs, out...)
+ for _, h := range stepHooks {
+ h(s)
+ }
+ return s
+}
+
+// DependsOn adds a new input dependancy to a Step.
+// If the Step already depends on an input, it will not be added again.
+func (s *Step) DependsOn(in ...interface{}) *Step {
+ for _, v := range in {
+ e := EntityOf(v)
+ if !s.HasInput(e) {
+ s.inputs = append(s.inputs, e)
+ }
+ }
+ for _, h := range stepHooks {
+ h(s)
+ }
+ return s
+}
+
+// HasInput returns true if the step already has the supplied entity in it's inputs.
+func (s *Step) HasInput(e Entity) bool {
+ for _, in := range s.inputs {
+ if in == e {
+ return true
+ }
+ }
+ return false
+}
+
+// HasOutput returns true if the step already has the supplied entity in it's outputs.
+func (s *Step) HasOutput(e Entity) bool {
+ for _, out := range s.outputs {
+ if out == e {
+ return true
+ }
+ }
+ return false
+}
+
+// AlwaysRun marks a step as always needing to run, rather than running only
+// when it's outputs need updating.
+func (s *Step) AlwaysRun() *Step {
+ s.always = true
+ return s
+}
+
+// String returns the name of the first output if present, for debugging.
+func (s *Step) String() string {
+ if len(s.outputs) > 0 {
+ return s.outputs[0].Name()
+ }
+ return ""
+}
+
+// UseDepsFile reads in a deps file and uses it to populuate the inputs and
+// outputs of the Step.
+func (s *Step) UseDepsFile(deps Entity) {
+ s.Creates(deps)
+ depsfile, err := os.Open(deps.Name())
+ if err != nil {
+ return
+ }
+ defer depsfile.Close()
+ scanner := bufio.NewScanner(depsfile)
+ inputs := true
+ for scanner.Scan() {
+ line := scanner.Text()
+ switch {
+ case line == "==Inputs==":
+ inputs = true
+ case line == "==Outputs==":
+ inputs = false
+ case inputs:
+ s.DependsOn(File(line))
+ default:
+ s.Creates(File(line))
+ }
+ }
+}
+
+// DependsStruct adds all the fields of the supplied struct as input dependacies
+// of the step.
+// It is an error if the struct has any fields that are not public fields of
+// types that implement Entity.
+func (s *Step) DependsStruct(v interface{}) {
+ val := reflect.ValueOf(v)
+ for i := 0; i < val.NumField(); i++ {
+ f := val.Field(i)
+ s.DependsOn(f.Interface().(Entity))
+ }
+}
+
+func (s *Step) updateInputs() {
+ deps := make([]*Step, 0, len(s.inputs))
+ // Bring all inputs up to date in parallel
+ for _, e := range s.inputs {
+ dep := Creator(e)
+ if dep != nil {
+ deps = append(deps, dep)
+ if Config.DisableParallel {
+ dep.start()
+ } else {
+ go dep.start()
+ }
+ }
+ }
+ // Wait for all inputs to be ready
+ for _, dep := range deps {
+ <-dep.done
+ if dep.err != nil && s.err == nil {
+ s.err = fmt.Errorf("failed in %s", dep)
+ }
+ }
+}
+
+func (s *Step) shouldRun() bool {
+ if s.always {
+ return true
+ }
+ // Find the newest input
+ t := time.Time{}
+ for _, e := range s.inputs {
+ t = Newest(t, e.Timestamp())
+ }
+ if t.IsZero() {
+ // No timestamped inputs, so always run
+ return true
+ }
+ // Ask the outputs if they want an update
+ for _, e := range s.outputs {
+ if e.NeedsUpdate(t) {
+ return true
+ }
+ }
+ return false
+}
+
+func (s *Step) run() {
+ if s.action == nil {
+ return
+ }
+ if err := s.action(s); err != nil {
+ Errors.Add(s, err)
+ }
+ // Mark all our ouptuts as potentially updated
+ for _, e := range s.outputs {
+ e.Updated()
+ }
+}
+
+func (s *Step) start() {
+ s.once.Do(func() {
+ s.updateInputs()
+ if s.err == nil && s.shouldRun() {
+ s.run()
+ }
+ // Signal we are complete
+ close(s.done)
+ })
+}
diff --git a/maker/util.go b/maker/util.go
new file mode 100644
index 000000000..4051841a8
--- /dev/null
+++ b/maker/util.go
@@ -0,0 +1,48 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package maker
+
+import (
+ "os/exec"
+ "time"
+)
+
+// FindTool finds an executable on the host search path, and returns a File
+// entity for the tool if found.
+func FindTool(name string) Entity {
+ path, err := exec.LookPath(name)
+ if err != nil {
+ return nil
+ }
+ return File(path)
+}
+
+// Oldest returns the oldest of two timestamps.
+// Timestamps with a zero value do not count as older.
+func Oldest(t1, t2 time.Time) time.Time {
+ if !t1.IsZero() && (t2.IsZero() || t1.Before(t2)) {
+ return t1
+ }
+ return t2
+}
+
+// Newest returns the newest of two timestamps.
+// Timestamps with a zero value do not count as newer.
+func Newest(t1, t2 time.Time) time.Time {
+ if !t1.IsZero() && (t2.IsZero() || t1.After(t2)) {
+ return t1
+ }
+ return t2
+}
diff --git a/maker/virtual.go b/maker/virtual.go
new file mode 100644
index 000000000..6d0376424
--- /dev/null
+++ b/maker/virtual.go
@@ -0,0 +1,79 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package maker
+
+import (
+ "log"
+ "time"
+)
+
+// Virtual returns an Entity that represents a virtual object in the build graph.
+// If name already represents a virtual entity, it will be returned, otherwise a
+// new one will be built.
+// This is used as the output for commands that have no file outputs.
+func Virtual(name string) *virtual {
+ e := FindEntity(name)
+ if e != nil {
+ l, is := e.(*virtual)
+ if !is {
+ log.Fatalf("%s is not a virtual entity", name)
+ }
+ return l
+ }
+ l := &virtual{name: name}
+ AddEntity(l)
+ return l
+}
+
+// List returns the Step for a virtual entity.
+// If the virtual entity does not exist, it will be created, and if it does not
+// have a Creator, then a new Step with no action will be created.
+// This is used to build lists of entities that want to be updated together.
+func List(name string) *Step {
+ l := Virtual(name)
+ s := Creator(l)
+ if s == nil {
+ s = NewStep(nil).Creates(l)
+ }
+ return s
+}
+
+// IsVirtual returns true if the supplied entity is a virtual type.
+func IsVirtual(e Entity) bool {
+ _, is := e.(*virtual)
+ return is
+}
+
+type virtual struct {
+ name string
+}
+
+// Name returns the name that was supplied when the virtual entity was built.
+func (l *virtual) Name() string { return l.name }
+
+// String returns the Name of the virtual for debugging.
+func (l *virtual) String() string { return l.name }
+
+// Exists returns false, virtual entities never exist.
+func (l *virtual) Exists() bool { return false }
+
+// Timestamp return the zero time, virtual entitis are not modified.
+func (l *virtual) Timestamp() time.Time { return time.Time{} }
+
+// NeedsUpdate returns true, actions that build virtual objects should always be run.
+func (l *virtual) NeedsUpdate(t time.Time) bool { return true }
+
+// Updated does nothing.
+func (l *virtual) Updated() {}