| // 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 ( |
| "archive/zip" |
| "bytes" |
| "fmt" |
| "io/ioutil" |
| "os" |
| "os/exec" |
| "path" |
| "path/filepath" |
| "regexp" |
| |
| "golang.org/x/tools/go/packages" |
| ) |
| |
| // 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{} |
| |
| 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) |
| exported.Config.Dir = primaryDir |
| 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 |
| } |
| primaryGomod += fmt.Sprintf("\t%v %v\n", other, moduleVersion(other)) |
| } |
| 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 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. |
| proxyDir := filepath.Join(exported.temp, "modproxy") |
| for module, files := range exported.written { |
| if module == exported.primary { |
| continue |
| } |
| dir := filepath.Join(proxyDir, module, "@v") |
| |
| if err := writeModuleProxy(dir, module, 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"), |
| "GOPROXY="+proxyDirToURL(proxyDir)) |
| |
| // 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. |
| if err := invokeGo(exported.Config, "mod", "download"); err != nil { |
| return err |
| } |
| |
| return nil |
| } |
| |
| // writeModuleProxy creates a directory in the proxy dir for a module. |
| func writeModuleProxy(dir, module string, files map[string]string) error { |
| ver := moduleVersion(module) |
| if err := os.MkdirAll(dir, 0755); err != nil { |
| return err |
| } |
| |
| // list file. Just the single version. |
| if err := ioutil.WriteFile(filepath.Join(dir, "list"), []byte(ver+"\n"), 0644); err != nil { |
| return err |
| } |
| |
| // go.mod, copied from the file written in Finalize. |
| modContents, err := ioutil.ReadFile(files["go.mod"]) |
| if err != nil { |
| return err |
| } |
| if err := ioutil.WriteFile(filepath.Join(dir, ver+".mod"), modContents, 0644); err != nil { |
| return err |
| } |
| |
| // info file, just the bare bones. |
| infoContents := []byte(fmt.Sprintf(`{"Version": "%v", "Time":"2017-12-14T13:08:43Z"}`, ver)) |
| if err := ioutil.WriteFile(filepath.Join(dir, ver+".info"), infoContents, 0644); err != nil { |
| return err |
| } |
| |
| // zip of all the source files. |
| f, err := os.OpenFile(filepath.Join(dir, ver+".zip"), os.O_CREATE|os.O_WRONLY, 0644) |
| if err != nil { |
| return err |
| } |
| z := zip.NewWriter(f) |
| for name, path := range files { |
| zf, err := z.Create(module + "@" + ver + "/" + name) |
| if err != nil { |
| return err |
| } |
| contents, err := ioutil.ReadFile(path) |
| if err != nil { |
| return err |
| } |
| if _, err := zf.Write(contents); err != nil { |
| return err |
| } |
| } |
| if err := z.Close(); err != nil { |
| return err |
| } |
| if err := f.Close(); err != nil { |
| return err |
| } |
| |
| return nil |
| } |
| |
| func invokeGo(cfg *packages.Config, args ...string) error { |
| stdout := new(bytes.Buffer) |
| stderr := new(bytes.Buffer) |
| cmd := exec.Command("go", args...) |
| cmd.Env = append(append([]string{}, cfg.Env...), "PWD="+cfg.Dir) |
| cmd.Dir = cfg.Dir |
| cmd.Stdout = stdout |
| cmd.Stderr = stderr |
| if err := cmd.Run(); err != nil { |
| return fmt.Errorf("go %v: %s: %s", args, err, stderr) |
| } |
| return nil |
| } |
| |
| func modCache(exported *Exported) string { |
| return filepath.Join(exported.temp, "modcache/pkg/mod") |
| } |
| |
| func primaryDir(exported *Exported) string { |
| return filepath.Join(exported.temp, "primarymod", path.Base(exported.primary)) |
| } |
| |
| func moduleDir(exported *Exported, module string) string { |
| 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" |
| } |