From 920f75650b0dbde678c7b1430222a38c340867cb Mon Sep 17 00:00:00 2001 From: Haolan Date: Fri, 25 Apr 2025 18:29:35 +0800 Subject: [PATCH 01/22] feat: allow custom name --- internal/actions/actions.go | 30 +++++--- internal/actions/actions_test.go | 15 +++- internal/actions/api.go | 62 ++++++----------- internal/actions/api_test.go | 72 +++++++++++++------- internal/actions/llpkg/package.go | 94 ++++++++++++++++++++++++++ internal/actions/llpkg/package_test.go | 72 ++++++++++++++++++++ 6 files changed, 263 insertions(+), 82 deletions(-) create mode 100644 internal/actions/llpkg/package.go create mode 100644 internal/actions/llpkg/package_test.go diff --git a/internal/actions/actions.go b/internal/actions/actions.go index de8ffd7..300342f 100644 --- a/internal/actions/actions.go +++ b/internal/actions/actions.go @@ -13,8 +13,8 @@ import ( "strings" "sync" - "github.com/goplus/llpkgstore/config" "github.com/goplus/llpkgstore/internal/actions/env" + "github.com/goplus/llpkgstore/internal/actions/llpkg" "github.com/goplus/llpkgstore/internal/actions/versions" "github.com/goplus/llpkgstore/internal/file" "github.com/goplus/llpkgstore/internal/pc" @@ -114,25 +114,35 @@ func parseMappedVersion(version string) (clib, mappedVersion string, err error) } // isValidLLPkg checks if directory contains both llpkg.cfg and llcppg.cfg -func isValidLLPkg(files []os.DirEntry) bool { +func isLLPkgRoot(path string) bool { + // don't retrieve files from pr changes, consider about maintenance case + files, err := os.ReadDir(path) + if err != nil { + return false + } fileMap := make(map[string]struct{}, len(files)) for _, file := range files { fileMap[filepath.Base(file.Name())] = struct{}{} } + _, hasLLPkg := fileMap["llpkg.cfg"] _, hasLLCppg := fileMap["llcppg.cfg"] - return hasLLCppg && hasLLPkg + _, hasGoMod := fileMap["go.mod"] + return hasLLCppg && hasLLPkg && hasGoMod } // checkLegacyVersion validates versioning strategy for legacy package submissions // Ensures semantic versioning compliance and proper branch maintenance strategy -func checkLegacyVersion(ver *versions.Versions, cfg config.LLPkgConfig, mappedVersion string, isLegacy bool) error { - if slices.Contains(ver.GoVersions(cfg.Upstream.Package.Name), mappedVersion) { +func checkLegacyVersion(ver *versions.Versions, pkg *llpkg.LLPkg, mappedVersion string, isLegacy bool) error { + clibName := pkg.ClibName() + clibVersion := pkg.ClibVersion() + + if slices.Contains(ver.GoVersions(clibName), mappedVersion) { return fmt.Errorf("actions: repeat semver %s", mappedVersion) } - vers := ver.CVersions(cfg.Upstream.Package.Name) - currentVersion := versions.ToSemVer(cfg.Upstream.Package.Version) + vers := ver.CVersions(clibName) + currentVersion := versions.ToSemVer(clibVersion) // skip when we're the only latest version or C version doesn't follow semver. if len(vers) == 0 || !semver.IsValid(currentVersion) { @@ -148,7 +158,7 @@ func checkLegacyVersion(ver *versions.Versions, cfg config.LLPkgConfig, mappedVe if isLatest { // case1: we're the latest version, but mapped version is not latest, invalid. // example: all version: 1.8.1 => v1.2.0 1.7.1 => v1.1.0 current: 1.9.1 => v1.0.0 - if semver.Compare(ver.LatestGoVersion(cfg.Upstream.Package.Name), mappedVersion) > 0 { + if semver.Compare(ver.LatestGoVersion(clibName), mappedVersion) > 0 { return fmt.Errorf("actions: mapped version should not less than the legacy one") } return nil @@ -186,11 +196,11 @@ func checkLegacyVersion(ver *versions.Versions, cfg config.LLPkgConfig, mappedVe // case5: we're the latest patch version for current major and minor, check the mapped version // our mapped version should be larger than the closest one. // example: current submit: 1.5.2 => v1.1.1, closest minor: 1.4.1 => v1.1.0, valid. - originalVersion := ver.SearchBySemVer(cfg.Upstream.Package.Name, vers[i]) + originalVersion := ver.SearchBySemVer(clibName, vers[i]) if originalVersion == "" { return fmt.Errorf("actions: cannot find original C version from semver") } - closestMappedVersion := ver.LatestGoVersionForCVersion(cfg.Upstream.Package.Name, originalVersion) + closestMappedVersion := ver.LatestGoVersionForCVersion(clibName, originalVersion) if closestMappedVersion == "" { return fmt.Errorf("cannot find latest Go version from C version") } diff --git a/internal/actions/actions_test.go b/internal/actions/actions_test.go index 3cac235..bbf6dee 100644 --- a/internal/actions/actions_test.go +++ b/internal/actions/actions_test.go @@ -10,16 +10,25 @@ import ( "github.com/goplus/llpkgstore/config" ) +func demoDir() (dir string, err error) { + dir, err = os.Getwd() + if err != nil { + return + } + // ../../_demo + + dir = filepath.Join(filepath.Dir(filepath.Dir(dir)), "_demo") + return +} + func TestBuildBinaryZip(t *testing.T) { - dir, err := os.Getwd() + demoDir, err := demoDir() if err != nil { t.Error(err) return } // ../../_demo - demoDir := filepath.Join(filepath.Dir(filepath.Dir(dir)), "_demo") - cfg, err := config.ParseLLPkgConfig(filepath.Join(demoDir, "llpkg.cfg")) if err != nil { t.Error(err) diff --git a/internal/actions/api.go b/internal/actions/api.go index 84e891f..6758bd1 100644 --- a/internal/actions/api.go +++ b/internal/actions/api.go @@ -15,8 +15,8 @@ import ( "time" "github.com/google/go-github/v69/github" - "github.com/goplus/llpkgstore/config" "github.com/goplus/llpkgstore/internal/actions/env" + "github.com/goplus/llpkgstore/internal/actions/llpkg" "github.com/goplus/llpkgstore/internal/actions/versions" "golang.org/x/sync/errgroup" ) @@ -244,8 +244,8 @@ func (d *DefaultClient) removeLabel(labelName string) error { // Panics: // // If no valid version found in PR commits -func (d *DefaultClient) checkMappedVersion(packageName string) (mappedVersion string, err error) { - matchMappedVersion := regex(packageName) +func (d *DefaultClient) checkMappedVersion(pkg *llpkg.LLPkg) (mappedVersion string, err error) { + matchMappedVersion := regex(pkg.Name()) allCommits, err := d.currentPRCommit() if err != nil { @@ -510,9 +510,9 @@ func (d *DefaultClient) removeBranch(branchName string) error { // // ver: Version store object // cfg: Package configuration -func (d *DefaultClient) checkVersion(ver *versions.Versions, cfg config.LLPkgConfig) error { +func (d *DefaultClient) checkVersion(ver *versions.Versions, pkg *llpkg.LLPkg) error { // 4. Check MappedVersion - version, err := d.checkMappedVersion(cfg.Upstream.Package.Name) + version, err := d.checkMappedVersion(pkg) if err != nil { return err } @@ -526,7 +526,7 @@ func (d *DefaultClient) checkVersion(ver *versions.Versions, cfg config.LLPkgCon if err != nil { return err } - return checkLegacyVersion(ver, cfg, mappedVersion, isLegacy) + return checkLegacyVersion(ver, pkg, mappedVersion, isLegacy) } // CheckPR validates PR changes and returns affected packages @@ -551,45 +551,21 @@ func (d *DefaultClient) CheckPR() ([]string, error) { ver := versions.Read("llpkgstore.json") for path := range pathMap { - // don't retrieve files from pr changes, consider about maintenance case - files, err := os.ReadDir(path) - if err != nil { - return nil, wrapActionError(err) - } - - if !isValidLLPkg(files) { + if !isLLPkgRoot(path) { delete(pathMap, path) continue } - // 3. Check directory name - llpkgFile := filepath.Join(path, "llpkg.cfg") - cfg, err := config.ParseLLPkgConfig(llpkgFile) + pkg, err := llpkg.NewLLPkg(path) if err != nil { return nil, err } - // in our design, directory name should equal to the package name, - // which means it's not required to be equal. - // - // However, at the current stage, if this is not equal, conan may panic, - // to aovid unexpected behavior, we assert it's equal temporarily. - // this logic may be changed in the future. - packageName := strings.TrimSpace(cfg.Upstream.Package.Name) - if packageName != path { - return nil, fmt.Errorf("actions: directory name is not equal to package name in llpkg.cfg") - } - err = d.checkVersion(ver, cfg) + err = d.checkVersion(ver, pkg) if err != nil { return nil, err } - allPaths = append(allPaths, path) } - // 1. Check there's only one directory in PR - if len(pathMap) > 1 { - return nil, fmt.Errorf("actions: too many to-be-converted directory") - } - // 2. Check config files(llpkg.cfg and llcppg.cfg) if len(pathMap) == 0 { return nil, fmt.Errorf("actions: no valid config files, llpkg.cfg and llcppg.cfg must exist") @@ -622,15 +598,14 @@ func (d *DefaultClient) Postprocessing() error { return err } - // the pr has merged, so we can read it. - cfg, err := config.ParseLLPkgConfig(filepath.Join(clib, "llpkg.cfg")) + pkg, err := llpkg.NewLLPkg(clib) if err != nil { return err } // write it to llpkgstore.json ver := versions.Read("llpkgstore.json") - ver.Write(clib, cfg.Upstream.Package.Version, mappedVersion) + ver.Write(clib, pkg.ClibName(), mappedVersion) if hasTag(version) { return fmt.Errorf("actions: tag has already existed") @@ -674,14 +649,11 @@ func (d *DefaultClient) Release() error { if err != nil { return err } - - // the pr has merged, so we can read it. - cfg, err := config.ParseLLPkgConfig(filepath.Join(clibName, "llpkg.cfg")) + pkg, err := llpkg.NewLLPkg(clibName) if err != nil { return err } - - uc, err := config.NewUpstreamFromConfig(cfg.Upstream) + uc, err := pkg.Upstream() if err != nil { return err } @@ -716,7 +688,11 @@ func (d *DefaultClient) CreateBranchFromLabel(labelName string) error { if version == branchName { return fmt.Errorf("actions: invalid label name format") } - clib, _, err := parseMappedVersion(version) + clibName, _, err := parseMappedVersion(version) + if err != nil { + return err + } + pkg, err := llpkg.NewLLPkg(clibName) if err != nil { return err } @@ -728,7 +704,7 @@ func (d *DefaultClient) CreateBranchFromLabel(labelName string) error { // get latest version of the clib ver := versions.Read("llpkgstore.json") - cversions := ver.CVersions(clib) + cversions := ver.CVersions(pkg.ClibName()) if len(cversions) == 0 { return fmt.Errorf("actions: no clib found") } diff --git a/internal/actions/api_test.go b/internal/actions/api_test.go index ce57523..81d370e 100644 --- a/internal/actions/api_test.go +++ b/internal/actions/api_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - "github.com/goplus/llpkgstore/config" + "github.com/goplus/llpkgstore/internal/actions/llpkg" "github.com/goplus/llpkgstore/internal/actions/versions" ) @@ -39,8 +39,8 @@ func TestLegacyVersion1(t *testing.T) { } } }` - os.WriteFile(".llpkg.cfg", []byte(testLLPkgConfig), 0755) - defer os.Remove(".llpkg.cfg") + os.WriteFile("llpkg.cfg", []byte(testLLPkgConfig), 0755) + defer os.Remove("llpkg.cfg") b := []byte(`{ "cjson": { @@ -55,11 +55,15 @@ func TestLegacyVersion1(t *testing.T) { os.WriteFile(".llpkgstore.json", []byte(b), 0755) defer os.Remove(".llpkgstore.json") - cfg, _ := config.ParseLLPkgConfig(".llpkg.cfg") + pkg, err := llpkg.NewLLPkg("") + if err != nil { + t.Error(err) + return + } ver := versions.Read(".llpkgstore.json") - err := actionFn("main", func(legacy bool) error { - return checkLegacyVersion(ver, cfg, "v0.1.1", legacy) + err = actionFn("main", func(legacy bool) error { + return checkLegacyVersion(ver, pkg, "v0.1.1", legacy) }) if err == nil { @@ -78,8 +82,8 @@ func TestLegacyVersion2(t *testing.T) { } } }` - os.WriteFile(".llpkg.cfg", []byte(testLLPkgConfig), 0755) - defer os.Remove(".llpkg.cfg") + os.WriteFile("llpkg.cfg", []byte(testLLPkgConfig), 0755) + defer os.Remove("llpkg.cfg") b := []byte(`{ "cjson": { @@ -94,11 +98,15 @@ func TestLegacyVersion2(t *testing.T) { os.WriteFile(".llpkgstore.json", []byte(b), 0755) defer os.Remove(".llpkgstore.json") - cfg, _ := config.ParseLLPkgConfig(".llpkg.cfg") + pkg, err := llpkg.NewLLPkg("") + if err != nil { + t.Error(err) + return + } ver := versions.Read(".llpkgstore.json") - err := actionFn("release-branch.cjson/v0.1.1", func(legacy bool) error { - return checkLegacyVersion(ver, cfg, "v0.1.2", legacy) + err = actionFn("release-branch.cjson/v0.1.1", func(legacy bool) error { + return checkLegacyVersion(ver, pkg, "v0.1.2", legacy) }) isValid := err == nil @@ -117,8 +125,8 @@ func TestLegacyVersion3(t *testing.T) { } } }` - os.WriteFile(".llpkg.cfg", []byte(testLLPkgConfig), 0755) - defer os.Remove(".llpkg.cfg") + os.WriteFile("llpkg.cfg", []byte(testLLPkgConfig), 0755) + defer os.Remove("llpkg.cfg") b := []byte(`{ "cjson": { @@ -133,11 +141,15 @@ func TestLegacyVersion3(t *testing.T) { os.WriteFile(".llpkgstore.json", []byte(b), 0755) defer os.Remove(".llpkgstore.json") - cfg, _ := config.ParseLLPkgConfig(".llpkg.cfg") + pkg, err := llpkg.NewLLPkg("") + if err != nil { + t.Error(err) + return + } ver := versions.Read(".llpkgstore.json") - err := actionFn("main", func(legacy bool) error { - return checkLegacyVersion(ver, cfg, "v0.3.0", legacy) + err = actionFn("main", func(legacy bool) error { + return checkLegacyVersion(ver, pkg, "v0.3.0", legacy) }) isValid := err == nil @@ -156,8 +168,8 @@ func TestLegacyVersion4(t *testing.T) { } } }` - os.WriteFile(".llpkg.cfg", []byte(testLLPkgConfig), 0755) - defer os.Remove(".llpkg.cfg") + os.WriteFile("llpkg.cfg", []byte(testLLPkgConfig), 0755) + defer os.Remove("llpkg.cfg") b := []byte(`{ "cjson": { @@ -172,11 +184,15 @@ func TestLegacyVersion4(t *testing.T) { os.WriteFile(".llpkgstore.json", []byte(b), 0755) defer os.Remove(".llpkgstore.json") - cfg, _ := config.ParseLLPkgConfig(".llpkg.cfg") + pkg, err := llpkg.NewLLPkg("") + if err != nil { + t.Error(err) + return + } ver := versions.Read(".llpkgstore.json") - err := actionFn("main", func(legacy bool) error { - return checkLegacyVersion(ver, cfg, "v0.0.1", legacy) + err = actionFn("main", func(legacy bool) error { + return checkLegacyVersion(ver, pkg, "v0.0.1", legacy) }) if err == nil { @@ -194,8 +210,8 @@ func TestLegacyVersion5(t *testing.T) { } } }` - os.WriteFile(".llpkg.cfg", []byte(testLLPkgConfig), 0755) - defer os.Remove(".llpkg.cfg") + os.WriteFile("llpkg.cfg", []byte(testLLPkgConfig), 0755) + defer os.Remove("llpkg.cfg") b := []byte(`{ "cjson": { @@ -210,11 +226,15 @@ func TestLegacyVersion5(t *testing.T) { os.WriteFile(".llpkgstore.json", []byte(b), 0755) defer os.Remove(".llpkgstore.json") - cfg, _ := config.ParseLLPkgConfig(".llpkg.cfg") + pkg, err := llpkg.NewLLPkg("") + if err != nil { + t.Error(err) + return + } ver := versions.Read(".llpkgstore.json") - err := actionFn("main", func(legacy bool) error { - return checkLegacyVersion(ver, cfg, "v0.1.1", legacy) + err = actionFn("main", func(legacy bool) error { + return checkLegacyVersion(ver, pkg, "v0.1.1", legacy) }) if err == nil { diff --git a/internal/actions/llpkg/package.go b/internal/actions/llpkg/package.go new file mode 100644 index 0000000..26cd0c3 --- /dev/null +++ b/internal/actions/llpkg/package.go @@ -0,0 +1,94 @@ +// Package llpkg provides utilities for managing language-linked packages (LLPkgs) +package llpkg + +import ( + "fmt" + "os" + "path" + "path/filepath" + + "github.com/goplus/llpkgstore/config" + "github.com/goplus/llpkgstore/upstream" + "golang.org/x/mod/modfile" + "golang.org/x/mod/semver" +) + +// Wraps an error with the "llpkg" prefix for better context +func wrapLLPkgError(err error) error { + return fmt.Errorf("llpkg: %w", err) +} + +// LLPkg represents a language-linked package with configuration and metadata +type LLPkg struct { + cfg config.LLPkgConfig + + packageName string + packagePath string +} + +// Creates a new LLPkg instance by reading configuration from the specified path +func NewLLPkg(packagePath string) (*LLPkg, error) { + l := &LLPkg{packagePath: packagePath} + + if err := l.readPackageConfig(); err != nil { + return nil, err + } + return l, nil +} + +// Returns the name of the package derived from the go.mod module path +func (p LLPkg) Name() string { + return p.packageName +} + +// Returns the name from llpkg.cfg config +func (p LLPkg) ClibName() string { + return p.cfg.Upstream.Package.Name +} + +// Returns the version from llpkg.cfg config +func (p LLPkg) ClibVersion() string { + return p.cfg.Upstream.Package.Version +} + +// Retrieves the upstream source configuration for this package +func (p *LLPkg) Upstream() (*upstream.Upstream, error) { + return config.NewUpstreamFromConfig(p.cfg.Upstream) +} + +// Reads and validates the package configuration from llpkg.cfg and go.mod files +func (p *LLPkg) readPackageConfig() (err error) { + llpkgFileName := filepath.Join(p.packagePath, "llpkg.cfg") + cfg, err := config.ParseLLPkgConfig(llpkgFileName) + if err != nil { + err = wrapLLPkgError(err) + return + } + goModFileName := filepath.Join(p.packagePath, "go.mod") + + goModContent, err := os.ReadFile(goModFileName) + if err != nil { + err = wrapLLPkgError(err) + return + } + modFile, err := modfile.Parse(goModFileName, goModContent, nil) + if err != nil { + err = wrapLLPkgError(err) + return + } + packageName := path.Base(modFile.Module.Mod.Path) + + if semver.IsValid(packageName) { + // step forward if the last element is version + // exmaple: + // github.com/goplus/llpkg/cjson/v2 + // got v2 + // step forward: github.com/goplus/llpkg/cjson + // got: cjson + packageName = path.Base(path.Dir(modFile.Module.Mod.Path)) + } + + p.cfg = cfg + p.packageName = packageName + return +} diff --git a/internal/actions/llpkg/package_test.go b/internal/actions/llpkg/package_test.go new file mode 100644 index 0000000..eb1dee3 --- /dev/null +++ b/internal/actions/llpkg/package_test.go @@ -0,0 +1,72 @@ +package llpkg + +import ( + "os" + "path/filepath" + "testing" +) + +func demoDir() (dir string, err error) { + dir, err = os.Getwd() + if err != nil { + return + } + // ../../../_demo + + dir = filepath.Join(filepath.Dir(filepath.Dir(filepath.Dir(dir))), "_demo") + return +} + +func checkName(t *testing.T, demoDir string) { + pkg, err := NewLLPkg(demoDir) + if err != nil { + t.Error(err) + return + } + + if pkg.Name() != "libcjson" { + t.Errorf("unexpected package name: want %s got %s", "libcjson", pkg.Name()) + } + + if pkg.ClibName() != "cjson" { + t.Errorf("unexpected llpkg package name: want %s got %s", "cjson", pkg.Name()) + } +} + +func TestReadConfig(t *testing.T) { + demoDir, err := demoDir() + if err != nil { + t.Error(err) + return + } + tempGoModFileName := filepath.Join(demoDir, "go.mod") + + t.Run("with-version-suffix", func(t *testing.T) { + err := os.WriteFile(tempGoModFileName, []byte(`module github.com/goplus/llpkg/libcjson/v2 + + go 1.22.0 + `), 0644) + if err != nil { + t.Error(err) + return + } + defer os.Remove(tempGoModFileName) + + checkName(t, demoDir) + }) + + t.Run("without-version-suffix", func(t *testing.T) { + err := os.WriteFile(tempGoModFileName, []byte(`module github.com/goplus/llpkg/libcjson + + go 1.22.0 + `), 0644) + if err != nil { + t.Error(err) + return + } + defer os.Remove(tempGoModFileName) + + checkName(t, demoDir) + }) + +} From 5258a2be911a5f3734c4203dae0559711ca5162d Mon Sep 17 00:00:00 2001 From: Haolan Date: Fri, 25 Apr 2025 18:52:08 +0800 Subject: [PATCH 02/22] fix: default to current dir when package path is empty --- internal/actions/llpkg/package.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/internal/actions/llpkg/package.go b/internal/actions/llpkg/package.go index 26cd0c3..2b6cd06 100644 --- a/internal/actions/llpkg/package.go +++ b/internal/actions/llpkg/package.go @@ -28,6 +28,14 @@ type LLPkg struct { // Creates a new LLPkg instance by reading configuration from the specified path func NewLLPkg(packagePath string) (*LLPkg, error) { + if packagePath == "" { + var err error + packagePath, err = os.Getwd() + if err != nil { + return nil, err + } + } + l := &LLPkg{packagePath: packagePath} if err := l.readPackageConfig(); err != nil { From 418f0b8c247e74146dd869644ad0441fbe14983e Mon Sep 17 00:00:00 2001 From: Meteor Date: Sun, 27 Apr 2025 06:42:55 +0000 Subject: [PATCH 03/22] fix: ci --- internal/actions/api_test.go | 113 ++++++++++++++++--------- internal/actions/llpkg/package_test.go | 20 ++++- 2 files changed, 92 insertions(+), 41 deletions(-) diff --git a/internal/actions/api_test.go b/internal/actions/api_test.go index 81d370e..8d29164 100644 --- a/internal/actions/api_test.go +++ b/internal/actions/api_test.go @@ -3,6 +3,7 @@ package actions import ( "os" "os/exec" + "path/filepath" "strings" "testing" @@ -30,6 +31,28 @@ func actionFn(branchName string, fn func(legacy bool) error) error { return fn(strings.HasPrefix(branchName, BranchPrefix)) } +func prepareEnv(llpkgConfig, mappingTable []byte) (testDir string, err error) { + testDir, err = os.MkdirTemp("", "action-test") + if err != nil { + return + } + err = os.WriteFile(filepath.Join(testDir, "llpkg.cfg"), []byte(llpkgConfig), 0755) + if err != nil { + os.RemoveAll(testDir) + return + } + err = os.WriteFile(filepath.Join(testDir, "llpkgstore.json"), mappingTable, 0644) + if err != nil { + os.RemoveAll(testDir) + return + } + + os.WriteFile(filepath.Join(testDir, "go.mod"), []byte(`module cjson + go 1.22 + `), 0644) + return +} + func TestLegacyVersion1(t *testing.T) { testLLPkgConfig := `{ "upstream": { @@ -39,10 +62,8 @@ func TestLegacyVersion1(t *testing.T) { } } }` - os.WriteFile("llpkg.cfg", []byte(testLLPkgConfig), 0755) - defer os.Remove("llpkg.cfg") - b := []byte(`{ + testMappingTable := `{ "cjson": { "versions" : { "1.7.16": ["v0.1.0"], @@ -50,17 +71,21 @@ func TestLegacyVersion1(t *testing.T) { "1.8.18": ["v0.1.0", "v0.1.1"] } } - }`) + }` - os.WriteFile(".llpkgstore.json", []byte(b), 0755) - defer os.Remove(".llpkgstore.json") + testDir, err := prepareEnv([]byte(testLLPkgConfig), []byte(testMappingTable)) + if err != nil { + t.Error(err) + return + } + defer os.RemoveAll(testDir) - pkg, err := llpkg.NewLLPkg("") + pkg, err := llpkg.NewLLPkg(testDir) if err != nil { t.Error(err) return } - ver := versions.Read(".llpkgstore.json") + ver := versions.Read(filepath.Join(testDir, "llpkgstore.json")) err = actionFn("main", func(legacy bool) error { return checkLegacyVersion(ver, pkg, "v0.1.1", legacy) @@ -82,10 +107,8 @@ func TestLegacyVersion2(t *testing.T) { } } }` - os.WriteFile("llpkg.cfg", []byte(testLLPkgConfig), 0755) - defer os.Remove("llpkg.cfg") - b := []byte(`{ + testMappingTable := `{ "cjson": { "versions" : { "1.8.18": ["v0.2.0", "v0.2.1"], @@ -93,17 +116,21 @@ func TestLegacyVersion2(t *testing.T) { "1.7.16: ["v1.1.0"] } } - }`) + }` - os.WriteFile(".llpkgstore.json", []byte(b), 0755) - defer os.Remove(".llpkgstore.json") + testDir, err := prepareEnv([]byte(testLLPkgConfig), []byte(testMappingTable)) + if err != nil { + t.Error(err) + return + } + defer os.RemoveAll(testDir) - pkg, err := llpkg.NewLLPkg("") + pkg, err := llpkg.NewLLPkg(testDir) if err != nil { t.Error(err) return } - ver := versions.Read(".llpkgstore.json") + ver := versions.Read(filepath.Join(testDir, "llpkgstore.json")) err = actionFn("release-branch.cjson/v0.1.1", func(legacy bool) error { return checkLegacyVersion(ver, pkg, "v0.1.2", legacy) @@ -125,10 +152,8 @@ func TestLegacyVersion3(t *testing.T) { } } }` - os.WriteFile("llpkg.cfg", []byte(testLLPkgConfig), 0755) - defer os.Remove("llpkg.cfg") - b := []byte(`{ + testMappingTable := `{ "cjson": { "versions" : { "1.7.16": ["v0.1.0"], @@ -136,17 +161,21 @@ func TestLegacyVersion3(t *testing.T) { "1.8.18": ["v0.2.0", "v0.2.1"] } } - }`) + }` - os.WriteFile(".llpkgstore.json", []byte(b), 0755) - defer os.Remove(".llpkgstore.json") + testDir, err := prepareEnv([]byte(testLLPkgConfig), []byte(testMappingTable)) + if err != nil { + t.Error(err) + return + } + defer os.RemoveAll(testDir) - pkg, err := llpkg.NewLLPkg("") + pkg, err := llpkg.NewLLPkg(testDir) if err != nil { t.Error(err) return } - ver := versions.Read(".llpkgstore.json") + ver := versions.Read(filepath.Join(testDir, "llpkgstore.json")) err = actionFn("main", func(legacy bool) error { return checkLegacyVersion(ver, pkg, "v0.3.0", legacy) @@ -168,10 +197,8 @@ func TestLegacyVersion4(t *testing.T) { } } }` - os.WriteFile("llpkg.cfg", []byte(testLLPkgConfig), 0755) - defer os.Remove("llpkg.cfg") - b := []byte(`{ + testMappingTable := `{ "cjson": { "versions" : { "1.8.18": ["v0.2.0", "v0.2.1"], @@ -179,17 +206,21 @@ func TestLegacyVersion4(t *testing.T) { "1.7.18": ["v0.1.1", "v0.1.2"] } } - }`) + }` - os.WriteFile(".llpkgstore.json", []byte(b), 0755) - defer os.Remove(".llpkgstore.json") + testDir, err := prepareEnv([]byte(testLLPkgConfig), []byte(testMappingTable)) + if err != nil { + t.Error(err) + return + } + defer os.RemoveAll(testDir) - pkg, err := llpkg.NewLLPkg("") + pkg, err := llpkg.NewLLPkg(testDir) if err != nil { t.Error(err) return } - ver := versions.Read(".llpkgstore.json") + ver := versions.Read(filepath.Join(testDir, "llpkgstore.json")) err = actionFn("main", func(legacy bool) error { return checkLegacyVersion(ver, pkg, "v0.0.1", legacy) @@ -210,10 +241,8 @@ func TestLegacyVersion5(t *testing.T) { } } }` - os.WriteFile("llpkg.cfg", []byte(testLLPkgConfig), 0755) - defer os.Remove("llpkg.cfg") - b := []byte(`{ + testMappingTable := `{ "cjson": { "versions" : { "1.7.16": ["v0.1.0"], @@ -221,17 +250,21 @@ func TestLegacyVersion5(t *testing.T) { "1.8.18": ["v0.2.0", "v0.2.1"] } } - }`) + }` - os.WriteFile(".llpkgstore.json", []byte(b), 0755) - defer os.Remove(".llpkgstore.json") + testDir, err := prepareEnv([]byte(testLLPkgConfig), []byte(testMappingTable)) + if err != nil { + t.Error(err) + return + } + defer os.RemoveAll(testDir) - pkg, err := llpkg.NewLLPkg("") + pkg, err := llpkg.NewLLPkg(testDir) if err != nil { t.Error(err) return } - ver := versions.Read(".llpkgstore.json") + ver := versions.Read(filepath.Join(testDir, "llpkgstore.json")) err = actionFn("main", func(legacy bool) error { return checkLegacyVersion(ver, pkg, "v0.1.1", legacy) diff --git a/internal/actions/llpkg/package_test.go b/internal/actions/llpkg/package_test.go index eb1dee3..839ce2e 100644 --- a/internal/actions/llpkg/package_test.go +++ b/internal/actions/llpkg/package_test.go @@ -23,7 +23,6 @@ func checkName(t *testing.T, demoDir string) { t.Error(err) return } - if pkg.Name() != "libcjson" { t.Errorf("unexpected package name: want %s got %s", "libcjson", pkg.Name()) } @@ -31,6 +30,10 @@ func checkName(t *testing.T, demoDir string) { if pkg.ClibName() != "cjson" { t.Errorf("unexpected llpkg package name: want %s got %s", "cjson", pkg.Name()) } + + if pkg.ClibVersion() != "1.7.18" { + t.Errorf("unexpected llpkg package version: want %s got %s", "1.7.18", pkg.ClibVersion()) + } } func TestReadConfig(t *testing.T) { @@ -69,4 +72,19 @@ func TestReadConfig(t *testing.T) { checkName(t, demoDir) }) + t.Run("raw-package-name", func(t *testing.T) { + err := os.WriteFile(tempGoModFileName, []byte(`module libcjson + + go 1.22.0 + `), 0644) + if err != nil { + t.Error(err) + return + } + defer os.Remove(tempGoModFileName) + + checkName(t, demoDir) + + }) + } From ceb23118259894e73b242e330c014c19bf391f28 Mon Sep 17 00:00:00 2001 From: Meteor Date: Sun, 27 Apr 2025 06:50:42 +0000 Subject: [PATCH 04/22] fix: add test --- internal/actions/llpkg/package_test.go | 30 +++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/internal/actions/llpkg/package_test.go b/internal/actions/llpkg/package_test.go index 839ce2e..39e046e 100644 --- a/internal/actions/llpkg/package_test.go +++ b/internal/actions/llpkg/package_test.go @@ -17,10 +17,16 @@ func demoDir() (dir string, err error) { return } -func checkName(t *testing.T, demoDir string) { +func checkName(t *testing.T, demoDir string, wantErr bool) { pkg, err := NewLLPkg(demoDir) if err != nil { - t.Error(err) + if !wantErr { + t.Error(err) + } + return + } + if wantErr { + t.Error("unexpected no error") return } if pkg.Name() != "libcjson" { @@ -55,7 +61,7 @@ func TestReadConfig(t *testing.T) { } defer os.Remove(tempGoModFileName) - checkName(t, demoDir) + checkName(t, demoDir, false) }) t.Run("without-version-suffix", func(t *testing.T) { @@ -69,7 +75,7 @@ func TestReadConfig(t *testing.T) { } defer os.Remove(tempGoModFileName) - checkName(t, demoDir) + checkName(t, demoDir, false) }) t.Run("raw-package-name", func(t *testing.T) { @@ -83,8 +89,22 @@ func TestReadConfig(t *testing.T) { } defer os.Remove(tempGoModFileName) - checkName(t, demoDir) + checkName(t, demoDir, false) + + }) + + t.Run("wrong-go-mod", func(t *testing.T) { + err := os.WriteFile(tempGoModFileName, []byte(`go 1.22.0`), 0644) + if err != nil { + t.Error(err) + return + } + + checkName(t, demoDir, true) + }) + t.Run("no-go-mod", func(t *testing.T) { + checkName(t, demoDir, true) }) } From 2c56c04ebdb13b911de8929038fb2dd8b10bc65b Mon Sep 17 00:00:00 2001 From: Meteor Date: Sun, 27 Apr 2025 06:55:32 +0000 Subject: [PATCH 05/22] fix: add nil check --- internal/actions/llpkg/package.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/internal/actions/llpkg/package.go b/internal/actions/llpkg/package.go index 2b6cd06..f18b109 100644 --- a/internal/actions/llpkg/package.go +++ b/internal/actions/llpkg/package.go @@ -2,6 +2,7 @@ package llpkg import ( + "errors" "fmt" "os" "path" @@ -13,6 +14,8 @@ import ( "golang.org/x/mod/semver" ) +var ErrNoModulePath = errors.New("llpkg: no module path") + // Wraps an error with the "llpkg" prefix for better context func wrapLLPkgError(err error) error { return fmt.Errorf("llpkg: %w", err) @@ -84,6 +87,10 @@ func (p *LLPkg) readPackageConfig() (err error) { err = wrapLLPkgError(err) return } + if modFile.Module == nil { + err = ErrNoModulePath + return + } packageName := path.Base(modFile.Module.Mod.Path) if semver.IsValid(packageName) { From 56d3dea99c5cbf950c3cc7b5d95fd13a59ce9218 Mon Sep 17 00:00:00 2001 From: Meteor Date: Sun, 27 Apr 2025 07:57:22 +0000 Subject: [PATCH 06/22] fix: use dir name instead of llpkg.cfg package name --- cmd/llpkgstore/internal/generate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/llpkgstore/internal/generate.go b/cmd/llpkgstore/internal/generate.go index da36d18..6711fba 100644 --- a/cmd/llpkgstore/internal/generate.go +++ b/cmd/llpkgstore/internal/generate.go @@ -65,7 +65,7 @@ func runLLCppgGenerateWithDir(dir string) error { } } - generator := llcppg.New(dir, cfg.Upstream.Package.Name, tempDir) + generator := llcppg.New(dir, filepath.Base(dir), tempDir) return generator.Generate(dir) } From 3220284ae5780ee6ce72b45fb07a3289faa36358 Mon Sep 17 00:00:00 2001 From: Meteor Date: Sun, 27 Apr 2025 08:09:37 +0000 Subject: [PATCH 07/22] fix: use user-provided go.mod last element name for package name --- cmd/llpkgstore/internal/verification.go | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/cmd/llpkgstore/internal/verification.go b/cmd/llpkgstore/internal/verification.go index 9179ec1..3f73f3a 100644 --- a/cmd/llpkgstore/internal/verification.go +++ b/cmd/llpkgstore/internal/verification.go @@ -2,15 +2,14 @@ package internal import ( "encoding/json" - "fmt" "os" "os/exec" "path/filepath" - "github.com/goplus/llpkgstore/config" "github.com/goplus/llpkgstore/internal/actions" "github.com/goplus/llpkgstore/internal/actions/env" "github.com/goplus/llpkgstore/internal/actions/generator/llcppg" + "github.com/goplus/llpkgstore/internal/actions/llpkg" "github.com/spf13/cobra" ) @@ -24,19 +23,15 @@ var verificationCmd = &cobra.Command{ } func runLLCppgVerificationWithDir(dir string) error { - cfg, err := config.ParseLLPkgConfig(filepath.Join(dir, LLGOModuleIdentifyFile)) - if err != nil { - return fmt.Errorf("parse config error: %v", err) - } - uc, err := config.NewUpstreamFromConfig(cfg.Upstream) + pkg, err := llpkg.NewLLPkg(dir) if err != nil { return err } - _, err = uc.Installer.Install(uc.Pkg, dir) + uc, err := pkg.Upstream() if err != nil { return err } - generator := llcppg.New(dir, cfg.Upstream.Package.Name, dir) + generator := llcppg.New(dir, pkg.Name(), dir) generated := filepath.Join(dir, ".generated") os.Mkdir(generated, 0777) From 575d87f465e1c73165313a2edffd3ff14f5eda05 Mon Sep 17 00:00:00 2001 From: Meteor Date: Sun, 27 Apr 2025 08:22:54 +0000 Subject: [PATCH 08/22] fix: install before verifcation --- cmd/llpkgstore/internal/verification.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/llpkgstore/internal/verification.go b/cmd/llpkgstore/internal/verification.go index 3f73f3a..5161af9 100644 --- a/cmd/llpkgstore/internal/verification.go +++ b/cmd/llpkgstore/internal/verification.go @@ -31,6 +31,10 @@ func runLLCppgVerificationWithDir(dir string) error { if err != nil { return err } + _, err = uc.Installer.Install(uc.Pkg, dir) + if err != nil { + return err + } generator := llcppg.New(dir, pkg.Name(), dir) generated := filepath.Join(dir, ".generated") From 3dd955bd109d0f18ec324ccba11113ffa1fb19c0 Mon Sep 17 00:00:00 2001 From: Rick Guo Date: Mon, 28 Apr 2025 09:33:51 +0800 Subject: [PATCH 09/22] Update internal/actions/llpkg/package_test.go MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 张之阳 <51194195+luoliwoshang@users.noreply.github.com> --- internal/actions/llpkg/package_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/internal/actions/llpkg/package_test.go b/internal/actions/llpkg/package_test.go index 39e046e..40acf64 100644 --- a/internal/actions/llpkg/package_test.go +++ b/internal/actions/llpkg/package_test.go @@ -11,9 +11,7 @@ func demoDir() (dir string, err error) { if err != nil { return } - // ../../../_demo - - dir = filepath.Join(filepath.Dir(filepath.Dir(filepath.Dir(dir))), "_demo") + return filepath.Join(dir, "..", "..", "..", "_demo"), nil return } From 6b13942d4a75292c500cd98a05613714ac00c6cb Mon Sep 17 00:00:00 2001 From: Haolan Date: Mon, 28 Apr 2025 10:07:03 +0800 Subject: [PATCH 10/22] feat: add new type to avoid misusing the function --- cmd/llpkgstore/internal/generate.go | 3 +- cmd/llpkgstore/internal/verification.go | 2 +- internal/actions/actions.go | 9 +- internal/actions/actions_test.go | 2 +- internal/actions/api.go | 34 ++-- internal/actions/api_test.go | 12 +- internal/actions/generator/llcppg/llcppg.go | 9 +- .../actions/generator/llcppg/llcppg_test.go | 3 +- internal/actions/llpkg/package.go | 16 +- internal/actions/llpkg/package_test.go | 102 +++++------ internal/actions/llpkg/type.go | 25 +++ internal/actions/mappingtable/table.go | 171 ++++++++++++++++++ .../table_test.go} | 2 +- 13 files changed, 291 insertions(+), 99 deletions(-) create mode 100644 internal/actions/llpkg/type.go create mode 100644 internal/actions/mappingtable/table.go rename internal/actions/{versions/versions_test.go => mappingtable/table_test.go} (99%) diff --git a/cmd/llpkgstore/internal/generate.go b/cmd/llpkgstore/internal/generate.go index 6711fba..2a25cc2 100644 --- a/cmd/llpkgstore/internal/generate.go +++ b/cmd/llpkgstore/internal/generate.go @@ -9,6 +9,7 @@ import ( "github.com/goplus/llpkgstore/config" "github.com/goplus/llpkgstore/internal/actions/generator/llcppg" + "github.com/goplus/llpkgstore/internal/actions/llpkg" "github.com/goplus/llpkgstore/internal/file" "github.com/goplus/llpkgstore/internal/pc" "github.com/spf13/cobra" @@ -65,7 +66,7 @@ func runLLCppgGenerateWithDir(dir string) error { } } - generator := llcppg.New(dir, filepath.Base(dir), tempDir) + generator := llcppg.New(dir, tempDir, llpkg.PackageName(filepath.Base(dir))) return generator.Generate(dir) } diff --git a/cmd/llpkgstore/internal/verification.go b/cmd/llpkgstore/internal/verification.go index 5161af9..8ba428c 100644 --- a/cmd/llpkgstore/internal/verification.go +++ b/cmd/llpkgstore/internal/verification.go @@ -35,7 +35,7 @@ func runLLCppgVerificationWithDir(dir string) error { if err != nil { return err } - generator := llcppg.New(dir, pkg.Name(), dir) + generator := llcppg.New(dir, dir, pkg.Name()) generated := filepath.Join(dir, ".generated") os.Mkdir(generated, 0777) diff --git a/internal/actions/actions.go b/internal/actions/actions.go index 300342f..a066822 100644 --- a/internal/actions/actions.go +++ b/internal/actions/actions.go @@ -15,6 +15,7 @@ import ( "github.com/goplus/llpkgstore/internal/actions/env" "github.com/goplus/llpkgstore/internal/actions/llpkg" + "github.com/goplus/llpkgstore/internal/actions/mappingtable" "github.com/goplus/llpkgstore/internal/actions/versions" "github.com/goplus/llpkgstore/internal/file" "github.com/goplus/llpkgstore/internal/pc" @@ -99,13 +100,13 @@ func shaFromTag(tag string) string { // parseMappedVersion splits the mapped version string into library name and version. // Input format: "clib/semver" where semver starts with 'v' // Panics if input format is invalid or version isn't valid semantic version -func parseMappedVersion(version string) (clib, mappedVersion string, err error) { +func parseMappedVersion(version string) (packageName llpkg.PackageName, mappedVersion string, err error) { arr := strings.Split(version, "/") if len(arr) != 2 { err = fmt.Errorf("actions: invalid mapped version format") return } - clib, mappedVersion = arr[0], arr[1] + packageName, mappedVersion = llpkg.PackageName(arr[0]), arr[1] if !semver.IsValid(mappedVersion) { err = fmt.Errorf("actions: invalid mapped version format: mappedVersion is not a semver") @@ -134,7 +135,7 @@ func isLLPkgRoot(path string) bool { // checkLegacyVersion validates versioning strategy for legacy package submissions // Ensures semantic versioning compliance and proper branch maintenance strategy -func checkLegacyVersion(ver *versions.Versions, pkg *llpkg.LLPkg, mappedVersion string, isLegacy bool) error { +func checkLegacyVersion(ver *mappingtable.Versions, pkg *llpkg.LLPkg, mappedVersion string, isLegacy bool) error { clibName := pkg.ClibName() clibVersion := pkg.ClibVersion() @@ -142,7 +143,7 @@ func checkLegacyVersion(ver *versions.Versions, pkg *llpkg.LLPkg, mappedVersion return fmt.Errorf("actions: repeat semver %s", mappedVersion) } vers := ver.CVersions(clibName) - currentVersion := versions.ToSemVer(clibVersion) + currentVersion := clibVersion.ToSemVer() // skip when we're the only latest version or C version doesn't follow semver. if len(vers) == 0 || !semver.IsValid(currentVersion) { diff --git a/internal/actions/actions_test.go b/internal/actions/actions_test.go index bbf6dee..512fbf0 100644 --- a/internal/actions/actions_test.go +++ b/internal/actions/actions_test.go @@ -17,7 +17,7 @@ func demoDir() (dir string, err error) { } // ../../_demo - dir = filepath.Join(filepath.Dir(filepath.Dir(dir)), "_demo") + dir = filepath.Join("..", "..", "_demo") return } diff --git a/internal/actions/api.go b/internal/actions/api.go index 6758bd1..902a52d 100644 --- a/internal/actions/api.go +++ b/internal/actions/api.go @@ -17,6 +17,7 @@ import ( "github.com/google/go-github/v69/github" "github.com/goplus/llpkgstore/internal/actions/env" "github.com/goplus/llpkgstore/internal/actions/llpkg" + "github.com/goplus/llpkgstore/internal/actions/mappingtable" "github.com/goplus/llpkgstore/internal/actions/versions" "golang.org/x/sync/errgroup" ) @@ -30,18 +31,11 @@ const ( regexString = `Release-as:\s%s/v(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)\.(?P0|[1-9]\d*)(?:-(?P(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?` ) -// regex compiles a regular expression pattern to detect "Release-as" directives in commit messages -// Parameters: -// -// packageName: Name of the package to format into the regex pattern -// -// Returns: -// -// *regexp.Regexp: Compiled regular expression for version parsing -func regex(packageName string) *regexp.Regexp { +// compileRawCommitVersionRegex compiles a regular expression pattern to detect "Release-as" directives in commit messages +func compileRawCommitVersionRegex(packageName llpkg.PackageName) *regexp.Regexp { // format: Release-as: clib/semver(with v prefix) // Must have one space in the end of Release-as: - return regexp.MustCompile(fmt.Sprintf(regexString, packageName)) + return regexp.MustCompile(fmt.Sprintf(regexString, packageName.String())) } func binaryZip(packageName string) string { @@ -245,7 +239,7 @@ func (d *DefaultClient) removeLabel(labelName string) error { // // If no valid version found in PR commits func (d *DefaultClient) checkMappedVersion(pkg *llpkg.LLPkg) (mappedVersion string, err error) { - matchMappedVersion := regex(pkg.Name()) + matchMappedVersion := compileRawCommitVersionRegex(pkg.Name()) allCommits, err := d.currentPRCommit() if err != nil { @@ -307,7 +301,7 @@ func (d *DefaultClient) mappedVersion() (string, error) { message := commit.GetCommit().GetMessage() // parse the mapped version - mappedVersion := regex(".*").FindString(message) + mappedVersion := compileRawCommitVersionRegex(llpkg.PackageName(".*")).FindString(message) // mapped version not found, a normal commit? if mappedVersion == "" { return "", ErrNoMappedVersion @@ -510,7 +504,7 @@ func (d *DefaultClient) removeBranch(branchName string) error { // // ver: Version store object // cfg: Package configuration -func (d *DefaultClient) checkVersion(ver *versions.Versions, pkg *llpkg.LLPkg) error { +func (d *DefaultClient) checkVersion(ver *mappingtable.Versions, pkg *llpkg.LLPkg) error { // 4. Check MappedVersion version, err := d.checkMappedVersion(pkg) if err != nil { @@ -548,7 +542,7 @@ func (d *DefaultClient) CheckPR() ([]string, error) { var allPaths []string - ver := versions.Read("llpkgstore.json") + ver := mappingtable.Read("llpkgstore.json") for path := range pathMap { if !isLLPkgRoot(path) { @@ -598,14 +592,14 @@ func (d *DefaultClient) Postprocessing() error { return err } - pkg, err := llpkg.NewLLPkg(clib) + pkg, err := llpkg.FromPackageName(clib) if err != nil { return err } // write it to llpkgstore.json - ver := versions.Read("llpkgstore.json") - ver.Write(clib, pkg.ClibName(), mappedVersion) + ver := mappingtable.Read("llpkgstore.json") + ver.Write(pkg.ClibName(), pkg.ClibVersion(), mappedVersion) if hasTag(version) { return fmt.Errorf("actions: tag has already existed") @@ -649,7 +643,7 @@ func (d *DefaultClient) Release() error { if err != nil { return err } - pkg, err := llpkg.NewLLPkg(clibName) + pkg, err := llpkg.FromPackageName(clibName) if err != nil { return err } @@ -692,7 +686,7 @@ func (d *DefaultClient) CreateBranchFromLabel(labelName string) error { if err != nil { return err } - pkg, err := llpkg.NewLLPkg(clibName) + pkg, err := llpkg.FromPackageName(clibName) if err != nil { return err } @@ -702,7 +696,7 @@ func (d *DefaultClient) CreateBranchFromLabel(labelName string) error { // according to branch maintenance strategy // get latest version of the clib - ver := versions.Read("llpkgstore.json") + ver := mappingtable.Read("llpkgstore.json") cversions := ver.CVersions(pkg.ClibName()) if len(cversions) == 0 { diff --git a/internal/actions/api_test.go b/internal/actions/api_test.go index 8d29164..89f76c8 100644 --- a/internal/actions/api_test.go +++ b/internal/actions/api_test.go @@ -8,7 +8,7 @@ import ( "testing" "github.com/goplus/llpkgstore/internal/actions/llpkg" - "github.com/goplus/llpkgstore/internal/actions/versions" + "github.com/goplus/llpkgstore/internal/actions/mappingtable" ) func TestHasTag(t *testing.T) { @@ -85,7 +85,7 @@ func TestLegacyVersion1(t *testing.T) { t.Error(err) return } - ver := versions.Read(filepath.Join(testDir, "llpkgstore.json")) + ver := mappingtable.Read(filepath.Join(testDir, "llpkgstore.json")) err = actionFn("main", func(legacy bool) error { return checkLegacyVersion(ver, pkg, "v0.1.1", legacy) @@ -130,7 +130,7 @@ func TestLegacyVersion2(t *testing.T) { t.Error(err) return } - ver := versions.Read(filepath.Join(testDir, "llpkgstore.json")) + ver := mappingtable.Read(filepath.Join(testDir, "llpkgstore.json")) err = actionFn("release-branch.cjson/v0.1.1", func(legacy bool) error { return checkLegacyVersion(ver, pkg, "v0.1.2", legacy) @@ -175,7 +175,7 @@ func TestLegacyVersion3(t *testing.T) { t.Error(err) return } - ver := versions.Read(filepath.Join(testDir, "llpkgstore.json")) + ver := mappingtable.Read(filepath.Join(testDir, "llpkgstore.json")) err = actionFn("main", func(legacy bool) error { return checkLegacyVersion(ver, pkg, "v0.3.0", legacy) @@ -220,7 +220,7 @@ func TestLegacyVersion4(t *testing.T) { t.Error(err) return } - ver := versions.Read(filepath.Join(testDir, "llpkgstore.json")) + ver := mappingtable.Read(filepath.Join(testDir, "llpkgstore.json")) err = actionFn("main", func(legacy bool) error { return checkLegacyVersion(ver, pkg, "v0.0.1", legacy) @@ -264,7 +264,7 @@ func TestLegacyVersion5(t *testing.T) { t.Error(err) return } - ver := versions.Read(filepath.Join(testDir, "llpkgstore.json")) + ver := mappingtable.Read(filepath.Join(testDir, "llpkgstore.json")) err = actionFn("main", func(legacy bool) error { return checkLegacyVersion(ver, pkg, "v0.1.1", legacy) diff --git a/internal/actions/generator/llcppg/llcppg.go b/internal/actions/generator/llcppg/llcppg.go index e219791..1f26d16 100644 --- a/internal/actions/generator/llcppg/llcppg.go +++ b/internal/actions/generator/llcppg/llcppg.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/goplus/llpkgstore/internal/actions/generator" + "github.com/goplus/llpkgstore/internal/actions/llpkg" "github.com/goplus/llpkgstore/internal/file" "github.com/goplus/llpkgstore/internal/hashutils" "github.com/goplus/llpkgstore/internal/pc" @@ -67,17 +68,17 @@ func isExitedUnexpectedly(err error) bool { type llcppgGenerator struct { dir string // llcppg.cfg abs path pcDir string - packageName string + packageName llpkg.PackageName } -func New(dir, packageName, pcDir string) generator.Generator { +func New(dir, pcDir string, packageName llpkg.PackageName) generator.Generator { return &llcppgGenerator{dir: dir, packageName: packageName, pcDir: pcDir} } // normalizeModulePath returns a normalized module path like // cjson => github.com/goplus/llpkg/cjson func (l *llcppgGenerator) normalizeModulePath() string { - return goplusRepo + l.packageName + return goplusRepo + l.packageName.String() } func (l *llcppgGenerator) findSymbJSON() string { @@ -134,7 +135,7 @@ func (l *llcppgGenerator) Generate(toDir string) error { return errors.Join(ErrLLCppgGenerate, err) } // check output again - generatedPath := filepath.Join(path, l.packageName) + generatedPath := filepath.Join(path, l.packageName.String()) if _, err := os.Stat(generatedPath); os.IsNotExist(err) { return errors.Join(ErrLLCppgCheck, errors.New("generate fail")) } diff --git a/internal/actions/generator/llcppg/llcppg_test.go b/internal/actions/generator/llcppg/llcppg_test.go index aceeaab..0dfa511 100644 --- a/internal/actions/generator/llcppg/llcppg_test.go +++ b/internal/actions/generator/llcppg/llcppg_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/goplus/llpkgstore/config" + "github.com/goplus/llpkgstore/internal/actions/llpkg" "github.com/goplus/llpkgstore/internal/hashutils" "golang.org/x/mod/modfile" ) @@ -125,7 +126,7 @@ func TestLLCppg(t *testing.T) { os.Mkdir("testgenerate", 0777) defer os.RemoveAll("testgenerate") path, _ := filepath.Abs("testgenerate") - generator := New(path, "cjson", path) + generator := New(path, path, llpkg.PackageName("cjson")) os.WriteFile("testgenerate/llcppg.cfg", []byte(testLLCppgConfig), 0755) os.WriteFile("testgenerate/llpkg.cfg", []byte(testLLPkgConfig), 0755) diff --git a/internal/actions/llpkg/package.go b/internal/actions/llpkg/package.go index f18b109..79d4fbe 100644 --- a/internal/actions/llpkg/package.go +++ b/internal/actions/llpkg/package.go @@ -47,19 +47,23 @@ func NewLLPkg(packagePath string) (*LLPkg, error) { return l, nil } +func FromPackageName(pkgName PackageName) (*LLPkg, error) { + return NewLLPkg(pkgName.String()) +} + // Returns the name of the package derived from the go.mod module path -func (p LLPkg) Name() string { - return p.packageName +func (p LLPkg) Name() PackageName { + return PackageName(p.packageName) } // Returns the name from llpkg.cfg config -func (p LLPkg) ClibName() string { - return p.cfg.Upstream.Package.Name +func (p LLPkg) ClibName() ClibName { + return ClibName(p.cfg.Upstream.Package.Name) } // Returns the version from llpkg.cfg config -func (p LLPkg) ClibVersion() string { - return p.cfg.Upstream.Package.Version +func (p LLPkg) ClibVersion() ClibVersion { + return ClibVersion(p.cfg.Upstream.Package.Version) } // Retrieves the upstream source configuration for this package diff --git a/internal/actions/llpkg/package_test.go b/internal/actions/llpkg/package_test.go index 39e046e..45f2bfb 100644 --- a/internal/actions/llpkg/package_test.go +++ b/internal/actions/llpkg/package_test.go @@ -13,7 +13,7 @@ func demoDir() (dir string, err error) { } // ../../../_demo - dir = filepath.Join(filepath.Dir(filepath.Dir(filepath.Dir(dir))), "_demo") + dir = filepath.Join("..", "..", "..", "_demo") return } @@ -48,61 +48,55 @@ func TestReadConfig(t *testing.T) { t.Error(err) return } + testCases := []struct { + name string + wantErr bool + goModContent []byte + }{ + { + name: "with-version-suffix", + wantErr: false, + goModContent: []byte(`module github.com/goplus/llpkg/libcjson/v2 + + go 1.22.0 + `), + }, + { + name: "without-version-suffix", + wantErr: false, + goModContent: []byte(`module github.com/goplus/llpkg/libcjson + + go 1.22.0 + `), + }, + { + name: "raw-package-name", + wantErr: false, + goModContent: []byte(`module libcjson + + go 1.22.0 + `), + }, + { + name: "wrong-go-mod", + wantErr: true, + goModContent: []byte(`go 1.22.0`), + }, + } tempGoModFileName := filepath.Join(demoDir, "go.mod") - t.Run("with-version-suffix", func(t *testing.T) { - err := os.WriteFile(tempGoModFileName, []byte(`module github.com/goplus/llpkg/libcjson/v2 - - go 1.22.0 - `), 0644) - if err != nil { - t.Error(err) - return - } - defer os.Remove(tempGoModFileName) - - checkName(t, demoDir, false) - }) - - t.Run("without-version-suffix", func(t *testing.T) { - err := os.WriteFile(tempGoModFileName, []byte(`module github.com/goplus/llpkg/libcjson - - go 1.22.0 - `), 0644) - if err != nil { - t.Error(err) - return - } - defer os.Remove(tempGoModFileName) - - checkName(t, demoDir, false) - }) - - t.Run("raw-package-name", func(t *testing.T) { - err := os.WriteFile(tempGoModFileName, []byte(`module libcjson - - go 1.22.0 - `), 0644) - if err != nil { - t.Error(err) - return - } - defer os.Remove(tempGoModFileName) - - checkName(t, demoDir, false) - - }) - - t.Run("wrong-go-mod", func(t *testing.T) { - err := os.WriteFile(tempGoModFileName, []byte(`go 1.22.0`), 0644) - if err != nil { - t.Error(err) - return - } - - checkName(t, demoDir, true) - }) - + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + err := os.WriteFile(tempGoModFileName, tt.goModContent, 0644) + if err != nil { + t.Error(err) + return + } + defer os.Remove(tempGoModFileName) + + checkName(t, demoDir, false) + }) + } t.Run("no-go-mod", func(t *testing.T) { checkName(t, demoDir, true) }) diff --git a/internal/actions/llpkg/type.go b/internal/actions/llpkg/type.go new file mode 100644 index 0000000..9910098 --- /dev/null +++ b/internal/actions/llpkg/type.go @@ -0,0 +1,25 @@ +package llpkg + +import "github.com/goplus/llpkgstore/internal/actions/versions" + +type ( + PackageName string + ClibName string + ClibVersion string +) + +func (p PackageName) String() string { + return string(p) +} + +func (p ClibName) String() string { + return string(p) +} + +func (p ClibVersion) String() string { + return string(p) +} + +func (p ClibVersion) ToSemVer() string { + return versions.ToSemVer(p.String()) +} diff --git a/internal/actions/mappingtable/table.go b/internal/actions/mappingtable/table.go new file mode 100644 index 0000000..53b9fc7 --- /dev/null +++ b/internal/actions/mappingtable/table.go @@ -0,0 +1,171 @@ +package mappingtable + +import ( + "encoding/json" + "io" + "log" + "os" + "slices" + + "github.com/goplus/llpkgstore/internal/actions/llpkg" + vrs "github.com/goplus/llpkgstore/internal/actions/versions" + "github.com/goplus/llpkgstore/metadata" + "golang.org/x/mod/semver" +) + +// Versions is a mapping table implement for Github Action only. +// It's recommend to use another implement in llgo for common usage. +type Versions struct { + metadata.MetadataMap + + fileName string +} + +// appendVersion adds a new version to the slice while preventing duplicates. +// It panics if the element already exists in the array to enforce uniqueness constraints. +// Parameters: +// +// arr: Slice of versions to modify +// elem: Version to append +func appendVersion(arr []string, elem string) []string { + if slices.Contains(arr, elem) { + log.Fatalf("version %s has already existed", elem) + } + return append(arr, elem) +} + +// Read initializes a Versions struct by reading version mappings from a file. +// It creates the file if it doesn't exist and parses the JSON content into the MetadataMap. +// Parameters: +// +// fileName: Path to the version mapping file +func Read(fileName string) *Versions { + // read or create a file + f, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDONLY, 0644) + if err != nil { + panic(err) + } + defer f.Close() + + b, err := io.ReadAll(f) + if err != nil { + panic(err) + } + + m := metadata.MetadataMap{} + + if len(b) > 0 { + json.Unmarshal(b, &m) + } + + return &Versions{ + MetadataMap: m, + fileName: f.Name(), + } +} + +// cVersions retrieves the version mappings for a specific C library. +// It returns a map where keys are C library versions and values are supported Go versions. +func (v *Versions) cVersions(clib llpkg.ClibName) map[metadata.CVersion][]metadata.GoVersion { + versions := v.MetadataMap[clib.String()] + if versions == nil { + return nil + } + return versions.Versions +} + +// CVersions returns all available versions of the specified C library. +// The versions are returned as semantic version strings. +func (v *Versions) CVersions(clib llpkg.ClibName) (ret []string) { + versions := v.MetadataMap[clib.String()] + if versions == nil { + return + } + for version := range versions.Versions { + ret = append(ret, vrs.ToSemVer(version)) + } + return +} + +// GoVersions lists all Go versions associated with the given C library. +func (v *Versions) GoVersions(clib llpkg.ClibName) (ret []string) { + versions := v.MetadataMap[clib.String()] + if versions == nil { + return + } + for _, goversion := range versions.Versions { + ret = append(ret, goversion...) + } + return +} + +// LatestGoVersionForCVersion finds the latest Go version compatible with a specific C library version. +func (v *Versions) LatestGoVersionForCVersion(clib llpkg.ClibName, cver llpkg.ClibVersion) string { + version := v.MetadataMap[clib.String()] + if version == nil { + return "" + } + goVersions := version.Versions[cver.String()] + if len(goVersions) == 0 { + return "" + } + semver.Sort(goVersions) + return goVersions[len(goVersions)-1] +} + +// SearchBySemVer looks up a C library version by its semantic version string. +func (v *Versions) SearchBySemVer(clib llpkg.ClibName, semver string) llpkg.ClibVersion { + for version := range v.cVersions(clib) { + if vrs.ToSemVer(version) == semver { + return llpkg.ClibVersion(version) + } + } + return "" +} + +// LatestGoVersion retrieves the latest Go version associated with the specified C library. +// It aggregates all Go versions across all C library versions and returns the highest one based on semantic versioning. +func (v *Versions) LatestGoVersion(clib llpkg.ClibName) string { + versions := v.GoVersions(clib) + if len(versions) == 0 { + return "" + } + semver.Sort(versions) + return versions[len(versions)-1] +} + +// Write records a new Go version mapping for a C library version and persists to file. +// Parameters: +// +// clib: The C library name. +// clibVersion: The specific version of the C library. +// mappedVersion: The Go version to map with the C library version. +// +// It appends the Go version to the existing list for the C library version and saves the updated metadata. +func (v *Versions) Write(clib llpkg.ClibName, clibVersion llpkg.ClibVersion, mappedVersion string) { + clibName := clib.String() + cversion := clibVersion.String() + + clibVersions := v.MetadataMap[clibName] + if clibVersions == nil { + clibVersions = &metadata.Metadata{ + Versions: map[metadata.CVersion][]metadata.GoVersion{}, + } + v.MetadataMap[clibName] = clibVersions + } + versions := clibVersions.Versions[cversion] + + versions = appendVersion(versions, mappedVersion) + + clibVersions.Versions[cversion] = versions + // sync to disk + b, _ := json.Marshal(&v.MetadataMap) + + os.WriteFile(v.fileName, []byte(b), 0644) +} + +// String returns the JSON representation of the Versions metadata. +func (v *Versions) String() string { + b, _ := json.Marshal(&v.MetadataMap) + return string(b) +} diff --git a/internal/actions/versions/versions_test.go b/internal/actions/mappingtable/table_test.go similarity index 99% rename from internal/actions/versions/versions_test.go rename to internal/actions/mappingtable/table_test.go index 43f4ca8..b88d400 100644 --- a/internal/actions/versions/versions_test.go +++ b/internal/actions/mappingtable/table_test.go @@ -1,4 +1,4 @@ -package versions +package mappingtable import ( "bytes" From 7360a4311802b6f4ef6f50a184605be5f01b2d6c Mon Sep 17 00:00:00 2001 From: Haolan Date: Mon, 28 Apr 2025 10:12:18 +0800 Subject: [PATCH 11/22] fix: remove old version implement --- internal/actions/versions/versions.go | 166 -------------------------- 1 file changed, 166 deletions(-) delete mode 100644 internal/actions/versions/versions.go diff --git a/internal/actions/versions/versions.go b/internal/actions/versions/versions.go deleted file mode 100644 index 395046f..0000000 --- a/internal/actions/versions/versions.go +++ /dev/null @@ -1,166 +0,0 @@ -package versions - -import ( - "encoding/json" - "io" - "log" - "os" - "slices" - - "github.com/goplus/llpkgstore/metadata" - "golang.org/x/mod/semver" -) - -// Versions is a mapping table implement for Github Action only. -// It's recommend to use another implement in llgo for common usage. -type Versions struct { - metadata.MetadataMap - - fileName string -} - -// appendVersion adds a new version to the slice while preventing duplicates. -// It panics if the element already exists in the array to enforce uniqueness constraints. -// Parameters: -// -// arr: Slice of versions to modify -// elem: Version to append -func appendVersion(arr []string, elem string) []string { - if slices.Contains(arr, elem) { - log.Fatalf("version %s has already existed", elem) - } - return append(arr, elem) -} - -// Read initializes a Versions struct by reading version mappings from a file. -// It creates the file if it doesn't exist and parses the JSON content into the MetadataMap. -// Parameters: -// -// fileName: Path to the version mapping file -func Read(fileName string) *Versions { - // read or create a file - f, err := os.OpenFile(fileName, os.O_CREATE|os.O_RDONLY, 0644) - if err != nil { - panic(err) - } - defer f.Close() - - b, err := io.ReadAll(f) - if err != nil { - panic(err) - } - - m := metadata.MetadataMap{} - - if len(b) > 0 { - json.Unmarshal(b, &m) - } - - return &Versions{ - MetadataMap: m, - fileName: f.Name(), - } -} - -// cVersions retrieves the version mappings for a specific C library. -// It returns a map where keys are C library versions and values are supported Go versions. -func (v *Versions) cVersions(clib string) map[metadata.CVersion][]metadata.GoVersion { - versions := v.MetadataMap[clib] - if versions == nil { - return nil - } - return versions.Versions -} - -// CVersions returns all available versions of the specified C library. -// The versions are returned as semantic version strings. -func (v *Versions) CVersions(clib string) (ret []string) { - versions := v.MetadataMap[clib] - if versions == nil { - return - } - for version := range versions.Versions { - ret = append(ret, ToSemVer(version)) - } - return -} - -// GoVersions lists all Go versions associated with the given C library. -func (v *Versions) GoVersions(clib string) (ret []string) { - versions := v.MetadataMap[clib] - if versions == nil { - return - } - for _, goversion := range versions.Versions { - ret = append(ret, goversion...) - } - return -} - -// LatestGoVersionForCVersion finds the latest Go version compatible with a specific C library version. -func (v *Versions) LatestGoVersionForCVersion(clib, cver string) string { - version := v.MetadataMap[clib] - if version == nil { - return "" - } - goVersions := version.Versions[cver] - if len(goVersions) == 0 { - return "" - } - semver.Sort(goVersions) - return goVersions[len(goVersions)-1] -} - -// SearchBySemVer looks up a C library version by its semantic version string. -func (v *Versions) SearchBySemVer(clib, semver string) string { - for version := range v.cVersions(clib) { - if ToSemVer(version) == semver { - return version - } - } - return "" -} - -// LatestGoVersion retrieves the latest Go version associated with the specified C library. -// It aggregates all Go versions across all C library versions and returns the highest one based on semantic versioning. -func (v *Versions) LatestGoVersion(clib string) string { - versions := v.GoVersions(clib) - if len(versions) == 0 { - return "" - } - semver.Sort(versions) - return versions[len(versions)-1] -} - -// Write records a new Go version mapping for a C library version and persists to file. -// Parameters: -// -// clib: The C library name. -// clibVersion: The specific version of the C library. -// mappedVersion: The Go version to map with the C library version. -// -// It appends the Go version to the existing list for the C library version and saves the updated metadata. -func (v *Versions) Write(clib, clibVersion, mappedVersion string) { - clibVersions := v.MetadataMap[clib] - if clibVersions == nil { - clibVersions = &metadata.Metadata{ - Versions: map[metadata.CVersion][]metadata.GoVersion{}, - } - v.MetadataMap[clib] = clibVersions - } - versions := clibVersions.Versions[clibVersion] - - versions = appendVersion(versions, mappedVersion) - - clibVersions.Versions[clibVersion] = versions - // sync to disk - b, _ := json.Marshal(&v.MetadataMap) - - os.WriteFile(v.fileName, []byte(b), 0644) -} - -// String returns the JSON representation of the Versions metadata. -func (v *Versions) String() string { - b, _ := json.Marshal(&v.MetadataMap) - return string(b) -} From 19e118f4a240c873280c30954b8c01b451b39e11 Mon Sep 17 00:00:00 2001 From: Haolan Date: Mon, 28 Apr 2025 10:17:29 +0800 Subject: [PATCH 12/22] fix: ci --- internal/actions/llpkg/package_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/actions/llpkg/package_test.go b/internal/actions/llpkg/package_test.go index 45f2bfb..8de3129 100644 --- a/internal/actions/llpkg/package_test.go +++ b/internal/actions/llpkg/package_test.go @@ -94,7 +94,7 @@ func TestReadConfig(t *testing.T) { } defer os.Remove(tempGoModFileName) - checkName(t, demoDir, false) + checkName(t, demoDir, tt.wantErr) }) } t.Run("no-go-mod", func(t *testing.T) { From 2ea2800204e5e291768f798443ef716bf55c5538 Mon Sep 17 00:00:00 2001 From: Haolan Date: Mon, 28 Apr 2025 11:26:36 +0800 Subject: [PATCH 13/22] refactor: parse package name instead --- internal/actions/llpkg/package.go | 51 +++++++------ internal/actions/llpkg/package_test.go | 102 +++++++++++++------------ 2 files changed, 78 insertions(+), 75 deletions(-) diff --git a/internal/actions/llpkg/package.go b/internal/actions/llpkg/package.go index 79d4fbe..5f34862 100644 --- a/internal/actions/llpkg/package.go +++ b/internal/actions/llpkg/package.go @@ -4,17 +4,25 @@ package llpkg import ( "errors" "fmt" + "go/parser" + "go/token" "os" - "path" "path/filepath" "github.com/goplus/llpkgstore/config" "github.com/goplus/llpkgstore/upstream" - "golang.org/x/mod/modfile" - "golang.org/x/mod/semver" ) -var ErrNoModulePath = errors.New("llpkg: no module path") +var ErrWrongPackagePath = errors.New("llpkg: wrong package path") + +func parsePackageName(goFile string) (string, error) { + fset := token.NewFileSet() + file, err := parser.ParseFile(fset, goFile, nil, parser.PackageClauseOnly) + if err != nil { + return "", err + } + return file.Name.Name, nil +} // Wraps an error with the "llpkg" prefix for better context func wrapLLPkgError(err error) error { @@ -79,33 +87,26 @@ func (p *LLPkg) readPackageConfig() (err error) { err = wrapLLPkgError(err) return } - goModFileName := filepath.Join(p.packagePath, "go.mod") + goFiles, _ := filepath.Glob(filepath.Join(p.packagePath, "*.go")) - goModContent, err := os.ReadFile(goModFileName) - if err != nil { - err = wrapLLPkgError(err) + if len(goFiles) == 0 { + err = ErrWrongPackagePath return } - modFile, err := modfile.Parse(goModFileName, goModContent, nil) - if err != nil { - err = wrapLLPkgError(err) - return + + var packageName string + + for _, goFile := range goFiles { + packageName, err = parsePackageName(goFile) + if packageName != "" { + break + } } - if modFile.Module == nil { - err = ErrNoModulePath + + if packageName == "" { + err = ErrWrongPackagePath return } - packageName := path.Base(modFile.Module.Mod.Path) - - if semver.IsValid(packageName) { - // step forward if the last element is version - // exmaple: - // github.com/goplus/llpkg/cjson/v2 - // got v2 - // step forward: github.com/goplus/llpkg/cjson - // got: cjson - packageName = path.Base(path.Dir(modFile.Module.Mod.Path)) - } p.cfg = cfg p.packageName = packageName diff --git a/internal/actions/llpkg/package_test.go b/internal/actions/llpkg/package_test.go index 8de3129..a29b402 100644 --- a/internal/actions/llpkg/package_test.go +++ b/internal/actions/llpkg/package_test.go @@ -4,6 +4,8 @@ import ( "os" "path/filepath" "testing" + + "github.com/goplus/llpkgstore/internal/file" ) func demoDir() (dir string, err error) { @@ -48,56 +50,56 @@ func TestReadConfig(t *testing.T) { t.Error(err) return } - testCases := []struct { - name string - wantErr bool - goModContent []byte - }{ - { - name: "with-version-suffix", - wantErr: false, - goModContent: []byte(`module github.com/goplus/llpkg/libcjson/v2 - - go 1.22.0 - `), - }, - { - name: "without-version-suffix", - wantErr: false, - goModContent: []byte(`module github.com/goplus/llpkg/libcjson - - go 1.22.0 - `), - }, - { - name: "raw-package-name", - wantErr: false, - goModContent: []byte(`module libcjson - - go 1.22.0 - `), - }, - { - name: "wrong-go-mod", - wantErr: true, - goModContent: []byte(`go 1.22.0`), - }, - } - tempGoModFileName := filepath.Join(demoDir, "go.mod") - - for _, tt := range testCases { - t.Run(tt.name, func(t *testing.T) { - err := os.WriteFile(tempGoModFileName, tt.goModContent, 0644) - if err != nil { - t.Error(err) - return - } - defer os.Remove(tempGoModFileName) - - checkName(t, demoDir, tt.wantErr) - }) - } - t.Run("no-go-mod", func(t *testing.T) { + + t.Run("one-go-file", func(t *testing.T) { + tempGoFileName := filepath.Join(demoDir, "x.go") + err := os.WriteFile(tempGoFileName, []byte(`package libcjson`), 0644) + if err != nil { + t.Error(err) + return + } + defer file.RemovePattern(filepath.Join(demoDir, "*.go")) + + checkName(t, demoDir, false) + }) + + t.Run("multi-go-files", func(t *testing.T) { + tempGoFileName1 := filepath.Join(demoDir, "a.go") + err := os.WriteFile(tempGoFileName1, []byte(`package libcjson`), 0644) + if err != nil { + t.Error(err) + return + } + tempGoFileName2 := filepath.Join(demoDir, "x.go") + err = os.WriteFile(tempGoFileName2, []byte(`package cjson`), 0644) + if err != nil { + t.Error(err) + return + } + defer file.RemovePattern(filepath.Join(demoDir, "*.go")) + + checkName(t, demoDir, false) + }) + + t.Run("multi-go-files-fallback", func(t *testing.T) { + tempGoFileName1 := filepath.Join(demoDir, "a.go") + err := os.WriteFile(tempGoFileName1, []byte(``), 0644) + if err != nil { + t.Error(err) + return + } + tempGoFileName2 := filepath.Join(demoDir, "x.go") + err = os.WriteFile(tempGoFileName2, []byte(`package libcjson`), 0644) + if err != nil { + t.Error(err) + return + } + defer file.RemovePattern(filepath.Join(demoDir, "*.go")) + + checkName(t, demoDir, false) + }) + + t.Run("no-go-files", func(t *testing.T) { checkName(t, demoDir, true) }) From f7f532dac94cf89f88b2c127a86afd9d75d62bab Mon Sep 17 00:00:00 2001 From: Haolan Date: Mon, 28 Apr 2025 11:34:40 +0800 Subject: [PATCH 14/22] fix: ci --- internal/actions/api_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/actions/api_test.go b/internal/actions/api_test.go index 89f76c8..9f04c3c 100644 --- a/internal/actions/api_test.go +++ b/internal/actions/api_test.go @@ -50,6 +50,8 @@ func prepareEnv(llpkgConfig, mappingTable []byte) (testDir string, err error) { os.WriteFile(filepath.Join(testDir, "go.mod"), []byte(`module cjson go 1.22 `), 0644) + + os.WriteFile(filepath.Join(testDir, "x.go"), []byte(`package cjson`), 0644) return } From c14824fa07c4a1287689abc8f78f45aa973ed2f9 Mon Sep 17 00:00:00 2001 From: Haolan Date: Mon, 28 Apr 2025 14:24:20 +0800 Subject: [PATCH 15/22] fix: add err check for getting workflow run id --- internal/actions/api.go | 4 ++++ internal/actions/env/env.go | 3 --- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/actions/api.go b/internal/actions/api.go index 902a52d..a9b1801 100644 --- a/internal/actions/api.go +++ b/internal/actions/api.go @@ -457,6 +457,10 @@ func (d *DefaultClient) uploadArtifactsToRelease(release *github.RepositoryRelea defer cancel() id, err := env.WorkflowRunID() + if err != nil { + return nil, err + } + artifacts, _, err := d.client.Actions.ListWorkflowRunArtifacts(ctx, d.owner, d.repo, id, &github.ListOptions{}) diff --git a/internal/actions/env/env.go b/internal/actions/env/env.go index ee8b076..2e70d13 100644 --- a/internal/actions/env/env.go +++ b/internal/actions/env/env.go @@ -112,9 +112,6 @@ func WorkflowRunID() (id int64, err error) { return } id, err = strconv.ParseInt(runId, 10, 64) - if err != nil { - return - } return } From 0916b1604476f2456a75003d5fd5f8d79758e725 Mon Sep 17 00:00:00 2001 From: Haolan Date: Mon, 28 Apr 2025 14:27:27 +0800 Subject: [PATCH 16/22] chore: move exported function to core --- internal/actions/api.go | 241 ------------------------------------- internal/actions/core.go | 252 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 252 insertions(+), 241 deletions(-) create mode 100644 internal/actions/core.go diff --git a/internal/actions/api.go b/internal/actions/api.go index a9b1801..65e6f46 100644 --- a/internal/actions/api.go +++ b/internal/actions/api.go @@ -9,7 +9,6 @@ import ( "mime" "net/http" "os" - "path/filepath" "regexp" "strings" "time" @@ -18,7 +17,6 @@ import ( "github.com/goplus/llpkgstore/internal/actions/env" "github.com/goplus/llpkgstore/internal/actions/llpkg" "github.com/goplus/llpkgstore/internal/actions/mappingtable" - "github.com/goplus/llpkgstore/internal/actions/versions" "golang.org/x/sync/errgroup" ) @@ -526,242 +524,3 @@ func (d *DefaultClient) checkVersion(ver *mappingtable.Versions, pkg *llpkg.LLPk } return checkLegacyVersion(ver, pkg, mappedVersion, isLegacy) } - -// CheckPR validates PR changes and returns affected packages -// Returns: -// -// []string: List of affected package paths -func (d *DefaultClient) CheckPR() ([]string, error) { - // build a file path map - pathMap := map[string][]string{} - changedFilePaths, err := env.Changes() - if err != nil { - return nil, err - } - for _, path := range changedFilePaths { - dir := filepath.Dir(path) - // initialize the dir - pathMap[dir] = nil - } - - var allPaths []string - - ver := mappingtable.Read("llpkgstore.json") - - for path := range pathMap { - if !isLLPkgRoot(path) { - delete(pathMap, path) - continue - } - pkg, err := llpkg.NewLLPkg(path) - if err != nil { - return nil, err - } - err = d.checkVersion(ver, pkg) - if err != nil { - return nil, err - } - allPaths = append(allPaths, path) - } - - // 2. Check config files(llpkg.cfg and llcppg.cfg) - if len(pathMap) == 0 { - return nil, fmt.Errorf("actions: no valid config files, llpkg.cfg and llcppg.cfg must exist") - } - - return allPaths, nil -} - -// Postprocessing handles version tagging and record updates after PR merge -// Creates Git tags, updates version records, and cleans up legacy branches -func (d *DefaultClient) Postprocessing() error { - // https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#push - sha, err := env.LatestCommitSHA() - if err != nil { - return err - } - // check it's associated with a pr - if !d.isAssociatedWithPullRequest(sha) { - // not a merge commit, skip it. - return fmt.Errorf("actions: not a merge request commit") - } - - version, err := d.mappedVersion() - if err != nil { - return err - } - - clib, mappedVersion, err := parseMappedVersion(version) - if err != nil { - return err - } - - pkg, err := llpkg.FromPackageName(clib) - if err != nil { - return err - } - - // write it to llpkgstore.json - ver := mappingtable.Read("llpkgstore.json") - ver.Write(pkg.ClibName(), pkg.ClibVersion(), mappedVersion) - - if hasTag(version) { - return fmt.Errorf("actions: tag has already existed") - } - - if err := d.createTag(version, sha); err != nil { - return err - } - - // create a release - release, err := d.createReleaseByTag(version) - if err != nil { - return err - } - - _, err = d.uploadArtifactsToRelease(release) - if err != nil { - return err - } - - // we have finished tagging the commit, safe to remove the branch - branchName, isLegacy, err := d.isLegacyVersion() - if err != nil { - return err - } - if isLegacy { - err = d.removeBranch(branchName) - } - return err - // move to website in Github Action... -} - -// Release must be called before Postprocessing -func (d *DefaultClient) Release() error { - version, err := d.mappedVersion() - if err != nil { - return err - } - - clibName, _, err := parseMappedVersion(version) - if err != nil { - return err - } - pkg, err := llpkg.FromPackageName(clibName) - if err != nil { - return err - } - uc, err := pkg.Upstream() - if err != nil { - return err - } - - zipFilename, zipFilePath, err := BuildBinaryZip(uc) - if err != nil { - return err - } - - // upload to artifacts in GitHub Action - // https://github.com/goplus/llpkg/pull/50/files#diff-95373be0ab51a56a2200c8c07981d82e81569f2cd1e4e2946e2002bb66de766fR56-R60 - return env.Setenv(env.Env{ - "BIN_PATH": zipFilePath, - "BIN_FILENAME": strings.TrimSuffix(zipFilename, ".zip"), - }) -} - -// CreateBranchFromLabel creates release branch based on label format -// Follows naming convention: release-branch./ -func (d *DefaultClient) CreateBranchFromLabel(labelName string) error { - // design: branch:release-branch.{CLibraryName}/{MappedVersion} - branchName := strings.TrimPrefix(strings.TrimSpace(labelName), LabelPrefix) - if branchName == labelName { - return fmt.Errorf("actions: invalid label name format") - } - - // fast-path: branch exists, can skip. - if d.hasBranch(branchName) { - return nil - } - version := strings.TrimPrefix(branchName, BranchPrefix) - if version == branchName { - return fmt.Errorf("actions: invalid label name format") - } - clibName, _, err := parseMappedVersion(version) - if err != nil { - return err - } - pkg, err := llpkg.FromPackageName(clibName) - if err != nil { - return err - } - // slow-path: check the condition if we can create a branch - // - // create a branch only when this version is legacy. - // according to branch maintenance strategy - - // get latest version of the clib - ver := mappingtable.Read("llpkgstore.json") - - cversions := ver.CVersions(pkg.ClibName()) - if len(cversions) == 0 { - return fmt.Errorf("actions: no clib found") - } - - if !versions.IsSemver(cversions) { - return fmt.Errorf("actions: c version dones't follow semver, skip maintaining") - } - - return d.createBranch(branchName, shaFromTag(version)) -} - -// CleanResource removes labels and resources after issue resolution -// Verifies issue closure via PR merge before deletion -func (d *DefaultClient) CleanResource() error { - issueEvent, err := IssueEvent() - if err != nil { - return err - } - - issueNumber := int(issueEvent["number"].(float64)) - regex := regexp.MustCompile(fmt.Sprintf(`(f|F)ix.*#%d`, issueNumber)) - - // 1. check this issue is closed by a PR - // In Github, close a issue with a commit whose message follows this format - // fix/Fix* #{IssueNumber} - found := false - allCommits, err := d.allCommits() - if err != nil { - return err - } - for _, commit := range allCommits { - message := commit.Commit.GetMessage() - - if regex.MatchString(message) && - d.isAssociatedWithPullRequest(commit.GetSHA()) { - found = true - break - } - } - - if !found { - return fmt.Errorf("actions: current issue isn't closed by merged PR") - } - - var labelName string - - // 2. find out the branch name from the label - for _, labels := range issueEvent["labels"].([]map[string]any) { - label := labels["name"].(string) - - if strings.HasPrefix(label, BranchPrefix) { - labelName = label - break - } - } - - if labelName == "" { - return fmt.Errorf("current issue hasn't labelled, this should not happen") - } - - return d.removeLabel(labelName) -} diff --git a/internal/actions/core.go b/internal/actions/core.go new file mode 100644 index 0000000..8b80cd1 --- /dev/null +++ b/internal/actions/core.go @@ -0,0 +1,252 @@ +package actions + +import ( + "fmt" + "path/filepath" + "regexp" + "strings" + + "github.com/goplus/llpkgstore/internal/actions/env" + "github.com/goplus/llpkgstore/internal/actions/llpkg" + "github.com/goplus/llpkgstore/internal/actions/mappingtable" + "github.com/goplus/llpkgstore/internal/actions/versions" +) + +// CheckPR validates PR changes and returns affected packages +// Returns: +// +// []string: List of affected package paths +func (d *DefaultClient) CheckPR() ([]string, error) { + // build a file path map + pathMap := map[string][]string{} + changedFilePaths, err := env.Changes() + if err != nil { + return nil, err + } + for _, path := range changedFilePaths { + dir := filepath.Dir(path) + // initialize the dir + pathMap[dir] = nil + } + + var allPaths []string + + ver := mappingtable.Read("llpkgstore.json") + + for path := range pathMap { + if !isLLPkgRoot(path) { + delete(pathMap, path) + continue + } + pkg, err := llpkg.NewLLPkg(path) + if err != nil { + return nil, err + } + err = d.checkVersion(ver, pkg) + if err != nil { + return nil, err + } + allPaths = append(allPaths, path) + } + + // 2. Check config files(llpkg.cfg and llcppg.cfg) + if len(pathMap) == 0 { + return nil, fmt.Errorf("actions: no valid config files, llpkg.cfg and llcppg.cfg must exist") + } + + return allPaths, nil +} + +// Postprocessing handles version tagging and record updates after PR merge +// Creates Git tags, updates version records, and cleans up legacy branches +func (d *DefaultClient) Postprocessing() error { + // https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#push + sha, err := env.LatestCommitSHA() + if err != nil { + return err + } + // check it's associated with a pr + if !d.isAssociatedWithPullRequest(sha) { + // not a merge commit, skip it. + return fmt.Errorf("actions: not a merge request commit") + } + + version, err := d.mappedVersion() + if err != nil { + return err + } + + clib, mappedVersion, err := parseMappedVersion(version) + if err != nil { + return err + } + + pkg, err := llpkg.FromPackageName(clib) + if err != nil { + return err + } + + // write it to llpkgstore.json + ver := mappingtable.Read("llpkgstore.json") + ver.Write(pkg.ClibName(), pkg.ClibVersion(), mappedVersion) + + if hasTag(version) { + return fmt.Errorf("actions: tag has already existed") + } + + if err := d.createTag(version, sha); err != nil { + return err + } + + // create a release + release, err := d.createReleaseByTag(version) + if err != nil { + return err + } + + _, err = d.uploadArtifactsToRelease(release) + if err != nil { + return err + } + + // we have finished tagging the commit, safe to remove the branch + branchName, isLegacy, err := d.isLegacyVersion() + if err != nil { + return err + } + if isLegacy { + err = d.removeBranch(branchName) + } + return err + // move to website in Github Action... +} + +// Release must be called before Postprocessing +func (d *DefaultClient) Release() error { + version, err := d.mappedVersion() + if err != nil { + return err + } + + clibName, _, err := parseMappedVersion(version) + if err != nil { + return err + } + pkg, err := llpkg.FromPackageName(clibName) + if err != nil { + return err + } + uc, err := pkg.Upstream() + if err != nil { + return err + } + + zipFilename, zipFilePath, err := BuildBinaryZip(uc) + if err != nil { + return err + } + + // upload to artifacts in GitHub Action + // https://github.com/goplus/llpkg/pull/50/files#diff-95373be0ab51a56a2200c8c07981d82e81569f2cd1e4e2946e2002bb66de766fR56-R60 + return env.Setenv(env.Env{ + "BIN_PATH": zipFilePath, + "BIN_FILENAME": strings.TrimSuffix(zipFilename, ".zip"), + }) +} + +// CreateBranchFromLabel creates release branch based on label format +// Follows naming convention: release-branch./ +func (d *DefaultClient) CreateBranchFromLabel(labelName string) error { + // design: branch:release-branch.{CLibraryName}/{MappedVersion} + branchName := strings.TrimPrefix(strings.TrimSpace(labelName), LabelPrefix) + if branchName == labelName { + return fmt.Errorf("actions: invalid label name format") + } + + // fast-path: branch exists, can skip. + if d.hasBranch(branchName) { + return nil + } + version := strings.TrimPrefix(branchName, BranchPrefix) + if version == branchName { + return fmt.Errorf("actions: invalid label name format") + } + clibName, _, err := parseMappedVersion(version) + if err != nil { + return err + } + pkg, err := llpkg.FromPackageName(clibName) + if err != nil { + return err + } + // slow-path: check the condition if we can create a branch + // + // create a branch only when this version is legacy. + // according to branch maintenance strategy + + // get latest version of the clib + ver := mappingtable.Read("llpkgstore.json") + + cversions := ver.CVersions(pkg.ClibName()) + if len(cversions) == 0 { + return fmt.Errorf("actions: no clib found") + } + + if !versions.IsSemver(cversions) { + return fmt.Errorf("actions: c version dones't follow semver, skip maintaining") + } + + return d.createBranch(branchName, shaFromTag(version)) +} + +// CleanResource removes labels and resources after issue resolution +// Verifies issue closure via PR merge before deletion +func (d *DefaultClient) CleanResource() error { + issueEvent, err := IssueEvent() + if err != nil { + return err + } + + issueNumber := int(issueEvent["number"].(float64)) + regex := regexp.MustCompile(fmt.Sprintf(`(f|F)ix.*#%d`, issueNumber)) + + // 1. check this issue is closed by a PR + // In Github, close a issue with a commit whose message follows this format + // fix/Fix* #{IssueNumber} + found := false + allCommits, err := d.allCommits() + if err != nil { + return err + } + for _, commit := range allCommits { + message := commit.Commit.GetMessage() + + if regex.MatchString(message) && + d.isAssociatedWithPullRequest(commit.GetSHA()) { + found = true + break + } + } + + if !found { + return fmt.Errorf("actions: current issue isn't closed by merged PR") + } + + var labelName string + + // 2. find out the branch name from the label + for _, labels := range issueEvent["labels"].([]map[string]any) { + label := labels["name"].(string) + + if strings.HasPrefix(label, BranchPrefix) { + labelName = label + break + } + } + + if labelName == "" { + return fmt.Errorf("current issue hasn't labelled, this should not happen") + } + + return d.removeLabel(labelName) +} From e7e0785dc01f5aeffc8d7879c2c206e13119a600 Mon Sep 17 00:00:00 2001 From: Haolan Date: Mon, 28 Apr 2025 16:37:58 +0800 Subject: [PATCH 17/22] fix: use table-driven test --- internal/actions/api_test.go | 263 ++++++++++++++--------------------- 1 file changed, 103 insertions(+), 160 deletions(-) diff --git a/internal/actions/api_test.go b/internal/actions/api_test.go index 9f04c3c..f9b1fc7 100644 --- a/internal/actions/api_test.go +++ b/internal/actions/api_test.go @@ -55,62 +55,55 @@ func prepareEnv(llpkgConfig, mappingTable []byte) (testDir string, err error) { return } -func TestLegacyVersion1(t *testing.T) { - testLLPkgConfig := `{ - "upstream": { - "package": { - "name": "cjson", - "version": "1.7.17" - } - } - }` - - testMappingTable := `{ - "cjson": { - "versions" : { - "1.7.16": ["v0.1.0"], - "1.7.18": ["v0.1.2", "v0.1.3"], - "1.8.18": ["v0.1.0", "v0.1.1"] - } - } - }` - - testDir, err := prepareEnv([]byte(testLLPkgConfig), []byte(testMappingTable)) - if err != nil { - t.Error(err) - return - } - defer os.RemoveAll(testDir) - - pkg, err := llpkg.NewLLPkg(testDir) - if err != nil { - t.Error(err) - return - } - ver := mappingtable.Read(filepath.Join(testDir, "llpkgstore.json")) - - err = actionFn("main", func(legacy bool) error { - return checkLegacyVersion(ver, pkg, "v0.1.1", legacy) - }) - - if err == nil { - t.Errorf("unexpected behavior: %v", err) - return - } - -} - -func TestLegacyVersion2(t *testing.T) { - testLLPkgConfig := `{ +func TestLegacyVersion(t *testing.T) { + testCases := []struct { + name string + branch string + llpkgContent string + mappedVersion string + mappingTableContent string + wantErr bool + }{ + { + name: "case1", + branch: "main", + mappedVersion: "v0.1.1", + wantErr: true, + llpkgContent: `{ + "upstream": { + "package": { + "name": "cjson", + "version": "1.7.17" + } + } + }`, + + mappingTableContent: `{ + "cjson": { + "versions" : { + "1.7.16": ["v0.1.0"], + "1.7.18": ["v0.1.2", "v0.1.3"], + "1.8.18": ["v0.1.0", "v0.1.1"] + } + } + }`, + }, + + { + name: "case2", + branch: "release-branch.cjson/v0.1.1", + mappedVersion: "v0.1.2", + wantErr: false, + llpkgContent: `{ "upstream": { "package": { "name": "cjson", "version": "1.7.19" } } - }` + }`, - testMappingTable := `{ + mappingTableContent: `{ "cjson": { "versions" : { "1.8.18": ["v0.2.0", "v0.2.1"], @@ -118,44 +111,24 @@ func TestLegacyVersion2(t *testing.T) { "1.7.16: ["v1.1.0"] } } - }` - - testDir, err := prepareEnv([]byte(testLLPkgConfig), []byte(testMappingTable)) - if err != nil { - t.Error(err) - return - } - defer os.RemoveAll(testDir) - - pkg, err := llpkg.NewLLPkg(testDir) - if err != nil { - t.Error(err) - return - } - ver := mappingtable.Read(filepath.Join(testDir, "llpkgstore.json")) - - err = actionFn("release-branch.cjson/v0.1.1", func(legacy bool) error { - return checkLegacyVersion(ver, pkg, "v0.1.2", legacy) - }) - isValid := err == nil - - if !isValid { - t.Errorf("unexpected behavior: %v", err) - return - } -} - -func TestLegacyVersion3(t *testing.T) { - testLLPkgConfig := `{ + }`, + }, + + { + name: "case3", + branch: "main", + mappedVersion: "v0.3.0", + wantErr: false, + llpkgContent: `{ "upstream": { "package": { "name": "cjson", "version": "1.9.1" } } - }` + }`, - testMappingTable := `{ + mappingTableContent: `{ "cjson": { "versions" : { "1.7.16": ["v0.1.0"], @@ -163,44 +136,24 @@ func TestLegacyVersion3(t *testing.T) { "1.8.18": ["v0.2.0", "v0.2.1"] } } - }` - - testDir, err := prepareEnv([]byte(testLLPkgConfig), []byte(testMappingTable)) - if err != nil { - t.Error(err) - return - } - defer os.RemoveAll(testDir) - - pkg, err := llpkg.NewLLPkg(testDir) - if err != nil { - t.Error(err) - return - } - ver := mappingtable.Read(filepath.Join(testDir, "llpkgstore.json")) - - err = actionFn("main", func(legacy bool) error { - return checkLegacyVersion(ver, pkg, "v0.3.0", legacy) - }) - isValid := err == nil - - if !isValid { - t.Errorf("unexpected behavior: %v", err) - return - } -} - -func TestLegacyVersion4(t *testing.T) { - testLLPkgConfig := `{ + }`, + }, + + { + name: "case4", + branch: "main", + mappedVersion: "v0.0.1", + wantErr: true, + llpkgContent: `{ "upstream": { "package": { "name": "cjson", "version": "1.9.1" } } - }` + }`, - testMappingTable := `{ + mappingTableContent: `{ "cjson": { "versions" : { "1.8.18": ["v0.2.0", "v0.2.1"], @@ -208,43 +161,24 @@ func TestLegacyVersion4(t *testing.T) { "1.7.18": ["v0.1.1", "v0.1.2"] } } - }` - - testDir, err := prepareEnv([]byte(testLLPkgConfig), []byte(testMappingTable)) - if err != nil { - t.Error(err) - return - } - defer os.RemoveAll(testDir) - - pkg, err := llpkg.NewLLPkg(testDir) - if err != nil { - t.Error(err) - return - } - ver := mappingtable.Read(filepath.Join(testDir, "llpkgstore.json")) - - err = actionFn("main", func(legacy bool) error { - return checkLegacyVersion(ver, pkg, "v0.0.1", legacy) - }) - - if err == nil { - t.Errorf("unexpected behavior: %v", err) - return - } -} - -func TestLegacyVersion5(t *testing.T) { - testLLPkgConfig := `{ + }`, + }, + + { + name: "case5", + branch: "main", + mappedVersion: "v0.1.1", + wantErr: true, + llpkgContent: `{ "upstream": { "package": { "name": "cjson", "version": "1.7.19" } } - }` + }`, - testMappingTable := `{ + mappingTableContent: `{ "cjson": { "versions" : { "1.7.16": ["v0.1.0"], @@ -252,28 +186,37 @@ func TestLegacyVersion5(t *testing.T) { "1.8.18": ["v0.2.0", "v0.2.1"] } } - }` - - testDir, err := prepareEnv([]byte(testLLPkgConfig), []byte(testMappingTable)) - if err != nil { - t.Error(err) - return + }`, + }, } - defer os.RemoveAll(testDir) - pkg, err := llpkg.NewLLPkg(testDir) - if err != nil { - t.Error(err) - return - } - ver := mappingtable.Read(filepath.Join(testDir, "llpkgstore.json")) + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + testDir, err := prepareEnv([]byte(tt.llpkgContent), []byte(tt.mappingTableContent)) + if err != nil { + t.Error(err) + return + } + defer os.RemoveAll(testDir) + + pkg, err := llpkg.NewLLPkg(testDir) + if err != nil { + t.Error(err) + return + } + ver := mappingtable.Read(filepath.Join(testDir, "llpkgstore.json")) - err = actionFn("main", func(legacy bool) error { - return checkLegacyVersion(ver, pkg, "v0.1.1", legacy) - }) + err = actionFn(tt.branch, func(legacy bool) error { + return checkLegacyVersion(ver, pkg, tt.mappedVersion, legacy) + }) - if err == nil { - t.Errorf("unexpected behavior: %v", err) - return + if tt.wantErr && err == nil { + t.Error("unexpected no error") + } + if !tt.wantErr && err != nil { + t.Errorf("unexpected error: %v", err) + } + }) } + } From 8b29ff33031f44dc5422789f175902eb7170bbd7 Mon Sep 17 00:00:00 2001 From: Haolan Date: Mon, 28 Apr 2025 16:38:56 +0800 Subject: [PATCH 18/22] fix: adjust var name --- internal/actions/api.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/actions/api.go b/internal/actions/api.go index 65e6f46..f211313 100644 --- a/internal/actions/api.go +++ b/internal/actions/api.go @@ -30,10 +30,10 @@ const ( ) // compileRawCommitVersionRegex compiles a regular expression pattern to detect "Release-as" directives in commit messages -func compileRawCommitVersionRegex(packageName llpkg.PackageName) *regexp.Regexp { +func compileRawCommitVersionRegex(packageNamePattern string) *regexp.Regexp { // format: Release-as: clib/semver(with v prefix) // Must have one space in the end of Release-as: - return regexp.MustCompile(fmt.Sprintf(regexString, packageName.String())) + return regexp.MustCompile(fmt.Sprintf(regexString, packageNamePattern)) } func binaryZip(packageName string) string { @@ -237,7 +237,7 @@ func (d *DefaultClient) removeLabel(labelName string) error { // // If no valid version found in PR commits func (d *DefaultClient) checkMappedVersion(pkg *llpkg.LLPkg) (mappedVersion string, err error) { - matchMappedVersion := compileRawCommitVersionRegex(pkg.Name()) + matchMappedVersion := compileRawCommitVersionRegex(pkg.Name().String()) allCommits, err := d.currentPRCommit() if err != nil { @@ -299,7 +299,7 @@ func (d *DefaultClient) mappedVersion() (string, error) { message := commit.GetCommit().GetMessage() // parse the mapped version - mappedVersion := compileRawCommitVersionRegex(llpkg.PackageName(".*")).FindString(message) + mappedVersion := compileRawCommitVersionRegex(".*").FindString(message) // mapped version not found, a normal commit? if mappedVersion == "" { return "", ErrNoMappedVersion From d8e5540f16f41f26e9488ef9f403d19152e70154 Mon Sep 17 00:00:00 2001 From: Haolan Date: Mon, 28 Apr 2025 16:40:53 +0800 Subject: [PATCH 19/22] fix: add comment --- internal/actions/core.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/actions/core.go b/internal/actions/core.go index 8b80cd1..657cd5e 100644 --- a/internal/actions/core.go +++ b/internal/actions/core.go @@ -81,6 +81,7 @@ func (d *DefaultClient) Postprocessing() error { return err } + // the pr has merged, so we can read it. pkg, err := llpkg.FromPackageName(clib) if err != nil { return err @@ -132,6 +133,8 @@ func (d *DefaultClient) Release() error { if err != nil { return err } + + // the pr has merged, so we can read it. pkg, err := llpkg.FromPackageName(clibName) if err != nil { return err From f8772e1093c8d8c8279c352e7a6f5f8563de6eb3 Mon Sep 17 00:00:00 2001 From: Meteor Date: Wed, 30 Apr 2025 02:08:48 +0000 Subject: [PATCH 20/22] chore: adjust some details --- internal/actions/api.go | 14 ++++++-------- internal/actions/core.go | 2 +- internal/actions/llpkg/package.go | 1 - 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/internal/actions/api.go b/internal/actions/api.go index f211313..efd6159 100644 --- a/internal/actions/api.go +++ b/internal/actions/api.go @@ -3,12 +3,10 @@ package actions import ( "context" - "errors" "fmt" "io" "mime" "net/http" - "os" "regexp" "strings" "time" @@ -442,7 +440,7 @@ func (d *DefaultClient) uploadArtifact(artifactID int64, release *github.Reposit fileName, ok := params["filename"] if !ok { - return errors.New("actions: no filename found in Content-Disposition") + return fmt.Errorf("actions: no filename found in Content-Disposition") } fmt.Printf("Upload %s to %s\n", fileName, release.GetName()) @@ -450,24 +448,24 @@ func (d *DefaultClient) uploadArtifact(artifactID int64, release *github.Reposit return d.uploadToRelease(fileName, resp.ContentLength, resp.Body, release) } -func (d *DefaultClient) uploadArtifactsToRelease(release *github.RepositoryRelease) (files []*os.File, err error) { +func (d *DefaultClient) uploadArtifactsToRelease(release *github.RepositoryRelease) error { ctx, cancel := context.WithTimeout(context.TODO(), 30*time.Second) defer cancel() id, err := env.WorkflowRunID() if err != nil { - return nil, err + return err } artifacts, _, err := d.client.Actions.ListWorkflowRunArtifacts(ctx, d.owner, d.repo, id, &github.ListOptions{}) if err != nil { - return nil, wrapActionError(err) + return wrapActionError(err) } if artifacts.GetTotalCount() == 0 { - return nil, errors.New("actions: no artifact found") + return fmt.Errorf("actions: no artifact found") } errGroup, _ := errgroup.WithContext(context.TODO()) @@ -481,7 +479,7 @@ func (d *DefaultClient) uploadArtifactsToRelease(release *github.RepositoryRelea }) } - return nil, errGroup.Wait() + return errGroup.Wait() } // removeBranch deletes a branch from the repository diff --git a/internal/actions/core.go b/internal/actions/core.go index 657cd5e..04860ad 100644 --- a/internal/actions/core.go +++ b/internal/actions/core.go @@ -105,7 +105,7 @@ func (d *DefaultClient) Postprocessing() error { return err } - _, err = d.uploadArtifactsToRelease(release) + err = d.uploadArtifactsToRelease(release) if err != nil { return err } diff --git a/internal/actions/llpkg/package.go b/internal/actions/llpkg/package.go index 5f34862..f53bdc5 100644 --- a/internal/actions/llpkg/package.go +++ b/internal/actions/llpkg/package.go @@ -1,4 +1,3 @@ -// Package llpkg provides utilities for managing language-linked packages (LLPkgs) package llpkg import ( From 35a7220dd3b8124fb298fcf73170c7e76744096b Mon Sep 17 00:00:00 2001 From: Meteor Date: Wed, 7 May 2025 05:56:10 +0000 Subject: [PATCH 21/22] feat: move llpkgstore.json to release --- internal/actions/actions.go | 23 +++++++++++++++ internal/actions/api.go | 18 +++++++++++ internal/actions/api_test.go | 13 ++++++++ internal/actions/core.go | 29 +++++++++++++----- internal/actions/mappingtable/table.go | 41 +++++++++++++++++++++++++- 5 files changed, 115 insertions(+), 9 deletions(-) diff --git a/internal/actions/actions.go b/internal/actions/actions.go index a066822..1c8b2da 100644 --- a/internal/actions/actions.go +++ b/internal/actions/actions.go @@ -2,6 +2,7 @@ package actions import ( "encoding/json" + "errors" "fmt" "log" "os" @@ -97,6 +98,14 @@ func shaFromTag(tag string) string { return strings.TrimSpace(string(ret)) } +func headSHA() (string, error) { + ret, err := exec.Command("git", "rev-list", "--max-parents", "0", "--abbrev-commit", "HEAD").CombinedOutput() + if err != nil { + return "", errors.New(string(ret)) + } + return strings.TrimSpace(string(ret)), nil +} + // parseMappedVersion splits the mapped version string into library name and version. // Input format: "clib/semver" where semver starts with 'v' // Panics if input format is invalid or version isn't valid semantic version @@ -212,6 +221,20 @@ func checkLegacyVersion(ver *mappingtable.Versions, pkg *llpkg.LLPkg, mappedVers return nil } +func readMappingTableCompatible() (*mappingtable.Versions, error) { + ver, created, err := mappingtable.FromRelease() + if err != nil { + return nil, err + } + // if specified release that stores `llpkgstore.json` has created, read llpkgstore.json from release. + // otherwise, read it from github pages for compatibility + if created { + return ver, nil + } + // fallback to legacy gh-pages + return mappingtable.Read("llpkgstore.json"), nil +} + func BuildBinaryZip(uc *upstream.Upstream) (zipFileName, zipFilePath string, err error) { tempDir, err := os.MkdirTemp("", "llpkg-tool") if err != nil { diff --git a/internal/actions/api.go b/internal/actions/api.go index efd6159..3e21048 100644 --- a/internal/actions/api.go +++ b/internal/actions/api.go @@ -522,3 +522,21 @@ func (d *DefaultClient) checkVersion(ver *mappingtable.Versions, pkg *llpkg.LLPk } return checkLegacyVersion(ver, pkg, mappedVersion, isLegacy) } + +func (d *DefaultClient) commitMappingTable(ver *mappingtable.Versions) error { + sha, err := headSHA() + if err != nil { + return err + } + // ignore error if created + d.createTag("mapping-table", sha) + + release, err := d.createReleaseByTag("mapping-table") + if err != nil { + return err + } + + buf := strings.NewReader(ver.String()) + + return d.uploadToRelease("llpkgstore.json", buf.Size(), buf, release) +} diff --git a/internal/actions/api_test.go b/internal/actions/api_test.go index f9b1fc7..f5e5dc5 100644 --- a/internal/actions/api_test.go +++ b/internal/actions/api_test.go @@ -27,6 +27,19 @@ func TestHasTag(t *testing.T) { } } +func TestHeadSHA(t *testing.T) { + sha, err := headSHA() + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + + if sha != "6d38ce6" { + t.Errorf("unexpected sha: got %s want %s", sha, "6d38ce6") + + } +} + func actionFn(branchName string, fn func(legacy bool) error) error { return fn(strings.HasPrefix(branchName, BranchPrefix)) } diff --git a/internal/actions/core.go b/internal/actions/core.go index 04860ad..5dafc0e 100644 --- a/internal/actions/core.go +++ b/internal/actions/core.go @@ -8,7 +8,6 @@ import ( "github.com/goplus/llpkgstore/internal/actions/env" "github.com/goplus/llpkgstore/internal/actions/llpkg" - "github.com/goplus/llpkgstore/internal/actions/mappingtable" "github.com/goplus/llpkgstore/internal/actions/versions" ) @@ -31,7 +30,10 @@ func (d *DefaultClient) CheckPR() ([]string, error) { var allPaths []string - ver := mappingtable.Read("llpkgstore.json") + ver, err := readMappingTableCompatible() + if err != nil { + return nil, err + } for path := range pathMap { if !isLLPkgRoot(path) { @@ -87,10 +89,6 @@ func (d *DefaultClient) Postprocessing() error { return err } - // write it to llpkgstore.json - ver := mappingtable.Read("llpkgstore.json") - ver.Write(pkg.ClibName(), pkg.ClibVersion(), mappedVersion) - if hasTag(version) { return fmt.Errorf("actions: tag has already existed") } @@ -99,6 +97,19 @@ func (d *DefaultClient) Postprocessing() error { return err } + // write it to llpkgstore.json + ver, err := readMappingTableCompatible() + if err != nil { + return err + } + ver.Write(pkg.ClibName(), pkg.ClibVersion(), mappedVersion) + + // commit changes + err = d.commitMappingTable(ver) + if err != nil { + return err + } + // create a release release, err := d.createReleaseByTag(version) if err != nil { @@ -119,7 +130,6 @@ func (d *DefaultClient) Postprocessing() error { err = d.removeBranch(branchName) } return err - // move to website in Github Action... } // Release must be called before Postprocessing @@ -188,7 +198,10 @@ func (d *DefaultClient) CreateBranchFromLabel(labelName string) error { // according to branch maintenance strategy // get latest version of the clib - ver := mappingtable.Read("llpkgstore.json") + ver, err := readMappingTableCompatible() + if err != nil { + return err + } cversions := ver.CVersions(pkg.ClibName()) if len(cversions) == 0 { diff --git a/internal/actions/mappingtable/table.go b/internal/actions/mappingtable/table.go index 53b9fc7..08633f2 100644 --- a/internal/actions/mappingtable/table.go +++ b/internal/actions/mappingtable/table.go @@ -1,11 +1,14 @@ package mappingtable import ( + "context" "encoding/json" "io" "log" + "net/http" "os" "slices" + "time" "github.com/goplus/llpkgstore/internal/actions/llpkg" vrs "github.com/goplus/llpkgstore/internal/actions/versions" @@ -13,6 +16,8 @@ import ( "golang.org/x/mod/semver" ) +const _releaseSource = "https://github.com/goplus/llpkg/releases/download/_mappingtable/llpkgstore.json" + // Versions is a mapping table implement for Github Action only. // It's recommend to use another implement in llgo for common usage. type Versions struct { @@ -52,6 +57,40 @@ func Read(fileName string) *Versions { panic(err) } + return readFromBytes(b, f.Name()) +} + +func FromRelease() (table *Versions, isCreated bool, err error) { + ctx, cancel := context.WithTimeout(context.TODO(), 30*time.Second) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, "GET", _releaseSource, nil) + if err != nil { + return + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return + } + defer resp.Body.Close() + // if specified release is not created yet, return a blank mapping table. + if resp.StatusCode == http.StatusNotFound { + table = readFromBytes(nil, "llpkgstore.json") + return + } + + b, err := io.ReadAll(resp.Body) + if err != nil { + return + } + + isCreated = true + table = readFromBytes(b, "llpkgstore.json") + return +} + +func readFromBytes(b []byte, fileName string) *Versions { m := metadata.MetadataMap{} if len(b) > 0 { @@ -60,7 +99,7 @@ func Read(fileName string) *Versions { return &Versions{ MetadataMap: m, - fileName: f.Name(), + fileName: fileName, } } From 5c57a42197f184dd4b1a6c0c0627ddfa9e53a5f3 Mon Sep 17 00:00:00 2001 From: Meteor Date: Wed, 7 May 2025 05:58:44 +0000 Subject: [PATCH 22/22] fix: release name --- internal/actions/api.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/actions/api.go b/internal/actions/api.go index 3e21048..a431775 100644 --- a/internal/actions/api.go +++ b/internal/actions/api.go @@ -529,9 +529,9 @@ func (d *DefaultClient) commitMappingTable(ver *mappingtable.Versions) error { return err } // ignore error if created - d.createTag("mapping-table", sha) + d.createTag("_mappingtable", sha) - release, err := d.createReleaseByTag("mapping-table") + release, err := d.createReleaseByTag("_mappingtable") if err != nil { return err }