// 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. //go:build ignore // 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) } }