diff --git a/scripts/gen_github_action_config/go.mod b/scripts/gen_github_action_config/go.mod index c8f2a21fe602..cd40b6b8c390 100644 --- a/scripts/gen_github_action_config/go.mod +++ b/scripts/gen_github_action_config/go.mod @@ -3,7 +3,7 @@ module github.com/golangci/golangci-lint/scripts/gen_github_action_config go 1.21 require ( - github.com/shurcooL/githubv4 v0.0.0-20240120211514-18a1ae0e79dc + github.com/shurcooL/githubv4 v0.0.0-20240429030203-be2daab69064 golang.org/x/oauth2 v0.21.0 ) diff --git a/scripts/gen_github_action_config/go.sum b/scripts/gen_github_action_config/go.sum index b6c12dce7108..0bd98dea2097 100644 --- a/scripts/gen_github_action_config/go.sum +++ b/scripts/gen_github_action_config/go.sum @@ -1,7 +1,7 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/shurcooL/githubv4 v0.0.0-20240120211514-18a1ae0e79dc h1:vH0NQbIDk+mJLvBliNGfcQgUmhlniWBDXC79oRxfZA0= -github.com/shurcooL/githubv4 v0.0.0-20240120211514-18a1ae0e79dc/go.mod h1:zqMwyHmnN/eDOZOdiTohqIUKUrTFX62PNlu7IJdu0q8= +github.com/shurcooL/githubv4 v0.0.0-20240429030203-be2daab69064 h1:RCQBSFx5JrsbHltqTtJ+kN3U0Y3a/N/GlVdmRSoxzyE= +github.com/shurcooL/githubv4 v0.0.0-20240429030203-be2daab69064/go.mod h1:zqMwyHmnN/eDOZOdiTohqIUKUrTFX62PNlu7IJdu0q8= github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 h1:17JxqqJY66GmZVHkmAsGEkcIu0oCe3AM420QDgGwZx0= github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466/go.mod h1:9dIRpgIY7hVhoqfe0/FcYp0bpInZaT7dc3BYOprrIUE= golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= diff --git a/scripts/gen_github_action_config/main.go b/scripts/gen_github_action_config/main.go index 809e8943de05..fea468c4f95e 100644 --- a/scripts/gen_github_action_config/main.go +++ b/scripts/gen_github_action_config/main.go @@ -14,39 +14,7 @@ import ( "golang.org/x/oauth2" ) -func main() { - if err := generate(context.Background()); err != nil { - log.Fatal(err) - } -} - -func generate(ctx context.Context) error { - allReleases, err := fetchAllReleases(ctx) - if err != nil { - return fmt.Errorf("failed to fetch all releases: %w", err) - } - - cfg, err := buildConfig(allReleases) - if err != nil { - return fmt.Errorf("failed to build config: %w", err) - } - - if len(os.Args) != 2 { //nolint:gomnd - return fmt.Errorf("usage: go run .../main.go out-path.json") - } - outFile, err := os.Create(os.Args[1]) - if err != nil { - return fmt.Errorf("failed to create output config file: %w", err) - } - defer outFile.Close() - enc := json.NewEncoder(outFile) - enc.SetIndent("", " ") - if err = enc.Encode(cfg); err != nil { - return fmt.Errorf("failed to json encode config: %w", err) - } - - return nil -} +const noPatch = -1 type logInfo struct { Warning string `json:",omitempty"` @@ -72,16 +40,19 @@ type version struct { func (v version) String() string { ret := fmt.Sprintf("v%d.%d", v.major, v.minor) + if v.patch != noPatch { ret += fmt.Sprintf(".%d", v.patch) } + return ret } -func (v *version) isAfterOrEq(vv *version) bool { +func (v version) isAfterOrEq(vv *version) bool { if v.major != vv.major { return v.major >= vv.major } + if v.minor != vv.minor { return v.minor >= vv.minor } @@ -89,62 +60,122 @@ func (v *version) isAfterOrEq(vv *version) bool { return v.patch >= vv.patch } -const noPatch = -1 +type release struct { + TagName string + ReleaseAssets struct { + Nodes []releaseAsset + } `graphql:"releaseAssets(first: 50)"` +} -func parseVersion(s string) (*version, error) { - const vPrefix = "v" - if !strings.HasPrefix(s, vPrefix) { - return nil, fmt.Errorf("version should start with %q", vPrefix) +type releaseAsset struct { + DownloadURL string +} + +func main() { + if err := generate(context.Background()); err != nil { + log.Fatal(err) } - s = strings.TrimPrefix(s, vPrefix) +} - parts := strings.Split(s, ".") +func generate(ctx context.Context) error { + if len(os.Args) != 2 { + return fmt.Errorf("usage: go run .../main.go out-path.json") + } - var nums []int - for _, part := range parts { - num, err := strconv.Atoi(part) - if err != nil { - return nil, fmt.Errorf("failed to parse version part: %w", err) - } - nums = append(nums, num) + allReleases, err := fetchAllReleases(ctx) + if err != nil { + return fmt.Errorf("failed to fetch all releases: %w", err) } - if len(nums) == 2 { //nolint:gomnd - return &version{major: nums[0], minor: nums[1], patch: noPatch}, nil + cfg, err := buildConfig(allReleases) + if err != nil { + return fmt.Errorf("failed to build config: %w", err) } - if len(nums) == 3 { //nolint:gomnd - return &version{major: nums[0], minor: nums[1], patch: nums[2]}, nil + + outFile, err := os.Create(os.Args[1]) + if err != nil { + return fmt.Errorf("failed to create output config file: %w", err) } - return nil, errors.New("invalid version format") + defer outFile.Close() + + enc := json.NewEncoder(outFile) + enc.SetIndent("", " ") + + if err = enc.Encode(cfg); err != nil { + return fmt.Errorf("failed to json encode config: %w", err) + } + + return nil } -func findLinuxAssetURL(ver *version, releaseAssets []releaseAsset) (string, error) { - pattern := fmt.Sprintf("golangci-lint-%d.%d.%d-linux-amd64.tar.gz", ver.major, ver.minor, ver.patch) - for _, relAsset := range releaseAssets { - if strings.HasSuffix(relAsset.DownloadURL, pattern) { - return relAsset.DownloadURL, nil +func fetchAllReleases(ctx context.Context) ([]release, error) { + githubToken := os.Getenv("GITHUB_TOKEN") + if githubToken == "" { + return nil, errors.New("no GITHUB_TOKEN environment variable") + } + + client := githubv4.NewClient(oauth2.NewClient(ctx, oauth2.StaticTokenSource(&oauth2.Token{AccessToken: githubToken}))) + + var q struct { + Repository struct { + Releases struct { + Nodes []release + PageInfo struct { + EndCursor githubv4.String + HasNextPage bool + } + } `graphql:"releases(first: 100, orderBy: { field: CREATED_AT, direction: DESC }, after: $releasesCursor)"` + } `graphql:"repository(owner: $owner, name: $name)"` + } + + vars := map[string]any{ + "owner": githubv4.String("golangci"), + "name": githubv4.String("golangci-lint"), + "releasesCursor": (*githubv4.String)(nil), + } + + var allReleases []release + for { + err := client.Query(ctx, &q, vars) + if err != nil { + return nil, fmt.Errorf("failed to fetch releases page from GitHub: %w", err) } + + releases := q.Repository.Releases + allReleases = append(allReleases, releases.Nodes...) + + if !releases.PageInfo.HasNextPage { + break + } + + vars["releasesCursor"] = githubv4.NewString(releases.PageInfo.EndCursor) } - return "", fmt.Errorf("no matched asset url for pattern %q", pattern) + + return allReleases, nil } func buildConfig(releases []release) (*actionConfig, error) { versionToRelease := map[version]release{} + for _, rel := range releases { ver, err := parseVersion(rel.TagName) if err != nil { return nil, fmt.Errorf("failed to parse release %s version: %w", rel.TagName, err) } + if _, ok := versionToRelease[*ver]; ok { return nil, fmt.Errorf("duplicate release %s", rel.TagName) } + versionToRelease[*ver] = rel } maxPatchReleases := map[string]version{} + for ver := range versionToRelease { key := fmt.Sprintf("v%d.%d", ver.major, ver.minor) + if mapVer, ok := maxPatchReleases[key]; !ok || ver.isAfterOrEq(&mapVer) { maxPatchReleases[key] = ver } @@ -155,6 +186,7 @@ func buildConfig(releases []release) (*actionConfig, error) { latestVersion := version{} latestVersionConfig := versionConfig{} + for minorVersionedStr, maxPatchVersion := range maxPatchReleases { if !maxPatchVersion.isAfterOrEq(&minAllowedVersion) { minorVersionToConfig[minorVersionedStr] = versionConfig{ @@ -163,77 +195,67 @@ func buildConfig(releases []release) (*actionConfig, error) { } continue } + maxPatchVersion := maxPatchVersion + assetURL, err := findLinuxAssetURL(&maxPatchVersion, versionToRelease[maxPatchVersion].ReleaseAssets.Nodes) if err != nil { return nil, fmt.Errorf("failed to find linux asset url for release %s: %w", maxPatchVersion, err) } + minorVersionToConfig[minorVersionedStr] = versionConfig{ TargetVersion: maxPatchVersion.String(), AssetURL: assetURL, } + if maxPatchVersion.isAfterOrEq(&latestVersion) { latestVersion = maxPatchVersion latestVersionConfig.TargetVersion = maxPatchVersion.String() latestVersionConfig.AssetURL = assetURL } } + minorVersionToConfig["latest"] = latestVersionConfig return &actionConfig{MinorVersionToConfig: minorVersionToConfig}, nil } -type release struct { - TagName string - ReleaseAssets struct { - Nodes []releaseAsset - } `graphql:"releaseAssets(first: 50)"` -} - -type releaseAsset struct { - DownloadURL string -} +func findLinuxAssetURL(ver *version, releaseAssets []releaseAsset) (string, error) { + pattern := fmt.Sprintf("golangci-lint-%d.%d.%d-linux-amd64.tar.gz", ver.major, ver.minor, ver.patch) -func fetchAllReleases(ctx context.Context) ([]release, error) { - githubToken := os.Getenv("GITHUB_TOKEN") - if githubToken == "" { - return nil, errors.New("no GITHUB_TOKEN environment variable") + for _, relAsset := range releaseAssets { + if strings.HasSuffix(relAsset.DownloadURL, pattern) { + return relAsset.DownloadURL, nil + } } - src := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: githubToken}) - httpClient := oauth2.NewClient(ctx, src) - client := githubv4.NewClient(httpClient) - var q struct { - Repository struct { - Releases struct { - Nodes []release - PageInfo struct { - EndCursor githubv4.String - HasNextPage bool - } - } `graphql:"releases(first: 100, orderBy: { field: CREATED_AT, direction: DESC }, after: $releasesCursor)"` - } `graphql:"repository(owner: $owner, name: $name)"` - } + return "", fmt.Errorf("no matched asset url for pattern %q", pattern) +} - vars := map[string]any{ - "owner": githubv4.String("golangci"), - "name": githubv4.String("golangci-lint"), - "releasesCursor": (*githubv4.String)(nil), +func parseVersion(s string) (*version, error) { + const vPrefix = "v" + if !strings.HasPrefix(s, vPrefix) { + return nil, fmt.Errorf("version %q should start with %q", s, vPrefix) } - var allReleases []release - for { - err := client.Query(ctx, &q, vars) + parts := strings.Split(strings.TrimPrefix(s, vPrefix), ".") + + var nums []int + for _, part := range parts { + num, err := strconv.Atoi(part) if err != nil { - return nil, fmt.Errorf("failed to fetch releases page from GitHub: %w", err) + return nil, fmt.Errorf("failed to parse version %q: %w", s, err) } - releases := q.Repository.Releases - allReleases = append(allReleases, releases.Nodes...) - if !releases.PageInfo.HasNextPage { - break - } - vars["releasesCursor"] = githubv4.NewString(releases.PageInfo.EndCursor) + + nums = append(nums, num) } - return allReleases, nil + switch len(nums) { + case 2: + return &version{major: nums[0], minor: nums[1], patch: noPatch}, nil + case 3: + return &version{major: nums[0], minor: nums[1], patch: nums[2]}, nil + default: + return nil, fmt.Errorf("invalid version format: %s", s) + } }