golang.org/x/exp@v0.0.0-20240506185415-9bf2ced13842/cmd/gorelease/proxy_test.go (about) 1 // Copyright 2019 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 main 6 7 import ( 8 "bytes" 9 "fmt" 10 "io" 11 "os" 12 "path/filepath" 13 "sort" 14 "strings" 15 "time" 16 17 "golang.org/x/mod/module" 18 "golang.org/x/mod/semver" 19 "golang.org/x/mod/zip" 20 "golang.org/x/tools/txtar" 21 ) 22 23 // buildProxyDir constructs a temporary directory suitable for use as a 24 // module proxy with a file:// URL. The caller is responsible for deleting 25 // the directory when it's no longer needed. 26 // 27 // proxyVersions must be a map of module version true. If proxyVersions is 28 // empty, all modules in mod/ will be included in the proxy list. If proxy 29 // versions is non-empty, only those modules in mod/ that match an entry in 30 // proxyVersions will be included. 31 func buildProxyDir(proxyVersions map[module.Version]bool, tests []*test) (proxyDir, proxyURL string, err error) { 32 proxyDir, err = os.MkdirTemp("", "gorelease-proxy") 33 if err != nil { 34 return "", "", err 35 } 36 37 txtarPaths, err := filepath.Glob(filepath.FromSlash("testdata/mod/*.txt")) 38 if err != nil { 39 return "", "", err 40 } 41 42 // Map of modPath to versions for that modPath. 43 versionLists := make(map[string][]string) 44 45 for _, t := range tests { 46 versionLists[t.modPath] = []string{} 47 modDir := filepath.Join(proxyDir, t.modPath, "@v") 48 if err := os.MkdirAll(modDir, 0777); err != nil { 49 return "", "", err 50 } 51 } 52 53 for _, txtarPath := range txtarPaths { 54 base := filepath.Base(txtarPath) 55 stem := base[:len(base)-len(".txt")] 56 i := strings.LastIndexByte(base, '_') 57 if i < 0 { 58 return "", "", fmt.Errorf("invalid module archive: %s", base) 59 } 60 modPath := strings.ReplaceAll(stem[:i], "_", "/") 61 version := stem[i+1:] 62 mv := module.Version{ 63 Path: modPath, 64 Version: version, 65 } 66 67 // User has supplied proxyVersions. Honor proxy versions by only 68 // accepting those versions supplied in proxyVersions. 69 if len(proxyVersions) > 0 { 70 if !proxyVersions[mv] { 71 // modPath@version is not in proxyVersions: skip. 72 continue 73 } 74 } 75 76 versionLists[modPath] = append(versionLists[modPath], version) 77 78 modDir := filepath.Join(proxyDir, modPath, "@v") 79 if err := os.MkdirAll(modDir, 0777); err != nil { 80 return "", "", err 81 } 82 83 arc, err := txtar.ParseFile(txtarPath) 84 if err != nil { 85 return "", "", err 86 } 87 88 isCanonical := version == module.CanonicalVersion(version) 89 var zipContents []zip.File 90 var haveInfo, haveMod bool 91 var goMod txtar.File 92 for _, af := range arc.Files { 93 if !isCanonical && af.Name != ".info" { 94 return "", "", fmt.Errorf("%s: version is non-canonical but contains files other than .info", txtarPath) 95 } 96 if af.Name == ".info" || af.Name == ".mod" { 97 if af.Name == ".info" { 98 haveInfo = true 99 } else { 100 haveMod = true 101 } 102 outPath := filepath.Join(modDir, version+af.Name) 103 if err := os.WriteFile(outPath, af.Data, 0666); err != nil { 104 return "", "", err 105 } 106 continue 107 } 108 if af.Name == "go.mod" { 109 goMod = af 110 } 111 112 zipContents = append(zipContents, txtarFile{af}) 113 } 114 if !isCanonical && !haveInfo { 115 return "", "", fmt.Errorf("%s: version is non-canonical but does not have .info", txtarPath) 116 } 117 118 if !haveInfo { 119 outPath := filepath.Join(modDir, version+".info") 120 outContent := fmt.Sprintf(`{"Version":"%s"}`, version) 121 if err := os.WriteFile(outPath, []byte(outContent), 0666); err != nil { 122 return "", "", err 123 } 124 } 125 if !haveMod && goMod.Name != "" { 126 outPath := filepath.Join(modDir, version+".mod") 127 if err := os.WriteFile(outPath, goMod.Data, 0666); err != nil { 128 return "", "", err 129 } 130 } 131 132 if len(zipContents) > 0 { 133 zipPath := filepath.Join(modDir, version+".zip") 134 zipFile, err := os.Create(zipPath) 135 if err != nil { 136 return "", "", err 137 } 138 defer zipFile.Close() 139 if err := zip.Create(zipFile, module.Version{Path: modPath, Version: version}, zipContents); err != nil { 140 return "", "", err 141 } 142 if err := zipFile.Close(); err != nil { 143 return "", "", err 144 } 145 } 146 } 147 148 buf := &bytes.Buffer{} 149 for modPath, versions := range versionLists { 150 outPath := filepath.Join(proxyDir, modPath, "@v", "list") 151 sort.Slice(versions, func(i, j int) bool { 152 return semver.Compare(versions[i], versions[j]) < 0 153 }) 154 for _, v := range versions { 155 fmt.Fprintln(buf, v) 156 } 157 if err := os.WriteFile(outPath, buf.Bytes(), 0666); err != nil { 158 return "", "", err 159 } 160 buf.Reset() 161 } 162 163 // Make sure the URL path starts with a slash on Windows. Absolute paths 164 // normally start with a drive letter. 165 // TODO(golang.org/issue/32456): use url.FromFilePath when implemented. 166 if strings.HasPrefix(proxyDir, "/") { 167 proxyURL = "file://" + proxyDir 168 } else { 169 proxyURL = "file:///" + filepath.FromSlash(proxyDir) 170 } 171 return proxyDir, proxyURL, nil 172 } 173 174 type txtarFile struct { 175 f txtar.File 176 } 177 178 func (f txtarFile) Path() string { return f.f.Name } 179 func (f txtarFile) Lstat() (os.FileInfo, error) { return txtarFileInfo{f.f}, nil } 180 func (f txtarFile) Open() (io.ReadCloser, error) { 181 return io.NopCloser(bytes.NewReader(f.f.Data)), nil 182 } 183 184 type txtarFileInfo struct { 185 f txtar.File 186 } 187 188 func (f txtarFileInfo) Name() string { return f.f.Name } 189 func (f txtarFileInfo) Size() int64 { return int64(len(f.f.Data)) } 190 func (f txtarFileInfo) Mode() os.FileMode { return 0444 } 191 func (f txtarFileInfo) ModTime() time.Time { return time.Time{} } 192 func (f txtarFileInfo) IsDir() bool { return false } 193 func (f txtarFileInfo) Sys() interface{} { return nil } 194 195 func extractTxtar(destDir string, arc *txtar.Archive) error { 196 for _, f := range arc.Files { 197 outPath := filepath.Join(destDir, f.Name) 198 if err := os.MkdirAll(filepath.Dir(outPath), 0777); err != nil { 199 return err 200 } 201 if err := os.WriteFile(outPath, f.Data, 0666); err != nil { 202 return err 203 } 204 } 205 return nil 206 }