You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

468 lines
13 KiB

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