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// +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.
9package main
10
11import (
12	"bytes"
13	"flag"
14	"fmt"
15	"go/build"
16	"io/ioutil"
17	"os"
18	"os/exec"
19	"path/filepath"
20	"runtime"
21	"strings"
22	"sync"
23	"unicode"
24
25	"golang.org/x/text/internal/gen"
26)
27
28var (
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	excludeList = flag.String("exclude", "",
33		"comma-separated list of packages to exclude")
34
35	// The user can specify a selection of packages to build on the command line.
36	args []string
37)
38
39func exclude(pkg string) bool {
40	if len(args) > 0 {
41		return !contains(args, pkg)
42	}
43	return contains(strings.Split(*excludeList, ","), pkg)
44}
45
46// TODO:
47// - Better version handling.
48// - Generate tables for the core unicode package?
49// - Add generation for encodings. This requires some retooling here and there.
50// - Running repo-wide "long" tests.
51
52var vprintf = fmt.Printf
53
54func main() {
55	gen.Init()
56	args = flag.Args()
57	if !*verbose {
58		// Set vprintf to a no-op.
59		vprintf = func(string, ...interface{}) (int, error) { return 0, nil }
60	}
61
62	// TODO: create temporary cache directory to load files and create and set
63	// a "cache" option if the user did not specify the UNICODE_DIR environment
64	// variable. This will prevent duplicate downloads and also will enable long
65	// tests, which really need to be run after each generated package.
66
67	updateCore := *doCore
68	if gen.UnicodeVersion() != unicode.Version {
69		fmt.Printf("Requested Unicode version %s; core unicode version is %s.\n",
70			gen.UnicodeVersion(),
71			unicode.Version)
72		// TODO: use collate to compare. Simple comparison will work, though,
73		// until Unicode reaches version 10. To avoid circular dependencies, we
74		// could use the NumericWeighter without using package collate using a
75		// trivial Weighter implementation.
76		if gen.UnicodeVersion() < unicode.Version && !*force {
77			os.Exit(2)
78		}
79		updateCore = true
80	}
81
82	var unicode = &dependency{}
83	if updateCore {
84		fmt.Printf("Updating core to version %s...\n", gen.UnicodeVersion())
85		unicode = generate("unicode")
86
87		// Test some users of the unicode packages, especially the ones that
88		// keep a mirrored table. These may need to be corrected by hand.
89		generate("regexp", unicode)
90		generate("strconv", unicode) // mimics Unicode table
91		generate("strings", unicode)
92		generate("testing", unicode) // mimics Unicode table
93	}
94
95	var (
96		cldr       = generate("./unicode/cldr", unicode)
97		language   = generate("./language", cldr)
98		internal   = generate("./internal", unicode, language)
99		norm       = generate("./unicode/norm", unicode)
100		rangetable = generate("./unicode/rangetable", unicode)
101		cases      = generate("./cases", unicode, norm, language, rangetable)
102		width      = generate("./width", unicode)
103		bidi       = generate("./unicode/bidi", unicode, norm, rangetable)
104		_          = generate("./secure/precis", unicode, norm, rangetable, cases, width, bidi)
105		_          = generate("./encoding/htmlindex", unicode, language)
106		_          = generate("./currency", unicode, cldr, language, internal)
107		_          = generate("./internal/number", unicode, cldr, language, internal)
108		_          = generate("./language/display", unicode, cldr, language, internal)
109		_          = generate("./collate", unicode, norm, cldr, language, rangetable)
110		_          = generate("./search", unicode, norm, cldr, language, rangetable)
111	)
112
113	if updateCore {
114		copyVendored()
115		generate("vendor/golang_org/x/net/idna", unicode, norm, width, cases)
116	}
117	all.Wait()
118
119	if hasErrors {
120		fmt.Println("FAIL")
121		os.Exit(1)
122	}
123	vprintf("SUCCESS\n")
124}
125
126var (
127	all       sync.WaitGroup
128	hasErrors bool
129)
130
131type dependency struct {
132	sync.WaitGroup
133	hasErrors bool
134}
135
136func generate(pkg string, deps ...*dependency) *dependency {
137	var wg dependency
138	if exclude(pkg) {
139		return &wg
140	}
141	wg.Add(1)
142	all.Add(1)
143	go func() {
144		defer wg.Done()
145		defer all.Done()
146		// Wait for dependencies to finish.
147		for _, d := range deps {
148			d.Wait()
149			if d.hasErrors && !*force {
150				fmt.Printf("--- ABORT: %s\n", pkg)
151				wg.hasErrors = true
152				return
153			}
154		}
155		vprintf("=== GENERATE %s\n", pkg)
156		args := []string{"generate"}
157		if *verbose {
158			args = append(args, "-v")
159		}
160		args = append(args, pkg)
161		cmd := exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"), args...)
162		w := &bytes.Buffer{}
163		cmd.Stderr = w
164		cmd.Stdout = w
165		if err := cmd.Run(); err != nil {
166			fmt.Printf("--- FAIL: %s:\n\t%v\n\tError: %v\n", pkg, indent(w), err)
167			hasErrors = true
168			wg.hasErrors = true
169			return
170		}
171
172		vprintf("=== TEST %s\n", pkg)
173		args[0] = "test"
174		cmd = exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"), args...)
175		wt := &bytes.Buffer{}
176		cmd.Stderr = wt
177		cmd.Stdout = wt
178		if err := cmd.Run(); err != nil {
179			fmt.Printf("--- FAIL: %s:\n\t%v\n\tError: %v\n", pkg, indent(wt), err)
180			hasErrors = true
181			wg.hasErrors = true
182			return
183		}
184		vprintf("--- SUCCESS: %s\n\t%v\n", pkg, indent(w))
185		fmt.Print(wt.String())
186	}()
187	return &wg
188}
189
190func copyVendored() {
191	// Copy the vendored files. Some more may need to be copied in by hand.
192	dir := filepath.Join(build.Default.GOROOT, "src/vendor/golang_org/x/text")
193	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
194		if info.IsDir() {
195			return nil
196		}
197		b, err := ioutil.ReadFile(path[len(dir)+1:])
198		if err != nil {
199			return err
200		}
201		vprintf("=== COPY %s\n", path)
202		b = bytes.Replace(b, []byte("golang.org"), []byte("golang_org"), -1)
203		return ioutil.WriteFile(path, b, 0666)
204	})
205	if err != nil {
206		fmt.Println("Copying vendored files failed:", err)
207		os.Exit(1)
208	}
209}
210
211func contains(a []string, s string) bool {
212	for _, e := range a {
213		if s == e {
214			return true
215		}
216	}
217	return false
218}
219
220func indent(b *bytes.Buffer) string {
221	return strings.Replace(strings.TrimSpace(b.String()), "\n", "\n\t", -1)
222}
223