diff options
author | Ian Cottrell <iancottrell@google.com> | 2015-05-05 19:09:36 +0100 |
---|---|---|
committer | Ian Cottrell <iancottrell@google.com> | 2015-05-07 14:58:05 +0100 |
commit | bf5b4e113443878dab37c276f2dc49e148e38560 (patch) | |
tree | 4d9b52c556f4f9ba5cecb2e58128557987e4f374 /maker | |
parent | 7f741334bb048724cea6cc42efde94f65c43ed0d (diff) | |
download | gpu-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.go | 92 | ||||
-rw-r--r-- | maker/config.go | 43 | ||||
-rw-r--r-- | maker/copy.go | 67 | ||||
-rw-r--r-- | maker/doc.go | 17 | ||||
-rw-r--r-- | maker/entity.go | 103 | ||||
-rw-r--r-- | maker/error.go | 68 | ||||
-rw-r--r-- | maker/file.go | 155 | ||||
-rw-r--r-- | maker/go.go | 74 | ||||
-rw-r--r-- | maker/host_linux.go | 20 | ||||
-rw-r--r-- | maker/host_osx.go | 20 | ||||
-rw-r--r-- | maker/host_windows.go | 20 | ||||
-rw-r--r-- | maker/run.go | 131 | ||||
-rw-r--r-- | maker/step.go | 244 | ||||
-rw-r--r-- | maker/util.go | 48 | ||||
-rw-r--r-- | maker/virtual.go | 79 |
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() {} |