github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/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  // +build go1.5
     6  
     7  // The bundle command concatenates the source files of a package,
     8  // renaming package-level names by adding a prefix and renaming
     9  // identifiers as needed to preserve referential integrity.
    10  //
    11  // Example:
    12  // 	$ bundle golang.org/x/net/http2 net/http http2
    13  //
    14  // The command above prints a single file containing the code of
    15  // golang.org/x/net/http2, suitable for inclusion in package net/http,
    16  // in which toplevel names have been prefixed with "http2".
    17  //
    18  // Assumptions:
    19  // - no file in the package imports "C", that is, uses cgo.
    20  // - no file in the package has GOOS or GOARCH build tags or file names.
    21  // - comments associated with the package or import declarations,
    22  //   may be discarded, as may comments associated with no top-level
    23  //   declaration at all.
    24  // - neither the original package nor the destination package contains
    25  //   any identifiers starting with the designated prefix.
    26  //   (This allows us to avoid various conflict checks.)
    27  // - there are no renaming imports.
    28  // - test files are ignored.
    29  // - none of the renamed identifiers is significant
    30  //   to reflection-based logic.
    31  //
    32  // Only package-level var, func, const, and type objects are renamed,
    33  // and embedded fields of renamed types.  No methods are renamed, so we
    34  // needn't worry about preserving interface satisfaction.
    35  //
    36  package main
    37  
    38  import (
    39  	"bytes"
    40  	"flag"
    41  	"fmt"
    42  	"go/ast"
    43  	"go/build"
    44  	"go/format"
    45  	"go/parser"
    46  	"go/token"
    47  	"go/types"
    48  	"io"
    49  	"log"
    50  	"os"
    51  	"path/filepath"
    52  	"strings"
    53  
    54  	"golang.org/x/tools/go/loader"
    55  )
    56  
    57  func main() {
    58  	log.SetPrefix("bundle: ")
    59  	log.SetFlags(0)
    60  
    61  	flag.Parse()
    62  	args := flag.Args()
    63  	if len(args) != 3 {
    64  		log.Fatal(`Usage: bundle package dest prefix
    65  
    66  Arguments:
    67   package is the import path of the package to concatenate.
    68   dest is the import path of the package in which the resulting file will reside.
    69   prefix is the string to attach to all renamed identifiers.
    70  `)
    71  	}
    72  	initialPkg, dest, prefix := args[0], args[1], args[2]
    73  
    74  	if err := bundle(os.Stdout, initialPkg, dest, prefix); err != nil {
    75  		log.Fatal(err)
    76  	}
    77  }
    78  
    79  var ctxt = &build.Default
    80  
    81  func bundle(w io.Writer, initialPkg, dest, prefix string) error {
    82  	// Load the initial package.
    83  	conf := loader.Config{ParserMode: parser.ParseComments, Build: ctxt}
    84  	conf.TypeCheckFuncBodies = func(p string) bool { return p == initialPkg }
    85  	conf.Import(initialPkg)
    86  
    87  	lprog, err := conf.Load()
    88  	if err != nil {
    89  		log.Fatal(err)
    90  	}
    91  
    92  	info := lprog.Package(initialPkg)
    93  
    94  	objsToUpdate := make(map[types.Object]bool)
    95  	var rename func(from types.Object)
    96  	rename = func(from types.Object) {
    97  		if !objsToUpdate[from] {
    98  			objsToUpdate[from] = true
    99  
   100  			// Renaming a type that is used as an embedded field
   101  			// requires renaming the field too. e.g.
   102  			// 	type T int // if we rename this to U..
   103  			// 	var s struct {T}
   104  			// 	print(s.T) // ...this must change too
   105  			if _, ok := from.(*types.TypeName); ok {
   106  				for id, obj := range info.Uses {
   107  					if obj == from {
   108  						if field := info.Defs[id]; field != nil {
   109  							rename(field)
   110  						}
   111  					}
   112  				}
   113  			}
   114  		}
   115  	}
   116  
   117  	// Rename each package-level object.
   118  	scope := info.Pkg.Scope()
   119  	for _, name := range scope.Names() {
   120  		rename(scope.Lookup(name))
   121  	}
   122  
   123  	var out bytes.Buffer
   124  
   125  	fmt.Fprintf(&out, "// Code generated by golang.org/x/tools/cmd/bundle command:\n")
   126  	fmt.Fprintf(&out, "//   $ bundle %s %s %s\n\n", initialPkg, dest, prefix)
   127  
   128  	// Concatenate package comments from all files...
   129  	for _, f := range info.Files {
   130  		if doc := f.Doc.Text(); strings.TrimSpace(doc) != "" {
   131  			for _, line := range strings.Split(doc, "\n") {
   132  				fmt.Fprintf(&out, "// %s\n", line)
   133  			}
   134  		}
   135  	}
   136  	// ...but don't let them become the actual package comment.
   137  	fmt.Fprintln(&out)
   138  
   139  	// TODO(adonovan): don't assume pkg.name == basename(pkg.path).
   140  	fmt.Fprintf(&out, "package %s\n\n", filepath.Base(dest))
   141  
   142  	// Print a single declaration that imports all necessary packages.
   143  	// TODO(adonovan):
   144  	// - support renaming imports.
   145  	// - preserve comments from the original import declarations.
   146  	for _, f := range info.Files {
   147  		for _, imp := range f.Imports {
   148  			if imp.Name != nil {
   149  				log.Fatalf("%s: renaming imports not supported",
   150  					lprog.Fset.Position(imp.Pos()))
   151  			}
   152  		}
   153  	}
   154  	fmt.Fprintln(&out, "import (")
   155  	for _, p := range info.Pkg.Imports() {
   156  		if p.Path() == dest {
   157  			continue
   158  		}
   159  		fmt.Fprintf(&out, "\t%q\n", p.Path())
   160  	}
   161  	fmt.Fprintln(&out, ")\n")
   162  
   163  	// Modify and print each file.
   164  	for _, f := range info.Files {
   165  		// Update renamed identifiers.
   166  		for id, obj := range info.Defs {
   167  			if objsToUpdate[obj] {
   168  				id.Name = prefix + obj.Name()
   169  			}
   170  		}
   171  		for id, obj := range info.Uses {
   172  			if objsToUpdate[obj] {
   173  				id.Name = prefix + obj.Name()
   174  			}
   175  		}
   176  
   177  		// For each qualified identifier that refers to the
   178  		// destination package, remove the qualifier.
   179  		// The "@@@." strings are removed in postprocessing.
   180  		ast.Inspect(f, func(n ast.Node) bool {
   181  			if sel, ok := n.(*ast.SelectorExpr); ok {
   182  				if id, ok := sel.X.(*ast.Ident); ok {
   183  					if obj, ok := info.Uses[id].(*types.PkgName); ok {
   184  						if obj.Imported().Path() == dest {
   185  							id.Name = "@@@"
   186  						}
   187  					}
   188  				}
   189  			}
   190  			return true
   191  		})
   192  
   193  		// Pretty-print package-level declarations.
   194  		// but no package or import declarations.
   195  		//
   196  		// TODO(adonovan): this may cause loss of comments
   197  		// preceding or associated with the package or import
   198  		// declarations or not associated with any declaration.
   199  		// Check.
   200  		var buf bytes.Buffer
   201  		for _, decl := range f.Decls {
   202  			if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT {
   203  				continue
   204  			}
   205  			buf.Reset()
   206  			format.Node(&buf, lprog.Fset, decl)
   207  			// Remove each "@@@." in the output.
   208  			// TODO(adonovan): not hygienic.
   209  			out.Write(bytes.Replace(buf.Bytes(), []byte("@@@."), nil, -1))
   210  			out.WriteString("\n\n")
   211  		}
   212  	}
   213  
   214  	// Now format the entire thing.
   215  	result, err := format.Source(out.Bytes())
   216  	if err != nil {
   217  		log.Fatalf("formatting failed: %v", err)
   218  	}
   219  
   220  	_, err = w.Write(result)
   221  	return err
   222  }