| // Copyright (C) 2016 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. |
| |
| // The do command wraps CMake, simplifying the building GAPID in common |
| // configurations. |
| package main |
| |
| import ( |
| "bufio" |
| "encoding/json" |
| "flag" |
| "fmt" |
| "io/ioutil" |
| "os" |
| "os/exec" |
| "os/user" |
| "path/filepath" |
| "reflect" |
| "runtime" |
| "strings" |
| ) |
| |
| const ( |
| cfgPath = ".gapid-config" |
| |
| usage = ` |
| go run do.go <verb> |
| |
| verb can be one of the following: |
| • build <target> - start a build of the optional target |
| • config - set configuration parameters |
| • clean - delete the output directory |
| • robo - runs an offline RoboTester instance |
| • help - show this message |
| ` |
| ) |
| |
| func main() { |
| flag.Parse() |
| verb := flag.Arg(0) |
| switch verb { |
| case "build": |
| maxArgs(2) |
| doBuild(getConfig(), flag.Arg(1)) |
| case "config": |
| maxArgs(1) |
| cfg, _ := readConfig() |
| setConfig(cfg) |
| case "clean": |
| maxArgs(1) |
| cfg := getConfig() |
| outPath := cfg.outputPath() |
| if exists(abs(outPath, "CMakeCache.txt")) { |
| os.RemoveAll(outPath) |
| } |
| os.Remove(cfg.currentPath()) |
| case "robo", "robotester": |
| maxArgs(1) |
| doBuild(getConfig(), "run-robotester") |
| case "help": |
| showUsage() |
| return |
| case "": |
| showUsage() |
| os.Exit(0) |
| default: |
| fmt.Printf("Unrecognised verb %s", verb) |
| showUsage() |
| os.Exit(1) |
| } |
| } |
| |
| func maxArgs(count int) { |
| if len(flag.Args()) > count { |
| showUsage() |
| os.Exit(1) |
| } |
| } |
| |
| func showUsage() { fmt.Print(usage) } |
| |
| // Config is the structure that holds all the configuration settings. |
| type Config struct { |
| OutRoot string `desc:"Build output directory"` |
| CMakePath string `file:"true" desc:"Path to cmake executable"` |
| NinjaPath string `file:"true" desc:"Path to ninja executable"` |
| Flavor string `enum:"release,debug" desc:"Build flavor"` |
| } |
| |
| func (cfg Config) outputPath() string { return abs(cfg.OutRoot, cfg.Flavor) } |
| func (cfg Config) currentPath() string { return abs(cfg.OutRoot, "current") } |
| func (cfg Config) setCurrent() { symlink(cfg.currentPath(), cfg.outputPath()) } |
| |
| var defaults = Config{ |
| OutRoot: "~/gapid", |
| CMakePath: abs("../../../../../../../prebuilts/cmake/" + host() + "-x86/bin/cmake"), // TODO: change for target OS. |
| NinjaPath: abs("../../../../../../../prebuilts/ninja/" + host() + "-x86/ninja"), // TODO: change for target OS. |
| Flavor: "release", |
| } |
| |
| func host() string { return runtime.GOOS } |
| |
| func lookPath(name string) string { |
| if path, err := exec.LookPath(name); err == nil { |
| return path |
| } |
| return name |
| } |
| |
| func getConfig() Config { |
| cfg, cfgRead := readConfig() |
| if !cfgRead { |
| cfg = setConfig(cfg) |
| } |
| return cfg |
| } |
| |
| func readConfig() (Config, bool) { |
| data, err := ioutil.ReadFile(cfgPath) |
| if err != nil { |
| return defaults, false |
| } |
| cfg := defaults |
| if err := json.Unmarshal(data, &cfg); err != nil { |
| return defaults, false |
| } |
| return cfg, true |
| } |
| |
| func writeConfig(cfg Config) { |
| data, err := json.Marshal(cfg) |
| if err != nil { |
| panic(err) |
| } |
| if err := ioutil.WriteFile(cfgPath, data, 0666); err != nil { |
| panic(err) |
| } |
| } |
| |
| func setConfig(cfg Config) Config { |
| v, t := reflect.ValueOf(&cfg).Elem(), reflect.TypeOf(cfg) |
| for i, c := 0, t.NumField(); i < c; i++ { |
| v, t := v.Field(i), t.Field(i) |
| desc := t.Tag.Get("desc") |
| if desc == "" { |
| desc = t.Name |
| } |
| |
| enum := t.Tag.Get("enum") |
| var options []string |
| if enum != "" { |
| options = strings.Split(enum, ",") |
| desc += fmt.Sprintf(". One of: %v", strings.Join(options, ", ")) |
| } |
| |
| isFile := t.Tag.Get("file") == "true" |
| |
| fmt.Printf(" • %s [Default: %v]\n", desc, v.Interface()) |
| in := readLine() |
| |
| switch t.Type.Kind() { |
| case reflect.String: |
| def := v.Interface().(string) |
| if in == "" { |
| in = def |
| } |
| if isFile && !exists(in) { |
| fmt.Printf("%v does not exist\n", in) |
| i-- |
| continue |
| } |
| if options != nil && !isIn(in, options) { |
| fmt.Printf("Must be one of: %v\n", strings.Join(options, ", ")) |
| i-- |
| continue |
| } |
| v.Set(reflect.ValueOf(in)) |
| |
| case reflect.Bool: |
| val := v.Interface().(bool) |
| if in != "" { |
| var ok bool |
| if val, ok = parseBool(in); !ok { |
| fmt.Printf("Must be yes/true or no/false\n") |
| i-- |
| continue |
| } |
| } |
| v.Set(reflect.ValueOf(val)) |
| } |
| } |
| writeConfig(cfg) |
| |
| outPath := cfg.outputPath() |
| mkdir(outPath) |
| cfg.setCurrent() |
| |
| return cfg |
| } |
| |
| func doBuild(cfg Config, target string) { |
| outPath := cfg.outputPath() |
| mkdir(outPath) |
| cfg.setCurrent() |
| if !exists(abs(outPath, "CMakeCache.txt")) { |
| args := []string{ |
| "-GNinja", |
| "-DCMAKE_MAKE_PROGRAM=" + abs(cfg.NinjaPath), |
| "-DCMAKE_BUILD_TYPE=" + strings.ToUpper(cfg.Flavor), |
| } |
| if runtime.GOOS == "darwin" { |
| args = append(args, "-DPREBUILTS=NO") |
| } |
| args = append(args, srcPath()) |
| run(outPath, abs(cfg.CMakePath), args...) |
| } |
| args := []string{"--build", outPath} |
| if target != "" { |
| args = append(args, "--target", target) |
| } |
| run(outPath, abs(cfg.CMakePath), args...) |
| } |
| |
| func srcPath() string { |
| return abs(os.Getenv("GOPATH"), "src/android.googlesource.com/platform/tools/gpu") |
| } |
| |
| func isIn(str string, list []string) bool { |
| for _, s := range list { |
| if s == str { |
| return true |
| } |
| } |
| return false |
| } |
| |
| func readLine() string { |
| r := bufio.NewReader(os.Stdin) |
| l, _ := r.ReadString('\n') |
| return strings.Trim(l, "\n") |
| } |
| |
| func parseBool(str string) (val, ok bool) { |
| switch strings.ToLower(str) { |
| case "yes", "y", "true": |
| return true, true |
| case "no", "n", "false": |
| return false, true |
| } |
| return false, false |
| } |
| |
| func mkdir(path string) { |
| if err := os.MkdirAll(path, 0755); err != nil { |
| panic(err) |
| } |
| } |
| |
| func symlink(dst, src string) { |
| os.Remove(dst) |
| if err := os.Symlink(src, dst); err != nil { |
| panic(err) |
| } |
| } |
| |
| func exists(path string) bool { |
| info, _ := os.Stat(path) |
| return info != nil |
| } |
| |
| func abs(path string, subdirs ...string) string { |
| if strings.HasPrefix(path, "~") { |
| u, err := user.Current() |
| if err != nil { |
| panic(err) |
| } |
| path = filepath.Join(u.HomeDir, strings.TrimLeft(path, "~")) |
| } |
| path, err := filepath.Abs(path) |
| if err != nil { |
| panic(err) |
| } |
| for _, s := range subdirs { |
| path = filepath.Join(path, s) |
| } |
| return path |
| } |
| |
| func run(cwd string, exe string, args ...string) { |
| cmd := exec.Command(exe, args...) |
| cmd.Dir = cwd |
| cmd.Stdin = os.Stdin |
| cmd.Stdout = os.Stdout |
| cmd.Stderr = os.Stderr |
| if err := cmd.Run(); err != nil { |
| fmt.Printf("Error running %s: %v\n", exe, err) |
| os.Exit(1) |
| } |
| } |