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