--- /dev/null
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package packagestest
+
+import (
+ "context"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path"
+ "path/filepath"
+ "regexp"
+ "strings"
+
+ "golang.org/x/tools/internal/gocommand"
+ "golang.org/x/tools/internal/packagesinternal"
+ "golang.org/x/tools/internal/proxydir"
+)
+
+// Modules is the exporter that produces module layouts.
+// Each "repository" is put in it's own module, and the module file generated
+// will have replace directives for all other modules.
+// Given the two files
+// golang.org/repoa#a/a.go
+// golang.org/repob#b/b.go
+// You would get the directory layout
+// /sometemporarydirectory
+// ├── repoa
+// │ ├── a
+// │ │ └── a.go
+// │ └── go.mod
+// └── repob
+// ├── b
+// │ └── b.go
+// └── go.mod
+// and the working directory would be
+// /sometemporarydirectory/repoa
+var Modules = modules{}
+
+type modules struct{}
+
+type moduleAtVersion struct {
+ module string
+ version string
+}
+
+func (modules) Name() string {
+ return "Modules"
+}
+
+func (modules) Filename(exported *Exported, module, fragment string) string {
+ if module == exported.primary {
+ return filepath.Join(primaryDir(exported), fragment)
+ }
+ return filepath.Join(moduleDir(exported, module), fragment)
+}
+
+func (modules) Finalize(exported *Exported) error {
+ // Write out the primary module. This module can use symlinks and
+ // other weird stuff, and will be the working dir for the go command.
+ // It depends on all the other modules.
+ primaryDir := primaryDir(exported)
+ if err := os.MkdirAll(primaryDir, 0755); err != nil {
+ return err
+ }
+ exported.Config.Dir = primaryDir
+ if exported.written[exported.primary] == nil {
+ exported.written[exported.primary] = make(map[string]string)
+ }
+
+ // Create a map of modulepath -> {module, version} for modulepaths
+ // that are of the form `repoa/mod1@v1.1.0`.
+ versions := make(map[string]moduleAtVersion)
+ for module := range exported.written {
+ if splt := strings.Split(module, "@"); len(splt) > 1 {
+ versions[module] = moduleAtVersion{
+ module: splt[0],
+ version: splt[1],
+ }
+ }
+ }
+
+ // If the primary module already has a go.mod, write the contents to a temp
+ // go.mod for now and then we will reset it when we are getting all the markers.
+ if gomod := exported.written[exported.primary]["go.mod"]; gomod != "" {
+ contents, err := ioutil.ReadFile(gomod)
+ if err != nil {
+ return err
+ }
+ if err := ioutil.WriteFile(gomod+".temp", contents, 0644); err != nil {
+ return err
+ }
+ }
+
+ exported.written[exported.primary]["go.mod"] = filepath.Join(primaryDir, "go.mod")
+ primaryGomod := "module " + exported.primary + "\nrequire (\n"
+ for other := range exported.written {
+ if other == exported.primary {
+ continue
+ }
+ version := moduleVersion(other)
+ // If other is of the form `repo1/mod1@v1.1.0`,
+ // then we need to extract the module and the version.
+ if v, ok := versions[other]; ok {
+ other = v.module
+ version = v.version
+ }
+ primaryGomod += fmt.Sprintf("\t%v %v\n", other, version)
+ }
+ primaryGomod += ")\n"
+ if err := ioutil.WriteFile(filepath.Join(primaryDir, "go.mod"), []byte(primaryGomod), 0644); err != nil {
+ return err
+ }
+
+ // Create the mod cache so we can rename it later, even if we don't need it.
+ if err := os.MkdirAll(modCache(exported), 0755); err != nil {
+ return err
+ }
+
+ // Write out the go.mod files for the other modules.
+ for module, files := range exported.written {
+ if module == exported.primary {
+ continue
+ }
+ dir := moduleDir(exported, module)
+ modfile := filepath.Join(dir, "go.mod")
+ // If other is of the form `repo1/mod1@v1.1.0`,
+ // then we need to extract the module name without the version.
+ if v, ok := versions[module]; ok {
+ module = v.module
+ }
+ if err := ioutil.WriteFile(modfile, []byte("module "+module+"\n"), 0644); err != nil {
+ return err
+ }
+ files["go.mod"] = modfile
+ }
+
+ // Zip up all the secondary modules into the proxy dir.
+ modProxyDir := filepath.Join(exported.temp, "modproxy")
+ for module, files := range exported.written {
+ if module == exported.primary {
+ continue
+ }
+ version := moduleVersion(module)
+ // If other is of the form `repo1/mod1@v1.1.0`,
+ // then we need to extract the module and the version.
+ if v, ok := versions[module]; ok {
+ module = v.module
+ version = v.version
+ }
+ if err := writeModuleFiles(modProxyDir, module, version, files); err != nil {
+ return fmt.Errorf("creating module proxy dir for %v: %v", module, err)
+ }
+ }
+
+ // Discard the original mod cache dir, which contained the files written
+ // for us by Export.
+ if err := os.Rename(modCache(exported), modCache(exported)+".orig"); err != nil {
+ return err
+ }
+ exported.Config.Env = append(exported.Config.Env,
+ "GO111MODULE=on",
+ "GOPATH="+filepath.Join(exported.temp, "modcache"),
+ "GOMODCACHE=",
+ "GOPROXY="+proxydir.ToURL(modProxyDir),
+ "GOSUMDB=off",
+ )
+ gocmdRunner := &gocommand.Runner{}
+ packagesinternal.SetGoCmdRunner(exported.Config, gocmdRunner)
+
+ // Run go mod download to recreate the mod cache dir with all the extra
+ // stuff in cache. All the files created by Export should be recreated.
+ inv := gocommand.Invocation{
+ Verb: "mod",
+ Args: []string{"download"},
+ Env: exported.Config.Env,
+ BuildFlags: exported.Config.BuildFlags,
+ WorkingDir: exported.Config.Dir,
+ }
+ if _, err := gocmdRunner.Run(context.Background(), inv); err != nil {
+ return err
+ }
+ return nil
+}
+
+func writeModuleFiles(rootDir, module, ver string, filePaths map[string]string) error {
+ fileData := make(map[string][]byte)
+ for name, path := range filePaths {
+ contents, err := ioutil.ReadFile(path)
+ if err != nil {
+ return err
+ }
+ fileData[name] = contents
+ }
+ return proxydir.WriteModuleVersion(rootDir, module, ver, fileData)
+}
+
+func modCache(exported *Exported) string {
+ return filepath.Join(exported.temp, "modcache/pkg/mod")
+}
+
+func primaryDir(exported *Exported) string {
+ return filepath.Join(exported.temp, path.Base(exported.primary))
+}
+
+func moduleDir(exported *Exported, module string) string {
+ if strings.Contains(module, "@") {
+ return filepath.Join(modCache(exported), module)
+ }
+ return filepath.Join(modCache(exported), path.Dir(module), path.Base(module)+"@"+moduleVersion(module))
+}
+
+var versionSuffixRE = regexp.MustCompile(`v\d+`)
+
+func moduleVersion(module string) string {
+ if versionSuffixRE.MatchString(path.Base(module)) {
+ return path.Base(module) + ".0.0"
+ }
+ return "v1.0.0"
+}