Skip to content

Commit

Permalink
feat: record dependency version and auto upgrade (#276)
Browse files Browse the repository at this point in the history
* feat: record dependency version and auto upgrade

* add dep version json file

* fix version compare

* pin deps

* fix

* remove cargo.lock

* use `deps.json`

* minor

* minor
  • Loading branch information
j178 committed Jan 30, 2024
1 parent 5d8d653 commit 4e002e3
Show file tree
Hide file tree
Showing 12 changed files with 220 additions and 105 deletions.
4 changes: 4 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,10 @@ func (c *Config) StateFile() string {
return filepath.Join(c.CacheDir(), constants.StateFilename)
}

func (c *Config) DepVersionFile() string {
return filepath.Join(c.CacheDir(), constants.DepVersionFilename)
}

func (c *Config) QuestionCacheFile(ext string) string {
return filepath.Join(c.CacheDir(), constants.QuestionCacheBaseName+ext)
}
Expand Down
4 changes: 1 addition & 3 deletions constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,8 @@ const (
ConfigFilename = "leetgo.yaml"
QuestionCacheBaseName = "leetcode-questions"
StateFilename = "state.json"
DepVersionFilename = "deps.json"
CodeBeginMarker = "@lc code=begin"
CodeEndMarker = "@lc code=end"
ProjectURL = "https://github.com/j178/leetgo"
GoTestUtilsModPath = "github.com/j178/leetgo/testutils/go"
RustTestUtilsCrate = "leetgo_rs"
PythonTestUtilsMode = "leetgo_py"
)
14 changes: 6 additions & 8 deletions lang/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,20 +103,14 @@ type Lang interface {
ShortName() string
// Slug returns the slug of the language. e.g. "cpp", "javascript", "python3"
Slug() string
// InitWorkspace initializes the language workspace for code running.
InitWorkspace(dir string) error
// Generate generates code files for the question.
Generate(q *leetcode.QuestionData) (*GenerateResult, error)
// GeneratePaths generates the paths of the code files for the question, without generating the real files.
GeneratePaths(q *leetcode.QuestionData) (*GenerateResult, error)
}

// NeedInitialization is an interface for languages that need to be initialized before generating code.
type NeedInitialization interface {
// HasInitialized returns whether the language workspace has been initialized.
HasInitialized(dir string) (bool, error)
// Initialize initializes the language workspace.
Initialize(dir string) error
}

// LocalTestable is an interface for languages that can run local test.
type LocalTestable interface {
// RunLocalTest runs local test for the question.
Expand Down Expand Up @@ -369,6 +363,10 @@ func (l baseLang) ShortName() string {
return l.shortName
}

func (l baseLang) InitWorkspace(_ string) error {
return nil
}

func (l baseLang) generateCodeContent(
q *leetcode.QuestionData,
blocks []config.Block,
Expand Down
26 changes: 15 additions & 11 deletions lang/cpp.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"github.com/google/shlex"

"github.com/j178/leetgo/config"
"github.com/j178/leetgo/constants"
"github.com/j178/leetgo/leetcode"
cppUtils "github.com/j178/leetgo/testutils/cpp"
"github.com/j178/leetgo/utils"
Expand All @@ -18,7 +17,11 @@ type cpp struct {
baseLang
}

func (c cpp) Initialize(outDir string) error {
func (c cpp) InitWorkspace(outDir string) error {
if should, err := c.shouldInit(outDir); err != nil || !should {
return err
}

headerPath := filepath.Join(outDir, cppUtils.HeaderName)
err := utils.WriteFile(headerPath, cppUtils.HeaderContent)
if err != nil {
Expand All @@ -29,28 +32,29 @@ func (c cpp) Initialize(outDir string) error {
if err != nil {
return err
}
return nil

err = UpdateDep(c)
return err
}

func (c cpp) HasInitialized(outDir string) (bool, error) {
func (c cpp) shouldInit(outDir string) (bool, error) {
headerPath := filepath.Join(outDir, cppUtils.HeaderName)
if !utils.IsExist(headerPath) {
return false, nil
return true, nil
}
stdCxxPath := filepath.Join(outDir, "bits", "stdc++.h")
if !utils.IsExist(stdCxxPath) {
return false, nil
return true, nil
}

version, err := ReadVersion(headerPath)
update, err := IsDepUpdateToDate(c)
if err != nil {
return false, err
}
currVersion := constants.Version
if version != currVersion {
return false, nil
if !update {
return true, nil
}
return true, nil
return false, nil
}

var cppTypes = map[string]string{
Expand Down
83 changes: 83 additions & 0 deletions lang/dep.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package lang

import (
"errors"
"os"

"github.com/goccy/go-json"

"github.com/j178/leetgo/config"
)

// If client dependency needs to be updated, update this version number.
var depVersions = map[string]int{
cppGen.slug: 1,
golangGen.slug: 1,
python3Gen.slug: 1,
rustGen.slug: 1,
}

func readDepVersions() (map[string]int, error) {
depVersionFile := config.Get().DepVersionFile()
records := make(map[string]int)
f, err := os.Open(depVersionFile)
if errors.Is(err, os.ErrNotExist) {
return records, nil
}
if err != nil {
return nil, err
}
defer f.Close()
err = json.NewDecoder(f).Decode(&records)
if err != nil {
return nil, err
}
return records, nil
}

func IsDepUpdateToDate(lang Lang) (bool, error) {
ver := depVersions[lang.Slug()]
if ver == 0 {
return true, nil
}

records, err := readDepVersions()
if err != nil {
return false, err
}
old := records[lang.Slug()]
if old == 0 || old != ver {
return false, nil
}

return true, nil
}

func UpdateDep(lang Lang) error {
ver := depVersions[lang.Slug()]
if ver == 0 {
return nil
}

records, err := readDepVersions()
if err != nil {
return err
}

records[lang.Slug()] = ver

depVersionFile := config.Get().DepVersionFile()
f, err := os.Create(depVersionFile)
if err != nil {
return err
}
defer f.Close()
enc := json.NewEncoder(f)
enc.SetIndent("", " ")
err = enc.Encode(records)
if err != nil {
return err
}

return nil
}
19 changes: 3 additions & 16 deletions lang/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,22 +74,9 @@ func generate(q *leetcode.QuestionData) (Lang, *GenerateResult, error) {
return nil, nil, err
}

// Check and generate necessary library files.
if t, ok := gen.(NeedInitialization); ok {
ok, err := t.HasInitialized(outDir)
if err == nil && !ok {
err = t.Initialize(outDir)
if err != nil {
return nil, nil, err
}
}
if err != nil {
log.Error(
"check initialization failed, skip initialization",
"lang", gen.Slug(),
"err", err,
)
}
err = gen.InitWorkspace(outDir)
if err != nil {
return nil, nil, err
}

// Generate files
Expand Down
54 changes: 35 additions & 19 deletions lang/go.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package lang

import (
"bytes"
"fmt"
"io"
"os"
Expand All @@ -12,11 +11,16 @@ import (
"github.com/charmbracelet/log"

"github.com/j178/leetgo/config"
"github.com/j178/leetgo/constants"
"github.com/j178/leetgo/leetcode"
"github.com/j178/leetgo/utils"
)

const leetgoGo = "github.com/j178/leetgo/testutils/go"

var goDeps = []string{
leetgoGo + "@v0.2.0",
}

type golang struct {
baseLang
}
Expand Down Expand Up @@ -102,44 +106,56 @@ func addMod(code string, q *leetcode.QuestionData) string {
return strings.Join(newLines, "\n")
}

func (g golang) HasInitialized(outDir string) (bool, error) {
func (g golang) shouldInit(outDir string) (bool, error) {
if !utils.IsExist(filepath.Join(outDir, "go.mod")) {
return false, nil
return true, nil
}
cmd := exec.Command("go", "list", "-m", "-json", constants.GoTestUtilsModPath)
cmd.Dir = outDir
output, err := cmd.CombinedOutput()

update, err := IsDepUpdateToDate(g)
if err != nil {
if bytes.Contains(output, []byte("not a known dependency")) || bytes.Contains(
output,
[]byte("go.mod file not found"),
) {
return false, nil
}
return false, fmt.Errorf("go list failed: %w: %s", err, output)
return false, err
}
if !update {
return true, nil
}
return true, nil
return false, nil
}

func (g golang) Initialize(outDir string) error {
func (g golang) InitWorkspace(outDir string) error {
if should, err := g.shouldInit(outDir); err != nil || !should {
return err
}

err := utils.RemoveIfExist(filepath.Join(outDir, "go.mod"))
if err != nil {
return err
}
_ = utils.RemoveIfExist(filepath.Join(outDir, "go.sum"))

const modPath = "leetcode-solutions"
var stderr strings.Builder
cmd := exec.Command("go", "mod", "init", modPath)
log.Info("go mod init", "cmd", cmd.String())
cmd.Dir = outDir
cmd.Stdout = os.Stdout
cmd.Stderr = io.MultiWriter(os.Stderr, &stderr)
err := cmd.Run()
err = cmd.Run()
if err != nil && !strings.Contains(stderr.String(), "go.mod already exists") {
return err
}

cmd = exec.Command("go", "get", constants.GoTestUtilsModPath)
cmd = exec.Command("go", "get")
cmd.Args = append(cmd.Args, goDeps...)
log.Info("go get", "cmd", cmd.String())
cmd.Dir = outDir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
return err
}

err = UpdateDep(g)
return err
}

Expand Down Expand Up @@ -350,7 +366,7 @@ import (
"os"
. "%s"
)`, constants.GoTestUtilsModPath,
)`, leetgoGo,
)
testContent, err := g.generateTestContent(q)
if err != nil {
Expand Down
Loading

0 comments on commit 4e002e3

Please sign in to comment.