gen.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. // Copyright 2015 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. //go:build ignore
  5. // +build ignore
  6. // gen runs go generate on Unicode- and CLDR-related package in the text
  7. // repositories, taking into account dependencies and versions.
  8. package main
  9. import (
  10. "bytes"
  11. "flag"
  12. "fmt"
  13. "go/format"
  14. "io/ioutil"
  15. "os"
  16. "os/exec"
  17. "path"
  18. "path/filepath"
  19. "regexp"
  20. "runtime"
  21. "strings"
  22. "sync"
  23. "unicode"
  24. "golang.org/x/text/collate"
  25. "golang.org/x/text/internal/gen"
  26. "golang.org/x/text/language"
  27. )
  28. var (
  29. verbose = flag.Bool("v", false, "verbose output")
  30. force = flag.Bool("force", false, "ignore failing dependencies")
  31. doCore = flag.Bool("core", false, "force an update to core")
  32. skipTest = flag.Bool("skiptest", false, "skip tests")
  33. excludeList = flag.String("exclude", "",
  34. "comma-separated list of packages to exclude")
  35. // The user can specify a selection of packages to build on the command line.
  36. args []string
  37. )
  38. func exclude(pkg string) bool {
  39. if len(args) > 0 {
  40. return !contains(args, pkg)
  41. }
  42. return contains(strings.Split(*excludeList, ","), pkg)
  43. }
  44. // TODO:
  45. // - Better version handling.
  46. // - Generate tables for the core unicode package?
  47. // - Add generation for encodings. This requires some retooling here and there.
  48. // - Running repo-wide "long" tests.
  49. var vprintf = fmt.Printf
  50. func main() {
  51. gen.Init()
  52. args = flag.Args()
  53. if !*verbose {
  54. // Set vprintf to a no-op.
  55. vprintf = func(string, ...interface{}) (int, error) { return 0, nil }
  56. }
  57. // TODO: create temporary cache directory to load files and create and set
  58. // a "cache" option if the user did not specify the UNICODE_DIR environment
  59. // variable. This will prevent duplicate downloads and also will enable long
  60. // tests, which really need to be run after each generated package.
  61. updateCore := *doCore
  62. if gen.UnicodeVersion() != unicode.Version {
  63. fmt.Printf("Requested Unicode version %s; core unicode version is %s.\n",
  64. gen.UnicodeVersion(),
  65. unicode.Version)
  66. c := collate.New(language.Und, collate.Numeric)
  67. if c.CompareString(gen.UnicodeVersion(), unicode.Version) < 0 && !*force {
  68. os.Exit(2)
  69. }
  70. updateCore = true
  71. goroot := os.Getenv("GOROOT")
  72. appendToFile(
  73. filepath.Join(goroot, "api", "except.txt"),
  74. fmt.Sprintf("pkg unicode, const Version = %q\n", unicode.Version),
  75. )
  76. const lines = `pkg unicode, const Version = %q
  77. // TODO: add a new line of the following form for each new script and property.
  78. pkg unicode, var <new script or property> *RangeTable
  79. `
  80. appendToFile(
  81. filepath.Join(goroot, "api", "next.txt"),
  82. fmt.Sprintf(lines, gen.UnicodeVersion()),
  83. )
  84. }
  85. var unicode = &dependency{}
  86. if updateCore {
  87. fmt.Printf("Updating core to version %s...\n", gen.UnicodeVersion())
  88. unicodeInternal := generate("./internal/export/unicode")
  89. unicode = generate("unicode", unicodeInternal)
  90. // Test some users of the unicode packages, especially the ones that
  91. // keep a mirrored table. These may need to be corrected by hand.
  92. generate("regexp", unicode)
  93. generate("strconv", unicode) // mimics Unicode table
  94. generate("strings", unicode)
  95. generate("testing", unicode) // mimics Unicode table
  96. }
  97. var (
  98. cldr = generate("./unicode/cldr", unicode)
  99. intlang = generate("./internal/language", cldr)
  100. compact = generate("./internal/language/compact", intlang, cldr)
  101. language = generate("./language", cldr, compact)
  102. internal = generate("./internal", unicode, language)
  103. norm = generate("./unicode/norm", unicode)
  104. rangetable = generate("./unicode/rangetable", unicode)
  105. cases = generate("./cases", unicode, norm, language, rangetable)
  106. width = generate("./width", unicode)
  107. bidi = generate("./unicode/bidi", unicode, norm, rangetable)
  108. mib = generate("./encoding/internal/identifier", unicode)
  109. number = generate("./internal/number", unicode, cldr, language, internal)
  110. cldrtree = generate("./internal/cldrtree", language, internal)
  111. _ = generate("./unicode/runenames", unicode)
  112. _ = generate("./encoding/htmlindex", unicode, language, mib)
  113. _ = generate("./encoding/ianaindex", unicode, language, mib)
  114. _ = generate("./secure/precis", unicode, norm, rangetable, cases, width, bidi)
  115. _ = generate("./currency", unicode, cldr, language, internal, number)
  116. _ = generate("./feature/plural", unicode, cldr, language, internal, number)
  117. _ = generate("./internal/export/idna", unicode, bidi, norm)
  118. _ = generate("./language/display", unicode, cldr, language, internal, number)
  119. _ = generate("./collate", unicode, norm, cldr, language, rangetable)
  120. _ = generate("./search", unicode, norm, cldr, language, rangetable)
  121. _ = generate("./date", cldr, language, cldrtree)
  122. )
  123. all.Wait()
  124. // Copy exported packages to the destination golang.org repo.
  125. copyExported("golang.org/x/net/idna")
  126. if hasErrors {
  127. fmt.Println("FAIL")
  128. os.Exit(1)
  129. }
  130. vprintf("SUCCESS\n")
  131. }
  132. func appendToFile(file, text string) {
  133. fmt.Println("Augmenting", file)
  134. w, err := os.OpenFile(file, os.O_APPEND|os.O_WRONLY, 0600)
  135. if err != nil {
  136. fmt.Println("Failed to open file:", err)
  137. os.Exit(1)
  138. }
  139. defer w.Close()
  140. if _, err := w.WriteString(text); err != nil {
  141. fmt.Println("Failed to write to file:", err)
  142. os.Exit(1)
  143. }
  144. }
  145. var (
  146. all sync.WaitGroup
  147. hasErrors bool
  148. )
  149. type dependency struct {
  150. sync.WaitGroup
  151. hasErrors bool
  152. }
  153. func generate(pkg string, deps ...*dependency) *dependency {
  154. var wg dependency
  155. if exclude(pkg) {
  156. return &wg
  157. }
  158. wg.Add(1)
  159. all.Add(1)
  160. go func() {
  161. defer wg.Done()
  162. defer all.Done()
  163. // Wait for dependencies to finish.
  164. for _, d := range deps {
  165. d.Wait()
  166. if d.hasErrors && !*force {
  167. fmt.Printf("--- ABORT: %s\n", pkg)
  168. wg.hasErrors = true
  169. return
  170. }
  171. }
  172. vprintf("=== GENERATE %s\n", pkg)
  173. args := []string{"generate"}
  174. if *verbose {
  175. args = append(args, "-v")
  176. }
  177. args = append(args, pkg)
  178. cmd := exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"), args...)
  179. w := &bytes.Buffer{}
  180. cmd.Stderr = w
  181. cmd.Stdout = w
  182. if err := cmd.Run(); err != nil {
  183. fmt.Printf("--- FAIL: %s:\n\t%v\n\tError: %v\n", pkg, indent(w), err)
  184. hasErrors = true
  185. wg.hasErrors = true
  186. return
  187. }
  188. if *skipTest {
  189. return
  190. }
  191. vprintf("=== TEST %s\n", pkg)
  192. args[0] = "test"
  193. cmd = exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"), args...)
  194. wt := &bytes.Buffer{}
  195. cmd.Stderr = wt
  196. cmd.Stdout = wt
  197. if err := cmd.Run(); err != nil {
  198. fmt.Printf("--- FAIL: %s:\n\t%v\n\tError: %v\n", pkg, indent(wt), err)
  199. hasErrors = true
  200. wg.hasErrors = true
  201. return
  202. }
  203. vprintf("--- SUCCESS: %s\n\t%v\n", pkg, indent(w))
  204. fmt.Print(wt.String())
  205. }()
  206. return &wg
  207. }
  208. // copyExported copies a package in x/text/internal/export to the
  209. // destination repository.
  210. func copyExported(p string) {
  211. copyPackage(
  212. filepath.Join("internal", "export", path.Base(p)),
  213. filepath.Join("..", filepath.FromSlash(p[len("golang.org/x"):])),
  214. "golang.org/x/text/internal/export/"+path.Base(p),
  215. p)
  216. }
  217. // goGenRE is used to remove go:generate lines.
  218. var goGenRE = regexp.MustCompile("//go:generate[^\n]*\n")
  219. // copyPackage copies relevant files from a directory in x/text to the
  220. // destination package directory. The destination package is assumed to have
  221. // the same name. For each copied file go:generate lines are removed and
  222. // package comments are rewritten to the new path.
  223. func copyPackage(dirSrc, dirDst, search, replace string) {
  224. err := filepath.Walk(dirSrc, func(file string, info os.FileInfo, err error) error {
  225. base := filepath.Base(file)
  226. if err != nil || info.IsDir() ||
  227. !strings.HasSuffix(base, ".go") ||
  228. strings.HasSuffix(base, "_test.go") ||
  229. // Don't process subdirectories.
  230. filepath.Dir(file) != dirSrc {
  231. return nil
  232. }
  233. if strings.HasPrefix(base, "tables") {
  234. if !strings.HasSuffix(base, gen.UnicodeVersion()+".go") {
  235. return nil
  236. }
  237. base = "tables.go"
  238. }
  239. b, err := ioutil.ReadFile(file)
  240. if err != nil || bytes.Contains(b, []byte("\n// +build ignore")) {
  241. return err
  242. }
  243. // Fix paths.
  244. b = bytes.Replace(b, []byte(search), []byte(replace), -1)
  245. b = bytes.Replace(b, []byte("internal/export"), []byte(""), -1)
  246. // Remove go:generate lines.
  247. b = goGenRE.ReplaceAllLiteral(b, nil)
  248. comment := "// Code generated by running \"go generate\" in golang.org/x/text. DO NOT EDIT.\n\n"
  249. if !bytes.HasPrefix(b, []byte(comment)) {
  250. b = append([]byte(comment), b...)
  251. }
  252. if b, err = format.Source(b); err != nil {
  253. fmt.Println("Failed to format file:", err)
  254. os.Exit(1)
  255. }
  256. file = filepath.Join(dirDst, base)
  257. vprintf("=== COPY %s\n", file)
  258. return ioutil.WriteFile(file, b, 0666)
  259. })
  260. if err != nil {
  261. fmt.Println("Copying exported files failed:", err)
  262. os.Exit(1)
  263. }
  264. }
  265. func contains(a []string, s string) bool {
  266. for _, e := range a {
  267. if s == e {
  268. return true
  269. }
  270. }
  271. return false
  272. }
  273. func indent(b *bytes.Buffer) string {
  274. return strings.Replace(strings.TrimSpace(b.String()), "\n", "\n\t", -1)
  275. }