1 // Copyright (c) 2019, Daniel Martà <mvdan@mvdan.cc>
2 // See LICENSE for licensing information
22 pkgs, err := listPackages(context.TODO(), nil,
25 // These are internal cmd dependencies. Copy them.
28 "golang.org/x/tools/cmd/goimports",
30 // These are internal goimports dependencies. Copy them.
31 "golang.org/x/tools/internal/event",
32 "golang.org/x/tools/internal/event/core",
33 "golang.org/x/tools/internal/event/keys",
34 "golang.org/x/tools/internal/event/label",
35 "golang.org/x/tools/internal/fastwalk",
36 "golang.org/x/tools/internal/gocommand",
37 "golang.org/x/tools/internal/gopathwalk",
38 "golang.org/x/tools/internal/imports",
39 "golang.org/x/tools/internal/module",
40 "golang.org/x/tools/internal/semver",
41 "golang.org/x/tools/internal/telemetry/event",
46 for _, pkg := range pkgs {
47 switch pkg.ImportPath {
50 case "golang.org/x/tools/cmd/goimports":
53 parts := strings.Split(pkg.ImportPath, "/")
54 if parts[0] == "cmd" {
55 copyInternal(pkg, filepath.Join(parts[1:]...))
57 dir := filepath.Join(append([]string{"gofumports"}, parts[3:]...)...)
58 copyInternal(pkg, dir)
65 Path string // module path
66 Version string // module version
67 Versions []string // available module versions (with -versions)
68 Replace *Module // replaced by this module
69 Time *time.Time // time version was created
70 Update *Module // available update, if any (with -u)
71 Main bool // is this the main module?
72 Indirect bool // is this module only an indirect dependency of main module?
73 Dir string // directory holding files for this module, if any
74 GoMod string // path to go.mod file used when loading this module, if any
75 GoVersion string // go version used in module
76 Error *ModuleError // error loading module
79 type ModuleError struct {
80 Err string // the error itself
84 Dir string // directory containing package sources
85 ImportPath string // import path of package in dir
86 ImportComment string // path in import comment on package statement
87 Name string // package name
88 Doc string // package documentation string
89 Target string // install path
90 Shlib string // the shared library that contains this package (only set when -linkshared)
91 Goroot bool // is this package in the Go root?
92 Standard bool // is this package part of the standard Go library?
93 Stale bool // would 'go install' do anything for this package?
94 StaleReason string // explanation for Stale==true
95 Root string // Go root or Go path dir containing this package
96 ConflictDir string // this directory shadows Dir in $GOPATH
97 BinaryOnly bool // binary-only package (no longer supported)
98 ForTest string // package is only for use in named test
99 Export string // file containing export data (when using -export)
100 Module *Module // info about package's containing module, if any (can be nil)
101 Match []string // command-line patterns matching this package
102 DepOnly bool // package is only a dependency, not explicitly listed
105 GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, XTestGoFiles)
106 CgoFiles []string // .go source files that import "C"
107 CompiledGoFiles []string // .go files presented to compiler (when using -compiled)
108 IgnoredGoFiles []string // .go source files ignored due to build constraints
109 CFiles []string // .c source files
110 CXXFiles []string // .cc, .cxx and .cpp source files
111 MFiles []string // .m source files
112 HFiles []string // .h, .hh, .hpp and .hxx source files
113 FFiles []string // .f, .F, .for and .f90 Fortran source files
114 SFiles []string // .s source files
115 SwigFiles []string // .swig files
116 SwigCXXFiles []string // .swigcxx files
117 SysoFiles []string // .syso object files to add to archive
118 TestGoFiles []string // _test.go files in package
119 XTestGoFiles []string // _test.go files outside package
122 CgoCFLAGS []string // cgo: flags for C compiler
123 CgoCPPFLAGS []string // cgo: flags for C preprocessor
124 CgoCXXFLAGS []string // cgo: flags for C++ compiler
125 CgoFFLAGS []string // cgo: flags for Fortran compiler
126 CgoLDFLAGS []string // cgo: flags for linker
127 CgoPkgConfig []string // cgo: pkg-config names
129 // Dependency information
130 Imports []string // import paths used by this package
131 ImportMap map[string]string // map from source import to ImportPath (identity entries omitted)
132 Deps []string // all (recursively) imported dependencies
133 TestImports []string // imports from TestGoFiles
134 XTestImports []string // imports from XTestGoFiles
137 Incomplete bool // this package or a dependency has an error
138 Error *PackageError // error loading package
139 DepsErrors []*PackageError // errors loading dependencies
142 type PackageError struct {
143 ImportStack []string // shortest path from package named on command line to this one
144 Pos string // position of error (if present, file:line:col)
145 Err string // the error itself
148 func getEnv(env []string, name string) string {
149 for _, kv := range env {
150 if i := strings.IndexByte(kv, '='); i > 0 && name == kv[:i] {
157 // listPackages is a wrapper for 'go list -json -e', which can take arbitrary
158 // environment variables and arguments as input. The working directory can be
159 // fed by adding $PWD to env; otherwise, it will default to the current
162 // Since -e is used, the returned error will only be non-nil if a JSON result
163 // could not be obtained. Such examples are if the Go command is not installed,
164 // or if invalid flags are used as arguments.
166 // Errors encountered when loading packages will be returned for each package,
167 // in the form of PackageError. See 'go help list'.
168 func listPackages(ctx context.Context, env []string, args ...string) (pkgs []*Package, finalErr error) {
169 goArgs := append([]string{"list", "-json", "-e"}, args...)
170 cmd := exec.CommandContext(ctx, "go", goArgs...)
172 cmd.Dir = getEnv(env, "PWD")
174 stdout, err := cmd.StdoutPipe()
178 var stderrBuf bytes.Buffer
179 cmd.Stderr = &stderrBuf
181 if finalErr != nil && stderrBuf.Len() > 0 {
182 // TODO: wrap? but the format is backwards, given that
183 // stderr is likely multi-line
184 finalErr = fmt.Errorf("%v\n%s", finalErr, stderrBuf.Bytes())
188 if err := cmd.Start(); err != nil {
191 dec := json.NewDecoder(stdout)
194 if err := dec.Decode(&pkg); err != nil {
197 pkgs = append(pkgs, &pkg)
199 if err := cmd.Wait(); err != nil {
205 func readFile(path string) string {
206 body, err := ioutil.ReadFile(path)
213 func writeFile(path, body string) {
214 if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
217 if err := ioutil.WriteFile(path, []byte(body), 0o644); err != nil {
222 func sourceFiles(pkg *Package) (paths []string) {
223 var combined []string
224 for _, list := range [...][]string{
228 for _, name := range list {
229 if strings.HasSuffix(name, "_test.go") {
230 // IgnoredGoFiles can contain test files too.
233 combined = append(combined, filepath.Join(pkg.Dir, name))
239 const extraImport = `gformat "mvdan.cc/gofumpt/format"; `
241 const extraSrcLangVersion = `` +
242 `if *langVersion == "" {
243 out, err := exec.Command("go", "list", "-m", "-f", "{{.GoVersion}}").Output()
244 out = bytes.TrimSpace(out)
245 if err == nil && len(out) > 0 {
246 *langVersion = string(out)
250 func copyGofmt(pkg *Package) {
252 // This is the only gofumpt change on gofmt's codebase, besides changing
253 // the name in the usage text.
254 ` + extraSrcLangVersion + `
255 gformat.File(fileSet, file, gformat.Options{
256 LangVersion: *langVersion,
257 ExtraRules: *extraRules,
260 for _, path := range sourceFiles(pkg) {
261 body := readFile(path)
262 body = fixImports(body)
263 name := filepath.Base(path)
266 continue // we have our own
268 if i := strings.Index(body, "\t\"mvdan.cc/gofumpt"); i > 0 {
269 body = body[:i] + "\n" + extraImport + "\n" + body[i:]
271 if i := strings.Index(body, "res, err := format("); i > 0 {
272 body = body[:i] + "\n" + extraSrc + "\n" + body[i:]
275 body = strings.Replace(body, "gofmt", "gofumpt", -1)
276 writeFile(name, body)
280 func copyGoimports(pkg *Package) {
282 // This is the only gofumpt change on goimports's codebase, besides changing
283 // the name in the usage text.
284 ` + extraSrcLangVersion + `
285 res, err = gformat.Source(res, gformat.Options{LangVersion: *langVersion})
290 for _, path := range sourceFiles(pkg) {
291 body := readFile(path)
292 body = fixImports(body)
293 name := filepath.Base(path)
296 continue // we have our own
298 if i := strings.Index(body, "\t\"mvdan.cc/gofumpt"); i > 0 {
299 body = body[:i] + "\n" + extraImport + "\n" + body[i:]
301 if i := strings.Index(body, "if !bytes.Equal"); i > 0 {
302 body = body[:i] + "\n" + extraSrc + "\n" + body[i:]
305 body = strings.Replace(body, "goimports", "gofumports", -1)
307 writeFile(filepath.Join("gofumports", name), body)
311 func copyInternal(pkg *Package, dir string) {
312 for _, path := range sourceFiles(pkg) {
313 body := readFile(path)
314 body = fixImports(body)
315 name := filepath.Base(path)
316 writeFile(filepath.Join(dir, name), body)
320 func fixImports(body string) string {
321 body = strings.Replace(body,
322 "golang.org/x/tools/internal/",
323 "mvdan.cc/gofumpt/gofumports/internal/",
325 body = strings.Replace(body,
327 "mvdan.cc/gofumpt/internal/",