|
- // 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)
- }
- }
|