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