Skip to content

Commit c2d4b62

Browse files
author
Luke Sneeringer
authored
chore: Add release-on-a-schedule. (#573)
1 parent 30577c8 commit c2d4b62

4 files changed

Lines changed: 260 additions & 13 deletions

File tree

.github/release-tool/changelog.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package main
16+
17+
import (
18+
"fmt"
19+
"strings"
20+
)
21+
22+
type commit struct {
23+
category string
24+
message string
25+
breaking bool
26+
}
27+
28+
func newCommit(line string) *commit {
29+
var cat, msg string
30+
breaking := false
31+
lineSplit := strings.SplitN(line, ":", 2)
32+
if len(lineSplit) > 1 {
33+
cat = strings.TrimSpace(lineSplit[0])
34+
if strings.HasSuffix(cat, "!") {
35+
breaking = true
36+
cat = strings.TrimSuffix(cat, "!")
37+
}
38+
msg = strings.TrimSpace(lineSplit[1])
39+
} else {
40+
cat = "unknown"
41+
msg = strings.TrimSpace(lineSplit[0])
42+
}
43+
return &commit{category: cat, message: msg, breaking: breaking}
44+
}
45+
46+
func (c *commit) String() string {
47+
return c.message
48+
}
49+
50+
type changelog struct {
51+
features []*commit
52+
fixes []*commit
53+
otherVisible []*commit
54+
otherInvisible []*commit
55+
breaking bool
56+
}
57+
58+
func newChangelog(gitlog string) *changelog {
59+
breaking := false
60+
var feat, fix, vis, invis []*commit
61+
for _, line := range strings.Split(gitlog, "\n") {
62+
cmt := newCommit(line)
63+
if cmt.breaking {
64+
breaking = true
65+
}
66+
switch cmt.category {
67+
case "feat":
68+
feat = append(feat, cmt)
69+
case "fix":
70+
fix = append(fix, cmt)
71+
case "docs", "refactor":
72+
vis = append(vis, cmt)
73+
default:
74+
invis = append(invis, cmt)
75+
}
76+
}
77+
return &changelog{
78+
features: feat,
79+
fixes: fix,
80+
otherVisible: vis,
81+
otherInvisible: invis,
82+
breaking: breaking,
83+
}
84+
}
85+
86+
func (cl *changelog) incrVersion(v *version) *version {
87+
if cl.breaking {
88+
return v.incrMajor()
89+
}
90+
if len(cl.features) > 0 {
91+
return v.incrMinor()
92+
}
93+
if len(cl.fixes) > 0 || len(cl.otherVisible) > 0 {
94+
return v.incrPatch()
95+
}
96+
return v
97+
}
98+
99+
func (cl *changelog) notes() string {
100+
section := func(title string, commits []*commit) string {
101+
if len(commits) > 0 {
102+
// %0A is newline: https://github.community/t/set-output-truncates-multiline-strings/16852
103+
answer := fmt.Sprintf("## %s%%0A%%0A", title)
104+
for _, cmt := range commits {
105+
answer += fmt.Sprintf("- %s%%0A", cmt)
106+
}
107+
return answer + "%0A"
108+
}
109+
return ""
110+
}
111+
return strings.TrimSuffix(
112+
section("Features", cl.features)+section("Fixes", cl.fixes)+section("Other", cl.otherVisible),
113+
"%0A",
114+
)
115+
}

.github/release-tool/main.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// This tool reads from the `.git` directory and determines the upcoming
16+
// version tag and changelog.
17+
//
18+
// Usage (see .github/workflows/release.yaml):
19+
// go run ./.github/release-tool
20+
package main
21+
22+
import (
23+
"fmt"
24+
"log"
25+
"os/exec"
26+
"strings"
27+
)
28+
29+
func main() {
30+
// Get the most recent release version.
31+
lastTaggedRev := mustExec("git", "rev-list", "--tags", "--max-count=1")
32+
lastVersion := versionFromString(mustExec("git", "describe", "--tags", lastTaggedRev))
33+
34+
// Get the changelog between the most recent release version and now.
35+
cl := newChangelog(
36+
mustExec("git", "log", fmt.Sprintf("%s..HEAD", lastVersion), "--oneline", "--pretty=format:%s"),
37+
)
38+
39+
// Dump output.
40+
nextVersion := cl.incrVersion(lastVersion)
41+
expr := "::set-output name=%s::%s\n"
42+
if lastVersion != nextVersion {
43+
fmt.Printf("New version: %s\n", nextVersion)
44+
fmt.Printf(expr, "version", nextVersion.String()[1:])
45+
fmt.Printf(expr, "release_notes", cl.notes())
46+
} else {
47+
fmt.Printf("No changes from %s to now.", lastVersion)
48+
}
49+
}
50+
51+
func mustExec(cmd string, args ...string) string {
52+
out, err := exec.Command(cmd, args...).CombinedOutput()
53+
if err != nil {
54+
log.Fatalf("exec failed: %s\n%s", out, err)
55+
}
56+
return strings.TrimSpace(string(out))
57+
}

.github/release-tool/version.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package main
16+
17+
import (
18+
"fmt"
19+
"log"
20+
"strconv"
21+
"strings"
22+
)
23+
24+
type version struct {
25+
Major int `json:"major"`
26+
Minor int `json:"minor"`
27+
Patch int `json:"patch"`
28+
}
29+
30+
func versionFromString(ver string) *version {
31+
// ver will be "vM.M.P", always. We will just panic if not.
32+
split := strings.Split(ver[1:], ".")
33+
return &version{
34+
Major: toInt(split[0]),
35+
Minor: toInt(split[1]),
36+
Patch: toInt(split[2]),
37+
}
38+
}
39+
40+
func (v version) incrMajor() *version {
41+
return &version{Major: v.Major + 1, Minor: 0, Patch: 0}
42+
}
43+
44+
func (v version) incrMinor() *version {
45+
return &version{Major: v.Major, Minor: v.Minor + 1, Patch: 0}
46+
}
47+
48+
func (v version) incrPatch() *version {
49+
return &version{Major: v.Major, Minor: v.Minor, Patch: v.Patch + 1}
50+
}
51+
52+
func (v version) String() string {
53+
return fmt.Sprintf("v%d.%d.%d", v.Major, v.Minor, v.Patch)
54+
}
55+
56+
func toInt(s string) int {
57+
i, err := strconv.Atoi(s)
58+
if err != nil {
59+
log.Fatalf("%s", err)
60+
}
61+
return i
62+
}

.github/workflows/release.yaml

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,45 @@
11
---
22
name: release
33
on:
4-
push:
5-
tags:
6-
- v[0-9]+.[0-9]+.[0-9]+
4+
schedule:
5+
- cron: 0 20 * * 3 # Wednesdays at 20:00 UTC
76
jobs:
7+
inspect:
8+
runs-on: ubuntu-latest
9+
container: golang:1.14
10+
steps:
11+
- uses: actions/checkout@v2
12+
with:
13+
fetch-depth: 0
14+
- name: Get release information
15+
id: release_tool
16+
run: go run ./.github/release-tool/
17+
outputs:
18+
version: ${{ steps.release_tool.outputs.version }}
19+
release_notes: ${{ steps.release_tool.outputs.release_notes }}
820
release:
921
runs-on: ubuntu-latest
22+
needs: inspect
23+
if: ${{ needs.inspect.outputs.version }}
1024
steps:
11-
- name: Set the version number.
12-
id: version
13-
run: echo ::set-output name=version::${GITHUB_REF#refs/tags/v}
1425
- name: Create the GitHub release.
1526
id: create_release
1627
uses: actions/create-release@v1
1728
env:
1829
GITHUB_TOKEN: ${{ github.token }}
1930
with:
20-
tag_name: ${{ github.ref }}
21-
release_name: api-linter ${{ steps.version.outputs.version }}
22-
draft: true # Change to false once release notes are automatic.
31+
tag_name: v${{ needs.inspect.outputs.version }}
32+
release_name: api-linter ${{ needs.inspect.outputs.version }}
33+
body: ${{ needs.inspect.outputs.release_notes }}
34+
draft: false
2335
prerelease: false
2436
outputs:
25-
version: ${{ steps.version.outputs.version }}
2637
upload_url: ${{ steps.create_release.outputs.upload_url }}
2738
build:
2839
runs-on: ubuntu-latest
29-
needs: release
40+
needs:
41+
- inspect
42+
- release
3043
strategy:
3144
matrix:
3245
osarch:
@@ -57,7 +70,7 @@ jobs:
5770
run: |
5871
cat > cmd/api-linter/version.go <<EOF
5972
package main
60-
const version = "${{ needs.release.outputs.version }}"
73+
const version = "${{ needs.inspect.outputs.version }}"
6174
EOF
6275
- name: Build for the ${{ matrix.osarch.os }}/${{ matrix.osarch.arch }} platform.
6376
run: |
@@ -70,5 +83,5 @@ jobs:
7083
with:
7184
upload_url: ${{ needs.release.outputs.upload_url }}
7285
asset_path: ./api-linter.tar.gz
73-
asset_name: api-linter-${{ needs.release.outputs.version }}-${{ matrix.osarch.os }}-${{ matrix.osarch.arch }}.tar.gz
86+
asset_name: api-linter-${{ needs.inspect.outputs.version }}-${{ matrix.osarch.os }}-${{ matrix.osarch.arch }}.tar.gz
7487
asset_content_type: application/tar+gzip

0 commit comments

Comments
 (0)