blob: ae73655ebf172ee58059d65d437b26ab401f6007 [file] [log] [blame]
// 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)
}
}