diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 074adbf7354e..dc8cb2968994 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -65,6 +65,9 @@ jobs: check_generated: needs: golangci-lint # run after golangci-lint action to not produce duplicated errors runs-on: ubuntu-latest + env: + # needed for github-action-config.json generation + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - uses: actions/checkout@v2 - name: Unshallow diff --git a/Makefile b/Makefile index bfe177dc2a41..f6523ef2ca8a 100644 --- a/Makefile +++ b/Makefile @@ -44,14 +44,14 @@ test_linters: # Maintenance -generate: README.md docs/demo.svg install.sh +generate: README.md assets/demo.svg install.sh assets/github-action-config.json .PHONY: generate fast_generate: README.md .PHONY: fast_generate maintainer-clean: clean - rm -rf docs/demo.svg README.md install.sh + rm -rf assets/demo.svg README.md install.sh .PHONY: maintainer-clean check_generated: @@ -94,8 +94,8 @@ tools/svg-term: tools/package.json tools/package-lock.json tools/Dracula.itermcolors: curl -fL -o $@ https://raw.githubusercontent.com/dracula/iterm/master/Dracula.itermcolors -docs/demo.svg: tools/svg-term tools/Dracula.itermcolors - ./tools/svg-term --cast=183662 --out docs/demo.svg --window --width 110 --height 30 --from 2000 --to 20000 --profile ./tools/Dracula.itermcolors --term iterm2 +assets/demo.svg: tools/svg-term tools/Dracula.itermcolors + ./tools/svg-term --cast=183662 --out assets/demo.svg --window --width 110 --height 30 --from 2000 --to 20000 --profile ./tools/Dracula.itermcolors --term iterm2 install.sh: .goreleaser.yml tools/godownloader ./tools/godownloader .goreleaser.yml | sed '/DO NOT EDIT/s/ on [0-9TZ:-]*//' > $@ @@ -103,6 +103,9 @@ install.sh: .goreleaser.yml tools/godownloader README.md: FORCE golangci-lint go run ./scripts/gen_readme/main.go +assets/github-action-config.json: FORCE golangci-lint + go run ./scripts/gen_github_action_config/main.go $@ + go.mod: FORCE go mod tidy go mod verify diff --git a/README.md b/README.md index 22053bbbc8ce..cd727ec6519c 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Follow the news and releases on our [twitter](https://twitter.com/golangci) and Sponsored by [GolangCI.com](https://golangci.com): SaaS service for running linters on GitHub pull requests. Free for Open Source. - + - [GolangCI-Lint](#golangci-lint) - [Demo](#demo) @@ -60,7 +60,7 @@ Sponsored by [GolangCI.com](https://golangci.com): SaaS service for running lint ## Demo

- +

