Skip to content

Commit

Permalink
chore: Add release-on-a-schedule. (#573)
Browse files Browse the repository at this point in the history
  • Loading branch information
Luke Sneeringer committed Jun 22, 2020
1 parent 30577c8 commit c2d4b62
Show file tree
Hide file tree
Showing 4 changed files with 260 additions and 13 deletions.
115 changes: 115 additions & 0 deletions .github/release-tool/changelog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
"fmt"
"strings"
)

type commit struct {
category string
message string
breaking bool
}

func newCommit(line string) *commit {
var cat, msg string
breaking := false
lineSplit := strings.SplitN(line, ":", 2)
if len(lineSplit) > 1 {
cat = strings.TrimSpace(lineSplit[0])
if strings.HasSuffix(cat, "!") {
breaking = true
cat = strings.TrimSuffix(cat, "!")
}
msg = strings.TrimSpace(lineSplit[1])
} else {
cat = "unknown"
msg = strings.TrimSpace(lineSplit[0])
}
return &commit{category: cat, message: msg, breaking: breaking}
}

func (c *commit) String() string {
return c.message
}

type changelog struct {
features []*commit
fixes []*commit
otherVisible []*commit
otherInvisible []*commit
breaking bool
}

func newChangelog(gitlog string) *changelog {
breaking := false
var feat, fix, vis, invis []*commit
for _, line := range strings.Split(gitlog, "\n") {
cmt := newCommit(line)
if cmt.breaking {
breaking = true
}
switch cmt.category {
case "feat":
feat = append(feat, cmt)
case "fix":
fix = append(fix, cmt)
case "docs", "refactor":
vis = append(vis, cmt)
default:
invis = append(invis, cmt)
}
}
return &changelog{
features: feat,
fixes: fix,
otherVisible: vis,
otherInvisible: invis,
breaking: breaking,
}
}

func (cl *changelog) incrVersion(v *version) *version {
if cl.breaking {
return v.incrMajor()
}
if len(cl.features) > 0 {
return v.incrMinor()
}
if len(cl.fixes) > 0 || len(cl.otherVisible) > 0 {
return v.incrPatch()
}
return v
}

func (cl *changelog) notes() string {
section := func(title string, commits []*commit) string {
if len(commits) > 0 {
// %0A is newline: https://github.community/t/set-output-truncates-multiline-strings/16852
answer := fmt.Sprintf("## %s%%0A%%0A", title)
for _, cmt := range commits {
answer += fmt.Sprintf("- %s%%0A", cmt)
}
return answer + "%0A"
}
return ""
}
return strings.TrimSuffix(
section("Features", cl.features)+section("Fixes", cl.fixes)+section("Other", cl.otherVisible),
"%0A",
)
}
57 changes: 57 additions & 0 deletions .github/release-tool/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// This tool reads from the `.git` directory and determines the upcoming
// version tag and changelog.
//
// Usage (see .github/workflows/release.yaml):
// go run ./.github/release-tool
package main

import (
"fmt"
"log"
"os/exec"
"strings"
)

func main() {
// Get the most recent release version.
lastTaggedRev := mustExec("git", "rev-list", "--tags", "--max-count=1")
lastVersion := versionFromString(mustExec("git", "describe", "--tags", lastTaggedRev))

// Get the changelog between the most recent release version and now.
cl := newChangelog(
mustExec("git", "log", fmt.Sprintf("%s..HEAD", lastVersion), "--oneline", "--pretty=format:%s"),
)

// Dump output.
nextVersion := cl.incrVersion(lastVersion)
expr := "::set-output name=%s::%s\n"
if lastVersion != nextVersion {
fmt.Printf("New version: %s\n", nextVersion)
fmt.Printf(expr, "version", nextVersion.String()[1:])
fmt.Printf(expr, "release_notes", cl.notes())
} else {
fmt.Printf("No changes from %s to now.", lastVersion)
}
}

