Skip to content

Commit

Permalink
[TT-1921] fix breaking changes detection (#1529)
Browse files Browse the repository at this point in the history
* respect retracted tag, make tag match less greedy, change dir ignore from prefix to regex
* fix formatting, go mod tidy in all folders
  • Loading branch information
Tofel authored Jan 8, 2025
1 parent 46ffce8 commit f3dea2c
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 28 deletions.
8 changes: 5 additions & 3 deletions .github/workflows/rc-breaking-changes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ jobs:
with:
fetch-depth: 0
fetch-tags: true
- name: Set up Go 1.22.6
- name: Set up Go 1.23.3
uses: actions/setup-go@v5
with:
go-version: '1.22.6'
go-version: '1.23.3'
- name: Install gorelease tool
run: |
go install golang.org/x/exp/cmd/gorelease@latest
- name: Run Breaking Changes Script
run: |
go run ./tools/breakingchanges/cmd/main.go --ignore tools
cd ./tools/breakingchanges
go mod tidy
go run cmd/main.go --ignore tools --root ../../
4 changes: 2 additions & 2 deletions .github/workflows/release-go-module.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ jobs:
fi
# Extract the version part of the last tag
LAST_TAG_VERSION="${LAST_TAG##*/}"
LAST_TAG_VERSION="${LAST_TAG##*/}"
echo "Last tag version: $LAST_TAG_VERSION"
echo "LAST_TAG_VERSION=${LAST_TAG_VERSION}" >> "$GITHUB_ENV"
Expand All @@ -69,7 +69,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.22.6'
go-version: '1.23.3'
- name: Install gorelease tool
run: |
go install golang.org/x/exp/cmd/gorelease@latest
Expand Down
126 changes: 104 additions & 22 deletions tools/breakingchanges/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import (
"os/exec"
"path/filepath"
"regexp"
"sort"
"strings"

"github.com/Masterminds/semver/v3"
"golang.org/x/mod/modfile"
)

const (
Expand Down Expand Up @@ -47,31 +51,101 @@ func findGoModDirs(rootFolder, subDir string) ([]string, error) {
return goModDirs, nil
}

func getLastTag(pathPrefix string) (string, error) {
func getRetractedTags(goModPath string) ([]*semver.Constraints, error) {
data, err := os.ReadFile(goModPath)
if err != nil {
return nil, fmt.Errorf("error reading go.mod file: %w", err)
}

modFile, err := modfile.Parse("go.mod", data, nil)
if err != nil {
return nil, fmt.Errorf("error parsing go.mod file: %w", err)
}

var retractedTags []*semver.Constraints
for _, retract := range modFile.Retract {
lowVersion, err := semver.NewVersion(retract.Low)
if err != nil {
return nil, fmt.Errorf("error parsing retracted version: %w", err)
}
highVersion, err := semver.NewVersion(retract.High)
if err != nil {
return nil, fmt.Errorf("error parsing retracted version: %w", err)
}
constraint, err := semver.NewConstraint(fmt.Sprintf(">= %s, <= %s", lowVersion.String(), highVersion.String()))
if err != nil {
return nil, fmt.Errorf("error parsing retracted version: %w", err)
}
retractedTags = append(retractedTags, constraint)
fmt.Printf("Retracted version: %s\n", constraint)
}

return retractedTags, nil
}

func getLatestTag(pathPrefix string, retractedTags []*semver.Constraints) (string, error) {
// use regex to find exact matches, as otherwise might include pre-release versions
// or versions that partially match the path prefix, e.g. when seraching for 'lib'
// we won't make sure we won't include tags like `lib/grafana/v1.0.0`
grepRegex := fmt.Sprintf("^%s/v[0-9]+\\.[0-9]+\\.[0-9]+$", pathPrefix)

//nolint
cmd := exec.Command("sh", "-c", fmt.Sprintf("git tag | grep '%s' | tail -1", pathPrefix))
cmd := exec.Command("sh", "-c", fmt.Sprintf("git tag | grep -E '%s'", grepRegex))
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return "", fmt.Errorf("error fetching tags: %w", err)
}

tag := strings.TrimSpace(out.String())
if tag == "" {
return "", nil
tags := strings.Split(strings.TrimSpace(out.String()), "\n")
if len(tags) == 0 {
return "", fmt.Errorf("no tags found for regex: %s", grepRegex)
}

// Use regex to find the version tag starting with 'v'
re := regexp.MustCompile(`v\d+\.\d+\.\d+`)
matches := re.FindStringSubmatch(tag)
if len(matches) > 0 {
tag = matches[0]
} else {
return "", fmt.Errorf("no valid version tag found in '%s'", tag)
// Parse the tags into semver versions
var allTags []*semver.Version
for _, tag := range tags {
v, err := semver.NewVersion(strings.TrimPrefix(tag, pathPrefix+"/"))
if err != nil {
return "", fmt.Errorf("error parsing version tag: %w", err)
}
allTags = append(allTags, v)
}

// Sort the tags in descending order
sort.Sort(sort.Reverse(semver.Collection(allTags)))

if len(retractedTags) == 0 {
tag := fmt.Sprintf("v%s", allTags[0].String())
return tag, nil
}

// Find the latest tag that doesn't match any of the retracted tags
for _, tag := range allTags {
isRetracted := false
for _, constraint := range retractedTags {
if constraint.Check(tag) {
isRetracted = true
break
}
}

if !isRetracted {
tag := fmt.Sprintf("v%s", tag.String())
fmt.Printf("Found non-retracted tag: %s\n", tag)
return tag, nil
}
}

return tag, nil
fmt.Println("No non-retracted tags found")
fmt.Printf("All tags: %s\n", strings.Join(tags, ", "))
fmt.Println("Retracted tags:")
for _, constraint := range retractedTags {
fmt.Printf("%s\n", constraint)
}

return "", fmt.Errorf("failed to find a non-retracted tag got path prefix: %s", pathPrefix)
}

func checkBreakingChanges(tag string) (string, string, error) {
Expand All @@ -94,13 +168,15 @@ func getIgnoredDirs(flag *string) []string {
return ignoredDirs
}

func isIgnoredDirPrefix(pathPrefix string, ignoredDirs []string) bool {
func isIgnoredDirRegex(pathPrefix string, ignoredDirs []string) bool {
for _, d := range ignoredDirs {
if d == "" {
continue
}
fmt.Printf("Checking prefix: %s, path: %s\n", d, pathPrefix)
if strings.HasPrefix(pathPrefix, d) {

fmt.Printf("Checking regex: %s, path: %s\n", d, pathPrefix)
re := regexp.MustCompile(d)
if re.MatchString(pathPrefix) {
fmt.Printf("Path is ignored, skipping: %s\n", pathPrefix)
return true
}
Expand All @@ -111,7 +187,7 @@ func isIgnoredDirPrefix(pathPrefix string, ignoredDirs []string) bool {
func main() {
rootFolder := flag.String("root", ".", "The root folder to start scanning from")
subDir := flag.String("subdir", "", "The subdirectory inside the root folder to scan for modules")
ignoreDirs := flag.String("ignore", "", "Ignore directory paths starting with prefix")
ignoreDirs := flag.String("ignore", "", "Ignore directory paths matching regex (comma-separated)")
flag.Parse()

absRootFolder, err := filepath.Abs(*rootFolder)
Expand All @@ -133,24 +209,30 @@ func main() {
// Convert the stripped path back to absolute
pathPrefix := strings.TrimPrefix(dirPath, absRootFolder+string(os.PathSeparator))

if isIgnoredDirPrefix(pathPrefix, ignoredDirs) {
if isIgnoredDirRegex(pathPrefix, ignoredDirs) {
continue
}

retractedVersions, err := getRetractedTags(filepath.Join(dirPath, "go.mod"))
if err != nil {
fmt.Printf("Error getting retracted versions: %v\n", err)
continue
}

lastTag, err := getLastTag(pathPrefix)
latestTag, err := getLatestTag(pathPrefix, retractedVersions)
if err != nil {
fmt.Printf("Error finding last tag: %v\n", err)
fmt.Printf("Error finding latest tag: %v\n", err)
continue
}

if lastTag != "" {
if latestTag != "" {
fmt.Printf("%sProcessing directory: %s%s\n", Yellow, dirPath, Reset)
if err := os.Chdir(dirPath); err != nil {
fmt.Printf("Error changing directory: %v\n", err)
continue
}

stdout, stderr, err := checkBreakingChanges(lastTag)
stdout, stderr, err := checkBreakingChanges(latestTag)
if err != nil {
fmt.Printf("Error running gorelease: %v\n", err)
breakingChanges = true
Expand Down
7 changes: 6 additions & 1 deletion tools/breakingchanges/go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
module github.com/smartcontractkit/chainlink-testing-framework/tools/breakingchanges

go 1.22.6
go 1.23.3

retract [v1.999.0-test-release, v1.999.999-test-release]

require (
github.com/Masterminds/semver/v3 v3.3.1
golang.org/x/mod v0.22.0
)
4 changes: 4 additions & 0 deletions tools/breakingchanges/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4=
github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=

0 comments on commit f3dea2c

Please sign in to comment.