From 8660de2a949dc82cd4fad532114ea8727a636df7 Mon Sep 17 00:00:00 2001 From: Samuel Sadok Date: Thu, 1 Oct 2020 23:45:30 +0200 Subject: [PATCH] Implement GitHub workflow to automate releases The workflow creates a GitHub Release Draft as follows: - Title with a version number parsed from the git ref that triggerd the workflow - Body parsed from the changelog - Compiled .elf and .hex files for board versions 3.5 and 3.6 - Python package (as .tar.gz file) The workflow was tested by adding the trigger `on.push.branches: [autorelease/v0.5.2]` and then pushing onto that branch. --- .github/actions/release-firmware/action.yml | 57 ++++++++++ .github/workflows/release.yaml | 109 ++++++++++++++++++++ docs/developer-guide.md | 14 ++- tools/odrive/version.py | 2 +- 4 files changed, 178 insertions(+), 4 deletions(-) create mode 100644 .github/actions/release-firmware/action.yml create mode 100644 .github/workflows/release.yaml diff --git a/.github/actions/release-firmware/action.yml b/.github/actions/release-firmware/action.yml new file mode 100644 index 00000000..0ce949da --- /dev/null +++ b/.github/actions/release-firmware/action.yml @@ -0,0 +1,57 @@ +name: 'Release Firmware' +description: 'Compiles the firmware for the specified board version and uploads it to a GitHub release url' +inputs: + board_version: + required: true + url: + required: true + token: + required: true +runs: + using: "composite" + steps: + - name: Compile for ODrive ${{ inputs.board_version }} + shell: bash + run: | + # for debugging + arm-none-eabi-gcc --version + python3 --version + + cd ${{ github.workspace }}/Firmware + + echo "CONFIG_STRICT=true" > tup.config + echo "CONFIG_BOARD_VERSION=${{ inputs.board_version }}" >> tup.config + tup init + tup generate ./tup_build.sh + bash -xe ./tup_build.sh + + - name: Upload ELF file + shell: bash + run: | + urls=( ${{ inputs.url }} ) + URL="${urls[0]}=ODriveFirmware_${{ inputs.board_version }}.elf" + echo "uploading to $URL" + + curl --silent --show-error --fail \ + -H "Content-Type: application/octet-stream" \ + -H "Authorization: token ${{ inputs.token }}" \ + --data-binary @"Firmware/build/ODriveFirmware.elf" \ + "$URL" + + - name: Upload HEX file + shell: bash + run: | + urls=( ${{ inputs.url }} ) + URL="${urls[0]}=ODriveFirmware_${{ inputs.board_version }}.hex" + echo "uploading to $URL" + + curl --silent --show-error --fail \ + -H "Content-Type: application/octet-stream" \ + -H "Authorization: token ${{ inputs.token }}" \ + --data-binary @"Firmware/build/ODriveFirmware.hex" \ + "$URL" + + # This is required to make the action repeatable + - name: Clean up + shell: bash + run: git clean -dffx diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 00000000..0be5fc10 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,109 @@ +name: Create Release Draft + +on: + push: + tags: ['fw-v*'] + +jobs: + release: + runs-on: ubuntu-latest + strategy: + fail-fast: true + + steps: + - uses: actions/checkout@v2 + + - name: Parse Release Specs + id: release_specs + run: | + echo "github.ref = ${{ github.ref }}" + + function get_version() { sed -E -n "s|.*([0-9]+\.[0-9]+\.[0-9]+).*|\1|p"; } + + #LAST_VERSION="$(curl -s https://api.github.com/repos/madcowswe/ODrive/releases/latest | jq '.name' | get_version)" + #echo "previous version = $LAST_VERSION" + + NEW_VERSION="$(echo "${{ github.ref }}" | get_version)" + echo "version = $NEW_VERSION" + + ALL_TITLES="$(grep -E -n "^## .*[0-9]+\.[0-9]+\.[0-9]+" CHANGELOG.md)" + + echo "I found the following version headers in the changelog:" + echo "$ALL_TITLES" + + START_LINE="$(head -1 <<< "$ALL_TITLES" | cut -f 1 -d ':')" + END_LINE="$(head -2 <<< "$ALL_TITLES" | tail -1 | cut -f 1 -d ':')" + + BODY="$(head -$(("$END_LINE" - 1)) CHANGELOG.md | tail +$(("$START_LINE" + 1)))" + + IS_PRERELEASE="$(grep -F "$NEW_VERSION"-rc <<< "${{ github.ref }}" >/dev/null && echo true || echo false)" + echo "Is prerelease: $IS_PRERELEASE" + + # The git tag is later read when packaging the python tools and + # compiling the firmware. We set it explicitly here in case the + # workflow was triggered by something other than a git tag. + git tag "${{ github.ref }}" + + # Escape BODY string to set it as GitHub Actions output + BODY="${BODY//'%'/'%25'}" + BODY="${BODY//$'\n'/'%0A'}" + BODY="${BODY//$'\r'/'%0D'}" + + echo "::set-output name=body::$BODY" + echo "::set-output name=version::$NEW_VERSION" + echo "::set-output name=is_prerelease::$IS_PRERELEASE" + + - name: Create Release Draft + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Firmware version ${{ steps.release_specs.outputs.version }} + body: ${{ steps.release_specs.outputs.body }} + draft: true # Only do a draft so we have a change to manually review + # the release before it goes live. + prerelease: ${{ steps.release_specs.outputs.is_prerelease }} + + - name: Install Prerequisites + run: | + sudo add-apt-repository ppa:team-gcc-arm-embedded/ppa + sudo add-apt-repository ppa:jonathonf/tup + sudo apt-get update + sudo apt-get install gcc-arm-embedded + sudo apt-get install tup + sudo apt install python3 python3-yaml python3-jinja2 python3-jsonschema + + - name: Build Python Tools + run: | + cd ${{ github.workspace }}/tools + python setup.py sdist + + cd dist + PKG_NAME="$(ls)" + if [ "$(wc -l <<< "$PKG_NAME")" != 1 ]; then + echo "Expected exactly one output file but got:" + ls + exit 1 + fi + + urls=( ${{ steps.create_release.outputs.upload_url }} ) + URL="${urls[0]}=python-${PKG_NAME}" + echo "uploading to $URL" + + curl --silent --show-error --fail \ + -H "Content-Type: application/octet-stream" \ + -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + --data-binary @"${PKG_NAME}" \ + "$URL" + + # Run the action under .github/actions/release-firmware for every board version + #- {uses: ./.github/actions/release-firmware, with: {board-version: v3.2, url: '${{ steps.create_release.outputs.upload_url }}', token: '${{ secrets.GITHUB_TOKEN }}' }} + #- {uses: ./.github/actions/release-firmware, with: {board-version: v3.3, url: '${{ steps.create_release.outputs.upload_url }}', token: '${{ secrets.GITHUB_TOKEN }}' }} + #- {uses: ./.github/actions/release-firmware, with: {board-version: v3.4-24V, url: '${{ steps.create_release.outputs.upload_url }}', token: '${{ secrets.GITHUB_TOKEN }}' }} + #- {uses: ./.github/actions/release-firmware, with: {board-version: v3.4-48V, url: '${{ steps.create_release.outputs.upload_url }}', token: '${{ secrets.GITHUB_TOKEN }}' }} + - {uses: ./.github/actions/release-firmware, with: {board_version: v3.5-24V, url: '${{ steps.create_release.outputs.upload_url }}', token: '${{ secrets.GITHUB_TOKEN }}' }} + - {uses: ./.github/actions/release-firmware, with: {board_version: v3.5-48V, url: '${{ steps.create_release.outputs.upload_url }}', token: '${{ secrets.GITHUB_TOKEN }}' }} + - {uses: ./.github/actions/release-firmware, with: {board_version: v3.6-24V, url: '${{ steps.create_release.outputs.upload_url }}', token: '${{ secrets.GITHUB_TOKEN }}' }} + - {uses: ./.github/actions/release-firmware, with: {board_version: v3.6-56V, url: '${{ steps.create_release.outputs.upload_url }}', token: '${{ secrets.GITHUB_TOKEN }}' }} diff --git a/docs/developer-guide.md b/docs/developer-guide.md index f9fa7159..fefd660e 100644 --- a/docs/developer-guide.md +++ b/docs/developer-guide.md @@ -274,9 +274,17 @@ We use GitHub Releases to provide firmware releases. 1. Cut off the changelog to reflect the new release 2. Merge the release candidate into master. -3. Push a (lightweight) tag to the master branch. Follow the existing naming convention. -4. Push the python tools to PyPI. -5. Edit the release on GitHub to add a title and description (copy&paste from changelog). +3. Push a (lightweight) tag to the master branch. + - Releases should be named like `fw-v0.1.23` + - Prereleases should be named like `fw-v0.1.23-rc` or `fw-v0.1.23-rc4` +4. Go to the GitHub Releases page and review the draft that will have been created by a GitHub Action by now. If you're satisfied, go ahead and publish it by pressing Edit > **Publish Release**. +5. Download `python-odrive-*.tar.gz` from the Releases page. The following step assumes that the version is `0.1.2` +6. Run + ``` + mv {python-,}odrive-0.1.23.tar.gz + pip3 install twine + python3 -m twine upload odrive-0.1.23.tar.gz # Only works once per version! + ``` ## Other code maintenance notes The cortex M4F processor has hardware single precision float unit. However double precision operations are not accelerated, and hence should be avoided. The following regex is helpful for cleaning out double constants: diff --git a/tools/odrive/version.py b/tools/odrive/version.py index 7a323899..53cf0dfa 100644 --- a/tools/odrive/version.py +++ b/tools/odrive/version.py @@ -30,7 +30,7 @@ def get_version_from_git(): # Determine the current git commit version git_tag = subprocess.check_output(["git", "describe", "--always", "--tags", "--dirty=*"], cwd=script_dir) - git_tag = git_tag.decode(sys.stdout.encoding).rstrip('\n') + git_tag = git_tag.decode(sys.stdout.encoding or 'ascii').rstrip('\n') (major, minor, revision, is_prerelease) = version_str_to_tuple(git_tag)