Skip to content

Commit

Permalink
mod/modfile: add ModuleForImportPath
Browse files Browse the repository at this point in the history
This makes it straightforward to resolve modules and default major
versions for packages without consulting a registry or invoking any of
the code inside internal/mod. This is valid to do for packages that
are part of the current dependency graph as long as the module is
tidy, because the tidy algorithm guarantees that all dependency
modules are present in the module.cue file.

Signed-off-by: Roger Peppe <[email protected]>
Change-Id: Ia0d86b335e2c1f2963e75f7901322fb55a842643
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1207985
Reviewed-by: Daniel Martí <[email protected]>
TryBot-Result: CUEcueckoo <[email protected]>
Unity-Result: CUE porcuepine <[email protected]>
  • Loading branch information
rogpeppe committed Jan 30, 2025
1 parent c269767 commit b684a52
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 17 deletions.
50 changes: 43 additions & 7 deletions mod/modfile/modfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ package modfile
import (
_ "embed"
"fmt"
"path"
"slices"
"strings"
"sync"
Expand Down Expand Up @@ -53,12 +54,13 @@ type File struct {
// Use the [File.QualifiedModule] method to obtain a module
// path that's always qualified. See also the
// [File.ModulePath] and [File.MajorVersion] methods.
Module string `json:"module"`
Language *Language `json:"language,omitempty"`
Source *Source `json:"source,omitempty"`
Deps map[string]*Dep `json:"deps,omitempty"`
Custom map[string]map[string]any `json:"custom,omitempty"`
versions []module.Version
Module string `json:"module"`
Language *Language `json:"language,omitempty"`
Source *Source `json:"source,omitempty"`
Deps map[string]*Dep `json:"deps,omitempty"`
Custom map[string]map[string]any `json:"custom,omitempty"`
versions []module.Version
versionByModule map[string]module.Version
// defaultMajorVersions maps from module base path to the
// major version default for that path.
defaultMajorVersions map[string]string
Expand Down Expand Up @@ -463,6 +465,7 @@ func parse(modfile []byte, filename string, strict bool) (*File, error) {
return nil, fmt.Errorf("language version %v in %s is not canonical", vers, filename)
}
}
mf.versionByModule = make(map[string]module.Version)
var versions []module.Version
defaultMajorVersions := make(map[string]string)
if mainPath != "" {
Expand All @@ -486,8 +489,17 @@ func parse(modfile []byte, filename string, strict bool) (*File, error) {
}
defaultMajorVersions[mp] = semver.Major(vers.Version())
}
mf.versionByModule[vers.Path()] = vers
}
if mainPath != "" {
// We don't necessarily have a full version for the main module.
mainWithMajor := mainPath + "@" + mainMajor
mainVersion, err := module.NewVersion(mainWithMajor, "")
if err != nil {
return nil, err
}
mf.versionByModule[mainWithMajor] = mainVersion
}

