golang.org/x/tools@v0.21.1-0.20240520172518-788d39e776b1/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 "bytes" 9 "context" 10 "fmt" 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/proxydir" 19 ) 20 21 // Modules is the exporter that produces module layouts. 22 // Each "repository" is put in its own module, and the module file generated 23 // will have replace directives for all other modules. 24 // Given the two files 25 // 26 // golang.org/repoa#a/a.go 27 // golang.org/repob#b/b.go 28 // 29 // You would get the directory layout 30 // 31 // /sometemporarydirectory 32 // ├── repoa 33 // │ ├── a 34 // │ │ └── a.go 35 // │ └── go.mod 36 // └── repob 37 // ├── b 38 // │ └── b.go 39 // └── go.mod 40 // 41 // and the working directory would be 42 // 43 // /sometemporarydirectory/repoa 44 var Modules = modules{} 45 46 type modules struct{} 47 48 type moduleAtVersion struct { 49 module string 50 version string 51 } 52 53 func (modules) Name() string { 54 return "Modules" 55 } 56 57 func (modules) Filename(exported *Exported, module, fragment string) string { 58 if module == exported.primary { 59 return filepath.Join(primaryDir(exported), fragment) 60 } 61 return filepath.Join(moduleDir(exported, module), fragment) 62 } 63 64 func (modules) Finalize(exported *Exported) error { 65 // Write out the primary module. This module can use symlinks and 66 // other weird stuff, and will be the working dir for the go command. 67 // It depends on all the other modules. 68 primaryDir := primaryDir(exported) 69 if err := os.MkdirAll(primaryDir, 0755); err != nil { 70 return err 71 } 72 exported.Config.Dir = primaryDir 73 if exported.written[exported.primary] == nil { 74 exported.written[exported.primary] = make(map[string]string) 75 } 76 77 // Create a map of modulepath -> {module, version} for modulepaths 78 // that are of the form `repoa/mod1@v1.1.0`. 79 versions := make(map[string]moduleAtVersion) 80 for module := range exported.written { 81 if splt := strings.Split(module, "@"); len(splt) > 1 { 82 versions[module] = moduleAtVersion{ 83 module: splt[0], 84 version: splt[1], 85 } 86 } 87 } 88 89 // If the primary module already has a go.mod, write the contents to a temp 90 // go.mod for now and then we will reset it when we are getting all the markers. 91 if gomod := exported.written[exported.primary]["go.mod"]; gomod != "" { 92 contents, err := os.ReadFile(gomod) 93 if err != nil { 94 return err 95 } 96 if err := os.WriteFile(gomod+".temp", contents, 0644); err != nil { 97 return err 98 } 99 } 100 101 exported.written[exported.primary]["go.mod"] = filepath.Join(primaryDir, "go.mod") 102 var primaryGomod bytes.Buffer 103 fmt.Fprintf(&primaryGomod, "module %s\nrequire (\n", exported.primary) 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 fmt.Fprintf(&primaryGomod, "\t%v %v\n", other, version) 116 } 117 fmt.Fprintf(&primaryGomod, ")\n") 118 if err := os.WriteFile(filepath.Join(primaryDir, "go.mod"), primaryGomod.Bytes(), 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 := os.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 176 // Run go mod download to recreate the mod cache dir with all the extra 177 // stuff in cache. All the files created by Export should be recreated. 178 inv := gocommand.Invocation{ 179 Verb: "mod", 180 Args: []string{"download", "all"}, 181 Env: exported.Config.Env, 182 BuildFlags: exported.Config.BuildFlags, 183 WorkingDir: exported.Config.Dir, 184 } 185 _, err := new(gocommand.Runner).Run(context.Background(), inv) 186 return err 187 } 188 189 func writeModuleFiles(rootDir, module, ver string, filePaths map[string]string) error { 190 fileData := make(map[string][]byte) 191 for name, path := range filePaths { 192 contents, err := os.ReadFile(path) 193 if err != nil { 194 return err 195 } 196 fileData[name] = contents 197 } 198 return proxydir.WriteModuleVersion(rootDir, module, ver, fileData) 199 } 200 201 func modCache(exported *Exported) string { 202 return filepath.Join(exported.temp, "modcache/pkg/mod") 203 } 204 205 func primaryDir(exported *Exported) string { 206 return filepath.Join(exported.temp, path.Base(exported.primary)) 207 } 208 209 func moduleDir(exported *Exported, module string) string { 210 if strings.Contains(module, "@") { 211 return filepath.Join(modCache(exported), module) 212 } 213 return filepath.Join(modCache(exported), path.Dir(module), path.Base(module)+"@"+moduleVersion(module)) 214 } 215 216 var versionSuffixRE = regexp.MustCompile(`v\d+`) 217 218 func moduleVersion(module string) string { 219 if versionSuffixRE.MatchString(path.Base(module)) { 220 return path.Base(module) + ".0.0" 221 } 222 return "v1.0.0" 223 }