diff --git a/.github/workflows/apidiff.yaml b/.github/workflows/apidiff.yaml new file mode 100644 index 0000000..340fffb --- /dev/null +++ b/.github/workflows/apidiff.yaml @@ -0,0 +1,29 @@ +name: Run apidiff + +on: [ pull_request ] + +jobs: + apidiff: + runs-on: ubuntu-latest + if: github.base_ref + steps: + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: 1.16.x + - name: Add GOBIN to PATH + run: echo "PATH=$(go env GOPATH)/bin:$PATH" >>$GITHUB_ENV + - name: Install dependencies + run: GO111MODULE=off go get golang.org/x/exp/cmd/apidiff + - name: Checkout old code + uses: actions/checkout@v2 + with: + ref: ${{ github.base_ref }} + path: "old" + - name: Checkout new code + uses: actions/checkout@v2 + with: + path: "new" + - name: APIDiff + run: ./_tools/apidiff.sh -d ../old + working-directory: "new" diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 0000000..5c84b2d --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,28 @@ +name: Run lint + +on: [ push, pull_request ] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Install Go + uses: actions/setup-go@v2 + - name: Checkout code + uses: actions/checkout@v2 + - name: Lint + run: | + docker run --rm -v `pwd`:/go/src/prj -w /go/src/prj \ + golangci/golangci-lint golangci-lint run \ + -v \ + --disable-all \ + -E deadcode \ + -E gocritic \ + -E gofmt \ + -E govet \ + -E ineffassign \ + -E misspell \ + -E revive \ + -E staticcheck \ + -E unused \ + -E varcheck diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 0000000..e99538e --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,22 @@ +name: Run tests + +on: [ push, pull_request ] + +jobs: + test: + strategy: + matrix: + go-versions: [ 1.14.x, 1.15.x, 1.16.x ] + platform: [ ubuntu-latest, macos-latest, windows-latest ] + runs-on: ${{ matrix.platform }} + steps: + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go-version }} + - name: Checkout code + uses: actions/checkout@v2 + - name: Build + run: go build -v ./... + - name: Test + run: go test -v -race ./... diff --git a/_tools/apidiff.sh b/_tools/apidiff.sh new file mode 100755 index 0000000..81ec70a --- /dev/null +++ b/_tools/apidiff.sh @@ -0,0 +1,127 @@ +#!/usr/bin/env bash + +# Copyright 2020 The Kubernetes Authors. +# Copyright 2021 The logr Authors. +# +# 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 +# +# http://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. + +set -o errexit +set -o nounset +set -o pipefail + +function usage { + local script="$(basename $0)" + + echo >&2 "Usage: ${script} [-r | -d ] + +This script should be run at the root of a module. + +-r + Compare the exported API of the local working copy with the + exported API of the local repo at the specified branch or tag. + +-d + Compare the exported API of the local working copy with the + exported API of the specified directory, which should point + to the root of a different version of the same module. + +Examples: + ${script} -r master + ${script} -r v1.10.0 + ${script} -r release-1.10 + ${script} -d /path/to/historical/version +" + exit 1 +} + +ref="" +dir="" +while getopts r:d: o +do case "$o" in + r) ref="$OPTARG";; + d) dir="$OPTARG";; + [?]) usage;; + esac +done + +# If REF and DIR are empty, print usage and error +if [[ -z "${ref}" && -z "${dir}" ]]; then + usage; +fi +# If REF and DIR are both set, print usage and error +if [[ -n "${ref}" && -n "${dir}" ]]; then + usage; +fi + +if ! which apidiff > /dev/null; then + echo "Installing golang.org/x/exp/cmd/apidiff" + pushd "${TMPDIR:-/tmp}" > /dev/null + GO111MODULE=off go get golang.org/x/exp/cmd/apidiff + popd > /dev/null +fi + +output=$(mktemp -d -t "apidiff.output.XXXX") +cleanup_output () { rm -fr "${output}"; } +trap cleanup_output EXIT + +# If ref is set, clone . to temp dir at $ref, and set $dir to the temp dir +clone="" +base="${dir}" +if [[ -n "${ref}" ]]; then + base="${ref}" + clone=$(mktemp -d -t "apidiff.clone.XXXX") + cleanup_clone_and_output () { rm -fr "${clone}"; cleanup_output; } + trap cleanup_clone_and_output EXIT + git clone . -q --no-tags "${clone}" + git -C "${clone}" co "${ref}" + dir="${clone}" +fi + +pushd "${dir}" >/dev/null + echo "Inspecting API of ${base}" + go list ./... > packages.txt + for pkg in $(cat packages.txt); do + mkdir -p "${output}/${pkg}" + apidiff -w "${output}/${pkg}/apidiff.output" "${pkg}" + done +popd >/dev/null + +retval=0 + +echo "Comparing with ${base}" +for pkg in $(go list ./...); do + # New packages are ok + if [ ! -f "${output}/${pkg}/apidiff.output" ]; then + continue + fi + + # Check for incompatible changes to previous packages + incompatible=$(apidiff -incompatible "${output}/${pkg}/apidiff.output" "${pkg}") + if [[ -n "${incompatible}" ]]; then + echo >&2 "FAIL: ${pkg} contains incompatible changes: +${incompatible} +" + retval=1 + fi +done + +# Check for removed packages +removed=$(comm -23 "${dir}/packages.txt" <(go list ./...)) +if [[ -n "${removed}" ]]; then + echo >&2 "FAIL: removed packages: +${removed} +" + retval=1 +fi + +exit $retval