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 }