blob: 960faa46be8775991b930fef201140b5c4279528 [file] [log] [blame]
// 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)
}
}