github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/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 "context" 9 "fmt" 10 "io/ioutil" 11 "os" 12 "path" 13 "path/filepath" 14 "regexp" 15 "strings" 16 17 "github.com/powerman/golang-tools/internal/gocommand" 18 "github.com/powerman/golang-tools/internal/packagesinternal" 19 "github.com/powerman/golang-tools/internal/proxydir" 20 ) 21 22 // Modules is the exporter that produces module layouts. 23 // Each "repository" is put in it's own module, and the module file generated 24 // will have replace directives for all other modules. 25 // Given the two files 26 // golang.org/repoa#a/a.go 27 // golang.org/repob#b/b.go 28 // You would get the directory layout 29 // /sometemporarydirectory 30 // ├── repoa 31 // │ ├── a 32 // │ │ └── a.go 33 // │ └── go.mod 34 // └── repob 35 // ├── b 36 // │ └── b.go 37 // └── go.mod 38 // and the working directory would be 39 // /sometemporarydirectory/repoa 40 var Modules = modules{} 41 42 type modules struct{} 43 44 type moduleAtVersion struct { 45 module string 46 version string 47 } 48 49 func (modules) Name() string { 50 return "Modules" 51 } 52 53 func (modules) Filename(exported *Exported, module, fragment string) string { 54 if module == exported.primary { 55 return filepath.Join(primaryDir(exported), fragment) 56 } 57 return filepath.Join(moduleDir(exported, module), fragment) 58 } 59 60 func (modules) Finalize(exported *Exported) error { 61 // Write out the primary module. This module can use symlinks and 62 // other weird stuff, and will be the working dir for the go command. 63 // It depends on all the other modules. 64 primaryDir := primaryDir(exported) 65 if err := os.MkdirAll(primaryDir, 0755); err != nil { 66 return err 67 } 68 exported.Config.Dir = primaryDir 69 if exported.written[exported.primary] == nil { 70 exported.written[exported.primary] = make(map[string]string) 71 } 72 73 // Create a map of modulepath -> {module, version} for modulepaths 74 // that are of the form `repoa/mod1@v1.1.0`. 75 versions := make(map[string]moduleAtVersion) 76 for module := range exported.written { 77 if splt := strings.Split(module, "@"); len(splt) > 1 { 78 versions[module] = moduleAtVersion{ 79 module: splt[0], 80 version: splt[1], 81 } 82 } 83 } 84 85 // If the primary module already has a go.mod, write the contents to a temp 86 // go.mod for now and then we will reset it when we are getting all the markers. 87 if gomod := exported.written[exported.primary]["go.mod"]; gomod != "" { 88 contents, err := ioutil.ReadFile(gomod) 89 if err != nil { 90 return err 91 } 92 if err := ioutil.WriteFile(gomod+".temp", contents, 0644); err != nil { 93 return err 94 } 95 } 96 97 exported.written[exported.primary]["go.mod"] = filepath.Join(primaryDir, "go.mod") 98 primaryGomod := "module " + exported.primary + "\nrequire (\n" 99 for other := range exported.written { 100 if other == exported.primary { 101 continue 102 } 103 version := moduleVersion(other) 104 // If other is of the form `repo1/mod1@v1.1.0`, 105 // then we need to extract the module and the version. 106 if v, ok := versions[other]; ok { 107 other = v.module 108 version = v.version 109 } 110 primaryGomod += fmt.Sprintf("\t%v %v\n", other, version) 111 } 112 primaryGomod += ")\n" 113 if err := ioutil.WriteFile(filepath.Join(primaryDir, "go.mod"), []byte(primaryGomod), 0644); err != nil { 114 return err 115 } 116 117 // Create the mod cache so we can rename it later, even if we don't need it. 118 if err := os.MkdirAll(modCache(exported), 0755); err != nil { 119 return err 120 } 121 122 // Write out the go.mod files for the other modules. 123 for module, files := range exported.written { 124 if module == exported.primary { 125 continue 126 } 127 dir := moduleDir(exported, module) 128 modfile := filepath.Join(dir, "go.mod") 129 // If other is of the form `repo1/mod1@v1.1.0`, 130 // then we need to extract the module name without the version. 131 if v, ok := versions[module]; ok { 132 module = v.module 133 } 134 if err := ioutil.WriteFile(modfile, []byte("module "+module+"\n"), 0644); err != nil { 135 return err 136 } 137 files["go.mod"] = modfile 138 } 139 140 // Zip up all the secondary modules into the proxy dir. 141 modProxyDir := filepath.Join(exported.temp, "modproxy") 142 for module, files := range exported.written { 143 if module == exported.primary { 144 continue 145 } 146 version := moduleVersion(module) 147 // If other is of the form `repo1/mod1@v1.1.0`, 148 // then we need to extract the module and the version. 149 if v, ok := versions[module]; ok { 150 module = v.module 151 version = v.version 152 } 153 if err := writeModuleFiles(modProxyDir, module, version, files); err != nil { 154 return fmt.Errorf("creating module proxy dir for %v: %v", module, err) 155 } 156 } 157 158 // Discard the original mod cache dir, which contained the files written 159 // for us by Export. 160 if err := os.Rename(modCache(exported), modCache(exported)+".orig"); err != nil { 161 return err 162 } 163 exported.Config.Env = append(exported.Config.Env, 164 "GO111MODULE=on", 165 "GOPATH="+filepath.Join(exported.temp, "modcache"), 166 "GOMODCACHE=", 167 "GOPROXY="+proxydir.ToURL(modProxyDir), 168 "GOSUMDB=off", 169 ) 170 gocmdRunner := &gocommand.Runner{} 171 packagesinternal.SetGoCmdRunner(exported.Config, gocmdRunner) 172 173 // Run go mod download to recreate the mod cache dir with all the extra 174 // stuff in cache. All the files created by Export should be recreated. 175 inv := gocommand.Invocation{ 176 Verb: "mod", 177 Args: []string{"download", "all"}, 178 Env: exported.Config.Env, 179 BuildFlags: exported.Config.BuildFlags, 180 WorkingDir: exported.Config.Dir, 181 } 182 if _, err := gocmdRunner.Run(context.Background(), inv); err != nil { 183 return err 184 } 185 return nil 186 } 187 188 func writeModuleFiles(rootDir, module, ver string, filePaths map[string]string) error { 189 fileData := make(map[string][]byte) 190 for name, path := range filePaths { 191 contents, err := ioutil.ReadFile(path) 192 if err != nil { 193 return err 194 } 195 fileData[name] = contents 196 } 197 return proxydir.WriteModuleVersion(rootDir, module, ver, fileData) 198 } 199 200 func modCache(exported *Exported) string { 201 return filepath.Join(exported.temp, "modcache/pkg/mod") 202 } 203 204 func primaryDir(exported *Exported) string { 205 return filepath.Join(exported.temp, path.Base(exported.primary)) 206 } 207 208 func moduleDir(exported *Exported, module string) string { 209 if strings.Contains(module, "@") { 210 return filepath.Join(modCache(exported), module) 211 } 212 return filepath.Join(modCache(exported), path.Dir(module), path.Base(module)+"@"+moduleVersion(module)) 213 } 214 215 var versionSuffixRE = regexp.MustCompile(`v\d+`) 216 217 func moduleVersion(module string) string { 218 if versionSuffixRE.MatchString(path.Base(module)) { 219 return path.Base(module) + ".0.0" 220 } 221 return "v1.0.0" 222 }