diff options
Diffstat (limited to 'src/util/godeps.go')
-rw-r--r-- | src/util/godeps.go | 203 |
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) + } +} |