1
2
3
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
24
25
26
27
28
29
30
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
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
68
69 if len(proxyVersions) > 0 {
70 if !proxyVersions[mv] {
71
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
164
165
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 }
207
View as plain text