if len(defaultMajorVersions) > 0 {
mf.defaultMajorVersions = defaultMajorVersions
}
Expand Down Expand Up @@ -582,3 +594,27 @@ func (f *File) DepVersions() []module.Version {
func (f *File) DefaultMajorVersions() map[string]string {
return f.defaultMajorVersions
}

// ModuleForImportPath returns the module that should contain the given
// import path and reports whether the module was found.
// It does not check to see if the import path actually exists within the module.
//
// It works entirely from information in f, meaning that it does
// not consult a registry to resolve a package whose module is not
// mentioned in the file, which means it will not work in general unless
// the module is tidy (as with `cue mod tidy`).
func (f *File) ModuleForImportPath(importPath string) (module.Version, bool) {
ip := module.ParseImportPath(importPath)
for prefix := ip.Path; prefix != "."; prefix = path.Dir(prefix) {
pkgVersion := ip.Version
if pkgVersion == "" {
if pkgVersion = f.defaultMajorVersions[prefix]; pkgVersion == "" {
continue
}
}
if mv, ok := f.versionByModule[prefix+"@"+pkgVersion]; ok {
return mv, true
}
}
return module.Version{}, false
}
76 changes: 66 additions & 10 deletions mod/modfile/modfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,14 @@ import (
)

var parseTests = []struct {
testName string
parse func(modfile []byte, filename string) (*File, error)
data string
wantError string
want *File
wantVersions []module.Version
wantDefaults map[string]string
testName string
parse func(modfile []byte, filename string) (*File, error)
data string
wantError string
want *File
wantVersions []module.Version
wantDefaults map[string]string
wantModVersionForPkg map[string]string
}{{
testName: "NoDeps",
parse: Parse,
Expand All @@ -50,6 +51,15 @@ language: version: "v0.8.0-alpha.0"
wantDefaults: map[string]string{
"foo.com/bar": "v0",
},
wantModVersionForPkg: map[string]string{
"foo.com/bar": "foo.com/bar@v0",
"foo.com/bar@v0": "foo.com/bar@v0",
"foo.com/bar/baz@v0": "foo.com/bar@v0",
"foo.com/bar@v1": "",
"foo.com/bar:hello": "foo.com/bar@v0",
"foo.com/bar/baz:hello": "foo.com/bar@v0",
"foo.com/bar/baz@v0:hello": "foo.com/bar@v0",
},
}, {
testName: "WithDeps",
parse: Parse,
Expand All @@ -60,6 +70,11 @@ deps: "example.com@v1": {
default: true
v: "v1.2.3"
}
deps: "example.com/other@v1": v: "v1.9.10"
deps: "example.com/other/more/nested@v2": {
v: "v2.9.20"
default: true
}
deps: "other.com/something@v0": v: "v0.2.3"
`,
want: &File{
Expand All @@ -75,12 +90,36 @@ deps: "other.com/something@v0": v: "v0.2.3"
"other.com/something@v0": {
Version: "v0.2.3",
},
"example.com/other@v1": {
Version: "v1.9.10",
},
"example.com/other/more/nested@v2": {
Version: "v2.9.20",
Default: true,
},
},
},
wantVersions: parseVersions("[email protected]", "other.com/[email protected]"),
wantVersions: parseVersions(
"example.com/other/more/[email protected]",
"example.com/[email protected]",
"[email protected]",
"other.com/[email protected]",
),
wantDefaults: map[string]string{
"foo.com/bar": "v0",
"example.com": "v1",
"example.com/other/more/nested": "v2",
"foo.com/bar": "v0",
"example.com": "v1",
},
wantModVersionForPkg: map[string]string{
"example.com": "[email protected]",
"example.com/x/y@v1": "[email protected]",
"example.com/x/y@v1:x": "[email protected]",
"example.com/other@v1": "example.com/[email protected]",
"example.com/other/p@v1": "example.com/[email protected]",
"example.com/other/more": "[email protected]",
"example.com/other/more@v1": "example.com/[email protected]",
"example.com/other/more/nested": "example.com/other/more/[email protected]",
"example.com/other/more/nested/x:p": "example.com/other/more/[email protected]",
},
}, {
testName: "WithSource",
Expand Down Expand Up @@ -270,6 +309,12 @@ deps: "example.com": v: "v1.2.3"
wantDefaults: map[string]string{
"foo.com/bar": "v0",
},
wantModVersionForPkg: map[string]string{
"example.com": "", // No default major version.
"example.com@v1": "[email protected]",
"example.com/x/y@v1": "[email protected]",
"example.com/x/y@v1:x": "[email protected]",
},
}, {
testName: "LegacyWithExtraFields",
parse: ParseLegacy,
Expand Down Expand Up @@ -445,6 +490,17 @@ func TestParse(t *testing.T) {
qt.Assert(t, qt.Equals(f.ModulePath(), f.Module))
qt.Assert(t, qt.Equals(f.MajorVersion(), "v0"))
}
for p, m := range test.wantModVersionForPkg {
t.Run("package-"+p, func(t *testing.T) {
mv, ok := f.ModuleForImportPath(p)
if m == "" {
qt.Assert(t, qt.IsFalse(ok), qt.Commentf("got version %v", mv))
return
}
qt.Check(t, qt.IsTrue(ok))
qt.Check(t, qt.Equals(mv.String(), m))
})
}
})
}
}
Expand Down

0 comments on commit b684a52

Please sign in to comment.