gomod2nix/fetch/fetch.go

274 lines
6.3 KiB
Go
Raw Normal View History

2020-07-20 11:38:32 +00:00
package fetch
2020-07-20 10:52:58 +00:00
import (
"encoding/json"
"fmt"
2020-07-21 09:42:32 +00:00
log "github.com/sirupsen/logrus"
2020-07-21 07:20:36 +00:00
"github.com/tweag/gomod2nix/formats/buildgopackage"
"github.com/tweag/gomod2nix/formats/gomod2nix"
2020-07-20 11:38:32 +00:00
"github.com/tweag/gomod2nix/types"
2020-07-20 11:29:04 +00:00
"golang.org/x/mod/modfile"
"golang.org/x/mod/module"
2020-07-20 10:52:58 +00:00
"golang.org/x/tools/go/vcs"
2020-07-20 11:29:04 +00:00
"io/ioutil"
2020-07-22 11:57:13 +00:00
"os"
2020-07-20 10:52:58 +00:00
"os/exec"
2020-07-22 11:57:13 +00:00
"path/filepath"
"regexp"
2020-07-20 11:29:04 +00:00
"sort"
"strings"
2020-07-20 10:52:58 +00:00
)
2020-07-20 11:38:32 +00:00
type packageJob struct {
importPath string
goPackagePath string
sumVersion string
2020-07-20 10:52:58 +00:00
}
2020-07-20 11:38:32 +00:00
type packageResult struct {
pkg *types.Package
err error
}
2020-07-21 07:20:36 +00:00
func worker(id int, caches []map[string]*types.Package, jobs <-chan *packageJob, results chan<- *packageResult) {
2020-07-21 09:42:32 +00:00
log.WithField("workerId", id).Info("Starting worker process")
2020-07-20 11:38:32 +00:00
for j := range jobs {
2020-07-21 09:42:32 +00:00
log.WithFields(log.Fields{
"workerId": id,
"goPackagePath": j.goPackagePath,
}).Info("Worker received job")
pkg, err := fetchPackage(caches, j.importPath, j.goPackagePath, j.sumVersion)
2020-07-20 11:38:32 +00:00
results <- &packageResult{
err: err,
pkg: pkg,
}
}
}
2020-07-21 07:20:36 +00:00
func FetchPackages(goModPath string, goSumPath string, goMod2NixPath string, depsNixPath string, numWorkers int, keepGoing bool) ([]*types.Package, error) {
2020-07-20 10:52:58 +00:00
2020-07-21 09:42:32 +00:00
log.WithFields(log.Fields{
"modPath": goModPath,
}).Info("Parsing go.mod")
2020-07-20 11:29:04 +00:00
// Read go.mod
data, err := ioutil.ReadFile(goModPath)
if err != nil {
return nil, err
}
// Parse go.mod
mod, err := modfile.Parse(goModPath, data, nil)
if err != nil {
return nil, err
}
2020-07-21 09:42:32 +00:00
caches := []map[string]*types.Package{}
goModCache := gomod2nix.LoadGomod2Nix(goMod2NixPath)
if len(goModCache) > 0 {
caches = append(caches, goModCache)
}
buildGoCache := buildgopackage.LoadDepsNix(depsNixPath)
if len(buildGoCache) > 0 {
caches = append(caches, buildGoCache)
2020-07-21 07:20:36 +00:00
}
2020-07-20 11:29:04 +00:00
// Map repos -> replacement repo
replace := make(map[string]string)
for _, repl := range mod.Replace {
replace[repl.New.Path] = repl.Old.Path
2020-07-20 11:29:04 +00:00
}
2020-07-21 09:42:32 +00:00
log.WithFields(log.Fields{
"sumPath": goSumPath,
}).Info("Parsing go.sum")
sumVersions, err := parseGoSum(goSumPath)
2020-07-20 11:29:04 +00:00
if err != nil {
return nil, err
2020-07-20 10:52:58 +00:00
}
numJobs := len(sumVersions)
2020-07-20 11:29:04 +00:00
if numJobs < numWorkers {
numWorkers = numJobs
}
2020-07-21 09:42:32 +00:00
log.WithFields(log.Fields{
"numWorkers": numWorkers,
}).Info("Starting worker processes")
2020-07-20 11:29:04 +00:00
jobs := make(chan *packageJob, numJobs)
results := make(chan *packageResult, numJobs)
for i := 0; i <= numWorkers; i++ {
2020-07-21 07:20:36 +00:00
go worker(i, caches, jobs, results)
2020-07-20 11:29:04 +00:00
}
2020-07-21 09:42:32 +00:00
log.WithFields(log.Fields{
"numJobs": numJobs,
}).Info("Queuing jobs")
for importPath, sumVersion := range sumVersions {
2020-07-20 11:29:04 +00:00
// Check for replacement path (only original goPackagePath is recorded in go.sum)
goPackagePath := importPath
2020-07-20 11:29:04 +00:00
v, ok := replace[goPackagePath]
if ok {
goPackagePath = v
2020-07-20 11:29:04 +00:00
}
2020-07-20 11:29:04 +00:00
jobs <- &packageJob{
importPath: importPath,
goPackagePath: goPackagePath,
sumVersion: sumVersion,
2020-07-20 11:29:04 +00:00
}
}
close(jobs)
2020-07-20 11:38:32 +00:00
var pkgs []*types.Package
2020-07-20 11:29:04 +00:00
for i := 1; i <= numJobs; i++ {
result := <-results
2020-07-21 09:42:32 +00:00
log.WithFields(log.Fields{
"current": i,
"total": numJobs,
}).Info("Received finished job")
2020-07-20 11:29:04 +00:00
if result.err != nil {
if keepGoing {
fmt.Println(result.err)
continue
} else {
return nil, result.err
}
}
pkgs = append(pkgs, result.pkg)
}
sort.Slice(pkgs, func(i, j int) bool {
return pkgs[i].GoPackagePath < pkgs[j].GoPackagePath
})
return pkgs, nil
}
func fetchPackage(caches []map[string]*types.Package, importPath string, goPackagePath string, sumVersion string) (*types.Package, error) {
2020-07-20 11:29:04 +00:00
repoRoot, err := vcs.RepoRootForImportPath(importPath, false)
2020-07-20 10:52:58 +00:00
if err != nil {
return nil, err
}
commitShaRev := regexp.MustCompile(`v\d+\.\d+\.\d+-[\d+\.a-zA-Z]*?[0-9]{14}-(.*?)$`)
rev := strings.TrimSuffix(sumVersion, "+incompatible")
if commitShaRev.MatchString(rev) {
rev = commitShaRev.FindAllStringSubmatch(rev, -1)[0][1]
}
2020-07-22 11:57:13 +00:00
goPackagePathPrefix, pathMajor, _ := module.SplitPathVersion(goPackagePath)
// Relative path within the repo
relPath := strings.TrimPrefix(goPackagePathPrefix, repoRoot.Root+"/")
if relPath == goPackagePathPrefix {
relPath = ""
}
2020-07-21 09:42:32 +00:00
if len(caches) > 0 {
log.WithFields(log.Fields{
"goPackagePath": goPackagePath,
}).Info("Checking previous invocation cache")
for _, cache := range caches {
cached, ok := cache[goPackagePath]
if ok {
2020-07-22 11:57:13 +00:00
if cached.SumVersion == sumVersion {
log.WithFields(log.Fields{
"goPackagePath": goPackagePath,
}).Info("Returning cached entry")
return cached, nil
2020-07-21 07:20:36 +00:00
}
}
}
}
2020-07-20 10:52:58 +00:00
if repoRoot.VCS.Name != "Git" {
return nil, fmt.Errorf("Only git repositories are supported")
}
type prefetchOutput struct {
URL string `json:"url"`
Rev string `json:"rev"`
Sha256 string `json:"sha256"`
2020-07-22 11:57:13 +00:00
Path string `json:"path"`
2020-07-20 10:52:58 +00:00
// date string
// fetchSubmodules bool
// deepClone bool
// leaveDotGit bool
}
2020-07-21 09:42:32 +00:00
log.WithFields(log.Fields{
"goPackagePath": goPackagePath,
"rev": rev,
}).Info("Cache miss, fetching")
2020-07-20 10:52:58 +00:00
stdout, err := exec.Command(
"nix-prefetch-git",
"--quiet",
"--fetch-submodules",
"--url", repoRoot.Repo,
"--rev", rev).Output()
if err != nil {
2020-07-22 11:57:13 +00:00
newRev := fmt.Sprintf("%s/%s", relPath, rev)
2020-07-21 09:42:32 +00:00
log.WithFields(log.Fields{
"goPackagePath": goPackagePath,
"rev": newRev,
}).Info("Fetching failed, retrying with different rev format")
originalErr := err
stdout, err = exec.Command(
"nix-prefetch-git",
"--quiet",
"--fetch-submodules",
"--url", repoRoot.Repo,
"--rev", newRev).Output()
if err != nil {
2020-07-21 09:42:32 +00:00
log.WithFields(log.Fields{
"goPackagePath": goPackagePath,
}).Error("Fetching failed")
return nil, originalErr
}
rev = newRev
2020-07-20 10:52:58 +00:00
}
var output *prefetchOutput
err = json.Unmarshal(stdout, &output)
if err != nil {
return nil, err
}
vendorPath := ""
if importPath != goPackagePath {
2020-07-22 11:57:13 +00:00
vendorPath = importPath
}
if relPath == "" && pathMajor != "" {
p := filepath.Join(output.Path, pathMajor)
_, err := os.Stat(p)
if err == nil {
fmt.Println(pathMajor)
relPath = strings.TrimPrefix(pathMajor, "/")
}
}
2020-07-20 11:38:32 +00:00
return &types.Package{
2020-07-20 10:52:58 +00:00
GoPackagePath: goPackagePath,
URL: repoRoot.Repo,
Rev: output.Rev,
Sha256: output.Sha256,
// This is used to skip fetching where the previous package path & versions are still the same
// It's also used to construct the vendor directory in the Nix build
SumVersion: sumVersion,
RelPath: relPath,
VendorPath: vendorPath,
2020-07-20 10:52:58 +00:00
}, nil
}