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.
5 // Bundle creates a single-source-file version of a source package
6 // suitable for inclusion in a particular target package.
10 // bundle [-o file] [-dst path] [-pkg name] [-prefix p] [-import old=new] [-tags build_constraints] <src>
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.
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.''
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 -pkg option is given, bundle uses that name.
37 // Otherwise, the name of the destination package is used.
38 // Build constraints for the generated file can be specified using the -tags option.
40 // To avoid collisions, bundle inserts a prefix at the beginning of
41 // every package-level const, func, type, and var identifier in src's code,
42 // updating references accordingly. The default prefix is the package name
43 // of the source package followed by an underscore. The -prefix option
44 // specifies an alternate prefix.
46 // Occasionally it is necessary to rewrite imports during the bundling
47 // process. The -import option, which may be repeated, specifies that
48 // an import of "old" should be rewritten to import "new" instead.
52 // Bundle archive/zip for inclusion in cmd/dist:
54 // cd $GOROOT/src/cmd/dist
55 // bundle -o zip.go archive/zip
57 // Bundle golang.org/x/net/http2 for inclusion in net/http,
58 // prefixing all identifiers by "http2" instead of "http2_", and
59 // including a "!nethttpomithttp2" build constraint:
61 // cd $GOROOT/src/net/http
62 // bundle -o h2_bundle.go -prefix http2 -tags '!nethttpomithttp2' golang.org/x/net/http2
64 // Update the http2 bundle in net/http:
66 // go generate net/http
68 // Update all bundles in the standard library:
70 // go generate -run bundle std
89 "golang.org/x/tools/go/packages"
93 outputFile = flag.String("o", "", "write output to `file` (default standard output)")
94 dstPath = flag.String("dst", ".", "set destination import `path`")
95 pkgName = flag.String("pkg", "", "set destination package `name`")
96 prefix = flag.String("prefix", "&_", "set bundled identifier prefix to `p` (default is \"&_\", where & stands for the original name)")
97 buildTags = flag.String("tags", "", "the build constraints to be inserted into the generated file")
99 importMap = map[string]string{}
103 flag.Var(flagFunc(addImportMap), "import", "rewrite import using `map`, of form old=new (can be repeated)")
106 func addImportMap(s string) {
107 if strings.Count(s, "=") != 1 {
108 log.Fatal("-import argument must be of the form old=new")
110 i := strings.Index(s, "=")
111 old, new := s[:i], s[i+1:]
112 if old == "" || new == "" {
113 log.Fatal("-import argument must be of the form old=new; old and new must be non-empty")
119 fmt.Fprintf(os.Stderr, "Usage: bundle [options] <src>\n")
124 log.SetPrefix("bundle: ")
135 cfg := &packages.Config{Mode: packages.NeedName}
136 pkgs, err := packages.Load(cfg, *dstPath)
138 log.Fatalf("cannot load destination package: %v", err)
140 if packages.PrintErrors(pkgs) > 0 || len(pkgs) != 1 {
141 log.Fatalf("failed to load destination package")
144 *pkgName = pkgs[0].Name
147 code, err := bundle(args[0], pkgs[0].PkgPath, *pkgName, *prefix, *buildTags)
151 if *outputFile != "" {
152 err := ioutil.WriteFile(*outputFile, code, 0666)
157 _, err := os.Stdout.Write(code)
164 // isStandardImportPath is copied from cmd/go in the standard library.
165 func isStandardImportPath(path string) bool {
166 i := strings.Index(path, "/")
171 return !strings.Contains(elem, ".")
174 var testingOnlyPackagesConfig *packages.Config
176 func bundle(src, dst, dstpkg, prefix, buildTags string) ([]byte, error) {
177 // Load the initial package.
178 cfg := &packages.Config{}
179 if testingOnlyPackagesConfig != nil {
180 *cfg = *testingOnlyPackagesConfig
182 // Bypass default vendor mode, as we need a package not available in the
183 // std module vendor folder.
184 cfg.Env = append(os.Environ(), "GOFLAGS=-mod=mod")
186 cfg.Mode = packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo
187 pkgs, err := packages.Load(cfg, src)
191 if packages.PrintErrors(pkgs) > 0 || len(pkgs) != 1 {
192 return nil, fmt.Errorf("failed to load source package")
196 if strings.Contains(prefix, "&") {
197 prefix = strings.Replace(prefix, "&", pkg.Syntax[0].Name.Name, -1)
200 objsToUpdate := make(map[types.Object]bool)
201 var rename func(from types.Object)
202 rename = func(from types.Object) {
203 if !objsToUpdate[from] {
204 objsToUpdate[from] = true
206 // Renaming a type that is used as an embedded field
207 // requires renaming the field too. e.g.
208 // type T int // if we rename this to U..
210 // print(s.T) // ...this must change too
211 if _, ok := from.(*types.TypeName); ok {
212 for id, obj := range pkg.TypesInfo.Uses {
214 if field := pkg.TypesInfo.Defs[id]; field != nil {
223 // Rename each package-level object.
224 scope := pkg.Types.Scope()
225 for _, name := range scope.Names() {
226 rename(scope.Lookup(name))
231 fmt.Fprintf(&out, "//go:build %s\n", buildTags)
232 fmt.Fprintf(&out, "// +build %s\n\n", buildTags)
235 fmt.Fprintf(&out, "// Code generated by golang.org/x/tools/cmd/bundle. DO NOT EDIT.\n")
236 if *outputFile != "" && buildTags == "" {
237 fmt.Fprintf(&out, "//go:generate bundle %s\n", strings.Join(os.Args[1:], " "))
239 fmt.Fprintf(&out, "// $ bundle %s\n", strings.Join(os.Args[1:], " "))
241 fmt.Fprintf(&out, "\n")
243 // Concatenate package comments from all files...
244 for _, f := range pkg.Syntax {
245 if doc := f.Doc.Text(); strings.TrimSpace(doc) != "" {
246 for _, line := range strings.Split(doc, "\n") {
247 fmt.Fprintf(&out, "// %s\n", line)
251 // ...but don't let them become the actual package comment.
254 fmt.Fprintf(&out, "package %s\n\n", dstpkg)
256 // BUG(adonovan,shurcooL): bundle may generate incorrect code
257 // due to shadowing between identifiers and imported package names.
259 // The generated code will either fail to compile or
260 // (unlikely) compile successfully but have different behavior
261 // than the original package. The risk of this happening is higher
262 // when the original package has renamed imports (they're typically
263 // renamed in order to resolve a shadow inside that particular .go file).
265 // TODO(adonovan,shurcooL):
266 // - detect shadowing issues, and either return error or resolve them
267 // - preserve comments from the original import declarations.
269 // pkgStd and pkgExt are sets of printed import specs. This is done
270 // to deduplicate instances of the same import name and path.
271 var pkgStd = make(map[string]bool)
272 var pkgExt = make(map[string]bool)
273 for _, f := range pkg.Syntax {
274 for _, imp := range f.Imports {
275 path, err := strconv.Unquote(imp.Path.Value)
277 log.Fatalf("invalid import path string: %v", err) // Shouldn't happen here since packages.Load succeeded.
282 if newPath, ok := importMap[path]; ok {
290 spec := fmt.Sprintf("%s %q", name, path)
291 if isStandardImportPath(path) {
299 // Print a single declaration that imports all necessary packages.
300 fmt.Fprintln(&out, "import (")
301 for p := range pkgStd {
302 fmt.Fprintf(&out, "\t%s\n", p)
307 for p := range pkgExt {
308 fmt.Fprintf(&out, "\t%s\n", p)
310 fmt.Fprint(&out, ")\n\n")
312 // Modify and print each file.
313 for _, f := range pkg.Syntax {
314 // Update renamed identifiers.
315 for id, obj := range pkg.TypesInfo.Defs {
316 if objsToUpdate[obj] {
317 id.Name = prefix + obj.Name()
320 for id, obj := range pkg.TypesInfo.Uses {
321 if objsToUpdate[obj] {
322 id.Name = prefix + obj.Name()
326 // For each qualified identifier that refers to the
327 // destination package, remove the qualifier.
328 // The "@@@." strings are removed in postprocessing.
329 ast.Inspect(f, func(n ast.Node) bool {
330 if sel, ok := n.(*ast.SelectorExpr); ok {
331 if id, ok := sel.X.(*ast.Ident); ok {
332 if obj, ok := pkg.TypesInfo.Uses[id].(*types.PkgName); ok {
333 if obj.Imported().Path() == dst {
343 if len(f.Imports) > 0 {
344 imp := f.Imports[len(f.Imports)-1]
346 if imp.Comment != nil {
347 if e := imp.Comment.End(); e > last {
353 // Pretty-print package-level declarations.
354 // but no package or import declarations.
356 for _, decl := range f.Decls {
357 if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT {
361 beg, end := sourceRange(decl)
363 printComments(&out, f.Comments, last, beg)
366 format.Node(&buf, pkg.Fset, &printer.CommentedNode{Node: decl, Comments: f.Comments})
367 // Remove each "@@@." in the output.
368 // TODO(adonovan): not hygienic.
369 out.Write(bytes.Replace(buf.Bytes(), []byte("@@@."), nil, -1))
371 last = printSameLineComment(&out, f.Comments, pkg.Fset, end)
373 out.WriteString("\n\n")
376 printLastComments(&out, f.Comments, last)
379 // Now format the entire thing.
380 result, err := format.Source(out.Bytes())
382 log.Fatalf("formatting failed: %v", err)
388 // sourceRange returns the [beg, end) interval of source code
389 // belonging to decl (incl. associated comments).
390 func sourceRange(decl ast.Decl) (beg, end token.Pos) {
394 var doc, com *ast.CommentGroup
396 switch d := decl.(type) {
399 if len(d.Specs) > 0 {
400 switch spec := d.Specs[len(d.Specs)-1].(type) {
414 if com != nil && com.End() > end {
421 func printComments(out *bytes.Buffer, comments []*ast.CommentGroup, pos, end token.Pos) {
422 for _, cg := range comments {
423 if pos <= cg.Pos() && cg.Pos() < end {
424 for _, c := range cg.List {
425 fmt.Fprintln(out, c.Text)
432 const infinity = 1 << 30
434 func printLastComments(out *bytes.Buffer, comments []*ast.CommentGroup, pos token.Pos) {
435 printComments(out, comments, pos, infinity)
438 func printSameLineComment(out *bytes.Buffer, comments []*ast.CommentGroup, fset *token.FileSet, pos token.Pos) token.Pos {
440 for _, cg := range comments {
441 if pos <= cg.Pos() && tf.Line(cg.Pos()) == tf.Line(pos) {
442 for _, c := range cg.List {
443 fmt.Fprintln(out, c.Text)
451 type flagFunc func(string)
453 func (f flagFunc) Set(s string) error {
458 func (f flagFunc) String() string { return "" }