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