From 1b23f0525e090e8760702cec1b43a69dbd0cf374 Mon Sep 17 00:00:00 2001 From: caixw Date: Fri, 10 May 2024 11:29:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20ModSourceDir?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mod.go | 56 ++++++++++++++++++++++++++++++++++++++++++ mod_test.go | 30 ++++++++++++++++++++++ testdata/go.mod/go.mod | 9 ++++++- 3 files changed, 94 insertions(+), 1 deletion(-) diff --git a/mod.go b/mod.go index deeee50..69dc73f 100644 --- a/mod.go +++ b/mod.go @@ -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 文件位置,以及文件内容的解析。 diff --git a/mod_test.go b/mod_test.go index f1ef1f5..d263e67 100644 --- a/mod_test.go +++ b/mod_test.go @@ -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) diff --git a/testdata/go.mod/go.mod b/testdata/go.mod/go.mod index 0305d4e..c98f5fc 100644 --- a/testdata/go.mod/go.mod +++ b/testdata/go.mod/go.mod @@ -1 +1,8 @@ -module github.com/issue9/source/mod \ No newline at end of file +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 => ../../