godeps.go 5.0 KB

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