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.
17 "golang.org/x/tools/internal/gocommand"
18 "golang.org/x/tools/internal/packagesinternal"
19 "golang.org/x/tools/internal/proxydir"
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 // golang.org/repoa#a/a.go
27 // golang.org/repob#b/b.go
28 // You would get the directory layout
29 // /sometemporarydirectory
38 // and the working directory would be
39 // /sometemporarydirectory/repoa
40 var Modules = modules{}
44 type moduleAtVersion struct {
49 func (modules) Name() string {
53 func (modules) Filename(exported *Exported, module, fragment string) string {
54 if module == exported.primary {
55 return filepath.Join(primaryDir(exported), fragment)
57 return filepath.Join(moduleDir(exported, module), fragment)
60 func (modules) Finalize(exported *Exported) error {
61 // Write out the primary module. This module can use symlinks and
62 // other weird stuff, and will be the working dir for the go command.
63 // It depends on all the other modules.
64 primaryDir := primaryDir(exported)
65 if err := os.MkdirAll(primaryDir, 0755); err != nil {
68 exported.Config.Dir = primaryDir
69 if exported.written[exported.primary] == nil {
70 exported.written[exported.primary] = make(map[string]string)
73 // Create a map of modulepath -> {module, version} for modulepaths
74 // that are of the form `repoa/mod1@v1.1.0`.
75 versions := make(map[string]moduleAtVersion)
76 for module := range exported.written {
77 if splt := strings.Split(module, "@"); len(splt) > 1 {
78 versions[module] = moduleAtVersion{
85 // If the primary module already has a go.mod, write the contents to a temp
86 // go.mod for now and then we will reset it when we are getting all the markers.
87 if gomod := exported.written[exported.primary]["go.mod"]; gomod != "" {
88 contents, err := ioutil.ReadFile(gomod)
92 if err := ioutil.WriteFile(gomod+".temp", contents, 0644); err != nil {
97 exported.written[exported.primary]["go.mod"] = filepath.Join(primaryDir, "go.mod")
98 primaryGomod := "module " + exported.primary + "\nrequire (\n"
99 for other := range exported.written {
100 if other == exported.primary {
103 version := moduleVersion(other)
104 // If other is of the form `repo1/mod1@v1.1.0`,
105 // then we need to extract the module and the version.
106 if v, ok := versions[other]; ok {
110 primaryGomod += fmt.Sprintf("\t%v %v\n", other, version)
112 primaryGomod += ")\n"
113 if err := ioutil.WriteFile(filepath.Join(primaryDir, "go.mod"), []byte(primaryGomod), 0644); err != nil {
117 // Create the mod cache so we can rename it later, even if we don't need it.
118 if err := os.MkdirAll(modCache(exported), 0755); err != nil {
122 // Write out the go.mod files for the other modules.
123 for module, files := range exported.written {
124 if module == exported.primary {
127 dir := moduleDir(exported, module)
128 modfile := filepath.Join(dir, "go.mod")
129 // If other is of the form `repo1/mod1@v1.1.0`,
130 // then we need to extract the module name without the version.
131 if v, ok := versions[module]; ok {
134 if err := ioutil.WriteFile(modfile, []byte("module "+module+"\n"), 0644); err != nil {
137 files["go.mod"] = modfile
140 // Zip up all the secondary modules into the proxy dir.
141 modProxyDir := filepath.Join(exported.temp, "modproxy")
142 for module, files := range exported.written {
143 if module == exported.primary {
146 version := moduleVersion(module)
147 // If other is of the form `repo1/mod1@v1.1.0`,
148 // then we need to extract the module and the version.
149 if v, ok := versions[module]; ok {
153 if err := writeModuleFiles(modProxyDir, module, version, files); err != nil {
154 return fmt.Errorf("creating module proxy dir for %v: %v", module, err)
158 // Discard the original mod cache dir, which contained the files written
160 if err := os.Rename(modCache(exported), modCache(exported)+".orig"); err != nil {
163 exported.Config.Env = append(exported.Config.Env,
165 "GOPATH="+filepath.Join(exported.temp, "modcache"),
167 "GOPROXY="+proxydir.ToURL(modProxyDir),
170 gocmdRunner := &gocommand.Runner{}
171 packagesinternal.SetGoCmdRunner(exported.Config, gocmdRunner)
173 // Run go mod download to recreate the mod cache dir with all the extra
174 // stuff in cache. All the files created by Export should be recreated.
175 inv := gocommand.Invocation{
177 Args: []string{"download"},
178 Env: exported.Config.Env,
179 BuildFlags: exported.Config.BuildFlags,
180 WorkingDir: exported.Config.Dir,
182 if _, err := gocmdRunner.Run(context.Background(), inv); err != nil {
188 func writeModuleFiles(rootDir, module, ver string, filePaths map[string]string) error {
189 fileData := make(map[string][]byte)
190 for name, path := range filePaths {
191 contents, err := ioutil.ReadFile(path)
195 fileData[name] = contents
197 return proxydir.WriteModuleVersion(rootDir, module, ver, fileData)
200 func modCache(exported *Exported) string {
201 return filepath.Join(exported.temp, "modcache/pkg/mod")
204 func primaryDir(exported *Exported) string {
205 return filepath.Join(exported.temp, path.Base(exported.primary))
208 func moduleDir(exported *Exported, module string) string {
209 if strings.Contains(module, "@") {
210 return filepath.Join(modCache(exported), module)
212 return filepath.Join(modCache(exported), path.Dir(module), path.Base(module)+"@"+moduleVersion(module))
215 var versionSuffixRE = regexp.MustCompile(`v\d+`)
217 func moduleVersion(module string) string {
218 if versionSuffixRE.MatchString(path.Base(module)) {
219 return path.Base(module) + ".0.0"