GoPLS Viewer

Home|gopls/cmd/bundle/main.go
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] [-tags build_constraints] <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 -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.
39//
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.
45//
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.
49//
50// # Example
51//
52// Bundle archive/zip for inclusion in cmd/dist:
53//
54//    cd $GOROOT/src/cmd/dist
55//    bundle -o zip.go archive/zip
56//
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:
60//
61//    cd $GOROOT/src/net/http
62//    bundle -o h2_bundle.go -prefix http2 -tags '!nethttpomithttp2' golang.org/x/net/http2
63//
64// Update the http2 bundle in net/http:
65//
66//    go generate net/http
67//
68// Update all bundles in the standard library:
69//
70//    go generate -run bundle std
71package main
72
73import (
74    "bytes"
75    "flag"
76    "fmt"
77    "go/ast"
78    "go/format"
79    "go/printer"
80    "go/token"
81    "go/types"
82    "io/ioutil"
83    "log"
84    "os"
85    "strconv"
86    "strings"
87    "unicode"
88
89    "golang.org/x/tools/go/packages"
90)
91
92var (
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")
98
99    importMap = map[string]string{}
100)
101
102func init() {
103    flag.Var(flagFunc(addImportMap), "import""rewrite import using `map`, of form old=new (can be repeated)")
104}
105
106func addImportMap(s string) {
107    if strings.Count(s"=") != 1 {
108        log.Fatal("-import argument must be of the form old=new")
109    }
110    i := strings.Index(s"=")
111    oldnew := 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")
114    }
115    importMap[old] = new
116}
117
118func usage() {
119    fmt.Fprintf(os.Stderr"Usage: bundle [options] <src>\n")
120    flag.PrintDefaults()
121}
122
123func main() {
124    log.SetPrefix("bundle: ")
125    log.SetFlags(0)
126
127    flag.Usage = usage
128    flag.Parse()
129    args := flag.Args()
130    if len(args) != 1 {
131        usage()
132        os.Exit(2)
133    }
134
135    cfg := &packages.Config{Modepackages.NeedName}
136    pkgserr := packages.Load(cfg, *dstPath)
137    if err != nil {
138        log.Fatalf("cannot load destination package: %v"err)
139    }
140    if packages.PrintErrors(pkgs) > 0 || len(pkgs) != 1 {
141        log.Fatalf("failed to load destination package")
142    }
143    if *pkgName == "" {
144        *pkgName = pkgs[0].Name
145    }
146
147    codeerr := bundle(args[0], pkgs[0].PkgPath, *pkgName, *prefix, *buildTags)
148    if err != nil {
149        log.Fatal(err)
150    }
151    if *outputFile != "" {
152        err := ioutil.WriteFile(*outputFilecode0666)
153        if err != nil {
154            log.Fatal(err)
155        }
156    } else {
157        _err := os.Stdout.Write(code)
158        if err != nil {
159            log.Fatal(err)
160        }
161    }
162}
163
164// isStandardImportPath is copied from cmd/go in the standard library.
165func isStandardImportPath(path stringbool {
166    i := strings.Index(path"/")
167    if i < 0 {
168        i = len(path)
169    }
170    elem := path[:i]
171    return !strings.Contains(elem".")
172}
173
174var testingOnlyPackagesConfig *packages.Config
175
176func bundle(srcdstdstpkgprefixbuildTags string) ([]byteerror) {
177    // Load the initial package.
178    cfg := &packages.Config{}
179    if testingOnlyPackagesConfig != nil {
180        *cfg = *testingOnlyPackagesConfig
181    } else {
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")
185    }
186    cfg.Mode = packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo
187    pkgserr := packages.Load(cfgsrc)
188    if err != nil {
189        return nilerr
190    }
191    if packages.PrintErrors(pkgs) > 0 || len(pkgs) != 1 {
192        return nilfmt.Errorf("failed to load source package")
193    }
194    pkg := pkgs[0]
195
196    if strings.Contains(prefix"&") {
197        prefix = strings.Replace(prefix"&"pkg.Syntax[0].Name.Name, -1)
198    }
199
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
205
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..
209            //     var s struct {T}
210            //     print(s.T) // ...this must change too
211            if _ok := from.(*types.TypeName); ok {
212                for idobj := range pkg.TypesInfo.Uses {
213                    if obj == from {
214                        if field := pkg.TypesInfo.Defs[id]; field != nil {
215                            rename(field)
216                        }
217                    }
218                }
219            }
220        }
221    }
222
223    // Rename each package-level object.
224    scope := pkg.Types.Scope()
225    for _name := range scope.Names() {
226        rename(scope.Lookup(name))
227    }
228
229    var out bytes.Buffer
230    if buildTags != "" {
231        fmt.Fprintf(&out"//go:build %s\n"buildTags)
232        fmt.Fprintf(&out"// +build %s\n\n"buildTags)
233    }
234
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(quoteArgs(os.Args[1:]), " "))
238    } else {
239        fmt.Fprintf(&out"//   $ bundle %s\n"strings.Join(os.Args[1:], " "))
240    }
241    fmt.Fprintf(&out"\n")
242
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)
248            }
249        }
250    }
251    // ...but don't let them become the actual package comment.
252    fmt.Fprintln(&out)
253
254    fmt.Fprintf(&out"package %s\n\n"dstpkg)
255
256    // BUG(adonovan,shurcooL): bundle may generate incorrect code
257    // due to shadowing between identifiers and imported package names.
258    //
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).
264
265    // TODO(adonovan,shurcooL):
266    // - detect shadowing issues, and either return error or resolve them
267    // - preserve comments from the original import declarations.
268
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            patherr := strconv.Unquote(imp.Path.Value)
276            if err != nil {
277                log.Fatalf("invalid import path string: %v"err// Shouldn't happen here since packages.Load succeeded.
278            }
279            if path == dst {
280                continue
281            }
282            if newPathok := importMap[path]; ok {
283                path = newPath
284            }
285
286            var name string
287            if imp.Name != nil {
288                name = imp.Name.Name
289            }
290            spec := fmt.Sprintf("%s %q"namepath)
291            if isStandardImportPath(path) {
292                pkgStd[spec] = true
293            } else {
294                pkgExt[spec] = true
295            }
296        }
297    }
298
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)
303    }
304    if len(pkgExt) > 0 {
305        fmt.Fprintln(&out)
306    }
307    for p := range pkgExt {
308        fmt.Fprintf(&out"\t%s\n"p)
309    }
310    fmt.Fprint(&out")\n\n")
311
312    // Modify and print each file.
313    for _f := range pkg.Syntax {
314        // Update renamed identifiers.
315        for idobj := range pkg.TypesInfo.Defs {
316            if objsToUpdate[obj] {
317                id.Name = prefix + obj.Name()
318            }
319        }
320        for idobj := range pkg.TypesInfo.Uses {
321            if objsToUpdate[obj] {
322                id.Name = prefix + obj.Name()
323            }
324        }
325
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.Nodebool {
330            if selok := n.(*ast.SelectorExpr); ok {
331                if idok := sel.X.(*ast.Ident); ok {
332                    if objok := pkg.TypesInfo.Uses[id].(*types.PkgName); ok {
333                        if obj.Imported().Path() == dst {
334                            id.Name = "@@@"
335                        }
336                    }
337                }
338            }
339            return true
340        })
341
342        last := f.Package
343        if len(f.Imports) > 0 {
344            imp := f.Imports[len(f.Imports)-1]
345            last = imp.End()
346            if imp.Comment != nil {
347                if e := imp.Comment.End(); e > last {
348                    last = e
349                }
350            }
351        }
352
353        // Pretty-print package-level declarations.
354        // but no package or import declarations.
355        var buf bytes.Buffer
356        for _decl := range f.Decls {
357            if declok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT {
358                continue
359            }
360
361            begend := sourceRange(decl)
362
363            printComments(&outf.Commentslastbeg)
364
365            buf.Reset()
366            format.Node(&bufpkg.Fset, &printer.CommentedNode{NodedeclCommentsf.Comments})
367            // Remove each "@@@." in the output.
368            // TODO(adonovan): not hygienic.
369            out.Write(bytes.Replace(buf.Bytes(), []byte("@@@."), nil, -1))
370
371            last = printSameLineComment(&outf.Commentspkg.Fsetend)
372
373            out.WriteString("\n\n")
374        }
375
376        printLastComments(&outf.Commentslast)
377    }
378
379    // Now format the entire thing.
380    resulterr := format.Source(out.Bytes())
381    if err != nil {
382        log.Fatalf("formatting failed: %v"err)
383    }
384
385    return resultnil
386}
387
388// sourceRange returns the [beg, end) interval of source code
389// belonging to decl (incl. associated comments).
390func sourceRange(decl ast.Decl) (begend token.Pos) {
391    beg = decl.Pos()
392    end = decl.End()
393
394    var doccom *ast.CommentGroup
395
396    switch d := decl.(type) {
397    case *ast.GenDecl:
398        doc = d.Doc
399        if len(d.Specs) > 0 {
400            switch spec := d.Specs[len(d.Specs)-1].(type) {
401            case *ast.ValueSpec:
402                com = spec.Comment
403            case *ast.TypeSpec:
404                com = spec.Comment
405            }
406        }
407    case *ast.FuncDecl:
408        doc = d.Doc
409    }
410
411    if doc != nil {
412        beg = doc.Pos()
413    }
414    if com != nil && com.End() > end {
415        end = com.End()
416    }
417
418    return begend
419}
420
421func printComments(out *bytes.Buffercomments []*ast.CommentGroupposend token.Pos) {
422    for _cg := range comments {
423        if pos <= cg.Pos() && cg.Pos() < end {
424            for _c := range cg.List {
425                fmt.Fprintln(outc.Text)
426            }
427            fmt.Fprintln(out)
428        }
429    }
430}
431
432const infinity = 1 << 30
433
434func printLastComments(out *bytes.Buffercomments []*ast.CommentGrouppos token.Pos) {
435    printComments(outcommentsposinfinity)
436}
437
438func printSameLineComment(out *bytes.Buffercomments []*ast.CommentGroupfset *token.FileSetpos token.Postoken.Pos {
439    tf := fset.File(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(outc.Text)
444            }
445            return cg.End()
446        }
447    }
448    return pos
449}
450
451func quoteArgs(ss []string) []string {
452    // From go help generate:
453    //
454    // > The arguments to the directive are space-separated tokens or
455    // > double-quoted strings passed to the generator as individual
456    // > arguments when it is run.
457    //
458    // > Quoted strings use Go syntax and are evaluated before execution; a
459    // > quoted string appears as a single argument to the generator.
460    //
461    var qs []string
462    for _s := range ss {
463        if s == "" || containsSpace(s) {
464            s = strconv.Quote(s)
465        }
466        qs = append(qss)
467    }
468    return qs
469}
470
471func containsSpace(s stringbool {
472    for _r := range s {
473        if unicode.IsSpace(r) {
474            return true
475        }
476    }
477    return false
478}
479
480type flagFunc func(string)
481
482func (f flagFuncSet(s stringerror {
483    f(s)
484    return nil
485}
486
487func (f flagFuncString() string { return "" }
488
MembersX
bundle.RangeStmt_9487.p
bundle.RangeStmt_9715.BlockStmt.RangeStmt_9894.id
printSameLineComment.RangeStmt_12740.BlockStmt.BlockStmt.RangeStmt_12833.c
main.BlockStmt._
bundle.RangeStmt_8808.f
flagFunc.String.f
main
quoteArgs.ss
containsSpace
addImportMap.i
bundle.RangeStmt_7046.name
printLastComments
quoteArgs.qs
bytes
main.cfg
flagFunc.Set
format
main.code
main.BlockStmt.err
bundle.scope
bundle.RangeStmt_8808.BlockStmt.RangeStmt_8841.BlockStmt.path
containsSpace.s
init
bundle.RangeStmt_9715.BlockStmt.buf
packages
bundle.dstpkg
bundle.BlockStmt.BlockStmt.BlockStmt.RangeStmt_6803.id
printComments.out
bundle.BlockStmt.BlockStmt.BlockStmt.RangeStmt_6803.obj
bundle.RangeStmt_9715.f
main.err
bundle.cfg
bundle.RangeStmt_9715.BlockStmt.last
printSameLineComment
bundle.RangeStmt_9715.BlockStmt.RangeStmt_9781.id
sourceRange.beg
addImportMap
main.args
bundle.RangeStmt_8808.BlockStmt.RangeStmt_8841.BlockStmt.spec
bundle.RangeStmt_9715.BlockStmt.BlockStmt.BlockStmt.e
printSameLineComment.tf
bundle.RangeStmt_7654.BlockStmt.doc
bundle.RangeStmt_9715.BlockStmt.RangeStmt_9894.obj
printSameLineComment.fset
token
strings
printComments
flagFunc.String
testingOnlyPackagesConfig
bundle
containsSpace.RangeStmt_13499.r
usage
bundle.RangeStmt_8808.BlockStmt.RangeStmt_8841.imp
bundle.RangeStmt_9715.BlockStmt.RangeStmt_9781.obj
bundle.RangeStmt_9715.BlockStmt.RangeStmt_10784.BlockStmt.beg
flagFunc
fmt
bundle.RangeStmt_8808.BlockStmt.RangeStmt_8841.BlockStmt.name
types
log
bundle.src
printSameLineComment.comments
printSameLineComment.RangeStmt_12740.cg
isStandardImportPath
sourceRange.end
printComments.RangeStmt_12263.cg
printer
os
addImportMap.s
bundle.RangeStmt_7654.BlockStmt.BlockStmt.RangeStmt_7745.line
printComments.comments
flagFunc.Set.f
bundle.pkgs
bundle.result
bundle.objsToUpdate
printComments.RangeStmt_12263.BlockStmt.BlockStmt.RangeStmt_12337.c
isStandardImportPath.i
bundle.RangeStmt_7654.f
ast
strconv
isStandardImportPath.path
unicode
flagFunc.Set.s
sourceRange.doc
main.pkgs
bundle.dst
bundle.buildTags
bundle.err
printLastComments.comments
printLastComments.pos
printSameLineComment.pos
quoteArgs
bundle.RangeStmt_9594.p
bundle.RangeStmt_9715.BlockStmt.RangeStmt_10784.BlockStmt.end
printComments.pos
printComments.end
bundle.out
bundle.RangeStmt_9715.BlockStmt.RangeStmt_10784.decl
sourceRange.decl
sourceRange.com
flag
bundle.prefix
printLastComments.out
printSameLineComment.out
ioutil
sourceRange
bundle.RangeStmt_8808.BlockStmt.RangeStmt_8841.BlockStmt.err
quoteArgs.RangeStmt_13338.s
Members
X