diff options
-rw-r--r-- | build/cpp/cpp.go | 95 | ||||
-rw-r--r-- | build/cpp/gcc/gcc.go | 34 | ||||
-rw-r--r-- | build/cpp/ndk/ndk.go | 50 | ||||
-rw-r--r-- | build/cpp/parse_depfile.go | 68 | ||||
-rw-r--r-- | build/cpp/parse_depfile_test.go | 78 | ||||
-rw-r--r-- | build/environment.go | 1 | ||||
-rw-r--r-- | build/file.go | 12 | ||||
-rw-r--r-- | build/file_set.go | 14 | ||||
-rw-r--r-- | cc/build.go | 2 | ||||
-rwxr-xr-x | tools/make_gpu_tools.sh | 4 | ||||
-rw-r--r-- | tools/make_gpu_tools_win.bat | 2 |
11 files changed, 322 insertions, 38 deletions
diff --git a/build/cpp/cpp.go b/build/cpp/cpp.go index ca208a64c..95ac91437 100644 --- a/build/cpp/cpp.go +++ b/build/cpp/cpp.go @@ -24,6 +24,7 @@ import ( ) type tool func(inputs build.FileSet, output build.File, cfg Config, env build.Environment) error +type depsFor func(output build.File, cfg Config, env build.Environment) (deps build.FileSet, valid bool) var sourcePatterns = []string{"*.cpp", "*.c", "*.cc", "*.mm"} @@ -32,6 +33,7 @@ type Toolchain struct { Compiler tool // Tool used to compile source to object files. Archiver tool // Tool used to package object files into archives. Linker tool // Tool used to link objects and packages into executables. + DepsFor depsFor // Returns the list of dependencies for the given file. ExeName func(Config) string // Returns the name of the emitted executable. LibName func(Config) string // Returns the name of the emitted static library file. ObjExt func(Config) string // Extension used for object files. @@ -117,18 +119,49 @@ func Compile(sources build.FileSet, cfg Config, env build.Environment) (build.Fi wg.Add(len(sources)) for i, source := range sources { i, source, env := i, source, env - env.Logger = env.Logger.Fork() // Give each go-routine a unique logger context id. - go func() { - defer wg.Done() - object := IntermediatePath(source, cfg.Toolchain.ObjExt(cfg), cfg, env) + object := IntermediatePath(source, cfg.Toolchain.ObjExt(cfg), cfg, env) + + if requiresCompile(source, object, cfg, env) { + env.Logger = env.Logger.Fork() // Give each go-routine a unique logger context id. + go func() { + defer wg.Done() + objects[i] = object + errors[i] = cfg.Toolchain.Compiler(build.FileSet{source}, object, cfg, env) + }() + } else { objects[i] = object - errors[i] = cfg.Toolchain.Compiler(build.FileSet{source}, object, cfg, env) - }() + wg.Done() + if env.Verbose { + env.Logger.Info("%s is up-to-date", object) + } + } } wg.Wait() return objects, combineErrors(errors) } +func requiresCompile(source, output build.File, cfg Config, env build.Environment) bool { + if env.ForceBuild { + return true + } + if !output.Exists() { + return true + } + t := output.LastModified() + if source.LastModified().After(t) { + return true + } + depsFor := cfg.Toolchain.DepsFor + if depsFor == nil { + return true // If the toolchain can't check dependencies, then we have to build + } + deps, valid := depsFor(output, cfg, env) + if !valid || deps.LastModified().After(t) { + return true + } + return false +} + // Executable links the list of input files into an executable using the Config // and build Environment. The inputs can be a combination of source files, // object files and / or library files. Compile returns the output executable @@ -143,19 +176,39 @@ func Executable(inputs build.FileSet, cfg Config, env build.Environment) (build. objects = objects.Append(inputs.Filter("*" + cfg.Toolchain.ObjExt(cfg))...) - sourceLibraries := build.FileSet{} + libraries := build.FileSet{} for _, library := range inputs.Filter("*" + cfg.Toolchain.LibExt(cfg)) { dir, base := filepath.Split(library.Absolute()) - sourceLibraries = sourceLibraries.Append(build.File(base)) + libraries = libraries.Append(build.File(base)) cfg.LibrarySearchPaths = cfg.LibrarySearchPaths.Append(build.File(dir)) } - cfg.Libraries = append(sourceLibraries, cfg.Libraries...) + cfg.Libraries = append(libraries, cfg.Libraries...) output := env.Output.Join(cfg.Toolchain.ExeName(cfg)) output.MkdirAll() - return output, cfg.Toolchain.Linker(objects, output, cfg, env) + if requiresLink(inputs.Append(objects...), output, env) { + return output, cfg.Toolchain.Linker(objects, output, cfg, env) + } else { + if env.Verbose { + env.Logger.Info("%s is up-to-date", output) + } + return output, nil + } +} + +func requiresLink(inputs build.FileSet, output build.File, env build.Environment) bool { + if env.ForceBuild { + return true + } + if !output.Exists() { + return true + } + if inputs.LastModified().After(output.LastModified()) { + return true + } + return false } // StaticLibrary archives the list of input files into an static library using @@ -176,7 +229,27 @@ func StaticLibrary(inputs build.FileSet, cfg Config, env build.Environment) (bui output := env.Intermediates.Join(Triplet(cfg), name).ChangeExt(cfg.Toolchain.LibExt(cfg)) output.MkdirAll() - return output, cfg.Toolchain.Archiver(objects, output, cfg, env) + if requiresArchive(objects, output, env) { + return output, cfg.Toolchain.Archiver(objects, output, cfg, env) + } else { + if env.Verbose { + env.Logger.Info("%s is up-to-date", output) + } + return output, nil + } +} + +func requiresArchive(objects build.FileSet, output build.File, env build.Environment) bool { + if env.ForceBuild { + return true + } + if !output.Exists() { + return true + } + if objects.LastModified().After(output.LastModified()) { + return true + } + return false } // Triplet returns a string combining the os, architecture and flavour of cfg. diff --git a/build/cpp/gcc/gcc.go b/build/cpp/gcc/gcc.go index 6297e6270..475b8647e 100644 --- a/build/cpp/gcc/gcc.go +++ b/build/cpp/gcc/gcc.go @@ -24,9 +24,10 @@ import ( ) var GCC = &cpp.Toolchain{ - Compiler: gccCompile, - Archiver: gccArchive, - Linker: gccLink, + Compiler: compile, + Archiver: archive, + Linker: link, + DepsFor: depsFor, ExeName: func(cfg cpp.Config) string { if cfg.OS == "windows" { return cfg.Name + ".exe" @@ -83,7 +84,11 @@ func getTools(cfg cpp.Config) (*tools, error) { return &t, nil } -func gccCompile(inputs build.FileSet, output build.File, cfg cpp.Config, env build.Environment) error { +func depFileFor(output build.File, cfg cpp.Config, env build.Environment) build.File { + return cpp.IntermediatePath(output, ".dep", cfg, env) +} + +func compile(inputs build.FileSet, output build.File, cfg cpp.Config, env build.Environment) error { env.Logger = env.Logger.Enter("GCC.Compile") tools, err := getTools(cfg) @@ -91,7 +96,12 @@ func gccCompile(inputs build.FileSet, output build.File, cfg cpp.Config, env bui return err } - a := append([]string{"-c"}, cfg.CompilerArgs...) + depfile := depFileFor(output, cfg, env) + + a := append([]string{ + "-c", // Compile to .o + "-MMD", "-MF", depfile.Absolute(), // Generate dependency file + }, cfg.CompilerArgs...) for _, isp := range cfg.IncludeSearchPaths { a = append(a, fmt.Sprintf("-I%s", isp)) } @@ -101,11 +111,11 @@ func gccCompile(inputs build.FileSet, output build.File, cfg cpp.Config, env bui for _, input := range inputs { a = append(a, input.Absolute()) } - a = append(a, "-v", "-o", string(output)) + a = append(a, "-o", string(output)) return tools.cc.Exec(env, a...) } -func gccArchive(inputs build.FileSet, output build.File, cfg cpp.Config, env build.Environment) error { +func archive(inputs build.FileSet, output build.File, cfg cpp.Config, env build.Environment) error { env.Logger = env.Logger.Enter("GCC.Archive") tools, err := getTools(cfg) @@ -122,7 +132,7 @@ func gccArchive(inputs build.FileSet, output build.File, cfg cpp.Config, env bui return tools.ar.Exec(env, a...) } -func gccLink(inputs build.FileSet, output build.File, cfg cpp.Config, env build.Environment) error { +func link(inputs build.FileSet, output build.File, cfg cpp.Config, env build.Environment) error { env.Logger = env.Logger.Enter("GCC.Link") tools, err := getTools(cfg) @@ -144,3 +154,11 @@ func gccLink(inputs build.FileSet, output build.File, cfg cpp.Config, env build. a = append(a, "-o", string(output)) return tools.cc.Exec(env, a...) } + +func depsFor(output build.File, cfg cpp.Config, env build.Environment) (deps build.FileSet, valid bool) { + env.Logger = env.Logger.Enter("GCC.Deps") + + depfile := depFileFor(output, cfg, env) + + return cpp.ParseDepFile(depfile, env) +} diff --git a/build/cpp/ndk/ndk.go b/build/cpp/ndk/ndk.go index 625e88233..b998e0e7c 100644 --- a/build/cpp/ndk/ndk.go +++ b/build/cpp/ndk/ndk.go @@ -49,9 +49,10 @@ var osToSystem = map[string]string{ // Toolchain for building an Android executable env, without being packaged into an // APK (user-debug only). var EXE = &cpp.Toolchain{ - Compiler: ndkCompile, - Archiver: ndkArchive, - Linker: ndkLinkExe, + Compiler: compile, + Archiver: archive, + Linker: linkExe, + DepsFor: depsFor, ExeName: func(cfg cpp.Config) string { return fmt.Sprintf("%s-%s", cfg.Name, cfg.Architecture) }, LibName: func(cfg cpp.Config) string { return "lib" + cfg.Name + ".a" }, ObjExt: func(cfg cpp.Config) string { return ".o" }, @@ -59,9 +60,10 @@ var EXE = &cpp.Toolchain{ // Toolchain for building an Android shared library. var SO = &cpp.Toolchain{ - Compiler: ndkCompile, - Archiver: ndkArchive, - Linker: ndkLinkSo, + Compiler: compile, + Archiver: archive, + Linker: linkSo, + DepsFor: depsFor, ExeName: func(cfg cpp.Config) string { return fmt.Sprintf("%s-%s.so", cfg.Name, cfg.Architecture) }, LibName: func(cfg cpp.Config) string { return "lib" + cfg.Name + ".a" }, ObjExt: func(cfg cpp.Config) string { return ".o" }, @@ -69,9 +71,10 @@ var SO = &cpp.Toolchain{ // Toolchain for building an Android APK. var APK = &cpp.Toolchain{ - Compiler: ndkCompile, - Archiver: ndkArchive, - Linker: ndkLinkAPK, + Compiler: compile, + Archiver: archive, + Linker: linkAPK, + DepsFor: depsFor, ExeName: func(cfg cpp.Config) string { return fmt.Sprintf("%s-%s.apk", cfg.Name, cfg.Architecture) }, LibName: func(cfg cpp.Config) string { return "lib" + cfg.Name + ".a" }, ObjExt: func(cfg cpp.Config) string { return ".o" }, @@ -131,7 +134,11 @@ func getTools(cfg cpp.Config) (*tools, error) { }, nil } -func ndkCompile(inputs build.FileSet, output build.File, cfg cpp.Config, env build.Environment) error { +func depFileFor(output build.File, cfg cpp.Config, env build.Environment) build.File { + return cpp.IntermediatePath(output, ".dep", cfg, env) +} + +func compile(inputs build.FileSet, output build.File, cfg cpp.Config, env build.Environment) error { env.Logger = env.Logger.Enter("NDK.Compile") tools, err := getTools(cfg) @@ -139,9 +146,12 @@ func ndkCompile(inputs build.FileSet, output build.File, cfg cpp.Config, env bui return err } + depfile := depFileFor(output, cfg, env) + a := []string{ "--sysroot=" + tools.sysroot.Absolute(), - "-c", + "-c", // Compile to .o + "-MMD", "-MF", depfile.Absolute(), // Generate dependency file "-fPIC", // TODO: Not required for exes } a = append(a, cfg.CompilerArgs...) @@ -158,7 +168,7 @@ func ndkCompile(inputs build.FileSet, output build.File, cfg cpp.Config, env bui return tools.cc.Exec(env, a...) } -func ndkArchive(inputs build.FileSet, output build.File, cfg cpp.Config, env build.Environment) error { +func archive(inputs build.FileSet, output build.File, cfg cpp.Config, env build.Environment) error { env.Logger = env.Logger.Enter("NDK.Archive") tools, err := getTools(cfg) @@ -175,7 +185,7 @@ func ndkArchive(inputs build.FileSet, output build.File, cfg cpp.Config, env bui return tools.ar.Exec(env, a...) } -func ndkLinkExe(inputs build.FileSet, output build.File, cfg cpp.Config, env build.Environment) error { +func linkExe(inputs build.FileSet, output build.File, cfg cpp.Config, env build.Environment) error { env.Logger = env.Logger.Enter("NDK.Link-exe") tools, err := getTools(cfg) @@ -200,7 +210,7 @@ func ndkLinkExe(inputs build.FileSet, output build.File, cfg cpp.Config, env bui return tools.cc.Exec(env, a...) } -func ndkLinkSo(inputs build.FileSet, output build.File, cfg cpp.Config, env build.Environment) error { +func linkSo(inputs build.FileSet, output build.File, cfg cpp.Config, env build.Environment) error { env.Logger = env.Logger.Enter("NDK.Link-so") tools, err := getTools(cfg) @@ -228,7 +238,7 @@ func ndkLinkSo(inputs build.FileSet, output build.File, cfg cpp.Config, env buil return tools.cc.Exec(env, a...) } -func ndkLinkAPK(inputs build.FileSet, output build.File, cfg cpp.Config, env build.Environment) error { +func linkAPK(inputs build.FileSet, output build.File, cfg cpp.Config, env build.Environment) error { env.Logger = env.Logger.Enter("NDK.Link-apk") paths, err := ResolvePaths() @@ -258,7 +268,7 @@ func ndkLinkAPK(inputs build.FileSet, output build.File, cfg cpp.Config, env bui ioutil.WriteFile(strings.Absolute(), stringsXml(cfg.Name), 0666) so := root.Join("lib", ndkArchToTarget[cfg.Architecture].abi, "lib"+cfg.Name+".so") so.MkdirAll() - if err := ndkLinkSo(inputs, so, cfg, env); err != nil { + if err := linkSo(inputs, so, cfg, env); err != nil { return err } @@ -302,6 +312,14 @@ func ndkLinkAPK(inputs build.FileSet, output build.File, cfg cpp.Config, env bui return nil } +func depsFor(output build.File, cfg cpp.Config, env build.Environment) (deps build.FileSet, valid bool) { + env.Logger = env.Logger.Enter("NDK.Deps") + + depfile := depFileFor(output, cfg, env) + + return cpp.ParseDepFile(depfile, env) +} + func androidManifest(cfg cpp.Config) []byte { permissions := make([]string, len(cfg.Permissions)) for i := range cfg.Permissions { diff --git a/build/cpp/parse_depfile.go b/build/cpp/parse_depfile.go new file mode 100644 index 000000000..98b015ccc --- /dev/null +++ b/build/cpp/parse_depfile.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 cpp + +import ( + "errors" + "io/ioutil" + "strings" + + "android.googlesource.com/platform/tools/gpu/build" +) + +// ParseDepFile loads and parses the makefile dependency file generated by the +// C++ compiler. If parsing succeeds then ParseDepFile returns the list of file +// dependencies in deps and depsValid is true, otherwise depsValid is false. +func ParseDepFile(file build.File, env build.Environment) (deps build.FileSet, depsValid bool) { + if !file.Exists() { + // Dependency file not found - needs building. + return build.FileSet{}, false + } + + f, err := ioutil.ReadFile(file.Absolute()) + if err != nil { + env.Logger.Error("Could not read dependencies from '%s': %v", file, err) + return build.FileSet{}, false + } + + deps, err = parseDeps(string(f)) + if err != nil { + env.Logger.Error("Could not parse dependencies from '%s': %v", file, err) + return build.FileSet{}, false + } + + return deps, true +} + +func parseDeps(s string) (build.FileSet, error) { + p := strings.Split(s, ":") + if len(p) != 2 { + return build.FileSet{}, errors.New("Parse failure - no colon found") + } + s = p[1] + + deps := build.FileSet{} + for _, s := range strings.FieldsFunc(s, func(r rune) bool { + switch r { + case '\\', '\t', '\n', ' ': + return true + default: + return false + } + }) { + deps = deps.Append(build.File(s)) + } + return deps, nil +} diff --git a/build/cpp/parse_depfile_test.go b/build/cpp/parse_depfile_test.go new file mode 100644 index 000000000..b04dd9716 --- /dev/null +++ b/build/cpp/parse_depfile_test.go @@ -0,0 +1,78 @@ +// 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 cpp + +import ( + "testing" + + "android.googlesource.com/platform/tools/gpu/build" +) + +func eq(a, b build.FileSet) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} + +func TestParseDeps(t *testing.T) { + for i, test := range []struct { + depfile string + expected build.FileSet + fail bool + }{ + { + depfile: `/dev/gpu/pkg/osx-x64-release/foo/bar.o: /dev/gpu/src/foo/A.cpp`, + expected: build.FileSet{"/dev/gpu/src/foo/A.cpp"}, + }, { + depfile: `/dev/gpu/pkg/osx-x64-release/foo/bar.o: \ + /dev/gpu/src/foo/A.cpp \ + /dev/gpu/src/foo/B.h /dev/gpu/src/foo/C.h \ + /dev/gpu/src/foo/D.h +`, + expected: build.FileSet{ + "/dev/gpu/src/foo/A.cpp", "/dev/gpu/src/foo/B.h", + "/dev/gpu/src/foo/C.h", "/dev/gpu/src/foo/D.h", + }, + }, { + depfile: ``, + fail: true, + }, { + depfile: `/dev/gpu/pkg/osx-x64-release/foo/bar.o /dev/gpu/src/foo/A.cpp`, + fail: true, + }, { + depfile: `/dev/gpu/pkg/osx-x64-release/foo/bar.o: a : b`, + fail: true, + }, + } { + deps, err := parseDeps(test.depfile) + switch { + case test.fail && err == nil: + t.Errorf("%d Expected error, got none.", i) + + case !test.fail && err != nil: + t.Errorf("%d Parse failure: %v", i, err) + + case !eq(deps, test.expected): + t.Errorf("Dependencies were not as expected.\nExpected: %v\nGot: %v", + test.expected, deps) + } + } +} diff --git a/build/environment.go b/build/environment.go index 77213b901..369fe2d5e 100644 --- a/build/environment.go +++ b/build/environment.go @@ -25,5 +25,6 @@ type Environment struct { Keypass string // The password to the key in the keystore. Keyalias string // The alias of the key used to sign APKs. Logger log.Logger // The logger to emit log messages to. + ForceBuild bool // If true, all build steps will be forced. Verbose bool // If true, logging should be verbose. } diff --git a/build/file.go b/build/file.go index b215389d0..f949789e9 100644 --- a/build/file.go +++ b/build/file.go @@ -19,6 +19,7 @@ import ( "os" "os/exec" "path/filepath" + "time" ) // File represents the path to a file or directory. @@ -58,6 +59,15 @@ func (f File) Exists() bool { return err == nil } +// LastModified returns the time the file was last modified. +func (f File) LastModified() time.Time { + s, err := os.Stat(string(f.Absolute())) + if err != nil { + panic(err) + } + return s.ModTime() +} + // Delete deletes the File. func (f File) Delete() error { return os.Remove(string(f)) @@ -166,6 +176,8 @@ func (f File) ExecAt(env Environment, wd File, args ...string) error { case env.Verbose: if msg := string(buffer.Bytes()); msg != "" { logger.Info("\n%s\n--- %s succeeded ---", string(buffer.Bytes()), f.Name()) + } else { + logger.Info("%s succeeded", f.Name()) } } return err diff --git a/build/file_set.go b/build/file_set.go index b69267728..ec404d399 100644 --- a/build/file_set.go +++ b/build/file_set.go @@ -14,6 +14,8 @@ package build +import "time" + // FileSet is a list of Files with no duplicates. type FileSet []File @@ -73,6 +75,18 @@ nextfile: return out } +// LastModified returns the most recent time any of the files were modified. +func (fs FileSet) LastModified() time.Time { + t := time.Time{} + for _, f := range fs { + m := f.LastModified() + if t.Before(m) { + t = m + } + } + return t +} + func (fs FileSet) toMap() map[File]struct{} { m := make(map[File]struct{}, len(fs)) for _, file := range fs { diff --git a/cc/build.go b/cc/build.go index ff652efa1..4e6b5a6dc 100644 --- a/cc/build.go +++ b/cc/build.go @@ -36,6 +36,7 @@ var ( keypass = flag.String("keypass", "android", "The password to the keystore's key") keyalias = flag.String("alias", "androiddebugkey", "The alias of the key used to sign APKs") logfile = flag.String("logfile", "", "Writes logging to a file instead of stdout") + forcebuild = flag.Bool("f", false, "All build steps will be forced") verbose = flag.Bool("v", false, "Enable verbose logging") ) @@ -73,6 +74,7 @@ func run() int { Keypass: *keypass, Keyalias: *keyalias, Logger: logger, + ForceBuild: *forcebuild, Verbose: *verbose, } diff --git a/tools/make_gpu_tools.sh b/tools/make_gpu_tools.sh index 403d736c3..59a9f2173 100755 --- a/tools/make_gpu_tools.sh +++ b/tools/make_gpu_tools.sh @@ -75,7 +75,7 @@ go generate $GO_GENERATE_FLAGS $GPU_RELATIVE_SOURCE go generate $GO_GENERATE_FLAGS $GPU_RELATIVE_SOURCE_PATH/memory go build $GO_BUILD_FLAGS $GPU_BUILD_ROOT/bin/gazer $GPU_RELATIVE_SOURCE_PATH/server/cmd -go run src/$GPU_RELATIVE_SOURCE_PATH/cc/build.go --v --runtests +go run src/$GPU_RELATIVE_SOURCE_PATH/cc/build.go --v --f --runtests # Kill any existing replay daemon before running tests. killall replayd || true @@ -95,7 +95,7 @@ fi killall replayd || true if [ $crosscompile_windows -eq 1 ]; then - go run src/$GPU_RELATIVE_SOURCE_PATH/cc/build.go --v --targets=windows + go run src/$GPU_RELATIVE_SOURCE_PATH/cc/build.go --v --f --targets=windows source $PROGDIR/setup_toolchain_linux_xc_win64.txt go build $GO_BUILD_FLAGS $GPU_BUILD_ROOT/bin/windows_amd64/gazer.exe -ldflags="-extld=$CC" $GPU_RELATIVE_SOURCE_PATH/server/cmd fi diff --git a/tools/make_gpu_tools_win.bat b/tools/make_gpu_tools_win.bat index 99dd6f5a9..f7838cbb0 100644 --- a/tools/make_gpu_tools_win.bat +++ b/tools/make_gpu_tools_win.bat @@ -32,4 +32,4 @@ go generate %GO_GENERATE_FLAGS% %GPU_RELATIVE_ go generate %GO_GENERATE_FLAGS% %GPU_RELATIVE_SOURCE_PATH%\memory go build %GO_BUILD_FLAGS% %GPU_BUILD_ROOT%\bin\gazer.exe %GPU_RELATIVE_SOURCE_PATH%\server\cmd -go run src/$GPU_RELATIVE_SOURCE_PATH/cc/build.go --v --runtests +go run src/$GPU_RELATIVE_SOURCE_PATH/cc/build.go --v --f --runtests |