github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/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  	"io/ioutil"
    11  	"os"
    12  	"path"
    13  	"path/filepath"
    14  	"regexp"
    15  	"strings"
    16  
    17  	"github.com/powerman/golang-tools/internal/gocommand"
    18  	"github.com/powerman/golang-tools/internal/packagesinternal"
    19  	"github.com/powerman/golang-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  //     golang.org/repoa#a/a.go
    27  //     golang.org/repob#b/b.go
    28  // You would get the directory layout
    29  //     /sometemporarydirectory
    30  //     ├── repoa
    31  //     │   ├── a
    32  //     │   │   └── a.go
    33  //     │   └── go.mod
    34  //     └── repob
    35  //         ├── b
    36  //         │   └── b.go
    37  //         └── go.mod
    38  // and the working directory would be
    39  //     /sometemporarydirectory/repoa
    40  var Modules = modules{}
    41  
    42  type modules struct{}
    43  
    44  type moduleAtVersion struct {
    45  	module  string
    46  	version string
    47  }
    48  
    49  func (modules) Name() string {
    50  	return "Modules"
    51  }
    52  
    53  func (modules) Filename(exported *Exported, module, fragment string) string {
    54  	if module == exported.primary {
    55  		return filepath.Join(primaryDir(exported), fragment)
    56  	}
    57  	return filepath.Join(moduleDir(exported, module), fragment)
    58  }
    59  
    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 {
    66  		return err
    67  	}
    68  	exported.Config.Dir = primaryDir
    69  	if exported.written[exported.primary] == nil {
    70  		exported.written[exported.primary] = make(map[string]string)
    71  	}
    72  
    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{
    79  				module:  splt[0],
    80  				version: splt[1],
    81  			}
    82  		}
    83  	}
    84  
    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)
    89  		if err != nil {
    90  			return err
    91  		}
    92  		if err := ioutil.WriteFile(gomod+".temp", contents, 0644); err != nil {
    93  			return err
    94  		}
    95  	}
    96  
    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 {
   101  			continue
   102  		}
   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 {
   107  			other = v.module
   108  			version = v.version
   109  		}
   110  		primaryGomod += fmt.Sprintf("\t%v %v\n", other, version)
   111  	}
   112  	primaryGomod += ")\n"
   113  	if err := ioutil.WriteFile(filepath.Join(primaryDir, "go.mod"), []byte(primaryGomod), 0644); err != nil {
   114  		return err
   115  	}
   116  
   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 {
   119  		return err
   120  	}
   121  
   122  	// Write out the go.mod files for the other modules.
   123  	for module, files := range exported.written {
   124  		if module == exported.primary {
   125  			continue
   126  		}
   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 {
   132  			module = v.module
   133  		}
   134  		if err := ioutil.WriteFile(modfile, []byte("module "+module+"\n"), 0644); err != nil {
   135  			return err
   136  		}
   137  		files["go.mod"] = modfile
   138  	}
   139  
   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 {
   144  			continue
   145  		}
   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 {
   150  			module = v.module
   151  			version = v.version
   152  		}
   153  		if err := writeModuleFiles(modProxyDir, module, version, files); err != nil {
   154  			return fmt.Errorf("creating module proxy dir for %v: %v", module, err)
   155  		}
   156  	}
   157  
   158  	// Discard the original mod cache dir, which contained the files written
   159  	// for us by Export.
   160  	if err := os.Rename(modCache(exported), modCache(exported)+".orig"); err != nil {
   161  		return err
   162  	}
   163  	exported.Config.Env = append(exported.Config.Env,
   164  		"GO111MODULE=on",
   165  		"GOPATH="+filepath.Join(exported.temp, "modcache"),
   166  		"GOMODCACHE=",
   167  		"GOPROXY="+proxydir.ToURL(modProxyDir),
   168  		"GOSUMDB=off",
   169  	)
   170  	gocmdRunner := &gocommand.Runner{}
   171  	packagesinternal.SetGoCmdRunner(exported.Config, gocmdRunner)
   172  
   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{
   176  		Verb:       "mod",
   177  		Args:       []string{"download", "all"},
   178  		Env:        exported.Config.Env,
   179  		BuildFlags: exported.Config.BuildFlags,
   180  		WorkingDir: exported.Config.Dir,
   181  	}
   182  	if _, err := gocmdRunner.Run(context.Background(), inv); err != nil {
   183  		return err
   184  	}
   185  	return nil
   186  }
   187  
   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)
   192  		if err != nil {
   193  			return err
   194  		}
   195  		fileData[name] = contents
   196  	}
   197  	return proxydir.WriteModuleVersion(rootDir, module, ver, fileData)
   198  }
   199  
   200  func modCache(exported *Exported) string {
   201  	return filepath.Join(exported.temp, "modcache/pkg/mod")
   202  }
   203  
   204  func primaryDir(exported *Exported) string {
   205  	return filepath.Join(exported.temp, path.Base(exported.primary))
   206  }
   207  
   208  func moduleDir(exported *Exported, module string) string {
   209  	if strings.Contains(module, "@") {
   210  		return filepath.Join(modCache(exported), module)
   211  	}
   212  	return filepath.Join(modCache(exported), path.Dir(module), path.Base(module)+"@"+moduleVersion(module))
   213  }
   214  
   215  var versionSuffixRE = regexp.MustCompile(`v\d+`)
   216  
   217  func moduleVersion(module string) string {
   218  	if versionSuffixRE.MatchString(path.Base(module)) {
   219  		return path.Base(module) + ".0.0"
   220  	}
   221  	return "v1.0.0"
   222  }