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 | } |
228 |
Members