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