From 86a4f3a20a021db76ee82817e55f2042f633fc45 Mon Sep 17 00:00:00 2001 From: Martin Geisler Date: Thu, 11 Feb 2021 23:09:04 +0100 Subject: [PATCH 1/2] Add GitHub actions --- .github/workflows/build.yml | 45 ++++++++ .github/workflows/coverage.yml | 23 ++++ .github/workflows/prepare-release.yml | 154 ++++++++++++++++++++++++++ .github/workflows/publish-crate.yml | 63 +++++++++++ 4 files changed, 285 insertions(+) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/coverage.yml create mode 100644 .github/workflows/prepare-release.yml create mode 100644 .github/workflows/publish-crate.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..f1949a1 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,45 @@ +name: build + +on: + pull_request: + push: + branches: + - master + +env: + CARGO_TERM_COLOR: always + +jobs: + + build: + name: Build on ${{ matrix.os }} (${{ matrix.rust }}) + runs-on: ubuntu-latest + strategy: + matrix: + os: + - ubuntu-latest + - windows-latest + rust: + - stable + - nightly + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Install ${{ matrix.rust }} Rust + run: rustup default ${{ matrix.rust }} + + - name: Build and test + run: cargo test + + build-documentation: + name: Build documentation + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Build documentation and check intra-doc links + env: + RUSTDOCFLAGS: --deny broken_intra_doc_links + run: cargo doc --all-features --no-deps diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 0000000..6badc3a --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,23 @@ +name: coverage + +on: + push: + branches: + - master + +env: + CARGO_TERM_COLOR: always + +jobs: + coverage: + name: Generate coverage + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Run cargo-tarpaulin + uses: actions-rs/tarpaulin@v0.1 + + - name: Upload to codecov.io + uses: codecov/codecov-action@v1 diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml new file mode 100644 index 0000000..d411065 --- /dev/null +++ b/.github/workflows/prepare-release.yml @@ -0,0 +1,154 @@ +name: Prepare Release PR + +on: + push: + branches: + - 'release-*' + +jobs: + setup: + runs-on: ubuntu-latest + outputs: + name: ${{ steps.vars.outputs.name }} + old-version: ${{ steps.vars.outputs.old-version }} + new-version: ${{ steps.vars.outputs.new-version }} + steps: + - name: Check out repository + uses: actions/checkout@v2 + + - name: Set variables + id: vars + run: | + NAME=$(cargo metadata -q --no-deps | jq -r '.packages[0].name') + OLD_VERSION=$(cargo metadata -q --no-deps | jq -r '.packages[0].version') + NEW_VERSION=$(echo ${{ github.ref }} | cut -d '-' -f 2-) + echo "Version from Cargo: $OLD_VERSION" + echo "Version from branch: $NEW_VERSION" + + echo "::set-output name=name::$NAME" + echo "::set-output name=old-version::$OLD_VERSION" + echo "::set-output name=new-version::$NEW_VERSION" + + - name: Verify version format + run: | + echo '${{ steps.vars.outputs.new-version }}' | grep -q '^[0-9]\+\.[0-9]\+\.[0-9]\+$' + + pull-request: + needs: setup + if: ${{ needs.setup.outputs.old-version != needs.setup.outputs.new-version }} + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v2 + + - name: Configure Git user + run: | + git config user.name "Martin Geisler" + git config user.email "martin@geisler.net" + + - name: Update changelog for version ${{ needs.setup.outputs.new-version }} + id: changelog + uses: actions/github-script@v3 + with: + script: | + var fs = require('fs') + const old_version = '${{ needs.setup.outputs.old-version }}' + const new_version = '${{ needs.setup.outputs.new-version }}' + + let cutoff = '1970-01-01' + const releases = await github.repos.listReleases(context.repo) + for (const release of releases.data) { + if (release.tag_name == old_version) { + cutoff = release.published_at + break + } + } + core.info(`Finding merged PRs after ${cutoff}`) + + let q = [`repo:${context.repo.owner}/${context.repo.repo}`, + 'is:pr', 'is:merged', `merged:>${cutoff}`] + // Need to use https://octokit.github.io/rest.js/v18#pagination! + const prs = await github.search.issuesAndPullRequests({ + q: q.join(' '), + sort: 'created', + order: 'asc', + }) + core.info(`Found ${prs.data.items.length} merged PRs`) + + const changelog = prs.data.items.map( + pr => `* [#${pr.number}](${pr.html_url}): ${pr.title}` + ).join('\n') + core.exportVariable('CHANGELOG', changelog) + + var content = fs.readFileSync('README.md', 'utf8') + const today = new Date().toISOString().split('T')[0] + const heading = `### Version ${new_version} (${today})\n` + if (content.match('### Unreleased')) { + content = content.replace('### Unreleased', `${heading}\n${changelog}`) + } else { + content = content.replace('### Version', `${heading}\n${changelog}\n\n### Version`) + } + fs.writeFileSync('README.md', content) + + - name: Commit changelog + run: | + git commit --all -m "Update changelog for version ${{ needs.setup.outputs.new-version }}" + + - name: Update TOML code blocks + run: | + import fileinput, re, sys + + NAME = '${{ needs.setup.outputs.name }}' + NEW_VERSION = '${{ needs.setup.outputs.new-version }}' + MAJOR_MINOR = '.'.join(NEW_VERSION.split('.')[:2]) + + for line in fileinput.input(inplace=True): + line = re.sub(f'{NAME} = "[^"]+"', + f'{NAME} = "{MAJOR_MINOR}"', line) + line = re.sub(f'{NAME} = {{ version = "[^"]+"', + f'{NAME} = {{ version = "{MAJOR_MINOR}"', line) + sys.stdout.write(line) + shell: python3 {0} README.md + + - name: Update html_root_url + run: | + import fileinput, re, sys + + NAME = '${{ needs.setup.outputs.name }}' + NEW_VERSION = '${{ needs.setup.outputs.new-version }}' + + for line in fileinput.input(inplace=True): + sys.stdout.write( + re.sub(f'html_root_url = "https://docs.rs/{NAME}/[^"]+"', + f'html_root_url = "https://docs.rs/{NAME}/{NEW_VERSION}"', line)) + shell: python3 {0} src/lib.rs + + - name: Update crate version to ${{ needs.setup.outputs.new-version }} + uses: thomaseizinger/set-crate-version@1.0.0 + with: + version: ${{ needs.setup.outputs.new-version }} + + - name: Build and test + run: | + cargo test + + - name: Commit version bump + run: | + git commit --all -m "Bump version to ${{ needs.setup.outputs.new-version }}" + + - name: Push version bump + run: git push origin + + - name: Create pull request + uses: actions/github-script@v3 + with: + script: | + const pr = await github.pulls.create({ + owner: context.repo.owner, + repo: context.repo.repo, + head: 'release-${{ needs.setup.outputs.new-version }}', + base: 'master', + title: 'Release ${{ needs.setup.outputs.new-version }}', + body: process.env.CHANGELOG, + }) + core.info(`Created PR: ${pr.data.html_url}`) diff --git a/.github/workflows/publish-crate.yml b/.github/workflows/publish-crate.yml new file mode 100644 index 0000000..aa195f1 --- /dev/null +++ b/.github/workflows/publish-crate.yml @@ -0,0 +1,63 @@ +name: Publish Crate + +on: + push: + branches: + - master + paths: + - Cargo.toml + repository_dispatch: + types: publish + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@v2 + + - name: Set variables + id: vars + run: | + NAME=$(cargo metadata -q --no-deps | jq -r '.packages[0].name') + VERSION=$(cargo metadata -q --no-deps | jq -r '.packages[0].version') + CHANGELOG=$(awk '/^## Version/ {i++}; i==1 {print}; i>1 {exit}' README.md \ + | python3 -c 'import sys, json; print(json.dumps(sys.stdin.read()))') + echo "::set-output name=name::$NAME" + echo "::set-output name=version::$VERSION" + echo "::set-output name=changelog::$CHANGELOG" + echo "Found $NAME-$VERSION" + + - name: Lookup ${{ steps.vars.outputs.version }} tag + id: need-release + uses: actions/github-script@v3 + with: + script: | + const version = '${{ steps.vars.outputs.version }}' + const tags = await github.repos.listTags(context.repo) + if (tags.data.some(tag => tag.name == version)) { + core.info(`Found ${version} tag -- will skip publish step`) + return false + } + core.info(`Found no ${version} tag -- will proceed with publishing`) + return true + + # The result from above is JSON-encoded, meaning that we + # end up with the string 'true', not the Boolean true. + - if: steps.need-release.outputs.result == 'true' + name: Publish crate to crates.io + run: | + echo "Publishing ${{ steps.vars.outputs.name }}-${{ steps.vars.outputs.version }}" + cargo publish --token ${{ secrets.CARGO_TOKEN }} + + - if: steps.need-release.outputs.result == 'true' + name: Create GitHub release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ steps.vars.outputs.version }} + release_name: ${{ steps.vars.outputs.name }}-${{ steps.vars.outputs.version }} + body: ${{ fromJson(steps.vars.outputs.changelog) }} + draft: false + prerelease: false From 83d7bc645471b31add7066416073f583792051af Mon Sep 17 00:00:00 2001 From: Martin Geisler Date: Thu, 11 Feb 2021 23:15:31 +0100 Subject: [PATCH 2/2] Remove Travis CI, Circle CI, and AppVeyor We can now consolidate everything with GitHub. --- .appveyor.yml | 14 -------------- .circleci/config.yml | 18 ------------------ .codecov.yml | 13 ------------- .travis.yml | 11 ----------- 4 files changed, 56 deletions(-) delete mode 100644 .appveyor.yml delete mode 100644 .circleci/config.yml delete mode 100644 .codecov.yml delete mode 100644 .travis.yml diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index 1e304af..0000000 --- a/.appveyor.yml +++ /dev/null @@ -1,14 +0,0 @@ -environment: - matrix: - - TOOLCHAIN: stable - -install: - - appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe - - rustup-init.exe -y --profile minimal --default-toolchain %TOOLCHAIN% - - set PATH=%PATH%;%USERPROFILE%\.cargo\bin - -build_script: - - cargo build - -test_script: - - cargo test diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 3b22ed3..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,18 +0,0 @@ -version: 2 -jobs: - build: - machine: true - steps: - - checkout - - run: - name: Pull xd009642/tarpaulin - command: docker pull xd009642/tarpaulin - - run: - name: Generate coverage report - command: >- - docker run --security-opt seccomp=unconfined - -v $PWD:/volume xd009642/tarpaulin - cargo tarpaulin --out Xml - - run: - name: Upload to codecov.io - command: bash <(curl -s https://codecov.io/bash) -Z -f cobertura.xml diff --git a/.codecov.yml b/.codecov.yml deleted file mode 100644 index a9b0bd2..0000000 --- a/.codecov.yml +++ /dev/null @@ -1,13 +0,0 @@ -codecov: - # Do not wait for these CI providers since they will not upload any - # coverage reports. - ci: - - !appveyor - - !travis - -coverage: - status: - project: - default: - # Allow a 5% drop in overall project coverage on a PR. - threshold: 5% diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 426aa17..0000000 --- a/.travis.yml +++ /dev/null @@ -1,11 +0,0 @@ -language: rust - -rust: - - stable - - nightly - -install: - - cargo build - -script: - - cargo test