You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
319 lines
9.3 KiB
319 lines
9.3 KiB
// Copyright 2015 The Go Authors. All rights reserved. |
|
// Use of this source code is governed by a BSD-style |
|
// license that can be found in the LICENSE file. |
|
|
|
// +build ignore |
|
|
|
// gen runs go generate on Unicode- and CLDR-related package in the text |
|
// repositories, taking into account dependencies and versions. |
|
package main |
|
|
|
import ( |
|
"bytes" |
|
"flag" |
|
"fmt" |
|
"go/build" |
|
"go/format" |
|
"io/ioutil" |
|
"os" |
|
"os/exec" |
|
"path" |
|
"path/filepath" |
|
"regexp" |
|
"runtime" |
|
"strings" |
|
"sync" |
|
"unicode" |
|
|
|
"golang.org/x/text/collate" |
|
"golang.org/x/text/internal/gen" |
|
"golang.org/x/text/language" |
|
) |
|
|
|
var ( |
|
verbose = flag.Bool("v", false, "verbose output") |
|
force = flag.Bool("force", false, "ignore failing dependencies") |
|
doCore = flag.Bool("core", false, "force an update to core") |
|
excludeList = flag.String("exclude", "", |
|
"comma-separated list of packages to exclude") |
|
|
|
// The user can specify a selection of packages to build on the command line. |
|
args []string |
|
) |
|
|
|
func exclude(pkg string) bool { |
|
if len(args) > 0 { |
|
return !contains(args, pkg) |
|
} |
|
return contains(strings.Split(*excludeList, ","), pkg) |
|
} |
|
|
|
// TODO: |
|
// - Better version handling. |
|
// - Generate tables for the core unicode package? |
|
// - Add generation for encodings. This requires some retooling here and there. |
|
// - Running repo-wide "long" tests. |
|
|
|
var vprintf = fmt.Printf |
|
|
|
func main() { |
|
gen.Init() |
|
args = flag.Args() |
|
if !*verbose { |
|
// Set vprintf to a no-op. |
|
vprintf = func(string, ...interface{}) (int, error) { return 0, nil } |
|
} |
|
|
|
// TODO: create temporary cache directory to load files and create and set |
|
// a "cache" option if the user did not specify the UNICODE_DIR environment |
|
// variable. This will prevent duplicate downloads and also will enable long |
|
// tests, which really need to be run after each generated package. |
|
|
|
updateCore := *doCore |
|
if gen.UnicodeVersion() != unicode.Version { |
|
fmt.Printf("Requested Unicode version %s; core unicode version is %s.\n", |
|
gen.UnicodeVersion(), |
|
unicode.Version) |
|
c := collate.New(language.Und, collate.Numeric) |
|
if c.CompareString(gen.UnicodeVersion(), unicode.Version) < 0 && !*force { |
|
os.Exit(2) |
|
} |
|
updateCore = true |
|
goroot := os.Getenv("GOROOT") |
|
appendToFile( |
|
filepath.Join(goroot, "api", "except.txt"), |
|
fmt.Sprintf("pkg unicode, const Version = %q\n", unicode.Version), |
|
) |
|
const lines = `pkg unicode, const Version = %q |
|
// TODO: add a new line of the following form for each new script and property. |
|
pkg unicode, var <new script or property> *RangeTable |
|
` |
|
appendToFile( |
|
filepath.Join(goroot, "api", "next.txt"), |
|
fmt.Sprintf(lines, gen.UnicodeVersion()), |
|
) |
|
} |
|
|
|
var unicode = &dependency{} |
|
if updateCore { |
|
fmt.Printf("Updating core to version %s...\n", gen.UnicodeVersion()) |
|
unicode = generate("unicode") |
|
|
|
// Test some users of the unicode packages, especially the ones that |
|
// keep a mirrored table. These may need to be corrected by hand. |
|
generate("regexp", unicode) |
|
generate("strconv", unicode) // mimics Unicode table |
|
generate("strings", unicode) |
|
generate("testing", unicode) // mimics Unicode table |
|
} |
|
|
|
var ( |
|
cldr = generate("./unicode/cldr", unicode) |
|
language = generate("./language", cldr) |
|
internal = generate("./internal", unicode, language) |
|
norm = generate("./unicode/norm", unicode) |
|
rangetable = generate("./unicode/rangetable", unicode) |
|
cases = generate("./cases", unicode, norm, language, rangetable) |
|
width = generate("./width", unicode) |
|
bidi = generate("./unicode/bidi", unicode, norm, rangetable) |
|
mib = generate("./encoding/internal/identifier", unicode) |
|
number = generate("./internal/number", unicode, cldr, language, internal) |
|
_ = generate("./encoding/htmlindex", unicode, language, mib) |
|
_ = generate("./encoding/ianaindex", unicode, language, mib) |
|
_ = generate("./secure/precis", unicode, norm, rangetable, cases, width, bidi) |
|
_ = generate("./internal/cldrtree", language) |
|
_ = generate("./currency", unicode, cldr, language, internal, number) |
|
_ = generate("./feature/plural", unicode, cldr, language, internal, number) |
|
_ = generate("./internal/export/idna", unicode, bidi, norm) |
|
_ = generate("./language/display", unicode, cldr, language, internal, number) |
|
_ = generate("./collate", unicode, norm, cldr, language, rangetable) |
|
_ = generate("./search", unicode, norm, cldr, language, rangetable) |
|
) |
|
all.Wait() |
|
|
|
// Copy exported packages to the destination golang.org repo. |
|
copyExported("golang.org/x/net/idna") |
|
|
|
if updateCore { |
|
copyVendored() |
|
} |
|
|
|
if hasErrors { |
|
fmt.Println("FAIL") |
|
os.Exit(1) |
|
} |
|
vprintf("SUCCESS\n") |
|
} |
|
|
|
func appendToFile(file, text string) { |
|
fmt.Println("Augmenting", file) |
|
w, err := os.OpenFile(file, os.O_APPEND|os.O_WRONLY, 0600) |
|
if err != nil { |
|
fmt.Println("Failed to open file:", err) |
|
os.Exit(1) |
|
} |
|
defer w.Close() |
|
if _, err := w.WriteString(text); err != nil { |
|
fmt.Println("Failed to write to file:", err) |
|
os.Exit(1) |
|
} |
|
} |
|
|
|
var ( |
|
all sync.WaitGroup |
|
hasErrors bool |
|
) |
|
|
|
type dependency struct { |
|
sync.WaitGroup |
|
hasErrors bool |
|
} |
|
|
|
func generate(pkg string, deps ...*dependency) *dependency { |
|
var wg dependency |
|
if exclude(pkg) { |
|
return &wg |
|
} |
|
wg.Add(1) |
|
all.Add(1) |
|
go func() { |
|
defer wg.Done() |
|
defer all.Done() |
|
// Wait for dependencies to finish. |
|
for _, d := range deps { |
|
d.Wait() |
|
if d.hasErrors && !*force { |
|
fmt.Printf("--- ABORT: %s\n", pkg) |
|
wg.hasErrors = true |
|
return |
|
} |
|
} |
|
vprintf("=== GENERATE %s\n", pkg) |
|
args := []string{"generate"} |
|
if *verbose { |
|
args = append(args, "-v") |
|
} |
|
args = append(args, pkg) |
|
cmd := exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"), args...) |
|
w := &bytes.Buffer{} |
|
cmd.Stderr = w |
|
cmd.Stdout = w |
|
if err := cmd.Run(); err != nil { |
|
fmt.Printf("--- FAIL: %s:\n\t%v\n\tError: %v\n", pkg, indent(w), err) |
|
hasErrors = true |
|
wg.hasErrors = true |
|
return |
|
} |
|
|
|
vprintf("=== TEST %s\n", pkg) |
|
args[0] = "test" |
|
cmd = exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"), args...) |
|
wt := &bytes.Buffer{} |
|
cmd.Stderr = wt |
|
cmd.Stdout = wt |
|
if err := cmd.Run(); err != nil { |
|
fmt.Printf("--- FAIL: %s:\n\t%v\n\tError: %v\n", pkg, indent(wt), err) |
|
hasErrors = true |
|
wg.hasErrors = true |
|
return |
|
} |
|
vprintf("--- SUCCESS: %s\n\t%v\n", pkg, indent(w)) |
|
fmt.Print(wt.String()) |
|
}() |
|
return &wg |
|
} |
|
|
|
// copyExported copies a package in x/text/internal/export to the |
|
// destination repository. |
|
func copyExported(p string) { |
|
copyPackage( |
|
filepath.Join("internal", "export", path.Base(p)), |
|
filepath.Join("..", filepath.FromSlash(p[len("golang.org/x"):])), |
|
"golang.org/x/text/internal/export/"+path.Base(p), |
|
p) |
|
} |
|
|
|
// copyVendored copies packages used by Go core into the vendored directory. |
|
func copyVendored() { |
|
root := filepath.Join(build.Default.GOROOT, filepath.FromSlash("src/vendor/golang_org/x")) |
|
|
|
err := filepath.Walk(root, func(dir string, info os.FileInfo, err error) error { |
|
if err != nil || !info.IsDir() || root == dir { |
|
return err |
|
} |
|
src := dir[len(root)+1:] |
|
const slash = string(filepath.Separator) |
|
if c := strings.Split(src, slash); c[0] == "text" { |
|
// Copy a text repo package from its normal location. |
|
src = strings.Join(c[1:], slash) |
|
} else { |
|
// Copy the vendored package if it exists in the export directory. |
|
src = filepath.Join("internal", "export", filepath.Base(src)) |
|
} |
|
copyPackage(src, dir, "golang.org", "golang_org") |
|
return nil |
|
}) |
|
if err != nil { |
|
fmt.Printf("Seeding directory %s has failed %v:", root, err) |
|
os.Exit(1) |
|
} |
|
} |
|
|
|
// goGenRE is used to remove go:generate lines. |
|
var goGenRE = regexp.MustCompile("//go:generate[^\n]*\n") |
|
|
|
// copyPackage copies relevant files from a directory in x/text to the |
|
// destination package directory. The destination package is assumed to have |
|
// the same name. For each copied file go:generate lines are removed and |
|
// and package comments are rewritten to the new path. |
|
func copyPackage(dirSrc, dirDst, search, replace string) { |
|
err := filepath.Walk(dirSrc, func(file string, info os.FileInfo, err error) error { |
|
base := filepath.Base(file) |
|
if err != nil || info.IsDir() || |
|
!strings.HasSuffix(base, ".go") || |
|
strings.HasSuffix(base, "_test.go") || |
|
// Don't process subdirectories. |
|
filepath.Dir(file) != dirSrc { |
|
return nil |
|
} |
|
b, err := ioutil.ReadFile(file) |
|
if err != nil || bytes.Contains(b, []byte("\n// +build ignore")) { |
|
return err |
|
} |
|
// Fix paths. |
|
b = bytes.Replace(b, []byte(search), []byte(replace), -1) |
|
// Remove go:generate lines. |
|
b = goGenRE.ReplaceAllLiteral(b, nil) |
|
comment := "// Code generated by running \"go generate\" in golang.org/x/text. DO NOT EDIT.\n\n" |
|
if *doCore { |
|
comment = "// Code generated by running \"go run gen.go -core\" in golang.org/x/text. DO NOT EDIT.\n\n" |
|
} |
|
if !bytes.HasPrefix(b, []byte(comment)) { |
|
b = append([]byte(comment), b...) |
|
} |
|
if b, err = format.Source(b); err != nil { |
|
fmt.Println("Failed to format file:", err) |
|
os.Exit(1) |
|
} |
|
file = filepath.Join(dirDst, base) |
|
vprintf("=== COPY %s\n", file) |
|
return ioutil.WriteFile(file, b, 0666) |
|
}) |
|
if err != nil { |
|
fmt.Println("Copying exported files failed:", err) |
|
os.Exit(1) |
|
} |
|
} |
|
|
|
func contains(a []string, s string) bool { |
|
for _, e := range a { |
|
if s == e { |
|
return true |
|
} |
|
} |
|
return false |
|
} |
|
|
|
func indent(b *bytes.Buffer) string { |
|
return strings.Replace(strings.TrimSpace(b.String()), "\n", "\n\t", -1) |
|
}
|
|
|