cargo-executable-payload 0.2.0

A Cargo subcommand for creating standalone source files with base64-encoded payload.
Documentation
name: CI

on:
  schedule:
    - cron: '0 0 * * 0'
  push:
    branches:
      - master
      - staging
      - trying
    tags:
      - 'v[0-9]+.[0-9]+.[0-9]+**'
  pull_request:

jobs:
  rustfmt:
    name: Rustfmt
    runs-on: ubuntu-20.04

    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Set up `stable-x86_64-unknown-linux-gnu`
        uses: actions-rs/toolchain@v1
        with:
          toolchain: stable-x86_64-unknown-linux-gnu
          default: true
          profile: minimal
          components: rustfmt

      - name: cargo-fmt
        uses: actions-rs/cargo@v1
        with:
          command: fmt
          args: --all -- --check

  build:
    strategy:
      fail-fast: false
      matrix:
        job-name:
          - stable-x86_64-pc-windows-msvc
          - stable-x86_64-apple-darwin
          - stable-x86_64-unknown-linux-gnu
        include:
          - { job-name: stable-x86_64-pc-windows-msvc  , exe-suffix: .exe, os: windows-2019 }
          - { job-name: stable-x86_64-apple-darwin     , exe-suffix: ''  , os: macos-10.15  }
          - { job-name: stable-x86_64-unknown-linux-gnu, exe-suffix: ''  , os: ubuntu-20.04 }
    env:
      NIGHTLY_TOOLCHAIN: nightly-2021-08-10

    name: ${{ matrix.job-name }}
    runs-on: ${{ matrix.os }}

    steps:
      - name: Disable `core.autocrlf`
        run: git config --global core.autocrlf false
        if: matrix.os == 'windows-2019'

      - name: Checkout
        uses: actions/checkout@v2

      - name: Set up `stable`
        id: install-toolchain
        uses: actions-rs/toolchain@v1
        with:
          toolchain: stable
          default: true
          profile: minimal
          components: clippy

      - name: Set up `${{ env.NIGHTLY_TOOLCHAIN }}`
        uses: actions-rs/toolchain@v1
        with:
          toolchain: ${{ env.NIGHTLY_TOOLCHAIN }}
          profile: minimal

      - name: Set up Python 3.9
        uses: actions/setup-python@v2
        with:
          python-version: '3.9'

      - name: Resolve `cargo-udeps ^0.1`
        id: resolve-versions
        run: |
          python - cargo-udeps@^0.1 <<'EOF'
          from argparse import ArgumentParser
          import json
          import urllib.request
          import re

          CRATE_SPEC = re.compile(r'\A([a-zA-Z0-9_-]+)@\^([0-9]+)\.([0-9]+)\Z')


          def main() -> None:
              parser = ArgumentParser()
              parser.add_argument('specs', metavar='PSEUDO_SPECS', type=CrateSpec, nargs='+')
              print('\n'.join(map(resolve, parser.parse_args().specs)))


          class CrateSpec:
              def __init__(self, s: str):
                  if not (m := CRATE_SPEC.match(s)):
                      raise RuntimeError(f'the version must be `{CRATE_SPEC.pattern}`')
                  self.package_name = m.group(1)
                  self.version_req_major = int(m.group(2))
                  self.version_req_minor = int(m.group(3))


          def resolve(spec: CrateSpec) -> str:
              with urllib.request.urlopen(f'https://crates.io/api/v1/crates/{spec.package_name}') as res:
                  versions = json.load(res)['versions']
              matched = set()
              for version in versions:
                  major, minor, patch_pre_build = version['num'].split('.')
                  major, minor = (int(major), int(minor))
                  if ((major, spec.version_req_major) == (0, 0) and minor == spec.version_req_minor or major == spec.version_req_major and minor >= spec.version_req_minor) and patch_pre_build.isdecimal():
                      matched.add((minor, int(patch_pre_build)))
              if not matched:
                  raise RuntimeError(f'No such package: `{spec.package_name} ^{spec.version_req_major}.{spec.version_req_minor}`')
              minor, patch = max(matched)
              return f'::set-output name={spec.package_name}::{spec.version_req_major}.{minor}.{patch}'


          if __name__ == '__main__':
              main()
          EOF
        shell: bash

      - name: Cache for cargo-udeps
        uses: actions/cache@v2
        with:
          path: |
            ~/.cargo/.crates.toml
            ~/.cargo/.crates2.json
            ~/.cargo/bin/cargo-udeps${{ matrix.exe-suffix }}
          key: build-crates-${{ matrix.job-name }}-${{ steps.resolve-versions.outputs.cargo-udeps }}

      - name: Cache for build cache
        uses: actions/cache@v2
        with:
          path: |
            ~/.cargo/git
            ~/.cargo/registry
            ./target
          key: build-build-${{ matrix.job-name }}-${{ steps.install-toolchain.outputs.rustc_hash }}-${{ hashFiles('./Cargo.*') }}

      - name: Install cargo-udeps v${{ steps.resolve-versions.outputs.cargo-udeps }}
        uses: actions-rs/cargo@v1
        with:
          command: install
          args: cargo-udeps --version '=${{ steps.resolve-versions.outputs.cargo-udeps }}'

      - name: cargo-clippy
        uses: actions-rs/cargo@v1
        with:
          command: clippy
          args: --all-targets -- -D warnings

      - name: cargo-test
        uses: actions-rs/cargo@v1
        with:
          command: test
          args: --no-fail-fast
        env:
          RUST_BACKTRACE: full

      - name: cargo-udeps
        uses: actions-rs/cargo@v1
        with:
          command: udeps
          args: --all-targets
          toolchain: ${{ env.NIGHTLY_TOOLCHAIN }}

  upload-archives:
    strategy:
      fail-fast: false
      matrix:
        target-triple:
          - x86_64-pc-windows-msvc
          - x86_64-apple-darwin
          - x86_64-unknown-linux-gnu
        include:
          - { target-triple: x86_64-pc-windows-msvc  , host-triple: x86_64-pc-windows-msvc  , os: windows-2019 }
          - { target-triple: x86_64-apple-darwin     , host-triple: x86_64-apple-darwin     , os: macos-10.15  }
          - { target-triple: x86_64-unknown-linux-gnu, host-triple: x86_64-unknown-linux-gnu, os: ubuntu-20.04 }

    name: Upload (${{ matrix.target-triple }})
    runs-on: ${{ matrix.os }}
    if: startsWith(github.ref, 'refs/tags/')

    steps:
      - name: Disable `core.autocrlf`
        run: git config --global core.autocrlf false
        if: matrix.os == 'windows-2019'

      - name: Checkout
        uses: actions/checkout@v2

      - name: 'Set up `stable-${{ matrix.host-triple }}` (target: `${{ matrix.target-triple }}`)'
        uses: actions-rs/toolchain@v1
        with:
          toolchain: stable-${{ matrix.host-triple }}
          target: ${{ matrix.target-triple }}
          default: true
          profile: minimal

      - name: cargo-install
        uses: actions-rs/cargo@v1
        with:
          command: install
          args: --path . --target ${{ matrix.target-triple }} -v --locked

      - name: Create an asset
        id: asset
        run: |
          BIN_TARGET=cargo-executable-payload
          if ${{ contains(matrix.target-triple, 'pc-windows') }}; then
            DOT_EXE=.exe
          fi
          ASSET_STEM="$BIN_TARGET-${GITHUB_REF#refs/tags/}-${{ matrix.target-triple }}"
          git archive -o "./$ASSET_STEM.tar" --prefix "$ASSET_STEM/" HEAD
          tar -xf "./$ASSET_STEM.tar"
          mv "$HOME/.cargo/bin/$BIN_TARGET$DOT_EXE" "./$ASSET_STEM/"
          if ${{ contains(matrix.target-triple, 'pc-windows') }}; then
            ASSET="$ASSET_STEM.zip"
            7z a "$ASSET" "./$ASSET_STEM"
            zipinfo "./$ASSET"
          else
            ASSET="$ASSET_STEM.tar.gz"
            tar -czvf "./$ASSET" "./$ASSET_STEM"
          fi
          echo "::set-output name=asset::$ASSET"
        shell: bash

      - name: Upload the artifact
        uses: actions/upload-artifact@v2
        with:
          name: assets
          path: ${{ steps.asset.outputs.asset }}

  upload-release-notes:
    name: Upload the release notes
    runs-on: ubuntu-20.04
    if: ${{ startsWith(github.ref, 'refs/tags/') }}

    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Set up Python 3.9
        uses: actions/setup-python@v2
        with:
          python-version: '3.9'

      - name: Prepare release notes
        run: |
          from pathlib import Path
          PATH = Path('./CHANGELOG.md')
          output = ''
          if PATH.exists():
              with open(PATH) as file:
                  changelog = file.read()
              inside_subsection = False
              for line in changelog.splitlines():
                  is_h2 = line.startswith('## ')
                  if not inside_subsection and is_h2:
                      inside_subsection = True
                  elif inside_subsection and not is_h2:
                      output += line + '\n'
                  elif inside_subsection:
                      break
          with open('./release-notes.md', 'w') as file:
              file.write(f'{output.strip()}\n')
        shell: python

      - name: Upload the release notes
        uses: actions/upload-artifact@v2
        with:
          name: release-notes
          path: release-notes.md

  release:
    name: GitHub Release
    runs-on: ubuntu-20.04
    needs: [rustfmt, build, upload-archives, upload-release-notes]

    steps:
      - name: Download the assets
        uses: actions/download-artifact@v2
        with:
          name: assets
          path: ./assets

      - name: Download the release notes
        uses: actions/download-artifact@v2
        with:
          name: release-notes
          path: .

      - name: GH Release
        uses: softprops/action-gh-release@v1
        with:
          body_path: ./release-notes.md
          files: ./assets/*
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}