github.com/jgarto/itcv@v0.0.0-20180826224514-4eea09c1aa0d/_vendor/src/golang.org/x/tools/cmd/bundle/main.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  // Bundle creates a single-source-file version of a source package
     6  // suitable for inclusion in a particular target package.
     7  //
     8  // Usage:
     9  //
    10  //	bundle [-o file] [-dst path] [-pkg name] [-prefix p] [-import old=new] <src>
    11  //
    12  // The src argument specifies the import path of the package to bundle.
    13  // The bundling of a directory of source files into a single source file
    14  // necessarily imposes a number of constraints.
    15  // The package being bundled must not use cgo; must not use conditional
    16  // file compilation, whether with build tags or system-specific file names
    17  // like code_amd64.go; must not depend on any special comments, which
    18  // may not be preserved; must not use any assembly sources;
    19  // must not use renaming imports; and must not use reflection-based APIs
    20  // that depend on the specific names of types or struct fields.
    21  //
    22  // By default, bundle writes the bundled code to standard output.
    23  // If the -o argument is given, bundle writes to the named file
    24  // and also includes a ``//go:generate'' comment giving the exact
    25  // command line used, for regenerating the file with ``go generate.''
    26  //
    27  // Bundle customizes its output for inclusion in a particular package, the destination package.
    28  // By default bundle assumes the destination is the package in the current directory,
    29  // but the destination package can be specified explicitly using the -dst option,
    30  // which takes an import path as its argument.
    31  // If the source package imports the destination package, bundle will remove
    32  // those imports and rewrite any references to use direct references to the
    33  // corresponding symbols.
    34  // Bundle also must write a package declaration in the output and must
    35  // choose a name to use in that declaration.
    36  // If the -package option is given, bundle uses that name.
    37  // Otherwise, if the -dst option is given, bundle uses the last
    38  // element of the destination import path.
    39  // Otherwise, by default bundle uses the package name found in the
    40  // package sources in the current directory.
    41  //
    42  // To avoid collisions, bundle inserts a prefix at the beginning of
    43  // every package-level const, func, type, and var identifier in src's code,
    44  // updating references accordingly. The default prefix is the package name
    45  // of the source package followed by an underscore. The -prefix option
    46  // specifies an alternate prefix.
    47  //
    48  // Occasionally it is necessary to rewrite imports during the bundling
    49  // process. The -import option, which may be repeated, specifies that
    50  // an import of "old" should be rewritten to import "new" instead.
    51  //
    52  // Example
    53  //
    54  // Bundle archive/zip for inclusion in cmd/dist:
    55  //
    56  //	cd $GOROOT/src/cmd/dist
    57  //	bundle -o zip.go archive/zip
    58  //
    59  // Bundle golang.org/x/net/http2 for inclusion in net/http,
    60  // prefixing all identifiers by "http2" instead of "http2_",
    61  // and rewriting the import "golang.org/x/net/http2/hpack"
    62  // to "internal/golang.org/x/net/http2/hpack":
    63  //
    64  //	cd $GOROOT/src/net/http
    65  //	bundle -o h2_bundle.go \
    66  //		-prefix http2 \
    67  //		-import golang.org/x/net/http2/hpack=internal/golang.org/x/net/http2/hpack \
    68  //		golang.org/x/net/http2
    69  //
    70  // Two ways to update the http2 bundle:
    71  //
    72  //	go generate net/http
    73  //
    74  //	cd $GOROOT/src/net/http
    75  //	go generate
    76  //
    77  // Update both bundles, restricting ``go generate'' to running bundle commands:
    78  //
    79  //	go generate -run bundle cmd/dist net/http
    80  //
    81  package main
    82  
    83  import (
    84  	"bytes"
    85  	"flag"
    86  	"fmt"
    87  	"go/ast"
    88  	"go/build"
    89  	"go/format"
    90  	"go/parser"
    91  	"go/printer"
    92  	"go/token"
    93  	"go/types"
    94  	"io/ioutil"
    95  	"log"
    96  	"os"
    97  	"path"
    98  	"strconv"
    99  	"strings"
   100  
   101  	"golang.org/x/tools/go/loader"
   102  )
   103  
   104  var (
   105  	outputFile = flag.String("o", "", "write output to `file` (default standard output)")
   106  	dstPath    = flag.String("dst", "", "set destination import `path` (default taken from current directory)")
   107  	pkgName    = flag.String("pkg", "", "set destination package `name` (default taken from current directory)")
   108  	prefix     = flag.String("prefix", "", "set bundled identifier prefix to `p` (default source package name + \"_\")")
   109  	underscore = flag.Bool("underscore", false, "rewrite golang.org to golang_org in imports; temporary workaround for golang.org/issue/16333")
   110  
   111  	importMap = map[string]string{}
   112  )
   113  
   114  func init() {
   115  	flag.Var(flagFunc(addImportMap), "import", "rewrite import using `map`, of form old=new (can be repeated)")
   116  }
   117  
   118  func addImportMap(s string) {
   119  	if strings.Count(s, "=") != 1 {
   120  		log.Fatal("-import argument must be of the form old=new")
   121  	}
   122  	i := strings.Index(s, "=")
   123  	old, new := s[:i], s[i+1:]
   124  	if old == "" || new == "" {
   125  		log.Fatal("-import argument must be of the form old=new; old and new must be non-empty")
   126  	}
   127  	importMap[old] = new
   128  }
   129  
   130  func usage() {
   131  	fmt.Fprintf(os.Stderr, "Usage: bundle [options] <src>\n")
   132  	flag.PrintDefaults()
   133  }
   134  
   135  func main() {
   136  	log.SetPrefix("bundle: ")
   137  	log.SetFlags(0)
   138  
   139  	flag.Usage = usage
   140  	flag.Parse()
   141  	args := flag.Args()
   142  	if len(args) != 1 {
   143  		usage()
   144  		os.Exit(2)
   145  	}
   146  
   147  	if *dstPath != "" {
   148  		if *pkgName == "" {
   149  			*pkgName = path.Base(*dstPath)
   150  		}
   151  	} else {
   152  		wd, _ := os.Getwd()
   153  		pkg, err := build.ImportDir(wd, 0)
   154  		if err != nil {
   155  			log.Fatalf("cannot find package in current directory: %v", err)
   156  		}
   157  		*dstPath = pkg.ImportPath
   158  		if *pkgName == "" {
   159  			*pkgName = pkg.Name
   160  		}
   161  	}
   162  
   163  	code, err := bundle(args[0], *dstPath, *pkgName, *prefix)
   164  	if err != nil {
   165  		log.Fatal(err)
   166  	}
   167  	if *outputFile != "" {
   168  		err := ioutil.WriteFile(*outputFile, code, 0666)
   169  		if err != nil {
   170  			log.Fatal(err)
   171  		}
   172  	} else {
   173  		_, err := os.Stdout.Write(code)
   174  		if err != nil {
   175  			log.Fatal(err)
   176  		}
   177  	}
   178  }
   179  
   180  // isStandardImportPath is copied from cmd/go in the standard library.
   181  func isStandardImportPath(path string) bool {
   182  	i := strings.Index(path, "/")
   183  	if i < 0 {
   184  		i = len(path)
   185  	}
   186  	elem := path[:i]
   187  	return !strings.Contains(elem, ".")
   188  }
   189  
   190  var ctxt = &build.Default
   191  
   192  func bundle(src, dst, dstpkg, prefix string) ([]byte, error) {
   193  	// Load the initial package.
   194  	conf := loader.Config{ParserMode: parser.ParseComments, Build: ctxt}
   195  	conf.TypeCheckFuncBodies = func(p string) bool { return p == src }
   196  	conf.Import(src)
   197  
   198  	lprog, err := conf.Load()
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  
   203  	// Because there was a single Import call and Load succeeded,
   204  	// InitialPackages is guaranteed to hold the sole requested package.
   205  	info := lprog.InitialPackages()[0]
   206  	if prefix == "" {
   207  		pkgName := info.Files[0].Name.Name
   208  		prefix = pkgName + "_"
   209  	}
   210  
   211  	objsToUpdate := make(map[types.Object]bool)
   212  	var rename func(from types.Object)
   213  	rename = func(from types.Object) {
   214  		if !objsToUpdate[from] {
   215  			objsToUpdate[from] = true
   216  
   217  			// Renaming a type that is used as an embedded field
   218  			// requires renaming the field too. e.g.
   219  			// 	type T int // if we rename this to U..
   220  			// 	var s struct {T}
   221  			// 	print(s.T) // ...this must change too
   222  			if _, ok := from.(*types.TypeName); ok {
   223  				for id, obj := range info.Uses {
   224  					if obj == from {
   225  						if field := info.Defs[id]; field != nil {
   226  							rename(field)
   227  						}
   228  					}
   229  				}
   230  			}
   231  		}
   232  	}
   233  
   234  	// Rename each package-level object.
   235  	scope := info.Pkg.Scope()
   236  	for _, name := range scope.Names() {
   237  		rename(scope.Lookup(name))
   238  	}
   239  
   240  	var out bytes.Buffer
   241  
   242  	fmt.Fprintf(&out, "// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT.\n")
   243  	if *outputFile != "" {
   244  		fmt.Fprintf(&out, "//go:generate bundle %s\n", strings.Join(os.Args[1:], " "))
   245  	} else {
   246  		fmt.Fprintf(&out, "//   $ bundle %s\n", strings.Join(os.Args[1:], " "))
   247  	}
   248  	fmt.Fprintf(&out, "\n")
   249  
   250  	// Concatenate package comments from all files...
   251  	for _, f := range info.Files {
   252  		if doc := f.Doc.Text(); strings.TrimSpace(doc) != "" {
   253  			for _, line := range strings.Split(doc, "\n") {
   254  				fmt.Fprintf(&out, "// %s\n", line)
   255  			}
   256  		}
   257  	}
   258  	// ...but don't let them become the actual package comment.
   259  	fmt.Fprintln(&out)
   260  
   261  	fmt.Fprintf(&out, "package %s\n\n", dstpkg)
   262  
   263  	// BUG(adonovan,shurcooL): bundle may generate incorrect code
   264  	// due to shadowing between identifiers and imported package names.
   265  	//
   266  	// The generated code will either fail to compile or
   267  	// (unlikely) compile successfully but have different behavior
   268  	// than the original package. The risk of this happening is higher
   269  	// when the original package has renamed imports (they're typically
   270  	// renamed in order to resolve a shadow inside that particular .go file).
   271  
   272  	// TODO(adonovan,shurcooL):
   273  	// - detect shadowing issues, and either return error or resolve them
   274  	// - preserve comments from the original import declarations.
   275  
   276  	// pkgStd and pkgExt are sets of printed import specs. This is done
   277  	// to deduplicate instances of the same import name and path.
   278  	var pkgStd = make(map[string]bool)
   279  	var pkgExt = make(map[string]bool)
   280  	for _, f := range info.Files {
   281  		for _, imp := range f.Imports {
   282  			path, err := strconv.Unquote(imp.Path.Value)
   283  			if err != nil {
   284  				log.Fatalf("invalid import path string: %v", err) // Shouldn't happen here since conf.Load succeeded.
   285  			}
   286  			if path == dst {
   287  				continue
   288  			}
   289  			if newPath, ok := importMap[path]; ok {
   290  				path = newPath
   291  			}
   292  
   293  			var name string
   294  			if imp.Name != nil {
   295  				name = imp.Name.Name
   296  			}
   297  			spec := fmt.Sprintf("%s %q", name, path)
   298  			if isStandardImportPath(path) {
   299  				pkgStd[spec] = true
   300  			} else {
   301  				if *underscore {
   302  					spec = strings.Replace(spec, "golang.org/", "golang_org/", 1)
   303  				}
   304  				pkgExt[spec] = true
   305  			}
   306  		}
   307  	}
   308  
   309  	// Print a single declaration that imports all necessary packages.
   310  	fmt.Fprintln(&out, "import (")
   311  	for p := range pkgStd {
   312  		fmt.Fprintf(&out, "\t%s\n", p)
   313  	}
   314  	if len(pkgExt) > 0 {
   315  		fmt.Fprintln(&out)
   316  	}
   317  	for p := range pkgExt {
   318  		fmt.Fprintf(&out, "\t%s\n", p)
   319  	}
   320  	fmt.Fprint(&out, ")\n\n")
   321  
   322  	// Modify and print each file.
   323  	for _, f := range info.Files {
   324  		// Update renamed identifiers.
   325  		for id, obj := range info.Defs {
   326  			if objsToUpdate[obj] {
   327  				id.Name = prefix + obj.Name()
   328  			}
   329  		}
   330  		for id, obj := range info.Uses {
   331  			if objsToUpdate[obj] {
   332  				id.Name = prefix + obj.Name()
   333  			}
   334  		}
   335  
   336  		// For each qualified identifier that refers to the
   337  		// destination package, remove the qualifier.
   338  		// The "@@@." strings are removed in postprocessing.
   339  		ast.Inspect(f, func(n ast.Node) bool {
   340  			if sel, ok := n.(*ast.SelectorExpr); ok {
   341  				if id, ok := sel.X.(*ast.Ident); ok {
   342  					if obj, ok := info.Uses[id].(*types.PkgName); ok {
   343  						if obj.Imported().Path() == dst {
   344  							id.Name = "@@@"
   345  						}
   346  					}
   347  				}
   348  			}
   349  			return true
   350  		})
   351  
   352  		last := f.Package
   353  		if len(f.Imports) > 0 {
   354  			imp := f.Imports[len(f.Imports)-1]
   355  			last = imp.End()
   356  			if imp.Comment != nil {
   357  				if e := imp.Comment.End(); e > last {
   358  					last = e
   359  				}
   360  			}
   361  		}
   362  
   363  		// Pretty-print package-level declarations.
   364  		// but no package or import declarations.
   365  		var buf bytes.Buffer
   366  		for _, decl := range f.Decls {
   367  			if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT {
   368  				continue
   369  			}
   370  
   371  			beg, end := sourceRange(decl)
   372  
   373  			printComments(&out, f.Comments, last, beg)
   374  
   375  			buf.Reset()
   376  			format.Node(&buf, lprog.Fset, &printer.CommentedNode{Node: decl, Comments: f.Comments})
   377  			// Remove each "@@@." in the output.
   378  			// TODO(adonovan): not hygienic.
   379  			out.Write(bytes.Replace(buf.Bytes(), []byte("@@@."), nil, -1))
   380  
   381  			last = printSameLineComment(&out, f.Comments, lprog.Fset, end)
   382  
   383  			out.WriteString("\n\n")
   384  		}
   385  
   386  		printLastComments(&out, f.Comments, last)
   387  	}
   388  
   389  	// Now format the entire thing.
   390  	result, err := format.Source(out.Bytes())
   391  	if err != nil {
   392  		log.Fatalf("formatting failed: %v", err)
   393  	}
   394  
   395  	return result, nil
   396  }
   397  
   398  // sourceRange returns the [beg, end) interval of source code
   399  // belonging to decl (incl. associated comments).
   400  func sourceRange(decl ast.Decl) (beg, end token.Pos) {
   401  	beg = decl.Pos()
   402  	end = decl.End()
   403  
   404  	var doc, com *ast.CommentGroup
   405  
   406  	switch d := decl.(type) {
   407  	case *ast.GenDecl:
   408  		doc = d.Doc
   409  		if len(d.Specs) > 0 {
   410  			switch spec := d.Specs[len(d.Specs)-1].(type) {
   411  			case *ast.ValueSpec:
   412  				com = spec.Comment
   413  			case *ast.TypeSpec:
   414  				com = spec.Comment
   415  			}
   416  		}
   417  	case *ast.FuncDecl:
   418  		doc = d.Doc
   419  	}
   420  
   421  	if doc != nil {
   422  		beg = doc.Pos()
   423  	}
   424  	if com != nil && com.End() > end {
   425  		end = com.End()
   426  	}
   427  
   428  	return beg, end
   429  }
   430  
   431  func printComments(out *bytes.Buffer, comments []*ast.CommentGroup, pos, end token.Pos) {
   432  	for _, cg := range comments {
   433  		if pos <= cg.Pos() && cg.Pos() < end {
   434  			for _, c := range cg.List {
   435  				fmt.Fprintln(out, c.Text)
   436  			}
   437  			fmt.Fprintln(out)
   438  		}
   439  	}
   440  }
   441  
   442  const infinity = 1 << 30
   443  
   444  func printLastComments(out *bytes.Buffer, comments []*ast.CommentGroup, pos token.Pos) {
   445  	printComments(out, comments, pos, infinity)
   446  }
   447  
   448  func printSameLineComment(out *bytes.Buffer, comments []*ast.CommentGroup, fset *token.FileSet, pos token.Pos) token.Pos {
   449  	tf := fset.File(pos)
   450  	for _, cg := range comments {
   451  		if pos <= cg.Pos() && tf.Line(cg.Pos()) == tf.Line(pos) {
   452  			for _, c := range cg.List {
   453  				fmt.Fprintln(out, c.Text)
   454  			}
   455  			return cg.End()
   456  		}
   457  	}
   458  	return pos
   459  }
   460  
   461  type flagFunc func(string)
   462  
   463  func (f flagFunc) Set(s string) error {
   464  	f(s)
   465  	return nil
   466  }
   467  
   468  func (f flagFunc) String() string { return "" }