214 lines
5.9 KiB
Go
214 lines
5.9 KiB
Go
// 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"
|
|
}
|