diff --git a/internal/wire/analyze.go b/internal/wire/analyze.go index 5b22628a..b716e14a 100644 --- a/internal/wire/analyze.go +++ b/internal/wire/analyze.go @@ -44,11 +44,11 @@ type call struct { // out is the type this step produces. out types.Type - // importPath and name identify the provider to call for kind == + // pkg and name identify the provider to call for kind == // funcProviderCall or the type to construct for kind == // structProvider. - importPath string - name string + pkg *types.Package + name string // args is a list of arguments to call the provider with. Each element is: // a) one of the givens (args[i] < len(given)), or @@ -199,7 +199,7 @@ dfs: } calls = append(calls, call{ kind: kind, - importPath: p.ImportPath, + pkg: p.Pkg, name: p.Name, args: args, fieldNames: p.Fields, @@ -419,7 +419,7 @@ func verifyAcyclic(providerMap *typeutil.Map, hasher typeutil.Hasher) []error { fmt.Fprintf(sb, "cycle for %s:\n", types.TypeString(a, nil)) for j := i; j < len(curr); j++ { p := providerMap.At(curr[j]).(*ProvidedType).Provider() - fmt.Fprintf(sb, "%s (%s.%s) ->\n", types.TypeString(curr[j], nil), p.ImportPath, p.Name) + fmt.Fprintf(sb, "%s (%s.%s) ->\n", types.TypeString(curr[j], nil), p.Pkg.Path(), p.Name) } fmt.Fprintf(sb, "%s\n", types.TypeString(a, nil)) ec.add(errors.New(sb.String())) diff --git a/internal/wire/parse.go b/internal/wire/parse.go index 3eeb01d8..58f5be12 100644 --- a/internal/wire/parse.go +++ b/internal/wire/parse.go @@ -19,14 +19,13 @@ import ( "errors" "fmt" "go/ast" - "go/build" "go/token" "go/types" "strconv" "strings" "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/go/loader" + "golang.org/x/tools/go/packages" "golang.org/x/tools/go/types/typeutil" ) @@ -135,8 +134,8 @@ type IfaceBinding struct { // Provider records the signature of a provider. A provider is a // single Go object, either a function or a named struct type. type Provider struct { - // ImportPath is the package path that the Go object resides in. - ImportPath string + // Pkg is the package that the Go object resides in. + Pkg *types.Package // Name is the name of the Go object. Name string @@ -203,22 +202,26 @@ type Value struct { // In case of duplicate environment variables, the last one in the list // takes precedence. func Load(ctx context.Context, wd string, env []string, patterns []string) (*Info, []error) { - prog, errs := load(ctx, wd, env, patterns) + pkgs, errs := load(ctx, wd, env, patterns) if len(errs) > 0 { return nil, errs } + if len(pkgs) == 0 { + return new(Info), nil + } + fset := pkgs[0].Fset info := &Info{ - Fset: prog.Fset, + Fset: fset, Sets: make(map[ProviderSetID]*ProviderSet), } - oc := newObjectCache(prog) + oc := newObjectCache(pkgs) ec := new(errorCollector) - for _, pkgInfo := range prog.InitialPackages() { - if isWireImport(pkgInfo.Pkg.Path()) { + for _, pkg := range pkgs { + if isWireImport(pkg.PkgPath) { // The marker function package confuses analysis. continue } - scope := pkgInfo.Pkg.Scope() + scope := pkg.Types.Scope() for _, name := range scope.Names() { obj := scope.Lookup(name) if !isProviderSetType(obj.Type()) { @@ -226,7 +229,7 @@ func Load(ctx context.Context, wd string, env []string, patterns []string) (*Inf } item, errs := oc.get(obj) if len(errs) > 0 { - ec.add(notePositionAll(prog.Fset.Position(obj.Pos()), errs)...) + ec.add(notePositionAll(fset.Position(obj.Pos()), errs)...) continue } pset := item.(*ProviderSet) @@ -235,47 +238,47 @@ func Load(ctx context.Context, wd string, env []string, patterns []string) (*Inf id := ProviderSetID{ImportPath: pset.PkgPath, VarName: name} info.Sets[id] = pset } - for _, f := range pkgInfo.Files { + for _, f := range pkg.Syntax { for _, decl := range f.Decls { fn, ok := decl.(*ast.FuncDecl) if !ok { continue } - buildCall, err := findInjectorBuild(&pkgInfo.Info, fn) + buildCall, err := findInjectorBuild(pkg.TypesInfo, fn) if err != nil { - ec.add(notePosition(prog.Fset.Position(fn.Pos()), fmt.Errorf("inject %s: %v", fn.Name.Name, err))) + ec.add(notePosition(fset.Position(fn.Pos()), fmt.Errorf("inject %s: %v", fn.Name.Name, err))) continue } if buildCall == nil { continue } - set, errs := oc.processNewSet(pkgInfo, buildCall, "") + set, errs := oc.processNewSet(pkg.TypesInfo, pkg.PkgPath, buildCall, "") if len(errs) > 0 { - ec.add(notePositionAll(prog.Fset.Position(fn.Pos()), errs)...) + ec.add(notePositionAll(fset.Position(fn.Pos()), errs)...) continue } - sig := pkgInfo.ObjectOf(fn.Name).Type().(*types.Signature) + sig := pkg.TypesInfo.ObjectOf(fn.Name).Type().(*types.Signature) ins, out, err := injectorFuncSignature(sig) if err != nil { if w, ok := err.(*wireErr); ok { ec.add(notePosition(w.position, fmt.Errorf("inject %s: %v", fn.Name.Name, w.error))) } else { - ec.add(notePosition(prog.Fset.Position(fn.Pos()), fmt.Errorf("inject %s: %v", fn.Name.Name, err))) + ec.add(notePosition(fset.Position(fn.Pos()), fmt.Errorf("inject %s: %v", fn.Name.Name, err))) } continue } - _, errs = solve(prog.Fset, out.out, ins, set) + _, errs = solve(fset, out.out, ins, set) if len(errs) > 0 { ec.add(mapErrors(errs, func(e error) error { if w, ok := e.(*wireErr); ok { return notePosition(w.position, fmt.Errorf("inject %s: %v", fn.Name.Name, w.error)) } - return notePosition(prog.Fset.Position(fn.Pos()), fmt.Errorf("inject %s: %v", fn.Name.Name, e)) + return notePosition(fset.Position(fn.Pos()), fmt.Errorf("inject %s: %v", fn.Name.Name, e)) })...) continue } info.Injectors = append(info.Injectors, &Injector{ - ImportPath: pkgInfo.Pkg.Path(), + ImportPath: pkg.PkgPath, FuncName: fn.Name.Name, }) } @@ -284,111 +287,43 @@ func Load(ctx context.Context, wd string, env []string, patterns []string) (*Inf return info, ec.errors } -// load typechecks the packages that match the given patterns, including -// function body type checking for the packages that directly match. The -// patterns are defined by the underlying build system. For the go tool, -// this is described at -// https://golang.org/cmd/go/#hdr-Package_lists_and_patterns +// load typechecks the packages that match the given patterns and +// includes source for all transitive dependencies. The patterns are +// defined by the underlying build system. For the go tool, this is +// described at https://golang.org/cmd/go/#hdr-Package_lists_and_patterns // // wd is the working directory and env is the set of environment // variables to use when loading the packages specified by patterns. If // env is nil or empty, it is interpreted as an empty set of variables. // In case of duplicate environment variables, the last one in the list // takes precedence. -func load(ctx context.Context, wd string, env []string, patterns []string) (*loader.Program, []error) { - bctx := buildContextFromEnv(env) - var foundPkgs []*build.Package - ec := new(errorCollector) - for _, name := range patterns { - p, err := bctx.Import(name, wd, build.FindOnly) - if err != nil { - ec.add(err) - continue - } - foundPkgs = append(foundPkgs, p) - } - if len(ec.errors) > 0 { - return nil, ec.errors - } - conf := &loader.Config{ - Build: bctx, - Cwd: wd, - TypeChecker: types.Config{ - Error: func(err error) { - ec.add(err) - }, - }, - TypeCheckFuncBodies: func(path string) bool { - return importPathInPkgList(foundPkgs, path) - }, - FindPackage: func(bctx *build.Context, importPath, fromDir string, mode build.ImportMode) (*build.Package, error) { - // Optimistically try to load in the package with normal build tags. - pkg, err := bctx.Import(importPath, fromDir, mode) - - // If this is the generated package, then load it in with the - // wireinject build tag to pick up the injector template. Since - // the *build.Context is shared between calls to FindPackage, this - // uses a copy. - if pkg != nil && importPathInPkgList(foundPkgs, pkg.ImportPath) { - bctx2 := new(build.Context) - *bctx2 = *bctx - n := len(bctx2.BuildTags) - bctx2.BuildTags = append(bctx2.BuildTags[:n:n], "wireinject") - pkg, err = bctx2.Import(importPath, fromDir, mode) - } - return pkg, err - }, - } - for _, name := range patterns { - conf.Import(name) - } - - prog, err := conf.Load() - if len(ec.errors) > 0 { - return nil, ec.errors - } +func load(ctx context.Context, wd string, env []string, patterns []string) ([]*packages.Package, []error) { + cfg := &packages.Config{ + Context: ctx, + Mode: packages.LoadAllSyntax, + Dir: wd, + Env: env, + BuildFlags: []string{"-tags=wireinject"}, + // TODO(light): Use ParseFile to skip function bodies and comments in indirect packages. + } + escaped := make([]string, len(patterns)) + for i := range patterns { + escaped[i] = "pattern=" + patterns[i] + } + pkgs, err := packages.Load(cfg, escaped...) if err != nil { return nil, []error{err} } - return prog, nil -} - -func buildContextFromEnv(env []string) *build.Context { - // TODO(#78): Remove this function in favor of using go/packages, - // which does not need a *build.Context. - - getenv := func(name string) string { - for i := len(env) - 1; i >= 0; i-- { - if strings.HasPrefix(env[i], name+"=") { - return env[i][len(name)+1:] - } - } - return "" - } - bctx := new(build.Context) - *bctx = build.Default - if v := getenv("GOARCH"); v != "" { - bctx.GOARCH = v - } - if v := getenv("GOOS"); v != "" { - bctx.GOOS = v - } - if v := getenv("GOROOT"); v != "" { - bctx.GOROOT = v - } - if v := getenv("GOPATH"); v != "" { - bctx.GOPATH = v - } - return bctx -} - -func importPathInPkgList(pkgs []*build.Package, path string) bool { + var errs []error for _, p := range pkgs { - if path == p.ImportPath { - return true + for _, e := range p.Errors { + errs = append(errs, e) } } - return false + if len(errs) > 0 { + return nil, errs + } + return pkgs, nil } // Info holds the result of Load. @@ -427,9 +362,10 @@ func (in *Injector) String() string { // objectCache is a lazily evaluated mapping of objects to Wire structures. type objectCache struct { - prog *loader.Program - objects map[objRef]objCacheEntry - hasher typeutil.Hasher + fset *token.FileSet + packages map[string]*packages.Package + objects map[objRef]objCacheEntry + hasher typeutil.Hasher } type objRef struct { @@ -442,12 +378,33 @@ type objCacheEntry struct { errs []error } -func newObjectCache(prog *loader.Program) *objectCache { - return &objectCache{ - prog: prog, - objects: make(map[objRef]objCacheEntry), - hasher: typeutil.MakeHasher(), +func newObjectCache(pkgs []*packages.Package) *objectCache { + if len(pkgs) == 0 { + panic("object cache must have packages to draw from") + } + oc := &objectCache{ + fset: pkgs[0].Fset, + packages: make(map[string]*packages.Package), + objects: make(map[objRef]objCacheEntry), + hasher: typeutil.MakeHasher(), + } + // Depth-first search of all dependencies to gather import path to + // packages.Package mapping. go/packages guarantees that for a single + // call to packages.Load and an import path X, there will exist only + // one *packages.Package value with PkgPath X. + stk := append([]*packages.Package(nil), pkgs...) + for len(stk) > 0 { + p := stk[len(stk)-1] + stk = stk[:len(stk)-1] + if oc.packages[p.PkgPath] != nil { + continue + } + oc.packages[p.PkgPath] = p + for _, imp := range p.Imports { + stk = append(stk, imp) + } } + return oc } // get converts a Go object into a Wire structure. It may return a @@ -478,9 +435,10 @@ func (oc *objectCache) get(obj types.Object) (val interface{}, errs []error) { break } } - return oc.processExpr(oc.prog.Package(obj.Pkg().Path()), spec.Values[i], obj.Name()) + pkgPath := obj.Pkg().Path() + return oc.processExpr(oc.packages[pkgPath].TypesInfo, pkgPath, spec.Values[i], obj.Name()) case *types.Func: - return processFuncProvider(oc.prog.Fset, obj) + return processFuncProvider(oc.fset, obj) default: return nil, []error{fmt.Errorf("%v is not a provider or a provider set", obj)} } @@ -490,10 +448,10 @@ func (oc *objectCache) get(obj types.Object) (val interface{}, errs []error) { func (oc *objectCache) varDecl(obj *types.Var) *ast.ValueSpec { // TODO(light): Walk files to build object -> declaration mapping, if more performant. // Recommended by https://golang.org/s/types-tutorial - pkg := oc.prog.Package(obj.Pkg().Path()) + pkg := oc.packages[obj.Pkg().Path()] pos := obj.Pos() - for _, f := range pkg.Files { - tokenFile := oc.prog.Fset.File(f.Pos()) + for _, f := range pkg.Syntax { + tokenFile := oc.fset.File(f.Pos()) if base := tokenFile.Base(); base <= int(pos) && int(pos) < base+tokenFile.Size() { path, _ := astutil.PathEnclosingInterval(f, pos, pos) for _, node := range path { @@ -508,38 +466,38 @@ func (oc *objectCache) varDecl(obj *types.Var) *ast.ValueSpec { // processExpr converts an expression into a Wire structure. It may // return a *Provider, an *IfaceBinding, a *ProviderSet, or a *Value. -func (oc *objectCache) processExpr(pkg *loader.PackageInfo, expr ast.Expr, varName string) (interface{}, []error) { - exprPos := oc.prog.Fset.Position(expr.Pos()) +func (oc *objectCache) processExpr(info *types.Info, pkgPath string, expr ast.Expr, varName string) (interface{}, []error) { + exprPos := oc.fset.Position(expr.Pos()) expr = astutil.Unparen(expr) - if obj := qualifiedIdentObject(&pkg.Info, expr); obj != nil { + if obj := qualifiedIdentObject(info, expr); obj != nil { item, errs := oc.get(obj) return item, mapErrors(errs, func(err error) error { return notePosition(exprPos, err) }) } if call, ok := expr.(*ast.CallExpr); ok { - fnObj := qualifiedIdentObject(&pkg.Info, call.Fun) + fnObj := qualifiedIdentObject(info, call.Fun) if fnObj == nil || !isWireImport(fnObj.Pkg().Path()) { return nil, []error{notePosition(exprPos, errors.New("unknown pattern"))} } switch fnObj.Name() { case "NewSet": - pset, errs := oc.processNewSet(pkg, call, varName) + pset, errs := oc.processNewSet(info, pkgPath, call, varName) return pset, notePositionAll(exprPos, errs) case "Bind": - b, err := processBind(oc.prog.Fset, &pkg.Info, call) + b, err := processBind(oc.fset, info, call) if err != nil { return nil, []error{notePosition(exprPos, err)} } return b, nil case "Value": - v, err := processValue(oc.prog.Fset, &pkg.Info, call) + v, err := processValue(oc.fset, info, call) if err != nil { return nil, []error{notePosition(exprPos, err)} } return v, nil case "InterfaceValue": - v, err := processInterfaceValue(oc.prog.Fset, &pkg.Info, call) + v, err := processInterfaceValue(oc.fset, info, call) if err != nil { return nil, []error{notePosition(exprPos, err)} } @@ -548,8 +506,8 @@ func (oc *objectCache) processExpr(pkg *loader.PackageInfo, expr ast.Expr, varNa return nil, []error{notePosition(exprPos, errors.New("unknown pattern"))} } } - if tn := structArgType(&pkg.Info, expr); tn != nil { - p, errs := processStructProvider(oc.prog.Fset, tn) + if tn := structArgType(info, expr); tn != nil { + p, errs := processStructProvider(oc.fset, tn) if len(errs) > 0 { return nil, notePositionAll(exprPos, errs) } @@ -558,17 +516,17 @@ func (oc *objectCache) processExpr(pkg *loader.PackageInfo, expr ast.Expr, varNa return nil, []error{notePosition(exprPos, errors.New("unknown pattern"))} } -func (oc *objectCache) processNewSet(pkg *loader.PackageInfo, call *ast.CallExpr, varName string) (*ProviderSet, []error) { +func (oc *objectCache) processNewSet(info *types.Info, pkgPath string, call *ast.CallExpr, varName string) (*ProviderSet, []error) { // Assumes that call.Fun is wire.NewSet or wire.Build. pset := &ProviderSet{ Pos: call.Pos(), - PkgPath: pkg.Pkg.Path(), + PkgPath: pkgPath, VarName: varName, } ec := new(errorCollector) for _, arg := range call.Args { - item, errs := oc.processExpr(pkg, arg, "") + item, errs := oc.processExpr(info, pkgPath, arg, "") if len(errs) > 0 { ec.add(errs...) continue @@ -590,7 +548,7 @@ func (oc *objectCache) processNewSet(pkg *loader.PackageInfo, call *ast.CallExpr return nil, ec.errors } var errs []error - pset.providerMap, pset.srcMap, errs = buildProviderMap(oc.prog.Fset, oc.hasher, pset) + pset.providerMap, pset.srcMap, errs = buildProviderMap(oc.fset, oc.hasher, pset) if len(errs) > 0 { return nil, errs } @@ -647,7 +605,7 @@ func processFuncProvider(fset *token.FileSet, fn *types.Func) (*Provider, []erro } params := sig.Params() provider := &Provider{ - ImportPath: fn.Pkg().Path(), + Pkg: fn.Pkg(), Name: fn.Name(), Pos: fn.Pos(), Args: make([]ProviderInput, params.Len()), @@ -733,13 +691,13 @@ func processStructProvider(fset *token.FileSet, typeName *types.TypeName) (*Prov pos := typeName.Pos() provider := &Provider{ - ImportPath: typeName.Pkg().Path(), - Name: typeName.Name(), - Pos: pos, - Args: make([]ProviderInput, st.NumFields()), - Fields: make([]string, st.NumFields()), - IsStruct: true, - Out: []types.Type{out, types.NewPointer(out)}, + Pkg: typeName.Pkg(), + Name: typeName.Name(), + Pos: pos, + Args: make([]ProviderInput, st.NumFields()), + Fields: make([]string, st.NumFields()), + IsStruct: true, + Out: []types.Type{out, types.NewPointer(out)}, } for i := 0; i < st.NumFields(); i++ { f := st.Field(i) diff --git a/internal/wire/testdata/BuildTagsMainOnly/bar/bar_inject.go b/internal/wire/testdata/BuildTagsAllPackages/bar/bar.go similarity index 89% rename from internal/wire/testdata/BuildTagsMainOnly/bar/bar_inject.go rename to internal/wire/testdata/BuildTagsAllPackages/bar/bar.go index 179a8bf8..00bee2e0 100644 --- a/internal/wire/testdata/BuildTagsMainOnly/bar/bar_inject.go +++ b/internal/wire/testdata/BuildTagsAllPackages/bar/bar.go @@ -12,8 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -//+build wireinject +//+build !wireinject +// Package bar includes both wireinject and non-wireinject variants. package bar import "github.com/google/go-cloud/wire" diff --git a/internal/wire/testdata/BuildTagsMainOnly/bar/bar.go b/internal/wire/testdata/BuildTagsAllPackages/bar/bar_inject.go similarity index 89% rename from internal/wire/testdata/BuildTagsMainOnly/bar/bar.go rename to internal/wire/testdata/BuildTagsAllPackages/bar/bar_inject.go index 1362eff9..944d1222 100644 --- a/internal/wire/testdata/BuildTagsMainOnly/bar/bar.go +++ b/internal/wire/testdata/BuildTagsAllPackages/bar/bar_inject.go @@ -12,9 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -//+build !wireinject +//+build wireinject -// Package bar includes both wireinject and non-wireinject variants. package bar import "github.com/google/go-cloud/wire" diff --git a/internal/wire/testdata/BuildTagsMainOnly/foo/foo.go b/internal/wire/testdata/BuildTagsAllPackages/foo/foo.go similarity index 100% rename from internal/wire/testdata/BuildTagsMainOnly/foo/foo.go rename to internal/wire/testdata/BuildTagsAllPackages/foo/foo.go diff --git a/internal/wire/testdata/BuildTagsMainOnly/foo/wire.go b/internal/wire/testdata/BuildTagsAllPackages/foo/wire.go similarity index 100% rename from internal/wire/testdata/BuildTagsMainOnly/foo/wire.go rename to internal/wire/testdata/BuildTagsAllPackages/foo/wire.go diff --git a/internal/wire/testdata/BuildTagsMainOnly/pkg b/internal/wire/testdata/BuildTagsAllPackages/pkg similarity index 100% rename from internal/wire/testdata/BuildTagsMainOnly/pkg rename to internal/wire/testdata/BuildTagsAllPackages/pkg diff --git a/internal/wire/testdata/BuildTagsMainOnly/want/program_out.txt b/internal/wire/testdata/BuildTagsAllPackages/want/program_out.txt similarity index 100% rename from internal/wire/testdata/BuildTagsMainOnly/want/program_out.txt rename to internal/wire/testdata/BuildTagsAllPackages/want/program_out.txt diff --git a/internal/wire/testdata/BuildTagsMainOnly/want/wire_gen.go b/internal/wire/testdata/BuildTagsAllPackages/want/wire_gen.go similarity index 100% rename from internal/wire/testdata/BuildTagsMainOnly/want/wire_gen.go rename to internal/wire/testdata/BuildTagsAllPackages/want/wire_gen.go diff --git a/internal/wire/testdata/BuildTagsRelativePkg/bar/bar.go b/internal/wire/testdata/BuildTagsRelativePkg/bar/bar.go deleted file mode 100644 index 1362eff9..00000000 --- a/internal/wire/testdata/BuildTagsRelativePkg/bar/bar.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2018 The Go Cloud Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//+build !wireinject - -// Package bar includes both wireinject and non-wireinject variants. -package bar - -import "github.com/google/go-cloud/wire" - -// Set provides a friendly user greeting. -var Set = wire.NewSet(wire.Value("Hello, World!")) diff --git a/internal/wire/testdata/BuildTagsRelativePkg/bar/bar_inject.go b/internal/wire/testdata/BuildTagsRelativePkg/bar/bar_inject.go deleted file mode 100644 index 179a8bf8..00000000 --- a/internal/wire/testdata/BuildTagsRelativePkg/bar/bar_inject.go +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2018 The Go Cloud Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//+build wireinject - -package bar - -import "github.com/google/go-cloud/wire" - -// Set provides an unfriendly user greeting. -var Set = wire.NewSet(wire.Value("Bah humbug! This is the wrong variant!")) diff --git a/internal/wire/testdata/BuildTagsRelativePkg/want/wire_gen.go b/internal/wire/testdata/BuildTagsRelativePkg/want/wire_gen.go deleted file mode 100644 index 2cb12acf..00000000 --- a/internal/wire/testdata/BuildTagsRelativePkg/want/wire_gen.go +++ /dev/null @@ -1,17 +0,0 @@ -// Code generated by Wire. DO NOT EDIT. - -//go:generate wire -//+build !wireinject - -package main - -// Injectors from wire.go: - -func injectedMessage() string { - string2 := _wireStringValue - return string2 -} - -var ( - _wireStringValue = "Hello, World!" -) diff --git a/internal/wire/testdata/CopyOtherDecls/want/wire_gen.go b/internal/wire/testdata/CopyOtherDecls/want/wire_gen.go index 70c13405..f76d5edb 100644 --- a/internal/wire/testdata/CopyOtherDecls/want/wire_gen.go +++ b/internal/wire/testdata/CopyOtherDecls/want/wire_gen.go @@ -22,6 +22,7 @@ func main() { fmt.Println(injectedMessage()) } +// provideMessage provides a friendly user greeting. func provideMessage() string { return "Hello, World!" } diff --git a/internal/wire/testdata/BuildTagsRelativePkg/foo/foo.go b/internal/wire/testdata/RelativePkg/foo/foo.go similarity index 85% rename from internal/wire/testdata/BuildTagsRelativePkg/foo/foo.go rename to internal/wire/testdata/RelativePkg/foo/foo.go index 4289d9f6..689a2ef5 100644 --- a/internal/wire/testdata/BuildTagsRelativePkg/foo/foo.go +++ b/internal/wire/testdata/RelativePkg/foo/foo.go @@ -19,3 +19,8 @@ import "fmt" func main() { fmt.Println(injectedMessage()) } + +// provideMessage provides a friendly user greeting. +func provideMessage() string { + return "Hello, World!" +} diff --git a/internal/wire/testdata/BuildTagsRelativePkg/foo/wire.go b/internal/wire/testdata/RelativePkg/foo/wire.go similarity index 94% rename from internal/wire/testdata/BuildTagsRelativePkg/foo/wire.go rename to internal/wire/testdata/RelativePkg/foo/wire.go index 4f5bec65..3661aed1 100644 --- a/internal/wire/testdata/BuildTagsRelativePkg/foo/wire.go +++ b/internal/wire/testdata/RelativePkg/foo/wire.go @@ -17,11 +17,10 @@ package main import ( - "example.com/bar" "github.com/google/go-cloud/wire" ) func injectedMessage() string { - wire.Build(bar.Set) + wire.Build(provideMessage) return "" } diff --git a/internal/wire/testdata/BuildTagsRelativePkg/pkg b/internal/wire/testdata/RelativePkg/pkg similarity index 100% rename from internal/wire/testdata/BuildTagsRelativePkg/pkg rename to internal/wire/testdata/RelativePkg/pkg diff --git a/internal/wire/testdata/BuildTagsRelativePkg/want/program_out.txt b/internal/wire/testdata/RelativePkg/want/program_out.txt similarity index 100% rename from internal/wire/testdata/BuildTagsRelativePkg/want/program_out.txt rename to internal/wire/testdata/RelativePkg/want/program_out.txt diff --git a/internal/wire/testdata/Vendor/want/wire_gen.go b/internal/wire/testdata/RelativePkg/want/wire_gen.go similarity index 73% rename from internal/wire/testdata/Vendor/want/wire_gen.go rename to internal/wire/testdata/RelativePkg/want/wire_gen.go index 3202b15a..a45f720f 100644 --- a/internal/wire/testdata/Vendor/want/wire_gen.go +++ b/internal/wire/testdata/RelativePkg/want/wire_gen.go @@ -5,13 +5,9 @@ package main -import ( - "example.com/bar" -) - // Injectors from wire.go: func injectedMessage() string { - string2 := bar.ProvideMessage() + string2 := provideMessage() return string2 } diff --git a/internal/wire/testdata/ReservedKeywords/want/wire_gen.go b/internal/wire/testdata/ReservedKeywords/want/wire_gen.go index 646e34e6..cd24e424 100644 --- a/internal/wire/testdata/ReservedKeywords/want/wire_gen.go +++ b/internal/wire/testdata/ReservedKeywords/want/wire_gen.go @@ -15,4 +15,7 @@ func injectInterface() Interface { // wire.go: +// Wire tries to disambiguate the variable "select" by prepending +// the package name; this package-scoped variable conflicts with that +// and forces a different name. var mainSelect = 0 diff --git a/internal/wire/testdata/Vendor/foo/foo.go b/internal/wire/testdata/Vendor/foo/foo.go deleted file mode 100644 index 4289d9f6..00000000 --- a/internal/wire/testdata/Vendor/foo/foo.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2018 The Go Cloud Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import "fmt" - -func main() { - fmt.Println(injectedMessage()) -} diff --git a/internal/wire/testdata/Vendor/foo/wire.go b/internal/wire/testdata/Vendor/foo/wire.go deleted file mode 100644 index b58e2f7d..00000000 --- a/internal/wire/testdata/Vendor/foo/wire.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2018 The Go Cloud Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//+build wireinject - -package main - -import ( - "example.com/bar" - "github.com/google/go-cloud/wire" -) - -func injectedMessage() string { - wire.Build(bar.ProvideMessage) - return "" -} diff --git a/internal/wire/testdata/Vendor/pkg b/internal/wire/testdata/Vendor/pkg deleted file mode 100644 index f7a5c8ce..00000000 --- a/internal/wire/testdata/Vendor/pkg +++ /dev/null @@ -1 +0,0 @@ -example.com/foo diff --git a/internal/wire/testdata/Vendor/vendor/example.com/bar/bar.go b/internal/wire/testdata/Vendor/vendor/example.com/bar/bar.go deleted file mode 100644 index 95caaba1..00000000 --- a/internal/wire/testdata/Vendor/vendor/example.com/bar/bar.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2018 The Go Cloud Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package bar is the vendored copy of bar which contains the real provider. -package bar - -// ProvideMessage provides a friendly user greeting. -func ProvideMessage() string { - return "Hello, World!" -} diff --git a/internal/wire/testdata/Vendor/want/program_out.txt b/internal/wire/testdata/Vendor/want/program_out.txt deleted file mode 100644 index 8ab686ea..00000000 --- a/internal/wire/testdata/Vendor/want/program_out.txt +++ /dev/null @@ -1 +0,0 @@ -Hello, World! diff --git a/internal/wire/wire.go b/internal/wire/wire.go index ab6983f0..c11c97ad 100644 --- a/internal/wire/wire.go +++ b/internal/wire/wire.go @@ -35,7 +35,7 @@ import ( "unicode/utf8" "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/go/loader" + "golang.org/x/tools/go/packages" ) // GeneratedFile stores the content of a call to Generate and the @@ -64,26 +64,25 @@ func (gen GeneratedFile) Commit() error { // In case of duplicate environment variables, the last one in the list // takes precedence. func Generate(ctx context.Context, wd string, env []string, pkgPattern string) (GeneratedFile, []error) { - prog, errs := load(ctx, wd, env, []string{pkgPattern}) + pkgs, errs := load(ctx, wd, env, []string{pkgPattern}) if len(errs) > 0 { return GeneratedFile{}, errs } - if len(prog.InitialPackages()) != 1 { + if len(pkgs) != 1 { // This is more of a violated precondition than anything else. - return GeneratedFile{}, []error{fmt.Errorf("load: got %d packages", len(prog.InitialPackages()))} + return GeneratedFile{}, []error{fmt.Errorf("load: got %d packages", len(pkgs))} } - pkgInfo := prog.InitialPackages()[0] - outDir, err := detectOutputDir(prog.Fset, pkgInfo.Files) + outDir, err := detectOutputDir(pkgs[0].GoFiles) if err != nil { return GeneratedFile{}, []error{fmt.Errorf("load: %v", err)} } outFname := filepath.Join(outDir, "wire_gen.go") - g := newGen(prog, pkgInfo.Pkg.Path()) - injectorFiles, errs := generateInjectors(g, pkgInfo) + g := newGen(pkgs[0]) + injectorFiles, errs := generateInjectors(g, pkgs[0]) if len(errs) > 0 { return GeneratedFile{}, errs } - copyNonInjectorDecls(g, injectorFiles, &pkgInfo.Info) + copyNonInjectorDecls(g, injectorFiles, pkgs[0].TypesInfo) goSrc := g.frame() fmtSrc, err := format.Source(goSrc) if err != nil { @@ -94,13 +93,13 @@ func Generate(ctx context.Context, wd string, env []string, pkgPattern string) ( return GeneratedFile{Path: outFname, Content: fmtSrc}, nil } -func detectOutputDir(fset *token.FileSet, files []*ast.File) (string, error) { - if len(files) == 0 { +func detectOutputDir(paths []string) (string, error) { + if len(paths) == 0 { return "", errors.New("no files to derive output directory from") } - dir := filepath.Dir(fset.File(files[0].Package).Name()) - for _, f := range files[1:] { - if dir2 := filepath.Dir(fset.File(f.Package).Name()); dir2 != dir { + dir := filepath.Dir(paths[0]) + for _, p := range paths[1:] { + if dir2 := filepath.Dir(p); dir2 != dir { return "", fmt.Errorf("found conflicting directories %q and %q", dir, dir2) } } @@ -108,17 +107,17 @@ func detectOutputDir(fset *token.FileSet, files []*ast.File) (string, error) { } // generateInjectors generates the injectors for a given package. -func generateInjectors(g *gen, pkgInfo *loader.PackageInfo) (injectorFiles []*ast.File, _ []error) { - oc := newObjectCache(g.prog) - injectorFiles = make([]*ast.File, 0, len(pkgInfo.Files)) +func generateInjectors(g *gen, pkg *packages.Package) (injectorFiles []*ast.File, _ []error) { + oc := newObjectCache([]*packages.Package{pkg}) + injectorFiles = make([]*ast.File, 0, len(pkg.Syntax)) ec := new(errorCollector) - for _, f := range pkgInfo.Files { + for _, f := range pkg.Syntax { for _, decl := range f.Decls { fn, ok := decl.(*ast.FuncDecl) if !ok { continue } - buildCall, err := findInjectorBuild(&pkgInfo.Info, fn) + buildCall, err := findInjectorBuild(pkg.TypesInfo, fn) if err != nil { ec.add(err) continue @@ -129,16 +128,16 @@ func generateInjectors(g *gen, pkgInfo *loader.PackageInfo) (injectorFiles []*as if len(injectorFiles) == 0 || injectorFiles[len(injectorFiles)-1] != f { // This is the first injector generated for this file. // Write a file header. - name := filepath.Base(g.prog.Fset.File(f.Pos()).Name()) + name := filepath.Base(g.pkg.Fset.File(f.Pos()).Name()) g.p("// Injectors from %s:\n\n", name) injectorFiles = append(injectorFiles, f) } - set, errs := oc.processNewSet(pkgInfo, buildCall, "") + set, errs := oc.processNewSet(pkg.TypesInfo, pkg.PkgPath, buildCall, "") if len(errs) > 0 { - ec.add(notePositionAll(g.prog.Fset.Position(fn.Pos()), errs)...) + ec.add(notePositionAll(g.pkg.Fset.Position(fn.Pos()), errs)...) continue } - sig := pkgInfo.ObjectOf(fn.Name).Type().(*types.Signature) + sig := pkg.TypesInfo.ObjectOf(fn.Name).Type().(*types.Signature) if errs := g.inject(fn.Pos(), fn.Name.Name, sig, set); len(errs) > 0 { ec.add(errs...) continue @@ -155,7 +154,7 @@ func generateInjectors(g *gen, pkgInfo *loader.PackageInfo) (injectorFiles []*as // given files into the generated output. func copyNonInjectorDecls(g *gen, files []*ast.File, info *types.Info) { for _, f := range files { - name := filepath.Base(g.prog.Fset.File(f.Pos()).Name()) + name := filepath.Base(g.pkg.Fset.File(f.Pos()).Name()) first := true for _, decl := range f.Decls { switch decl := decl.(type) { @@ -185,26 +184,26 @@ func copyNonInjectorDecls(g *gen, files []*ast.File, info *types.Info) { // importInfo holds info about an import. type importInfo struct { + // name is the identifier that is used in the generated source. name string - // fullpath is the full, possibly vendored, path. - fullpath string + // differs is true if the import is given an identifier that does not + // match the package's identifier. + differs bool } // gen is the file-wide generator state. type gen struct { - currPackage string - buf bytes.Buffer - imports map[string]*importInfo - values map[ast.Expr]string - prog *loader.Program // for positions and determining package names + pkg *packages.Package + buf bytes.Buffer + imports map[string]importInfo + values map[ast.Expr]string } -func newGen(prog *loader.Program, pkg string) *gen { +func newGen(pkg *packages.Package) *gen { return &gen{ - currPackage: pkg, - imports: make(map[string]*importInfo), - values: make(map[ast.Expr]string), - prog: prog, + pkg: pkg, + imports: make(map[string]importInfo), + values: make(map[ast.Expr]string), } } @@ -218,7 +217,7 @@ func (g *gen) frame() []byte { buf.WriteString("//go:generate wire\n") buf.WriteString("//+build !wireinject\n\n") buf.WriteString("package ") - buf.WriteString(g.prog.Package(g.currPackage).Pkg.Name()) + buf.WriteString(g.pkg.Name) buf.WriteString("\n\n") if len(g.imports) > 0 { buf.WriteString("import (\n") @@ -230,10 +229,10 @@ func (g *gen) frame() []byte { for _, path := range imps { // Omit the local package identifier if it matches the package name. info := g.imports[path] - if g.prog.Package(info.fullpath).Pkg.Name() == info.name { - fmt.Fprintf(&buf, "\t%q\n", path) - } else { + if info.differs { fmt.Fprintf(&buf, "\t%s %q\n", info.name, path) + } else { + fmt.Fprintf(&buf, "\t%q\n", path) } } buf.WriteString(")\n\n") @@ -246,7 +245,7 @@ func (g *gen) frame() []byte { func (g *gen) inject(pos token.Pos, name string, sig *types.Signature, set *ProviderSet) []error { injectSig, err := funcOutput(sig) if err != nil { - return []error{notePosition(g.prog.Fset.Position(pos), + return []error{notePosition(g.pkg.Fset.Position(pos), fmt.Errorf("inject %s: %v", name, err))} } params := sig.Params() @@ -254,13 +253,13 @@ func (g *gen) inject(pos token.Pos, name string, sig *types.Signature, set *Prov for i := 0; i < params.Len(); i++ { given[i] = params.At(i).Type() } - calls, errs := solve(g.prog.Fset, injectSig.out, given, set) + calls, errs := solve(g.pkg.Fset, injectSig.out, given, set) if len(errs) > 0 { return mapErrors(errs, func(e error) error { if w, ok := e.(*wireErr); ok { return notePosition(w.position, fmt.Errorf("inject %s: %v", name, w.error)) } - return notePosition(g.prog.Fset.Position(pos), fmt.Errorf("inject %s: %v", name, e)) + return notePosition(g.pkg.Fset.Position(pos), fmt.Errorf("inject %s: %v", name, e)) }) } type pendingVar struct { @@ -275,21 +274,21 @@ func (g *gen) inject(pos token.Pos, name string, sig *types.Signature, set *Prov if c.hasCleanup && !injectSig.cleanup { ts := types.TypeString(c.out, nil) ec.add(notePosition( - g.prog.Fset.Position(pos), + g.pkg.Fset.Position(pos), fmt.Errorf("inject %s: provider for %s returns cleanup but injection does not return cleanup function", name, ts))) } if c.hasErr && !injectSig.err { ts := types.TypeString(c.out, nil) ec.add(notePosition( - g.prog.Fset.Position(pos), + g.pkg.Fset.Position(pos), fmt.Errorf("inject %s: provider for %s returns error but injection not allowed to fail", name, ts))) } if c.kind == valueExpr { - if err := accessibleFrom(c.valueTypeInfo, c.valueExpr, g.currPackage); err != nil { + if err := accessibleFrom(c.valueTypeInfo, c.valueExpr, g.pkg.PkgPath); err != nil { // TODO(light): Display line number of value expression. ts := types.TypeString(c.out, nil) ec.add(notePosition( - g.prog.Fset.Position(pos), + g.pkg.Fset.Position(pos), fmt.Errorf("inject %s: value %s can't be used: %v", name, ts, err))) } if g.values[c.valueExpr] == "" { @@ -347,9 +346,9 @@ func (g *gen) rewritePkgRefs(info *types.Info, node ast.Node) ast.Node { if obj == nil { return false } - if pkg := obj.Pkg(); pkg != nil && obj.Parent() == pkg.Scope() && pkg.Path() != g.currPackage { + if pkg := obj.Pkg(); pkg != nil && obj.Parent() == pkg.Scope() && pkg.Path() != g.pkg.PkgPath { // An identifier from either a dot import or read from a different package. - newPkgID := g.qualifyImport(pkg.Path()) + newPkgID := g.qualifyImport(pkg.Name(), pkg.Path()) c.Replace(&ast.SelectorExpr{ X: ast.NewIdent(newPkgID), Sel: ast.NewIdent(node.Name), @@ -367,7 +366,8 @@ func (g *gen) rewritePkgRefs(info *types.Info, node ast.Node) ast.Node { return true } // This is a qualified identifier. Rewrite and avoid visiting subexpressions. - newPkgID := g.qualifyImport(pkgName.Imported().Path()) + imported := pkgName.Imported() + newPkgID := g.qualifyImport(imported.Name(), imported.Path()) c.Replace(&ast.SelectorExpr{ X: ast.NewIdent(newPkgID), Sel: ast.NewIdent(node.Sel.Name), @@ -389,7 +389,7 @@ func (g *gen) rewritePkgRefs(info *types.Info, node ast.Node) ast.Node { return false } var scopeStack []*types.Scope - pkgScope := g.prog.Package(g.currPackage).Pkg.Scope() + pkgScope := g.pkg.Types.Scope() node = astutil.Apply(node, func(c *astutil.Cursor) bool { if scope := info.Scopes[c.Node()]; scope != nil { scopeStack = append(scopeStack, scope) @@ -451,21 +451,21 @@ func (g *gen) rewritePkgRefs(info *types.Info, node ast.Node) ast.Node { // package references it encounters. func (g *gen) writeAST(info *types.Info, node ast.Node) { node = g.rewritePkgRefs(info, node) - if err := printer.Fprint(&g.buf, g.prog.Fset, node); err != nil { + if err := printer.Fprint(&g.buf, g.pkg.Fset, node); err != nil { panic(err) } } -func (g *gen) qualifiedID(path, sym string) string { - name := g.qualifyImport(path) +func (g *gen) qualifiedID(pkgName, pkgPath, sym string) string { + name := g.qualifyImport(pkgName, pkgPath) if name == "" { return sym } return name + "." + sym } -func (g *gen) qualifyImport(path string) string { - if path == g.currPackage { +func (g *gen) qualifyImport(name, path string) string { + if path == g.pkg.PkgPath { return "" } // TODO(light): This is depending on details of the current loader. @@ -474,16 +474,19 @@ func (g *gen) qualifyImport(path string) string { if i := strings.LastIndex(path, vendorPart); i != -1 && (i == 0 || path[i-1] == '/') { unvendored = path[i+len(vendorPart):] } - if info := g.imports[unvendored]; info != nil { + if info, ok := g.imports[unvendored]; ok { return info.name } // TODO(light): Use parts of import path to disambiguate. - name := disambiguate(g.prog.Package(path).Pkg.Name(), func(n string) bool { + newName := disambiguate(name, func(n string) bool { // Don't let an import take the "err" name. That's annoying. return n == "err" || g.nameInFileScope(n) }) - g.imports[unvendored] = &importInfo{name: name, fullpath: path} - return name + g.imports[unvendored] = importInfo{ + name: newName, + differs: newName != name, + } + return newName } func (g *gen) nameInFileScope(name string) bool { @@ -497,12 +500,12 @@ func (g *gen) nameInFileScope(name string) bool { return true } } - _, obj := g.prog.Package(g.currPackage).Pkg.Scope().LookupParent(name, token.NoPos) + _, obj := g.pkg.Types.Scope().LookupParent(name, token.NoPos) return obj != nil } func (g *gen) qualifyPkg(pkg *types.Package) string { - return g.qualifyImport(pkg.Path()) + return g.qualifyImport(pkg.Name(), pkg.Path()) } func (g *gen) p(format string, args ...interface{}) { @@ -601,7 +604,7 @@ func (ig *injectorGen) funcProviderCall(lname string, c *call, injectSig outputS ig.p(", %s", ig.errVar) } ig.p(" := ") - ig.p("%s(", ig.g.qualifiedID(c.importPath, c.name)) + ig.p("%s(", ig.g.qualifiedID(c.pkg.Name(), c.pkg.Path(), c.name)) for i, a := range c.args { if i > 0 { ig.p(", ") @@ -634,7 +637,7 @@ func (ig *injectorGen) structProviderCall(lname string, c *call) { if _, ok := c.out.(*types.Pointer); ok { ig.p("&") } - ig.p("%s{\n", ig.g.qualifiedID(c.importPath, c.name)) + ig.p("%s{\n", ig.g.qualifiedID(c.pkg.Name(), c.pkg.Path(), c.name)) for i, a := range c.args { ig.p("\t\t%s: ", c.fieldNames[i]) if a < len(ig.paramNames) { @@ -687,7 +690,7 @@ func (ig *injectorGen) writeAST(info *types.Info, node ast.Node) { if ig.discard { return } - if err := printer.Fprint(&ig.g.buf, ig.g.prog.Fset, node); err != nil { + if err := printer.Fprint(&ig.g.buf, ig.g.pkg.Fset, node); err != nil { panic(err) } } diff --git a/internal/wire/wire_test.go b/internal/wire/wire_test.go index 217f62d0..7194ab8a 100644 --- a/internal/wire/wire_test.go +++ b/internal/wire/wire_test.go @@ -78,6 +78,10 @@ func TestWire(t *testing.T) { t.Fatal(err) } defer os.RemoveAll(gopath) + gopath, err = filepath.EvalSymlinks(gopath) + if err != nil { + t.Fatal(err) + } if err := test.materialize(gopath); err != nil { t.Fatal(err) } @@ -150,18 +154,9 @@ func TestWire(t *testing.T) { } func goBuildCheck(goToolPath, gopath string, test *testCase) error { - // Write go.mod files for example.com and the wire package. - // TODO(#78): Move this to happen in materialize() once modules work. - if err := writeGoMod(gopath); err != nil { - return err - } - // Run `go build`. testExePath := filepath.Join(gopath, "bin", "testprog") buildCmd := []string{"build", "-o", testExePath} - if test.name == "Vendor" && os.Getenv("GO111MODULE") == "on" { - buildCmd = append(buildCmd, "-mod=vendor") - } buildCmd = append(buildCmd, test.pkg) cmd := exec.Command(goToolPath, buildCmd...) cmd.Dir = filepath.Join(gopath, "src", "example.com") @@ -532,28 +527,8 @@ func (test *testCase) materialize(gopath string) error { return fmt.Errorf("materialize GOPATH: %v", err) } } - return nil -} -// writeGoMod generates go.mod files for the test package and its dependency. -// The file structure looks like: -// -// gopath/src/ -// -// example.com/ -// -// go.mod -// replaces dependency with local copied one -// -// ... (Packages to be built and tested) -// any Go files copied recursively -// -// github.com/google/go-cloud/ -// -// go.mod -// -// ... (Dependency files copied) -func writeGoMod(gopath string) error { + // Add go.mod files to example.com and github.com/google/go-cloud. const importPath = "example.com" const depPath = "github.com/google/go-cloud" depLoc := filepath.Join(gopath, "src", filepath.FromSlash(depPath)) @@ -562,7 +537,7 @@ func writeGoMod(gopath string) error { if err := ioutil.WriteFile(gomod, []byte(example), 0666); err != nil { return fmt.Errorf("generate go.mod for %s: %v", gomod, err) } - if err := ioutil.WriteFile(filepath.Join(depLoc, "go.mod"), []byte("module "+depPath), 0666); err != nil { + if err := ioutil.WriteFile(filepath.Join(depLoc, "go.mod"), []byte("module "+depPath+"\n"), 0666); err != nil { return fmt.Errorf("generate go.mod for %s: %v", depPath, err) } return nil