...

Source file src/golang.org/x/text/gen.go

Documentation: golang.org/x/text

     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  }
   296  

View as plain text