Skip to content

Commit

Permalink
feat: 添加 ModSourceDir
Browse files Browse the repository at this point in the history
  • Loading branch information
caixw committed May 10, 2024
1 parent 8469cd1 commit 1b23f05
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 1 deletion.
56 changes: 56 additions & 0 deletions mod.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,72 @@ package source

import (
"errors"
"go/build"
"io/fs"
"os"
"path"
"path/filepath"
"slices"
"strings"

"golang.org/x/mod/modfile"
)

const modFile = "go.mod"

var (
pkgSource = filepath.Join(build.Default.GOPATH, "pkg", "mod")
stdSource = filepath.Join(build.Default.GOROOT, "src")
)

// 查找模块 modPath 的源码目录
//
// 如果 modPath 是标准库的名称,如 encoding/json 等,则返回当前使用的 Go 版本对应的标准库地址。
// 其它情况则从 modDir 指向的 go.mod 中查找 require 或是 replace 字段的定义,
// 并根据这些定义找到其指向的源码路径。
//
// modPath 需要查找到模块路径,如果指向的是模块下的包级别的导出路径,是找不到的;
// modDir go.mod 所在的目录;
// replace 是否考虑 go.mod 中的 replace 指令的影响;
//
// NOTE: 这并不会检测 dir 指向目录是否真实且准确。
func ModSourceDir(modPath, modDir string, replace bool) (dir string, err error) {
if strings.IndexByte(modPath, '.') < 0 {
return filepath.Join(stdSource, modPath), nil
}

_, mod, err := ModFile(modDir)
if err != nil {
return "", err
}

for _, pkg := range mod.Require {
if pkg.Mod.Path != modPath {
continue
}

if !replace {
return filepath.Join(pkgSource, pkg.Mod.Path+"@"+pkg.Mod.Version), nil
}

index := slices.IndexFunc(mod.Replace, func(r *modfile.Replace) bool {
return r.Old.Path == pkg.Mod.Path
})

if index < 0 {
return filepath.Join(pkgSource, pkg.Mod.Path+"@"+pkg.Mod.Version), nil
}

p := mod.Replace[index].New.Path
if !filepath.IsAbs(p) {
p = filepath.Join(modDir, p)
}
return filepath.Abs(p)
}

return "", fs.ErrNotExist
}

// ModFile 文件或目录 p 所在模块的 go.mod 内容
//
// 从当前目录开始依次向上查找 go.mod,从其中获取 go.mod 文件位置,以及文件内容的解析。
Expand Down
30 changes: 30 additions & 0 deletions mod_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,42 @@
package source

import (
"io/fs"
"runtime"
"testing"

"github.com/issue9/assert/v4"
)

func TestModSourceDir(t *testing.T) {
a := assert.New(t, false)

// std
dir, err := ModSourceDir("encoding/json", "./", false)
a.NotError(err).FileExists(dir)

dir, err = ModSourceDir("note-exists", "./", false)
a.NotError(err).FileNotExists(dir)

// require

dir, err = ModSourceDir("github.com/issue9/assert/v4", "./", false)
a.NotError(err).FileExists(dir)

// replace

dir, err = ModSourceDir("github.com/issue9/web/v2", "./testdata/go.mod", true)
a.NotError(err).NotEmpty(dir) // 此处 dir 可能不存在,因为 go.mod 关于 web 的包是随便指定的

dir, err = ModSourceDir("github.com/issue9/source", "./testdata/go.mod", true)
a.NotError(err).FileExists(dir)

// not exist

dir, err = ModSourceDir("github.com/issue9/not-exists", "./testdata/go.mod", true)
a.ErrorIs(err, fs.ErrNotExist).Empty(dir)
}

func TestModFile(t *testing.T) {
a := assert.New(t, false)

Expand Down
9 changes: 8 additions & 1 deletion testdata/go.mod/go.mod
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
module github.com/issue9/source/mod
module github.com/issue9/source/mod

require (
github.com/issue9/source v1.0.0
github.com/issue9/web/v2 v2.0.0
)

replace github.com/issue9/source => ../../

0 comments on commit 1b23f05

Please sign in to comment.