summaryrefslogtreecommitdiff
path: root/src/util/godeps.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/godeps.go')
-rw-r--r--src/util/godeps.go203
1 files changed, 203 insertions, 0 deletions
diff --git a/src/util/godeps.go b/src/util/godeps.go
new file mode 100644
index 00000000..960faa46
--- /dev/null
+++ b/src/util/godeps.go
@@ -0,0 +1,203 @@
+// Copyright (c) 2018, Google Inc.
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+// godeps prints out dependencies of a package in either CMake or Make depfile
+// format, for incremental rebuilds.
+//
+// The depfile format is preferred. It works correctly when new files are added.
+// However, CMake only supports depfiles for custom commands with Ninja and
+// starting CMake 3.7. For other configurations, we also support CMake's format,
+// but CMake must be rerun when file lists change.
+package main
+
+import (
+ "flag"
+ "fmt"
+ "go/build"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+)
+
+var (
+ format = flag.String("format", "cmake", "The format to output to, either 'cmake' or 'depfile'")
+ mainPkg = flag.String("pkg", "", "The package to print dependencies for")
+ target = flag.String("target", "", "The name of the output file")
+ out = flag.String("out", "", "The path to write the output to. If unset, this is stdout")
+)
+
+func cMakeQuote(in string) string {
+ // See https://cmake.org/cmake/help/v3.0/manual/cmake-language.7.html#quoted-argument
+ var b strings.Builder
+ b.Grow(len(in))
+ // Iterate over in as bytes.
+ for i := 0; i < len(in); i++ {
+ switch c := in[i]; c {
+ case '\\', '"':
+ b.WriteByte('\\')
+ b.WriteByte(c)
+ case '\t':
+ b.WriteString("\\t")
+ case '\r':
+ b.WriteString("\\r")
+ case '\n':
+ b.WriteString("\\n")
+ default:
+ b.WriteByte(in[i])
+ }
+ }
+ return b.String()
+}
+
+func writeCMake(outFile *os.File, files []string) error {
+ for i, file := range files {
+ if i != 0 {
+ if _, err := outFile.WriteString(";"); err != nil {
+ return err
+ }
+ }
+ if _, err := outFile.WriteString(cMakeQuote(file)); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func makeQuote(in string) string {
+ // See https://www.gnu.org/software/make/manual/make.html#Rule-Syntax
+ var b strings.Builder
+ b.Grow(len(in))
+ // Iterate over in as bytes.
+ for i := 0; i < len(in); i++ {
+ switch c := in[i]; c {
+ case '$':
+ b.WriteString("$$")
+ case '#', '\\', ' ':
+ b.WriteByte('\\')
+ b.WriteByte(c)
+ default:
+ b.WriteByte(c)
+ }
+ }
+ return b.String()
+}
+
+func writeDepfile(outFile *os.File, files []string) error {
+ if _, err := fmt.Fprintf(outFile, "%s:", makeQuote(*target)); err != nil {
+ return err
+ }
+ for _, file := range files {
+ if _, err := fmt.Fprintf(outFile, " %s", makeQuote(file)); err != nil {
+ return err
+ }
+ }
+ _, err := outFile.WriteString("\n")
+ return err
+}
+
+func appendPrefixed(list, newFiles []string, prefix string) []string {
+ for _, file := range newFiles {
+ list = append(list, filepath.Join(prefix, file))
+ }
+ return list
+}
+
+func main() {
+ flag.Parse()
+
+ if len(*mainPkg) == 0 {
+ fmt.Fprintf(os.Stderr, "-pkg argument is required.\n")
+ os.Exit(1)
+ }
+
+ var isDepfile bool
+ switch *format {
+ case "depfile":
+ isDepfile = true
+ case "cmake":
+ isDepfile = false
+ default:
+ fmt.Fprintf(os.Stderr, "Unknown format: %q\n", *format)
+ os.Exit(1)
+ }
+
+ if isDepfile && len(*target) == 0 {
+ fmt.Fprintf(os.Stderr, "-target argument is required for depfile.\n")
+ os.Exit(1)
+ }
+
+ done := make(map[string]struct{})
+ var files []string
+ var recurse func(pkgName string) error
+ recurse = func(pkgName string) error {
+ pkg, err := build.Default.Import(pkgName, ".", 0)
+ if err != nil {
+ return err
+ }
+
+ // Skip standard packages.
+ if pkg.Goroot {
+ return nil
+ }
+
+ // Skip already-visited packages.
+ if _, ok := done[pkg.Dir]; ok {
+ return nil
+ }
+ done[pkg.Dir] = struct{}{}
+
+ files = appendPrefixed(files, pkg.GoFiles, pkg.Dir)
+ files = appendPrefixed(files, pkg.CgoFiles, pkg.Dir)
+ // Include ignored Go files. A subsequent change may cause them
+ // to no longer be ignored.
+ files = appendPrefixed(files, pkg.IgnoredGoFiles, pkg.Dir)
+
+ // Recurse into imports.
+ for _, importName := range pkg.Imports {
+ if err := recurse(importName); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+ if err := recurse(*mainPkg); err != nil {
+ fmt.Fprintf(os.Stderr, "Error getting dependencies: %s\n", err)
+ os.Exit(1)
+ }
+
+ sort.Strings(files)
+
+ outFile := os.Stdout
+ if len(*out) != 0 {
+ var err error
+ outFile, err = os.Create(*out)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error writing output: %s\n", err)
+ os.Exit(1)
+ }
+ defer outFile.Close()
+ }
+
+ var err error
+ if isDepfile {
+ err = writeDepfile(outFile, files)
+ } else {
+ err = writeCMake(outFile, files)
+ }
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error writing output: %s\n", err)
+ os.Exit(1)
+ }
+}