gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/golang.org/x/tools/go/packages/packagestest/modules.go (about) 1 // Copyright 2018 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package packagestest 6 7 import ( 8 "archive/zip" 9 "bytes" 10 "fmt" 11 "io/ioutil" 12 "os" 13 "os/exec" 14 "path" 15 "path/filepath" 16 "regexp" 17 18 "golang.org/x/tools/go/packages" 19 ) 20 21 // Modules is the exporter that produces module layouts. 22 // Each "repository" is put in it's own module, and the module file generated 23 // will have replace directives for all other modules. 24 // Given the two files 25 // golang.org/repoa#a/a.go 26 // golang.org/repob#b/b.go 27 // You would get the directory layout 28 // /sometemporarydirectory 29 // ├── repoa 30 // │ ├── a 31 // │ │ └── a.go 32 // │ └── go.mod 33 // └── repob 34 // ├── b 35 // │ └── b.go 36 // └── go.mod 37 // and the working directory would be 38 // /sometemporarydirectory/repoa 39 var Modules = modules{} 40 41 type modules struct{} 42 43 func (modules) Name() string { 44 return "Modules" 45 } 46 47 func (modules) Filename(exported *Exported, module, fragment string) string { 48 if module == exported.primary { 49 return filepath.Join(primaryDir(exported), fragment) 50 } 51 return filepath.Join(moduleDir(exported, module), fragment) 52 } 53 54 func (modules) Finalize(exported *Exported) error { 55 // Write out the primary module. This module can use symlinks and 56 // other weird stuff, and will be the working dir for the go command. 57 // It depends on all the other modules. 58 primaryDir := primaryDir(exported) 59 exported.Config.Dir = primaryDir 60 exported.written[exported.primary]["go.mod"] = filepath.Join(primaryDir, "go.mod") 61 primaryGomod := "module " + exported.primary + "\nrequire (\n" 62 for other := range exported.written { 63 if other == exported.primary { 64 continue 65 } 66 primaryGomod += fmt.Sprintf("\t%v %v\n", other, moduleVersion(other)) 67 } 68 primaryGomod += ")\n" 69 if err := ioutil.WriteFile(filepath.Join(primaryDir, "go.mod"), []byte(primaryGomod), 0644); err != nil { 70 return err 71 } 72 73 // Create the mod cache so we can rename it later, even if we don't need it. 74 if err := os.MkdirAll(modCache(exported), 0755); err != nil { 75 return err 76 } 77 78 // Write out the go.mod files for the other modules. 79 for module, files := range exported.written { 80 if module == exported.primary { 81 continue 82 } 83 dir := moduleDir(exported, module) 84 85 modfile := filepath.Join(dir, "go.mod") 86 if err := ioutil.WriteFile(modfile, []byte("module "+module+"\n"), 0644); err != nil { 87 return err 88 } 89 files["go.mod"] = modfile 90 } 91 92 // Zip up all the secondary modules into the proxy dir. 93 proxyDir := filepath.Join(exported.temp, "modproxy") 94 for module, files := range exported.written { 95 if module == exported.primary { 96 continue 97 } 98 dir := filepath.Join(proxyDir, module, "@v") 99 100 if err := writeModuleProxy(dir, module, files); err != nil { 101 return fmt.Errorf("creating module proxy dir for %v: %v", module, err) 102 } 103 } 104 105 // Discard the original mod cache dir, which contained the files written 106 // for us by Export. 107 if err := os.Rename(modCache(exported), modCache(exported)+".orig"); err != nil { 108 return err 109 } 110 exported.Config.Env = append(exported.Config.Env, 111 "GO111MODULE=on", 112 "GOPATH="+filepath.Join(exported.temp, "modcache"), 113 "GOPROXY=file://"+filepath.ToSlash(proxyDir)) 114 115 // Run go mod download to recreate the mod cache dir with all the extra 116 // stuff in cache. All the files created by Export should be recreated. 117 if err := invokeGo(exported.Config, "mod", "download"); err != nil { 118 return err 119 } 120 121 return nil 122 } 123 124 // writeModuleProxy creates a directory in the proxy dir for a module. 125 func writeModuleProxy(dir, module string, files map[string]string) error { 126 ver := moduleVersion(module) 127 if err := os.MkdirAll(dir, 0755); err != nil { 128 return err 129 } 130 131 // list file. Just the single version. 132 if err := ioutil.WriteFile(filepath.Join(dir, "list"), []byte(ver+"\n"), 0644); err != nil { 133 return err 134 } 135 136 // go.mod, copied from the file written in Finalize. 137 modContents, err := ioutil.ReadFile(files["go.mod"]) 138 if err != nil { 139 return err 140 } 141 if err := ioutil.WriteFile(filepath.Join(dir, ver+".mod"), modContents, 0644); err != nil { 142 return err 143 } 144 145 // info file, just the bare bones. 146 infoContents := []byte(fmt.Sprintf(`{"Version": "%v", "Time":"2017-12-14T13:08:43Z"}`, ver)) 147 if err := ioutil.WriteFile(filepath.Join(dir, ver+".info"), infoContents, 0644); err != nil { 148 return err 149 } 150 151 // zip of all the source files. 152 f, err := os.OpenFile(filepath.Join(dir, ver+".zip"), os.O_CREATE|os.O_WRONLY, 0644) 153 if err != nil { 154 return err 155 } 156 z := zip.NewWriter(f) 157 for name, path := range files { 158 zf, err := z.Create(module + "@" + ver + "/" + name) 159 if err != nil { 160 return err 161 } 162 contents, err := ioutil.ReadFile(path) 163 if err != nil { 164 return err 165 } 166 if _, err := zf.Write(contents); err != nil { 167 return err 168 } 169 } 170 if err := z.Close(); err != nil { 171 return err 172 } 173 174 return nil 175 } 176 177 func invokeGo(cfg *packages.Config, args ...string) error { 178 stdout := new(bytes.Buffer) 179 stderr := new(bytes.Buffer) 180 cmd := exec.Command("go", args...) 181 cmd.Env = append(append([]string{}, cfg.Env...), "PWD="+cfg.Dir) 182 cmd.Dir = cfg.Dir 183 cmd.Stdout = stdout 184 cmd.Stderr = stderr 185 if err := cmd.Run(); err != nil { 186 return fmt.Errorf("go %v: %s: %s", args, err, stderr) 187 } 188 return nil 189 } 190 191 func modCache(exported *Exported) string { 192 return filepath.Join(exported.temp, "modcache/pkg/mod") 193 } 194 195 func primaryDir(exported *Exported) string { 196 return filepath.Join(exported.temp, "primarymod", path.Base(exported.primary)) 197 } 198 199 func moduleDir(exported *Exported, module string) string { 200 return filepath.Join(modCache(exported), path.Dir(module), path.Base(module)+"@"+moduleVersion(module)) 201 } 202 203 var versionSuffixRE = regexp.MustCompile(`v\d+`) 204 205 func moduleVersion(module string) string { 206 if versionSuffixRE.MatchString(path.Base(module)) { 207 return path.Base(module) + ".0.0" 208 } 209 return "v1.0.0" 210 }