diff --git a/gopmod/gopmod_test.go b/gopmod/gopmod_test.go index 3ddb76f..da78342 100644 --- a/gopmod/gopmod_test.go +++ b/gopmod/gopmod_test.go @@ -18,6 +18,7 @@ package gopmod import ( "log" + "os" "path/filepath" "runtime" "testing" @@ -29,6 +30,29 @@ import ( "golang.org/x/mod/module" ) +func TestPkgId(t *testing.T) { + mod := New(modtest.GopClass(t)) + if id, err := mod.PkgId(""); err != ErrInvalidPkgPath || id != "" { + t.Fatal("mod.PkgId:", id, err) + } + if id, err := mod.PkgId("."); err != ErrInvalidPkgPath || id != "" { + t.Fatal("mod.PkgId:", id, err) + } + if id, err := mod.PkgId("fmt"); err != nil || id != "fmt" { + t.Fatal("mod.PkgId fmt:", id, err) + } + if id, err := mod.PkgId("github.com/goplus/community/bar"); err != nil || id != string(os.PathSeparator)+"foo/bar" { + t.Fatal("mod.PkgId github.com/goplus/community/bar:", id, err) + } + xpath, _ := modcache.Path(module.Version{Path: "github.com/qiniu/x", Version: "v1.13.2"}) + if id, err := mod.PkgId("github.com/qiniu/x/mockhttp"); err != nil || id != xpath+"/mockhttp" { + t.Fatal("mod.PkgId github.com/qiniu/x/mockhttp:", err) + } + if _, err := mod.PkgId("github.com/qiniu/y/mockhttp"); err == nil { + t.Fatal("mod.PkgId github.com/qiniu/y/mockhttp: no error?") + } +} + func TestLookup(t *testing.T) { mod := New(modtest.GopClass(t)) if modv, ok := mod.LookupDepMod("github.com/qiniu/x"); !ok || modv.Version != "v1.13.2" { diff --git a/gopmod/module.go b/gopmod/module.go index c32a73c..da30caf 100644 --- a/gopmod/module.go +++ b/gopmod/module.go @@ -30,6 +30,10 @@ import ( "golang.org/x/mod/module" ) +var ( + ErrInvalidPkgPath = errors.New("invalid package path") +) + // ----------------------------------------------------------------------------- type Module struct { @@ -84,6 +88,34 @@ func (p *Module) PkgType(pkgPath string) PkgType { return PkgtStandard } +// PkgId returns an unique package id of specified package. +// PkgId of a Go standard package is its package path. +// ie. PkgId("fmt") == "fmt" +func (p *Module) PkgId(pkgPath string) (string, error) { + if pkgPath == "" { + return "", ErrInvalidPkgPath + } + modPath := p.Path() + if isPkgInMod(pkgPath, modPath) { + return p.Root() + pkgPath[len(modPath):], nil + } + if pkgPath[0] == '.' { // local package: please convert it first + return "", ErrInvalidPkgPath + } + domain := pkgPath + if pos := strings.Index(pkgPath, "/"); pos > 0 { + domain = pkgPath[:pos] + } + if strings.Contains(domain, ".") { + pkg, err := p.lookupExternPkg(pkgPath) + if err != nil { + return "", err + } + return pkg.Dir, nil + } + return pkgPath, nil +} + func isPkgInMod(pkgPath, modPath string) bool { if modPath != "" && strings.HasPrefix(pkgPath, modPath) { suffix := pkgPath[len(modPath):] @@ -103,7 +135,7 @@ type Package struct { func (p *Module) Lookup(pkgPath string) (pkg *Package, err error) { switch pt := p.PkgType(pkgPath); pt { case PkgtStandard: - modDir := runtime.GOROOT() + "/src" + modDir := goroot + "/src" pkg = &Package{Type: PkgtStandard, ModDir: modDir, Dir: filepath.Join(modDir, pkgPath)} case PkgtModule: modPath := p.Path() @@ -204,4 +236,6 @@ func (e *MissingError) Error() string { // Default represents the default gop.mod object. var Default = New(modload.Default) +var goroot = runtime.GOROOT() + // -----------------------------------------------------------------------------