Repository: goplus/gop
Pull request #1827 (2024-03-25 09:09:04)

NewDefaultConf: useCacheFile param; conf.UpdateCache


Patch:
diff --git a/build_install_run.go b/build_install_run.go
index 5102ded2..b0c6d138 100644
--- a/build_install_run.go
+++ b/build_install_run.go
@@ -24,11 +24,18 @@ import (
 	"github.com/qiniu/x/errors"
 )
 
+func genFlags(flags []GenFlags) GenFlags {
+	if flags != nil {
+		return flags[0]
+	}
+	return 0
+}
+
 // -----------------------------------------------------------------------------
 
 // InstallDir installs a Go+ package directory.
-func InstallDir(dir string, conf *Config, install *gocmd.InstallConfig) (err error) {
-	_, _, err = GenGo(dir, conf, false)
+func InstallDir(dir string, conf *Config, install *gocmd.InstallConfig, flags ...GenFlags) (err error) {
+	_, _, err = GenGoEx(dir, conf, false, genFlags(flags))
 	if err != nil {
 		return errors.NewWith(err, `GenGo(dir, conf, false)`, -2, "gop.GenGo", dir, conf, false)
 	}
@@ -36,8 +43,8 @@ func InstallDir(dir string, conf *Config, install *gocmd.InstallConfig) (err err
 }
 
 // InstallPkgPath installs a Go+ package.
-func InstallPkgPath(workDir, pkgPath string, conf *Config, install *gocmd.InstallConfig) (err error) {
-	localDir, recursively, err := GenGoPkgPath(workDir, pkgPath, conf, true)
+func InstallPkgPath(workDir, pkgPath string, conf *Config, install *gocmd.InstallConfig, flags ...GenFlags) (err error) {
+	localDir, recursively, err := GenGoPkgPathEx(workDir, pkgPath, conf, true, genFlags(flags))
 	if err != nil {
 		return errors.NewWith(err, `GenGoPkgPath(workDir, pkgPath, conf, true)`, -2, "gop.GenGoPkgPath", workDir, pkgPath, conf, true)
 	}
@@ -77,8 +84,8 @@ func chdir(dir string) string {
 // -----------------------------------------------------------------------------
 
 // BuildDir builds a Go+ package directory.
-func BuildDir(dir string, conf *Config, build *gocmd.BuildConfig) (err error) {
-	_, _, err = GenGo(dir, conf, false)
+func BuildDir(dir string, conf *Config, build *gocmd.BuildConfig, flags ...GenFlags) (err error) {
+	_, _, err = GenGoEx(dir, conf, false, genFlags(flags))
 	if err != nil {
 		return errors.NewWith(err, `GenGo(dir, conf, false)`, -2, "gop.GenGo", dir, conf, false)
 	}
@@ -86,8 +93,8 @@ func BuildDir(dir string, conf *Config, build *gocmd.BuildConfig) (err error) {
 }
 
 // BuildPkgPath builds a Go+ package.
-func BuildPkgPath(workDir, pkgPath string, conf *Config, build *gocmd.BuildConfig) (err error) {
-	localDir, recursively, err := GenGoPkgPath(workDir, pkgPath, conf, false)
+func BuildPkgPath(workDir, pkgPath string, conf *Config, build *gocmd.BuildConfig, flags ...GenFlags) (err error) {
+	localDir, recursively, err := GenGoPkgPathEx(workDir, pkgPath, conf, false, genFlags(flags))
 	if err != nil {
 		return errors.NewWith(err, `GenGoPkgPath(workDir, pkgPath, conf, false)`, -2, "gop.GenGoPkgPath", workDir, pkgPath, conf, false)
 	}
@@ -131,8 +138,8 @@ func getBuildDir(conf *Config) string {
 }
 
 // RunDir runs an application from a Go+ package directory.
-func RunDir(dir string, args []string, conf *Config, run *gocmd.RunConfig) (err error) {
-	_, _, err = GenGo(dir, conf, false)
+func RunDir(dir string, args []string, conf *Config, run *gocmd.RunConfig, flags ...GenFlags) (err error) {
+	_, _, err = GenGoEx(dir, conf, false, genFlags(flags))
 	if err != nil {
 		return errors.NewWith(err, `GenGo(dir, conf, false)`, -2, "gop.GenGo", dir, conf, false)
 	}
@@ -140,8 +147,8 @@ func RunDir(dir string, args []string, conf *Config, run *gocmd.RunConfig) (err
 }
 
 // RunPkgPath runs an application from a Go+ package.
-func RunPkgPath(pkgPath string, args []string, chDir bool, conf *Config, run *gocmd.RunConfig) (err error) {
-	localDir, recursively, err := GenGoPkgPath("", pkgPath, conf, true)
+func RunPkgPath(pkgPath string, args []string, chDir bool, conf *Config, run *gocmd.RunConfig, flags ...GenFlags) (err error) {
+	localDir, recursively, err := GenGoPkgPathEx("", pkgPath, conf, true, genFlags(flags))
 	if err != nil {
 		return errors.NewWith(err, `GenGoPkgPath("", pkgPath, conf, true)`, -2, "gop.GenGoPkgPath", "", pkgPath, conf, true)
 	}
@@ -168,8 +175,8 @@ func RunFiles(autogen string, files []string, args []string, conf *Config, run *
 // -----------------------------------------------------------------------------
 
 // TestDir tests a Go+ package directory.
-func TestDir(dir string, conf *Config, test *gocmd.TestConfig) (err error) {
-	_, _, err = GenGo(dir, conf, true)
+func TestDir(dir string, conf *Config, test *gocmd.TestConfig, flags ...GenFlags) (err error) {
+	_, _, err = GenGoEx(dir, conf, true, genFlags(flags))
 	if err != nil {
 		return errors.NewWith(err, `GenGo(dir, conf, true)`, -2, "gop.GenGo", dir, conf, true)
 	}
@@ -177,8 +184,8 @@ func TestDir(dir string, conf *Config, test *gocmd.TestConfig) (err error) {
 }
 
 // TestPkgPath tests a Go+ package.
-func TestPkgPath(workDir, pkgPath string, conf *Config, test *gocmd.TestConfig) (err error) {
-	localDir, recursively, err := GenGoPkgPath(workDir, pkgPath, conf, false)
+func TestPkgPath(workDir, pkgPath string, conf *Config, test *gocmd.TestConfig, flags ...GenFlags) (err error) {
+	localDir, recursively, err := GenGoPkgPathEx(workDir, pkgPath, conf, false, genFlags(flags))
 	if err != nil {
 		return errors.NewWith(err, `GenGoPkgPath(workDir, pkgPath, conf, false)`, -2, "gop.GenGoPkgPath", workDir, pkgPath, conf, false)
 	}
diff --git a/cmd/internal/build/build.go b/cmd/internal/build/build.go
index fdb2509b..5fe447fb 100644
--- a/cmd/internal/build/build.go
+++ b/cmd/internal/build/build.go
@@ -74,10 +74,12 @@ func runCmd(cmd *base.Command, args []string) {
 		log.Panicln("too many arguments:", args)
 	}
 
-	conf, err := gop.NewDefaultConf(".", true)
+	conf, err := gop.NewDefaultConf(".", gop.ConfFlagNoTestFiles)
 	if err != nil {
 		log.Panicln("gop.NewDefaultConf:", err)
 	}
+	defer conf.UpdateCache()
+
 	confCmd := &gocmd.BuildConfig{Gop: conf.Gop}
 	if *flagOutput != "" {
 		output, err := filepath.Abs(*flagOutput)
@@ -91,15 +93,16 @@ func runCmd(cmd *base.Command, args []string) {
 }
 
 func build(proj gopprojs.Proj, conf *gop.Config, build *gocmd.BuildConfig) {
+	const flags = gop.GenFlagPrompt
 	var obj string
 	var err error
 	switch v := proj.(type) {
 	case *gopprojs.DirProj:
 		obj = v.Dir
-		err = gop.BuildDir(obj, conf, build)
+		err = gop.BuildDir(obj, conf, build, flags)
 	case *gopprojs.PkgPathProj:
 		obj = v.Path
-		err = gop.BuildPkgPath("", v.Path, conf, build)
+		err = gop.BuildPkgPath("", v.Path, conf, build, flags)
 	case *gopprojs.FilesProj:
 		err = gop.BuildFiles(v.Files, conf, build)
 	default:
diff --git a/cmd/internal/gengo/go.go b/cmd/internal/gengo/go.go
index ac51e1c6..d66682b9 100644
--- a/cmd/internal/gengo/go.go
+++ b/cmd/internal/gengo/go.go
@@ -71,10 +71,11 @@ func runCmd(cmd *base.Command, args []string) {
 		cl.SetDisableRecover(true)
 	}
 
-	conf, err := gop.NewDefaultConf(".", false)
+	conf, err := gop.NewDefaultConf(".", 0)
 	if err != nil {
 		log.Panicln("gop.NewDefaultConf:", err)
 	}
+	defer conf.UpdateCache()
 
 	flags := gop.GenFlagPrintError | gop.GenFlagPrompt
 	if *flagCheckMode {
diff --git a/cmd/internal/install/install.go b/cmd/internal/install/install.go
index b469c1be..b343885a 100644
--- a/cmd/internal/install/install.go
+++ b/cmd/internal/install/install.go
@@ -71,10 +71,12 @@ func runCmd(cmd *base.Command, args []string) {
 		cl.SetDisableRecover(true)
 	}
 
-	conf, err := gop.NewDefaultConf(".", true)
+	conf, err := gop.NewDefaultConf(".", gop.ConfFlagNoTestFiles)
 	if err != nil {
 		log.Panicln("gop.NewDefaultConf:", err)
 	}
+	defer conf.UpdateCache()
+
 	confCmd := &gocmd.Config{Gop: conf.Gop}
 	confCmd.Flags = pass.Args
 	for _, proj := range projs {
@@ -83,15 +85,16 @@ func runCmd(cmd *base.Command, args []string) {
 }
 
 func install(proj gopprojs.Proj, conf *gop.Config, install *gocmd.InstallConfig) {
+	const flags = gop.GenFlagPrompt
 	var obj string
 	var err error
 	switch v := proj.(type) {
 	case *gopprojs.DirProj:
 		obj = v.Dir
-		err = gop.InstallDir(obj, conf, install)
+		err = gop.InstallDir(obj, conf, install, flags)
 	case *gopprojs.PkgPathProj:
 		obj = v.Path
-		err = gop.InstallPkgPath("", v.Path, conf, install)
+		err = gop.InstallPkgPath("", v.Path, conf, install, flags)
 	case *gopprojs.FilesProj:
 		err = gop.InstallFiles(v.Files, conf, install)
 	default:
diff --git a/cmd/internal/run/run.go b/cmd/internal/run/run.go
index b7d1c57f..9bcb3925 100644
--- a/cmd/internal/run/run.go
+++ b/cmd/internal/run/run.go
@@ -80,10 +80,12 @@ func runCmd(cmd *base.Command, args []string) {
 	}
 
 	noChdir := *flagNoChdir
-	conf, err := gop.NewDefaultConf(".", true)
+	conf, err := gop.NewDefaultConf(".", gop.ConfFlagNoTestFiles)
 	if err != nil {
 		log.Panicln("gop.NewDefaultConf:", err)
 	}
+	defer conf.UpdateCache()
+
 	if !conf.Mod.HasModfile() { // if no go.mod, check GopDeps
 		conf.GopDeps = new(int)
 	}
@@ -93,15 +95,16 @@ func runCmd(cmd *base.Command, args []string) {
 }
 
 func run(proj gopprojs.Proj, args []string, chDir bool, conf *gop.Config, run *gocmd.RunConfig) {
+	const flags = 0
 	var obj string
 	var err error
 	switch v := proj.(type) {
 	case *gopprojs.DirProj:
 		obj = v.Dir
-		err = gop.RunDir(obj, args, conf, run)
+		err = gop.RunDir(obj, args, conf, run, flags)
 	case *gopprojs.PkgPathProj:
 		obj = v.Path
-		err = gop.RunPkgPath(v.Path, args, chDir, conf, run)
+		err = gop.RunPkgPath(v.Path, args, chDir, conf, run, flags)
 	case *gopprojs.FilesProj:
 		err = gop.RunFiles("", v.Files, args, conf, run)
 	default:
diff --git a/go.mod b/go.mod
index 97ab1bea..7c4f0587 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,7 @@ go 1.18
 require (
 	github.com/fsnotify/fsnotify v1.7.0
 	github.com/goplus/c2go v0.7.25
-	github.com/goplus/gogen v1.15.2-0.20240325030304-38a18ebdfb1f
+	github.com/goplus/gogen v1.15.2-0.20240401172158-4586769ba6d4
 	github.com/goplus/mod v0.13.9
 	github.com/qiniu/x v1.13.9
 	golang.org/x/tools v0.19.0
diff --git a/go.sum b/go.sum
index 7f54aabb..e52fc530 100644
--- a/go.sum
+++ b/go.sum
@@ -8,6 +8,8 @@ github.com/goplus/c2go v0.7.25 h1:QvQfOwGVGKFYOTLry8i4p9U55jnIOQWeLsnSyFg0hhc=
 github.com/goplus/c2go v0.7.25/go.mod h1:e9oe4jDVhGFMJLEGmPSrVkLuXbLZAEmAu0/uD6fSz5E=
 github.com/goplus/gogen v1.15.2-0.20240325030304-38a18ebdfb1f h1:ScHZQ/KkVjVyea/1ivGUaBh24KnqG3PpQXn0LffjwUc=
 github.com/goplus/gogen v1.15.2-0.20240325030304-38a18ebdfb1f/go.mod h1:92qEzVgv7y8JEFICWG9GvYI5IzfEkxYdsA1DbmnTkqk=
+github.com/goplus/gogen v1.15.2-0.20240401172158-4586769ba6d4 h1:u4aI5iMvIEBHg3Ity6LtqM2JZeHecMtINqXyi563YHo=
+github.com/goplus/gogen v1.15.2-0.20240401172158-4586769ba6d4/go.mod h1:92qEzVgv7y8JEFICWG9GvYI5IzfEkxYdsA1DbmnTkqk=
 github.com/goplus/mod v0.13.9 h1:B9zZoHi2AzMltTSOFqZNVjqGlSMlhhNTWwEzVqhTQzg=
 github.com/goplus/mod v0.13.9/go.mod h1:MibsLSftGmxaQq78YzUzNviyFwB9RtpMaoscufvEKH4=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
diff --git a/imp.go b/imp.go
index 5e6f8edf..6a1d01f8 100644
--- a/imp.go
+++ b/imp.go
@@ -17,21 +17,31 @@
 package gop
 
 import (
+	"crypto/sha256"
+	"encoding/base64"
+	"fmt"
 	"go/token"
 	"go/types"
+	"io"
+	"log"
 	"os"
 	"os/exec"
+	"path"
 	"path/filepath"
+	"runtime"
 	"strings"
 
 	"github.com/goplus/gogen/packages"
+	"github.com/goplus/gogen/packages/cache"
 	"github.com/goplus/mod/env"
 	"github.com/goplus/mod/gopmod"
 	"github.com/goplus/mod/modfetch"
+	"github.com/goplus/mod/modfile"
 )
 
 // -----------------------------------------------------------------------------
 
+// Importer represents a Go+ importer.
 type Importer struct {
 	impFrom *packages.Importer
 	mod     *gopmod.Module
@@ -41,19 +51,68 @@ type Importer struct {
 	Flags GenFlags // can change this for loading Go+ modules
 }
 
+// NewImporter creates a Go+ Importer.
 func NewImporter(mod *gopmod.Module, gop *env.Gop, fset *token.FileSet) *Importer {
 	const (
 		defaultFlags = GenFlagPrompt | GenFlagPrintError
 	)
-	if mod == nil {
-		mod = gopmod.Default
-	}
-	dir := ""
-	if mod.HasModfile() {
-		dir = mod.Root()
+	if mod == nil || !mod.HasModfile() {
+		if modGop, e := gopmod.LoadFrom(filepath.Join(gop.Root, "go.mod"), ""); e == nil {
+			modGop.ImportClasses()
+			mod = modGop
+		} else {
+			mod = gopmod.Default
+		}
 	}
+	dir := mod.Root()
 	impFrom := packages.NewImporter(fset, dir)
-	return &Importer{mod: mod, gop: gop, impFrom: impFrom, fset: fset, Flags: defaultFlags}
+	ret := &Importer{mod: mod, gop: gop, impFrom: impFrom, fset: fset, Flags: defaultFlags}
+	impFrom.SetCache(cache.New(ret.PkgHash))
+	return ret
+}
+
+// CacheFile returns file path of the cache.
+func (p *Importer) CacheFile() string {
+	cacheDir, _ := os.UserCacheDir()
+	cacheDir += "/gop-build/"
+	os.MkdirAll(cacheDir, 0755)
+
+	fname := ""
+	h := sha256.New()
+	if root := p.mod.Root(); root != "" {
+		io.WriteString(h, root)
+		fname = filepath.Base(root)
+	}
+	hash := base64.RawURLEncoding.EncodeToString(h.Sum(nil))
+	return cacheDir + hash + fname
+}
+
+// Cache returns the cache object.
+func (p *Importer) Cache() *cache.Impl {
+	return p.impFrom.Cache().(*cache.Impl)
+}
+
+// PkgHash calculates hash value for a package.
+// It is required by cache.New func.
+func (p *Importer) PkgHash(pkgPath string, self bool) string {
+	if pkg, e := p.mod.Lookup(pkgPath); e == nil {
+		switch pkg.Type {
+		case gopmod.PkgtStandard:
+			return cache.HashSkip
+		case gopmod.PkgtExtern:
+			if pkg.Real.Version != "" {
+				return pkg.Real.String()
+			}
+			fallthrough
+		case gopmod.PkgtModule:
+			return dirHash(p.mod, p.gop, pkg.Dir, self)
+		}
+	}
+	if isPkgInMod(pkgPath, gopMod) {
+		return cache.HashSkip
+	}
+	log.Println("PkgHash: unexpected package -", pkgPath)
+	return cache.HashInvalid
 }
 
 const (
@@ -146,4 +205,37 @@ go 1.16
 `)
 }
 
+func dirHash(mod *gopmod.Module, gop *env.Gop, dir string, self bool) string {
+	h := sha256.New()
+	if self {
+		fmt.Fprintf(h, "go\t%s\n", runtime.Version())
+		fmt.Fprintf(h, "gop\t%s\n", gop.Version)
+	}
+	if fis, err := os.ReadDir(dir); err == nil {
+		for _, fi := range fis {
+			if fi.IsDir() {
+				continue
+			}
+			fname := fi.Name()
+			if strings.HasPrefix(fname, "_") || !canCl(mod, fname) {
+				continue
+			}
+			if v, e := fi.Info(); e == nil {
+				fmt.Fprintf(h, "file\t%s\t%x\t%x\n", fname, v.Size(), v.ModTime().UnixNano())
+			}
+		}
+	}
+	return base64.RawStdEncoding.EncodeToString(h.Sum(nil))
+}
+
+func canCl(mod *gopmod.Module, fname string) bool {
+	switch path.Ext(fname) {
+	case ".go", ".gop", ".gox":
+		return true
+	default:
+		ext := modfile.ClassExt(fname)
+		return mod.IsClass(ext)
+	}
+}
+
 // -----------------------------------------------------------------------------
diff --git a/load.go b/load.go
index bc958bef..05e8337e 100644
--- a/load.go
+++ b/load.go
@@ -18,7 +18,6 @@ package gop
 
 import (
 	"fmt"
-	"go/types"
 	"io/fs"
 	"os"
 	"path"
@@ -115,11 +114,12 @@ func isNotatedErr(err error, pkg *ast.Package, fset *token.FileSet) (notatedErr
 
 // -----------------------------------------------------------------------------
 
+// Config represents a configuration for loading Go+ packages.
 type Config struct {
 	Gop      *env.Gop
 	Fset     *token.FileSet
 	Mod      *gopmod.Module
-	Importer types.Importer
+	Importer *Importer
 
 	Filter func(fs.FileInfo) bool
 
@@ -127,12 +127,25 @@ type Config struct {
 	// see https://pkg.go.dev/github.com/goplus/gogen#File.CheckGopDeps
 	GopDeps *int
 
+	// CacheFile specifies the file path of the cache.
+	CacheFile string
+
 	IgnoreNotatedError bool
 	DontUpdateGoMod    bool
 }
 
+// ConfFlags represents configuration flags.
+type ConfFlags int
+
+const (
+	ConfFlagIgnoreNotatedError ConfFlags = 1 << iota
+	ConfFlagDontUpdateGoMod
+	ConfFlagNoTestFiles
+	ConfFlagNoCacheFile
+)
+
 // NewDefaultConf creates a dfault configuration for common cases.
-func NewDefaultConf(dir string, noTestFile bool) (conf *Config, err error) {
+func NewDefaultConf(dir string, flags ConfFlags) (conf *Config, err error) {
 	mod, err := LoadMod(dir)
 	if err != nil {
 		return
@@ -140,13 +153,32 @@ func NewDefaultConf(dir string, noTestFile bool) (conf *Config, err error) {
 	gop := gopenv.Get()
 	fset := token.NewFileSet()
 	imp := NewImporter(mod, gop, fset)
-	conf = &Config{Gop: gop, Fset: fset, Mod: mod, Importer: imp}
-	if noTestFile {
+	conf = &Config{
+		Gop: gop, Fset: fset, Mod: mod, Importer: imp,
+		IgnoreNotatedError: flags&ConfFlagIgnoreNotatedError != 0,
+		DontUpdateGoMod:    flags&ConfFlagDontUpdateGoMod != 0,
+	}
+	if flags&ConfFlagNoCacheFile == 0 {
+		conf.CacheFile = imp.CacheFile()
+		imp.Cache().Load(conf.CacheFile)
+	}
+	if flags&ConfFlagNoTestFiles != 0 {
 		conf.Filter = FilterNoTestFiles
 	}
 	return
 }
 
+// UpdateCache updates the cache.
+func (conf *Config) UpdateCache(verbose ...bool) {
+	if conf.CacheFile != "" {
+		c := conf.Importer.Cache()
+		c.Save(conf.CacheFile)
+		if verbose != nil && verbose[0] {
+			fmt.Println("Times of calling go list:", c.ListTimes())
+		}
+	}
+}
+
 // LoadMod loads a Go+ module from a specified directory.
 func LoadMod(dir string) (mod *gopmod.Module, err error) {
 	mod, err = gopmod.Load(dir)


Test patch:
diff --git a/cmd/internal/test/test.go b/cmd/internal/test/test.go
index d578ec0e..7a070f3c 100644
--- a/cmd/internal/test/test.go
+++ b/cmd/internal/test/test.go
@@ -69,10 +69,12 @@ func runCmd(cmd *base.Command, args []string) {
 		cl.SetDisableRecover(true)
 	}
 
-	conf, err := gop.NewDefaultConf(".", false)
+	conf, err := gop.NewDefaultConf(".", 0)
 	if err != nil {
 		log.Panicln("gop.NewDefaultConf:", err)
 	}
+	defer conf.UpdateCache()
+
 	confCmd := &gocmd.Config{Gop: conf.Gop}
 	confCmd.Flags = pass.Args
 	for _, proj := range projs {
@@ -81,15 +83,16 @@ func runCmd(cmd *base.Command, args []string) {
 }
 
 func test(proj gopprojs.Proj, conf *gop.Config, test *gocmd.TestConfig) {
+	const flags = gop.GenFlagPrompt
 	var obj string
 	var err error
 	switch v := proj.(type) {
 	case *gopprojs.DirProj:
 		obj = v.Dir
-		err = gop.TestDir(obj, conf, test)
+		err = gop.TestDir(obj, conf, test, flags)
 	case *gopprojs.PkgPathProj:
 		obj = v.Path
-		err = gop.TestPkgPath("", v.Path, conf, test)
+		err = gop.TestPkgPath("", v.Path, conf, test, flags)
 	case *gopprojs.FilesProj:
 		err = gop.TestFiles(v.Files, conf, test)
 	default: