+++ /dev/null
-// 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.
-
-// Bundle creates a single-source-file version of a source package
-// suitable for inclusion in a particular target package.
-//
-// Usage:
-//
-// bundle [-o file] [-dst path] [-pkg name] [-prefix p] [-import old=new] [-tags build_constraints] <src>
-//
-// The src argument specifies the import path of the package to bundle.
-// The bundling of a directory of source files into a single source file
-// necessarily imposes a number of constraints.
-// The package being bundled must not use cgo; must not use conditional
-// file compilation, whether with build tags or system-specific file names
-// like code_amd64.go; must not depend on any special comments, which
-// may not be preserved; must not use any assembly sources;
-// must not use renaming imports; and must not use reflection-based APIs
-// that depend on the specific names of types or struct fields.
-//
-// By default, bundle writes the bundled code to standard output.
-// If the -o argument is given, bundle writes to the named file
-// and also includes a ``//go:generate'' comment giving the exact
-// command line used, for regenerating the file with ``go generate.''
-//
-// Bundle customizes its output for inclusion in a particular package, the destination package.
-// By default bundle assumes the destination is the package in the current directory,
-// but the destination package can be specified explicitly using the -dst option,
-// which takes an import path as its argument.
-// If the source package imports the destination package, bundle will remove
-// those imports and rewrite any references to use direct references to the
-// corresponding symbols.
-// Bundle also must write a package declaration in the output and must
-// choose a name to use in that declaration.
-// If the -pkg option is given, bundle uses that name.
-// Otherwise, the name of the destination package is used.
-// Build constraints for the generated file can be specified using the -tags option.
-//
-// To avoid collisions, bundle inserts a prefix at the beginning of
-// every package-level const, func, type, and var identifier in src's code,
-// updating references accordingly. The default prefix is the package name
-// of the source package followed by an underscore. The -prefix option
-// specifies an alternate prefix.
-//
-// Occasionally it is necessary to rewrite imports during the bundling
-// process. The -import option, which may be repeated, specifies that
-// an import of "old" should be rewritten to import "new" instead.
-//
-// Example
-//
-// Bundle archive/zip for inclusion in cmd/dist:
-//
-// cd $GOROOT/src/cmd/dist
-// bundle -o zip.go archive/zip
-//
-// Bundle golang.org/x/net/http2 for inclusion in net/http,
-// prefixing all identifiers by "http2" instead of "http2_", and
-// including a "!nethttpomithttp2" build constraint:
-//
-// cd $GOROOT/src/net/http
-// bundle -o h2_bundle.go -prefix http2 -tags '!nethttpomithttp2' golang.org/x/net/http2
-//
-// Update the http2 bundle in net/http:
-//
-// go generate net/http
-//
-// Update all bundles in the standard library:
-//
-// go generate -run bundle std
-//
-package main
-
-import (
- "bytes"
- "flag"
- "fmt"
- "go/ast"
- "go/format"
- "go/printer"
- "go/token"
- "go/types"
- "io/ioutil"
- "log"
- "os"
- "strconv"
- "strings"
-
- "golang.org/x/tools/go/packages"
-)
-
-var (
- outputFile = flag.String("o", "", "write output to `file` (default standard output)")
- dstPath = flag.String("dst", ".", "set destination import `path`")
- pkgName = flag.String("pkg", "", "set destination package `name`")
- prefix = flag.String("prefix", "&_", "set bundled identifier prefix to `p` (default is \"&_\", where & stands for the original name)")
- buildTags = flag.String("tags", "", "the build constraints to be inserted into the generated file")
-
- importMap = map[string]string{}
-)
-
-func init() {
- flag.Var(flagFunc(addImportMap), "import", "rewrite import using `map`, of form old=new (can be repeated)")
-}
-
-func addImportMap(s string) {
- if strings.Count(s, "=") != 1 {
- log.Fatal("-import argument must be of the form old=new")
- }
- i := strings.Index(s, "=")
- old, new := s[:i], s[i+1:]
- if old == "" || new == "" {
- log.Fatal("-import argument must be of the form old=new; old and new must be non-empty")
- }
- importMap[old] = new
-}
-
-func usage() {
- fmt.Fprintf(os.Stderr, "Usage: bundle [options] <src>\n")
- flag.PrintDefaults()
-}
-
-func main() {
- log.SetPrefix("bundle: ")
- log.SetFlags(0)
-
- flag.Usage = usage
- flag.Parse()
- args := flag.Args()
- if len(args) != 1 {
- usage()
- os.Exit(2)
- }
-
- cfg := &packages.Config{Mode: packages.NeedName}
- pkgs, err := packages.Load(cfg, *dstPath)
- if err != nil {
- log.Fatalf("cannot load destination package: %v", err)
- }
- if packages.PrintErrors(pkgs) > 0 || len(pkgs) != 1 {
- log.Fatalf("failed to load destination package")
- }
- if *pkgName == "" {
- *pkgName = pkgs[0].Name
- }
-
- code, err := bundle(args[0], pkgs[0].PkgPath, *pkgName, *prefix, *buildTags)
- if err != nil {
- log.Fatal(err)
- }
- if *outputFile != "" {
- err := ioutil.WriteFile(*outputFile, code, 0666)
- if err != nil {
- log.Fatal(err)
- }
- } else {
- _, err := os.Stdout.Write(code)
- if err != nil {
- log.Fatal(err)
- }
- }
-}
-
-// isStandardImportPath is copied from cmd/go in the standard library.
-func isStandardImportPath(path string) bool {
- i := strings.Index(path, "/")
- if i < 0 {
- i = len(path)
- }
- elem := path[:i]
- return !strings.Contains(elem, ".")
-}
-
-var testingOnlyPackagesConfig *packages.Config
-
-func bundle(src, dst, dstpkg, prefix, buildTags string) ([]byte, error) {
- // Load the initial package.
- cfg := &packages.Config{}
- if testingOnlyPackagesConfig != nil {
- *cfg = *testingOnlyPackagesConfig
- } else {
- // Bypass default vendor mode, as we need a package not available in the
- // std module vendor folder.
- cfg.Env = append(os.Environ(), "GOFLAGS=-mod=mod")
- }
- cfg.Mode = packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo
- pkgs, err := packages.Load(cfg, src)
- if err != nil {
- return nil, err
- }
- if packages.PrintErrors(pkgs) > 0 || len(pkgs) != 1 {
- return nil, fmt.Errorf("failed to load source package")
- }
- pkg := pkgs[0]
-
- if strings.Contains(prefix, "&") {
- prefix = strings.Replace(prefix, "&", pkg.Syntax[0].Name.Name, -1)
- }
-
- objsToUpdate := make(map[types.Object]bool)
- var rename func(from types.Object)
- rename = func(from types.Object) {
- if !objsToUpdate[from] {
- objsToUpdate[from] = true
-
- // Renaming a type that is used as an embedded field
- // requires renaming the field too. e.g.
- // type T int // if we rename this to U..
- // var s struct {T}
- // print(s.T) // ...this must change too
- if _, ok := from.(*types.TypeName); ok {
- for id, obj := range pkg.TypesInfo.Uses {
- if obj == from {
- if field := pkg.TypesInfo.Defs[id]; field != nil {
- rename(field)
- }
- }
- }
- }
- }
- }
-
- // Rename each package-level object.
- scope := pkg.Types.Scope()
- for _, name := range scope.Names() {
- rename(scope.Lookup(name))
- }
-
- var out bytes.Buffer
- if buildTags != "" {
- fmt.Fprintf(&out, "// +build %s\n\n", buildTags)
- }
-
- fmt.Fprintf(&out, "// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT.\n")
- if *outputFile != "" && buildTags == "" {
- fmt.Fprintf(&out, "//go:generate bundle %s\n", strings.Join(os.Args[1:], " "))
- } else {
- fmt.Fprintf(&out, "// $ bundle %s\n", strings.Join(os.Args[1:], " "))
- }
- fmt.Fprintf(&out, "\n")
-
- // Concatenate package comments from all files...
- for _, f := range pkg.Syntax {
- if doc := f.Doc.Text(); strings.TrimSpace(doc) != "" {
- for _, line := range strings.Split(doc, "\n") {
- fmt.Fprintf(&out, "// %s\n", line)
- }
- }
- }
- // ...but don't let them become the actual package comment.
- fmt.Fprintln(&out)
-
- fmt.Fprintf(&out, "package %s\n\n", dstpkg)
-
- // BUG(adonovan,shurcooL): bundle may generate incorrect code
- // due to shadowing between identifiers and imported package names.
- //
- // The generated code will either fail to compile or
- // (unlikely) compile successfully but have different behavior
- // than the original package. The risk of this happening is higher
- // when the original package has renamed imports (they're typically
- // renamed in order to resolve a shadow inside that particular .go file).
-
- // TODO(adonovan,shurcooL):
- // - detect shadowing issues, and either return error or resolve them
- // - preserve comments from the original import declarations.
-
- // pkgStd and pkgExt are sets of printed import specs. This is done
- // to deduplicate instances of the same import name and path.
- var pkgStd = make(map[string]bool)
- var pkgExt = make(map[string]bool)
- for _, f := range pkg.Syntax {
- for _, imp := range f.Imports {
- path, err := strconv.Unquote(imp.Path.Value)
- if err != nil {
- log.Fatalf("invalid import path string: %v", err) // Shouldn't happen here since packages.Load succeeded.
- }
- if path == dst {
- continue
- }
- if newPath, ok := importMap[path]; ok {
- path = newPath
- }
-
- var name string
- if imp.Name != nil {
- name = imp.Name.Name
- }
- spec := fmt.Sprintf("%s %q", name, path)
- if isStandardImportPath(path) {
- pkgStd[spec] = true
- } else {
- pkgExt[spec] = true
- }
- }
- }
-
- // Print a single declaration that imports all necessary packages.
- fmt.Fprintln(&out, "import (")
- for p := range pkgStd {
- fmt.Fprintf(&out, "\t%s\n", p)
- }
- if len(pkgExt) > 0 {
- fmt.Fprintln(&out)
- }
- for p := range pkgExt {
- fmt.Fprintf(&out, "\t%s\n", p)
- }
- fmt.Fprint(&out, ")\n\n")
-
- // Modify and print each file.
- for _, f := range pkg.Syntax {
- // Update renamed identifiers.
- for id, obj := range pkg.TypesInfo.Defs {
- if objsToUpdate[obj] {
- id.Name = prefix + obj.Name()
- }
- }
- for id, obj := range pkg.TypesInfo.Uses {
- if objsToUpdate[obj] {
- id.Name = prefix + obj.Name()
- }
- }
-
- // For each qualified identifier that refers to the
- // destination package, remove the qualifier.
- // The "@@@." strings are removed in postprocessing.
- ast.Inspect(f, func(n ast.Node) bool {
- if sel, ok := n.(*ast.SelectorExpr); ok {
- if id, ok := sel.X.(*ast.Ident); ok {
- if obj, ok := pkg.TypesInfo.Uses[id].(*types.PkgName); ok {
- if obj.Imported().Path() == dst {
- id.Name = "@@@"
- }
- }
- }
- }
- return true
- })
-
- last := f.Package
- if len(f.Imports) > 0 {
- imp := f.Imports[len(f.Imports)-1]
- last = imp.End()
- if imp.Comment != nil {
- if e := imp.Comment.End(); e > last {
- last = e
- }
- }
- }
-
- // Pretty-print package-level declarations.
- // but no package or import declarations.
- var buf bytes.Buffer
- for _, decl := range f.Decls {
- if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT {
- continue
- }
-
- beg, end := sourceRange(decl)
-
- printComments(&out, f.Comments, last, beg)
-
- buf.Reset()
- format.Node(&buf, pkg.Fset, &printer.CommentedNode{Node: decl, Comments: f.Comments})
- // Remove each "@@@." in the output.
- // TODO(adonovan): not hygienic.
- out.Write(bytes.Replace(buf.Bytes(), []byte("@@@."), nil, -1))
-
- last = printSameLineComment(&out, f.Comments, pkg.Fset, end)
-
- out.WriteString("\n\n")
- }
-
- printLastComments(&out, f.Comments, last)
- }
-
- // Now format the entire thing.
- result, err := format.Source(out.Bytes())
- if err != nil {
- log.Fatalf("formatting failed: %v", err)
- }
-
- return result, nil
-}
-
-// sourceRange returns the [beg, end) interval of source code
-// belonging to decl (incl. associated comments).
-func sourceRange(decl ast.Decl) (beg, end token.Pos) {
- beg = decl.Pos()
- end = decl.End()
-
- var doc, com *ast.CommentGroup
-
- switch d := decl.(type) {
- case *ast.GenDecl:
- doc = d.Doc
- if len(d.Specs) > 0 {
- switch spec := d.Specs[len(d.Specs)-1].(type) {
- case *ast.ValueSpec:
- com = spec.Comment
- case *ast.TypeSpec:
- com = spec.Comment
- }
- }
- case *ast.FuncDecl:
- doc = d.Doc
- }
-
- if doc != nil {
- beg = doc.Pos()
- }
- if com != nil && com.End() > end {
- end = com.End()
- }
-
- return beg, end
-}
-
-func printComments(out *bytes.Buffer, comments []*ast.CommentGroup, pos, end token.Pos) {
- for _, cg := range comments {
- if pos <= cg.Pos() && cg.Pos() < end {
- for _, c := range cg.List {
- fmt.Fprintln(out, c.Text)
- }
- fmt.Fprintln(out)
- }
- }
-}
-
-const infinity = 1 << 30
-
-func printLastComments(out *bytes.Buffer, comments []*ast.CommentGroup, pos token.Pos) {
- printComments(out, comments, pos, infinity)
-}
-
-func printSameLineComment(out *bytes.Buffer, comments []*ast.CommentGroup, fset *token.FileSet, pos token.Pos) token.Pos {
- tf := fset.File(pos)
- for _, cg := range comments {
- if pos <= cg.Pos() && tf.Line(cg.Pos()) == tf.Line(pos) {
- for _, c := range cg.List {
- fmt.Fprintln(out, c.Text)
- }
- return cg.End()
- }
- }
- return pos
-}
-
-type flagFunc func(string)
-
-func (f flagFunc) Set(s string) error {
- f(s)
- return nil
-}
-
-func (f flagFunc) String() string { return "" }