Skip to content

Commit b5a6e24

Browse files
author
Jay Conrod
committed
cmd/gorelease: support comparing replacement modules
Downloaded modules (currently, those fetched according to the -base flag) may now have module paths in go.mod different than the module path used to fetch them with 'go mod download'. This lets gorelease compare modules that have been copied to another location (e.g., a soft fork). The module path in go.mod is used when loading packages. Fixes golang/go#39666 Change-Id: I33bbdab3fe5c4374f749fb965e9cc7339a1f6a8f Reviewed-on: https://go-review.googlesource.com/c/exp/+/277116 Trust: Jay Conrod <[email protected]> Trust: Jean de Klerk <[email protected]> Run-TryBot: Jay Conrod <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Jean de Klerk <[email protected]>
1 parent a20c86d commit b5a6e24

File tree

3 files changed

+70
-45
lines changed

3 files changed

+70
-45
lines changed

cmd/gorelease/gorelease.go

Lines changed: 40 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ func runRelease(w io.Writer, dir string, args []string) (success bool, err error
243243
type moduleInfo struct {
244244
modRoot string // module root directory
245245
repoRoot string // repository root directory (may be "")
246-
modPath string // module path
246+
modPath string // module path in go.mod
247247
version string // resolved version or "none"
248248
versionQuery string // a query like "latest" or "dev-branch", if specified
249249
versionInferred bool // true if the version was unspecified and inferred
@@ -393,7 +393,9 @@ func loadLocalModule(modRoot, repoRoot, version string) (m moduleInfo, err error
393393
// loadDownloadedModule downloads a module and loads information about it and
394394
// its packages from the module cache.
395395
//
396-
// modPath is the module's path.
396+
// modPath is the module path used to fetch the module. The module's path in
397+
// go.mod (m.modPath) may be different, for example in a soft fork intended as
398+
// a replacement.
397399
//
398400
// version is the version to load. It may be "none" (indicating nothing should
399401
// be loaded), "" (the highest available version below max should be used), a
@@ -403,9 +405,6 @@ func loadLocalModule(modRoot, repoRoot, version string) (m moduleInfo, err error
403405
// to max will not be considered. Typically, loadDownloadedModule is used to
404406
// load the base version, and max is the release version.
405407
func loadDownloadedModule(modPath, version, max string) (m moduleInfo, err error) {
406-
// TODO(#39666): support downloaded modules that are "soft forks", where the
407-
// module path in go.mod is different from modPath.
408-
409408
// Check the module path and version.
410409
// If the version is a query, resolve it to a canonical version.
411410
m = moduleInfo{modPath: modPath}
@@ -414,10 +413,10 @@ func loadDownloadedModule(modPath, version, max string) (m moduleInfo, err error
414413
}
415414

416415
var ok bool
417-
_, m.modPathMajor, ok = module.SplitPathVersion(m.modPath)
416+
_, m.modPathMajor, ok = module.SplitPathVersion(modPath)
418417
if !ok {
419418
// we just validated the path above.
420-
panic(fmt.Sprintf("could not find version suffix in module path %q", m.modPath))
419+
panic(fmt.Sprintf("could not find version suffix in module path %q", modPath))
421420
}
422421

423422
if version == "none" {
@@ -457,12 +456,27 @@ func loadDownloadedModule(modPath, version, max string) (m moduleInfo, err error
457456
m.version = version
458457
}
459458

460-
// Load packages.
459+
// Download the module into the cache and load the mod file.
460+
// Note that goModPath is $GOMODCACHE/cache/download/$modPath/@v/$version.mod,
461+
// which is not inside modRoot. This is what the go command uses. Even if
462+
// the module didn't have a go.mod file, one will be synthesized there.
461463
v := module.Version{Path: modPath, Version: m.version}
462-
if m.modRoot, err = downloadModule(v); err != nil {
464+
if m.modRoot, m.goModPath, err = downloadModule(v); err != nil {
465+
return moduleInfo{}, err
466+
}
467+
if m.goModData, err = ioutil.ReadFile(m.goModPath); err != nil {
468+
return moduleInfo{}, err
469+
}
470+
if m.goModFile, err = modfile.ParseLax(m.goModPath, m.goModData, nil); err != nil {
463471
return moduleInfo{}, err
464472
}
465-
tmpLoadDir, tmpGoModData, tmpGoSumData, err := prepareLoadDir(nil, modPath, m.modRoot, m.version, true)
473+
if m.goModFile.Module == nil {
474+
return moduleInfo{}, fmt.Errorf("%s: missing module directive", m.goModPath)
475+
}
476+
m.modPath = m.goModFile.Module.Mod.Path
477+
478+
// Load packages.
479+
tmpLoadDir, tmpGoModData, tmpGoSumData, err := prepareLoadDir(nil, m.modPath, m.modRoot, m.version, true)
466480
if err != nil {
467481
return moduleInfo{}, err
468482
}
@@ -471,23 +485,10 @@ func loadDownloadedModule(modPath, version, max string) (m moduleInfo, err error
471485
err = fmt.Errorf("removing temporary load directory: %v", err)
472486
}
473487
}()
474-
if m.pkgs, _, err = loadPackages(modPath, m.modRoot, tmpLoadDir, tmpGoModData, tmpGoSumData); err != nil {
488+
if m.pkgs, _, err = loadPackages(m.modPath, m.modRoot, tmpLoadDir, tmpGoModData, tmpGoSumData); err != nil {
475489
return moduleInfo{}, err
476490
}
477491

478-
// Attempt to load the mod file, if it exists.
479-
m.goModPath = filepath.Join(m.modRoot, "go.mod")
480-
if m.goModData, err = ioutil.ReadFile(m.goModPath); err != nil && !os.IsNotExist(err) {
481-
return moduleInfo{}, fmt.Errorf("reading go.mod: %v", err)
482-
}
483-
if err == nil {
484-
m.goModFile, err = modfile.ParseLax(m.goModPath, m.goModData, nil)
485-
if err != nil {
486-
return moduleInfo{}, err
487-
}
488-
}
489-
// The modfile might not exist, leading to err != nil. That's OK - continue.
490-
491492
return m, nil
492493
}
493494

@@ -578,10 +579,12 @@ func makeReleaseReport(base, release moduleInfo) (report, error) {
578579
}
579580
}
580581

581-
if release.version != "" {
582-
r.validateVersion()
583-
} else if r.similarModPaths() {
584-
r.suggestVersion()
582+
if r.canVerifyReleaseVersion() {
583+
if release.version == "" {
584+
r.suggestReleaseVersion()
585+
} else {
586+
r.validateReleaseVersion()
587+
}
585588
}
586589

587590
return r, nil
@@ -825,7 +828,7 @@ func copyModuleToTempDir(modPath, modRoot string) (dir string, err error) {
825828

826829
// downloadModule downloads a specific version of a module to the
827830
// module cache using 'go mod download'.
828-
func downloadModule(m module.Version) (modRoot string, err error) {
831+
func downloadModule(m module.Version) (modRoot, goModPath string, err error) {
829832
defer func() {
830833
if err != nil {
831834
err = &downloadError{m: m, err: cleanCmdError(err)}
@@ -840,7 +843,7 @@ func downloadModule(m module.Version) (modRoot string, err error) {
840843
// If it didn't read go.mod in this case, we wouldn't need a temp directory.
841844
tmpDir, err := ioutil.TempDir("", "gorelease-download")
842845
if err != nil {
843-
return "", err
846+
return "", "", err
844847
}
845848
defer os.Remove(tmpDir)
846849
cmd := exec.Command("go", "mod", "download", "-json", "--", m.Path+"@"+m.Version)
@@ -850,26 +853,26 @@ func downloadModule(m module.Version) (modRoot string, err error) {
850853
if err != nil {
851854
var ok bool
852855
if xerr, ok = err.(*exec.ExitError); !ok {
853-
return "", err
856+
return "", "", err
854857
}
855858
}
856859

857860
// If 'go mod download' exited unsuccessfully but printed well-formed JSON
858861
// with an error, return that error.
859-
parsed := struct{ Dir, Error string }{}
862+
parsed := struct{ Dir, GoMod, Error string }{}
860863
if jsonErr := json.Unmarshal(out, &parsed); jsonErr != nil {
861864
if xerr != nil {
862-
return "", cleanCmdError(xerr)
865+
return "", "", cleanCmdError(xerr)
863866
}
864-
return "", jsonErr
867+
return "", "", jsonErr
865868
}
866869
if parsed.Error != "" {
867-
return "", errors.New(parsed.Error)
870+
return "", "", errors.New(parsed.Error)
868871
}
869872
if xerr != nil {
870-
return "", cleanCmdError(xerr)
873+
return "", "", cleanCmdError(xerr)
871874
}
872-
return parsed.Dir, nil
875+
return parsed.Dir, parsed.GoMod, nil
873876
}
874877

875878
// prepareLoadDir creates a temporary directory and a go.mod file that requires

cmd/gorelease/report.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ func (r *report) Text(w io.Writer) error {
8787
} else {
8888
fmt.Fprintf(buf, "Suggested version: %[1]s (with tag %[2]s%[1]s)\n", r.release.version, r.release.tagPrefix)
8989
}
90-
} else if r.release.version != "" && r.similarModPaths() {
90+
} else if r.release.version != "" && r.canVerifyReleaseVersion() {
9191
if r.release.tagPrefix == "" {
9292
fmt.Fprintf(buf, "%s is a valid semantic version for this release.\n", r.release.version)
9393

@@ -131,10 +131,10 @@ func (r *report) addPackage(p packageReport) {
131131
}
132132
}
133133

134-
// validateVersion checks whether r.release.version is valid.
134+
// validateReleaseVersion checks whether r.release.version is valid.
135135
// If r.release.version is not valid, an error is returned explaining why.
136136
// r.release.version must be set.
137-
func (r *report) validateVersion() {
137+
func (r *report) validateReleaseVersion() {
138138
if r.release.version == "" {
139139
panic("validateVersion called without version")
140140
}
@@ -193,8 +193,9 @@ over the base version (%s).`, r.base.version)
193193
}
194194
}
195195

196-
// suggestVersion suggests a new version consistent with observed changes.
197-
func (r *report) suggestVersion() {
196+
// suggestReleaseVersion suggests a new version consistent with observed
197+
// changes.
198+
func (r *report) suggestReleaseVersion() {
198199
setNotValid := func(format string, args ...interface{}) {
199200
r.versionInvalid = &versionMessage{
200201
message: "Cannot suggest a release version.",
@@ -254,9 +255,13 @@ func (r *report) suggestVersion() {
254255
setVersion(fmt.Sprintf("v%s.%s.%s", major, minor, patch))
255256
}
256257

257-
// similarModPaths returns true if r.base and r.release are versions of the same
258-
// module or different major versions of the same module.
259-
func (r *report) similarModPaths() bool {
258+
// canVerifyReleaseVersion returns true if we can safely suggest a new version
259+
// or if we can verify the version passed in with -version is safe to tag.
260+
func (r *report) canVerifyReleaseVersion() bool {
261+
// For now, return true if the base and release module paths are the same,
262+
// ignoring the major version suffix.
263+
// TODO(#37562, #39192, #39666, #40267): there are many more situations when
264+
// we can't verify a new version.
260265
basePath := strings.TrimSuffix(r.base.modPath, r.base.modPathMajor)
261266
releasePath := strings.TrimSuffix(r.release.modPath, r.release.modPathMajor)
262267
return basePath == releasePath
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Compare a fork (with module path example.com/basic, downloaded from
2+
# example.com/basicfork) with a local module (with module path
3+
# example.com/basic).
4+
mod=example.com/basic
5+
version=v1.1.2
6+
base=example.com/[email protected]
7+
release=v1.1.2
8+
-- want --
9+
example.com/basicfork/a
10+
-----------------------
11+
Incompatible changes:
12+
- A3: removed
13+
14+
example.com/basicfork/b
15+
-----------------------
16+
Incompatible changes:
17+
- package removed

0 commit comments

Comments
 (0)