func mustExec(cmd string, args ...string) string {
out, err := exec.Command(cmd, args...).CombinedOutput()
if err != nil {
log.Fatalf("exec failed: %s\n%s", out, err)
}
return strings.TrimSpace(string(out))
}
62 changes: 62 additions & 0 deletions .github/release-tool/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
"fmt"
"log"
"strconv"
"strings"
)

type version struct {
Major int `json:"major"`
Minor int `json:"minor"`
Patch int `json:"patch"`
}

func versionFromString(ver string) *version {
// ver will be "vM.M.P", always. We will just panic if not.
split := strings.Split(ver[1:], ".")
return &version{
Major: toInt(split[0]),
Minor: toInt(split[1]),
Patch: toInt(split[2]),
}
}

func (v version) incrMajor() *version {
return &version{Major: v.Major + 1, Minor: 0, Patch: 0}
}

func (v version) incrMinor() *version {
return &version{Major: v.Major, Minor: v.Minor + 1, Patch: 0}
}

func (v version) incrPatch() *version {
return &version{Major: v.Major, Minor: v.Minor, Patch: v.Patch + 1}
}

func (v version) String() string {
return fmt.Sprintf("v%d.%d.%d", v.Major, v.Minor, v.Patch)
}

func toInt(s string) int {
i, err := strconv.Atoi(s)
if err != nil {
log.Fatalf("%s", err)
}
return i
}
39 changes: 26 additions & 13 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -1,32 +1,45 @@
---
name: release
on:
push:
tags:
- v[0-9]+.[0-9]+.[0-9]+
schedule:
- cron: 0 20 * * 3 # Wednesdays at 20:00 UTC
jobs:
inspect:
runs-on: ubuntu-latest
container: golang:1.14
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Get release information
id: release_tool
run: go run ./.github/release-tool/
outputs:
version: ${{ steps.release_tool.outputs.version }}
release_notes: ${{ steps.release_tool.outputs.release_notes }}
release:
runs-on: ubuntu-latest
needs: inspect
if: ${{ needs.inspect.outputs.version }}
steps:
- name: Set the version number.
id: version
run: echo ::set-output name=version::${GITHUB_REF#refs/tags/v}
- name: Create the GitHub release.
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ github.token }}
with:
tag_name: ${{ github.ref }}
release_name: api-linter ${{ steps.version.outputs.version }}
draft: true # Change to false once release notes are automatic.
tag_name: v${{ needs.inspect.outputs.version }}
release_name: api-linter ${{ needs.inspect.outputs.version }}
body: ${{ needs.inspect.outputs.release_notes }}
draft: false
prerelease: false
outputs:
version: ${{ steps.version.outputs.version }}
upload_url: ${{ steps.create_release.outputs.upload_url }}
build:
runs-on: ubuntu-latest
needs: release
needs:
- inspect
- release
strategy:
matrix:
osarch:
Expand Down Expand Up @@ -57,7 +70,7 @@ jobs:
run: |
cat > cmd/api-linter/version.go <<EOF
package main
const version = "${{ needs.release.outputs.version }}"
const version = "${{ needs.inspect.outputs.version }}"
EOF
- name: Build for the ${{ matrix.osarch.os }}/${{ matrix.osarch.arch }} platform.
run: |
Expand All @@ -70,5 +83,5 @@ jobs:
with:
upload_url: ${{ needs.release.outputs.upload_url }}
asset_path: ./api-linter.tar.gz
asset_name: api-linter-${{ needs.release.outputs.version }}-${{ matrix.osarch.os }}-${{ matrix.osarch.arch }}.tar.gz
asset_name: api-linter-${{ needs.inspect.outputs.version }}-${{ matrix.osarch.os }}-${{ matrix.osarch.arch }}.tar.gz
asset_content_type: application/tar+gzip

0 comments on commit c2d4b62

Please sign in to comment.