Short 1.5 min video demo of analyzing [beego](https://github.com/astaxie/beego). diff --git a/README.tmpl.md b/README.tmpl.md index 0efccd6fb195..b6f602be4d0f 100644 --- a/README.tmpl.md +++ b/README.tmpl.md @@ -14,7 +14,7 @@ Follow the news and releases on our [twitter](https://twitter.com/golangci) and Sponsored by [GolangCI.com](https://golangci.com): SaaS service for running linters on GitHub pull requests. Free for Open Source. - + - [GolangCI-Lint](#golangci-lint) - [Demo](#demo) @@ -60,7 +60,7 @@ Sponsored by [GolangCI.com](https://golangci.com): SaaS service for running lint ## Demo

- +

Short 1.5 min video demo of analyzing [beego](https://github.com/astaxie/beego). diff --git a/docs/demo.svg b/assets/demo.svg similarity index 100% rename from docs/demo.svg rename to assets/demo.svg diff --git a/assets/github-action-config.json b/assets/github-action-config.json new file mode 100644 index 000000000000..9b1d08c06610 --- /dev/null +++ b/assets/github-action-config.json @@ -0,0 +1,89 @@ +{ + "MinorVersionToConfig": { + "v1.10": { + "Error": "golangci-lint version 'v1.10' isn't supported: we support only v1.14.0 and later versions" + }, + "v1.11": { + "Error": "golangci-lint version 'v1.11' isn't supported: we support only v1.14.0 and later versions" + }, + "v1.12": { + "Error": "golangci-lint version 'v1.12' isn't supported: we support only v1.14.0 and later versions" + }, + "v1.13": { + "Error": "golangci-lint version 'v1.13' isn't supported: we support only v1.14.0 and later versions" + }, + "v1.14": { + "TargetVersion": "v1.14.0", + "AssetURL": "https://github.com/golangci/golangci-lint/releases/download/v1.14.0/golangci-lint-1.14.0-linux-amd64.tar.gz" + }, + "v1.15": { + "TargetVersion": "v1.15.0", + "AssetURL": "https://github.com/golangci/golangci-lint/releases/download/v1.15.0/golangci-lint-1.15.0-linux-amd64.tar.gz" + }, + "v1.16": { + "TargetVersion": "v1.16.0", + "AssetURL": "https://github.com/golangci/golangci-lint/releases/download/v1.16.0/golangci-lint-1.16.0-linux-amd64.tar.gz" + }, + "v1.17": { + "TargetVersion": "v1.17.1", + "AssetURL": "https://github.com/golangci/golangci-lint/releases/download/v1.17.1/golangci-lint-1.17.1-linux-amd64.tar.gz" + }, + "v1.18": { + "TargetVersion": "v1.18.0", + "AssetURL": "https://github.com/golangci/golangci-lint/releases/download/v1.18.0/golangci-lint-1.18.0-linux-amd64.tar.gz" + }, + "v1.19": { + "TargetVersion": "v1.19.1", + "AssetURL": "https://github.com/golangci/golangci-lint/releases/download/v1.19.1/golangci-lint-1.19.1-linux-amd64.tar.gz" + }, + "v1.20": { + "TargetVersion": "v1.20.1", + "AssetURL": "https://github.com/golangci/golangci-lint/releases/download/v1.20.1/golangci-lint-1.20.1-linux-amd64.tar.gz" + }, + "v1.21": { + "TargetVersion": "v1.21.0", + "AssetURL": "https://github.com/golangci/golangci-lint/releases/download/v1.21.0/golangci-lint-1.21.0-linux-amd64.tar.gz" + }, + "v1.22": { + "TargetVersion": "v1.22.2", + "AssetURL": "https://github.com/golangci/golangci-lint/releases/download/v1.22.2/golangci-lint-1.22.2-linux-amd64.tar.gz" + }, + "v1.23": { + "TargetVersion": "v1.23.8", + "AssetURL": "https://github.com/golangci/golangci-lint/releases/download/v1.23.8/golangci-lint-1.23.8-linux-amd64.tar.gz" + }, + "v1.24": { + "TargetVersion": "v1.24.0", + "AssetURL": "https://github.com/golangci/golangci-lint/releases/download/v1.24.0/golangci-lint-1.24.0-linux-amd64.tar.gz" + }, + "v1.25": { + "TargetVersion": "v1.25.1", + "AssetURL": "https://github.com/golangci/golangci-lint/releases/download/v1.25.1/golangci-lint-1.25.1-linux-amd64.tar.gz" + }, + "v1.26": { + "TargetVersion": "v1.26.0", + "AssetURL": "https://github.com/golangci/golangci-lint/releases/download/v1.26.0/golangci-lint-1.26.0-linux-amd64.tar.gz" + }, + "v1.3": { + "Error": "golangci-lint version 'v1.3' isn't supported: we support only v1.14.0 and later versions" + }, + "v1.4": { + "Error": "golangci-lint version 'v1.4' isn't supported: we support only v1.14.0 and later versions" + }, + "v1.5": { + "Error": "golangci-lint version 'v1.5' isn't supported: we support only v1.14.0 and later versions" + }, + "v1.6": { + "Error": "golangci-lint version 'v1.6' isn't supported: we support only v1.14.0 and later versions" + }, + "v1.7": { + "Error": "golangci-lint version 'v1.7' isn't supported: we support only v1.14.0 and later versions" + }, + "v1.8": { + "Error": "golangci-lint version 'v1.8' isn't supported: we support only v1.14.0 and later versions" + }, + "v1.9": { + "Error": "golangci-lint version 'v1.9' isn't supported: we support only v1.14.0 and later versions" + } + } +} diff --git a/docs/go.png b/assets/go.png similarity index 100% rename from docs/go.png rename to assets/go.png diff --git a/go.sum b/go.sum index 47defad91e08..f7cab823f798 100644 --- a/go.sum +++ b/go.sum @@ -323,6 +323,7 @@ golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/scripts/gen_github_action_config/go.mod b/scripts/gen_github_action_config/go.mod new file mode 100644 index 000000000000..dd8448b2e7d0 --- /dev/null +++ b/scripts/gen_github_action_config/go.mod @@ -0,0 +1,10 @@ +module github.com/golangci/golangci-lint/scripts/gen_github_action_config + +go 1.13 + +require ( + github.com/shurcooL/githubv4 v0.0.0-20200414012201-bbc966b061dd + github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f // indirect + golang.org/x/net v0.0.0-20200506145744-7e3656a0809f // indirect + golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d +) diff --git a/scripts/gen_github_action_config/go.sum b/scripts/gen_github_action_config/go.sum new file mode 100644 index 000000000000..145c6167363d --- /dev/null +++ b/scripts/gen_github_action_config/go.sum @@ -0,0 +1,22 @@ +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/shurcooL/githubv4 v0.0.0-20200414012201-bbc966b061dd h1:EwtC+kDj8s9OKiaStPZtTv3neldOyr98AXIxvmn3Gss= +github.com/shurcooL/githubv4 v0.0.0-20200414012201-bbc966b061dd/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo= +github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f h1:tygelZueB1EtXkPI6mQ4o9DQ0+FKW41hTbunoXZCTqk= +github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f h1:QBjCr1Fz5kw158VqdE9JfI9cJnl/ymnJWAdMuinqL7Y= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= diff --git a/scripts/gen_github_action_config/main.go b/scripts/gen_github_action_config/main.go new file mode 100644 index 000000000000..08b39a4a9418 --- /dev/null +++ b/scripts/gen_github_action_config/main.go @@ -0,0 +1,231 @@ +package main + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "log" + "os" + "strconv" + "strings" + + "github.com/shurcooL/githubv4" + "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 +} + +type logInfo struct { + Warning string `json:",omitempty"` + Info string `json:"omitempty"` +} + +type versionConfig struct { + Error string `json:",omitempty"` + + Log *logInfo `json:",omitempty"` + + TargetVersion string `json:",omitempty"` + AssetURL string `json:",omitempty"` +} + +type actionConfig struct { + MinorVersionToConfig map[string]versionConfig +} + +type version struct { + major, minor, patch int +} + +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 { + if v.major != vv.major { + return v.major >= vv.major + } + if v.minor != vv.minor { + return v.minor >= vv.minor + } + + return v.patch >= vv.patch +} + +const noPatch = -1 + +func parseVersion(s string) (*version, error) { + const vPrefix = "v" + if !strings.HasPrefix(s, vPrefix) { + return nil, fmt.Errorf("version should start with %q", vPrefix) + } + s = strings.TrimPrefix(s, vPrefix) + + parts := strings.Split(s, ".") + + 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) + } + + if len(nums) == 2 { //nolint:gomnd + return &version{major: nums[0], minor: nums[1], patch: noPatch}, nil + } + if len(nums) == 3 { //nolint:gomnd + return &version{major: nums[0], minor: nums[1], patch: nums[2]}, nil + } + + return nil, errors.New("invalid version format") +} + +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 + } + } + return "", fmt.Errorf("no matched asset url for pattern %q", pattern) +} + +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 + } + } + + minorVersionToConfig := map[string]versionConfig{} + minAllowedVersion := version{major: 1, minor: 14, patch: 0} + + for minorVersionedStr, maxPatchVersion := range maxPatchReleases { + if !maxPatchVersion.isAfterOrEq(&minAllowedVersion) { + minorVersionToConfig[minorVersionedStr] = versionConfig{ + Error: fmt.Sprintf("golangci-lint version '%s' isn't supported: we support only %s and later versions", + minorVersionedStr, minAllowedVersion), + } + 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, + } + } + + return &actionConfig{MinorVersionToConfig: minorVersionToConfig}, nil +} + +type release struct { + TagName string + ReleaseAssets struct { + Nodes []releaseAsset + } `graphql:"releaseAssets(first: 50)"` +} + +type releaseAsset struct { + DownloadURL string +} + +func fetchAllReleases(ctx context.Context) ([]release, error) { + githubToken := os.Getenv("GITHUB_TOKEN") + if githubToken == "" { + return nil, errors.New("no GITHUB_TOKEN environment variable") + } + src := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: githubToken}) + httpClient := oauth2.NewClient(context.Background(), 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, after: $releasesCursor)"` + } `graphql:"repository(owner: $owner, name: $name)"` + } + + vars := map[string]interface{}{ + "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 allReleases, nil +}