1// Copyright (c) 2018, Google Inc. 2// 3// Permission to use, copy, modify, and/or distribute this software for any 4// purpose with or without fee is hereby granted, provided that the above 5// copyright notice and this permission notice appear in all copies. 6// 7// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 10// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION 12// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 13// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 15// godeps prints out dependencies of a package in either CMake or Make depfile 16// format, for incremental rebuilds. 17// 18// The depfile format is preferred. It works correctly when new files are added. 19// However, CMake only supports depfiles for custom commands with Ninja and 20// starting CMake 3.7. For other configurations, we also support CMake's format, 21// but CMake must be rerun when file lists change. 22package main 23 24import ( 25 "flag" 26 "fmt" 27 "go/build" 28 "os" 29 "path/filepath" 30 "sort" 31 "strings" 32) 33 34var ( 35 format = flag.String("format", "cmake", "The format to output to, either 'cmake' or 'depfile'") 36 mainPkg = flag.String("pkg", "", "The package to print dependencies for") 37 target = flag.String("target", "", "The name of the output file") 38 out = flag.String("out", "", "The path to write the output to. If unset, this is stdout") 39) 40 41func cMakeQuote(in string) string { 42 // See https://cmake.org/cmake/help/v3.0/manual/cmake-language.7.html#quoted-argument 43 var b strings.Builder 44 b.Grow(len(in)) 45 // Iterate over in as bytes. 46 for i := 0; i < len(in); i++ { 47 switch c := in[i]; c { 48 case '\\', '"': 49 b.WriteByte('\\') 50 b.WriteByte(c) 51 case '\t': 52 b.WriteString("\\t") 53 case '\r': 54 b.WriteString("\\r") 55 case '\n': 56 b.WriteString("\\n") 57 default: 58 b.WriteByte(in[i]) 59 } 60 } 61 return b.String() 62} 63 64func writeCMake(outFile *os.File, files []string) error { 65 for i, file := range files { 66 if i != 0 { 67 if _, err := outFile.WriteString(";"); err != nil { 68 return err 69 } 70 } 71 if _, err := outFile.WriteString(cMakeQuote(file)); err != nil { 72 return err 73 } 74 } 75 return nil 76} 77 78func makeQuote(in string) string { 79 // See https://www.gnu.org/software/make/manual/make.html#Rule-Syntax 80 var b strings.Builder 81 b.Grow(len(in)) 82 // Iterate over in as bytes. 83 for i := 0; i < len(in); i++ { 84 switch c := in[i]; c { 85 case '$': 86 b.WriteString("$$") 87 case '#', '\\', ' ': 88 b.WriteByte('\\') 89 b.WriteByte(c) 90 default: 91 b.WriteByte(c) 92 } 93 } 94 return b.String() 95} 96 97func writeDepfile(outFile *os.File, files []string) error { 98 if _, err := fmt.Fprintf(outFile, "%s:", makeQuote(*target)); err != nil { 99 return err 100 } 101 for _, file := range files { 102 if _, err := fmt.Fprintf(outFile, " %s", makeQuote(file)); err != nil { 103 return err 104 } 105 } 106 _, err := outFile.WriteString("\n") 107 return err 108} 109 110func appendPrefixed(list, newFiles []string, prefix string) []string { 111 for _, file := range newFiles { 112 list = append(list, filepath.Join(prefix, file)) 113 } 114 return list 115} 116 117func main() { 118 flag.Parse() 119 120 if len(*mainPkg) == 0 { 121 fmt.Fprintf(os.Stderr, "-pkg argument is required.\n") 122 os.Exit(1) 123 } 124 125 var isDepfile bool 126 switch *format { 127 case "depfile": 128 isDepfile = true 129 case "cmake": 130 isDepfile = false 131 default: 132 fmt.Fprintf(os.Stderr, "Unknown format: %q\n", *format) 133 os.Exit(1) 134 } 135 136 if isDepfile && len(*target) == 0 { 137 fmt.Fprintf(os.Stderr, "-target argument is required for depfile.\n") 138 os.Exit(1) 139 } 140 141 done := make(map[string]struct{}) 142 var files []string 143 var recurse func(pkgName string) error 144 recurse = func(pkgName string) error { 145 pkg, err := build.Default.Import(pkgName, ".", 0) 146 if err != nil { 147 return err 148 } 149 150 // Skip standard packages. 151 if pkg.Goroot { 152 return nil 153 } 154 155 // Skip already-visited packages. 156 if _, ok := done[pkg.Dir]; ok { 157 return nil 158 } 159 done[pkg.Dir] = struct{}{} 160 161 files = appendPrefixed(files, pkg.GoFiles, pkg.Dir) 162 files = appendPrefixed(files, pkg.CgoFiles, pkg.Dir) 163 // Include ignored Go files. A subsequent change may cause them 164 // to no longer be ignored. 165 files = appendPrefixed(files, pkg.IgnoredGoFiles, pkg.Dir) 166 167 // Recurse into imports. 168 for _, importName := range pkg.Imports { 169 if err := recurse(importName); err != nil { 170 return err 171 } 172 } 173 return nil 174 } 175 if err := recurse(*mainPkg); err != nil { 176 fmt.Fprintf(os.Stderr, "Error getting dependencies: %s\n", err) 177 os.Exit(1) 178 } 179 180 sort.Strings(files) 181 182 outFile := os.Stdout 183 if len(*out) != 0 { 184 var err error 185 outFile, err = os.Create(*out) 186 if err != nil { 187 fmt.Fprintf(os.Stderr, "Error writing output: %s\n", err) 188 os.Exit(1) 189 } 190 defer outFile.Close() 191 } 192 193 var err error 194 if isDepfile { 195 err = writeDepfile(outFile, files) 196 } else { 197 err = writeCMake(outFile, files) 198 } 199 if err != nil { 200 fmt.Fprintf(os.Stderr, "Error writing output: %s\n", err) 201 os.Exit(1) 202 } 203} 204