name: Cross-platform Build on: push: tags: [ 'v*' ] workflow_dispatch: permissions: contents: write jobs: extract-version: name: Extract Version runs-on: ubuntu-latest outputs: version: ${{ steps.get_version.outputs.version }} version_valid: ${{ steps.validate_version.outputs.valid }} steps: - uses: actions/checkout@v4 - name: Get version from Cargo.toml id: get_version run: | VERSION=$(grep '^version =' Cargo.toml | sed -E 's/version = "([^"]+)"/\1/') echo "Extracted version: $VERSION" echo "version=$VERSION" >> $GITHUB_OUTPUT - name: Validate SemVer id: validate_version run: | VERSION=${{ steps.get_version.outputs.version }} # Comprehensive SemVer validation regex # Supports: MAJOR.MINOR.PATCH-PRERELEASE+BUILD # Where PRERELEASE can be: alpha, beta, rc.1, etc. # Where BUILD can be: build.123, sha.abc123, etc. if [[ ! $VERSION =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?(\+([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$ ]]; then echo "Error: Version '$VERSION' is not a valid SemVer string" echo "valid=false" >> $GITHUB_OUTPUT exit 1 else echo "Version '$VERSION' is valid SemVer" echo "valid=true" >> $GITHUB_OUTPUT fi build-windows: name: Build Windows runs-on: windows-latest needs: extract-version strategy: matrix: target: - x86_64-pc-windows-msvc - aarch64-pc-windows-msvc steps: - uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} - name: Build run: cargo build --release --target ${{ matrix.target }} - name: Prepare artifact run: | mkdir artifacts $arch = "${{ matrix.target }}".Split('-')[0] $version = "${{ needs.extract-version.outputs.version }}" copy "target\${{ matrix.target }}\release\mikusays.exe" "artifacts\mikusays-windows-$arch-$version.exe" - name: Upload artifact uses: actions/upload-artifact@v4 with: name: mikusays-windows-${{ matrix.target }} path: artifacts\* build-linux: name: Build Linux runs-on: ubuntu-latest needs: extract-version strategy: matrix: include: - target: x86_64-unknown-linux-gnu arch: x64 - target: aarch64-unknown-linux-gnu arch: arm64 steps: - uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} - name: Install cross-compilation tools if: matrix.target == 'aarch64-unknown-linux-gnu' run: | sudo apt-get update sudo apt-get install -y gcc-aarch64-linux-gnu echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV - name: Build run: cargo build --release --target ${{ matrix.target }} - name: Strip binary run: | if [[ "${{ matrix.target }}" == "aarch64-unknown-linux-gnu" ]]; then aarch64-linux-gnu-strip target/${{ matrix.target }}/release/mikusays else strip target/${{ matrix.target }}/release/mikusays fi - name: Prepare artifact run: | mkdir -p artifacts staging version="${{ needs.extract-version.outputs.version }}" stem="mikusays-linux-${{ matrix.arch }}-$version" cp target/${{ matrix.target }}/release/mikusays "staging/$stem" chmod 755 "staging/$stem" tar -C staging -czf "artifacts/$stem.tar.gz" "$stem" - name: Verify artifact run: | version="${{ needs.extract-version.outputs.version }}" stem="mikusays-linux-${{ matrix.arch }}-$version" extract_dir="$(mktemp -d)" tar -tzf "artifacts/$stem.tar.gz" | grep -Fx "$stem" tar -xzf "artifacts/$stem.tar.gz" -C "$extract_dir" test -x "$extract_dir/$stem" - name: Upload artifact uses: actions/upload-artifact@v4 with: name: mikusays-linux-${{ matrix.arch }} path: artifacts/* build-macos: name: Build macOS runs-on: macos-latest needs: extract-version strategy: matrix: include: - target: x86_64-apple-darwin arch: x64 - target: aarch64-apple-darwin arch: arm64 steps: - uses: actions/checkout@v4 - name: Install Rust uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} - name: Build run: cargo build --release --target ${{ matrix.target }} - name: Strip binary run: strip target/${{ matrix.target }}/release/mikusays - name: Prepare artifact run: | mkdir -p artifacts staging version="${{ needs.extract-version.outputs.version }}" stem="mikusays-macos-${{ matrix.arch }}-$version" cp target/${{ matrix.target }}/release/mikusays "staging/$stem" chmod 755 "staging/$stem" tar -C staging -czf "artifacts/$stem.tar.gz" "$stem" - name: Verify artifact run: | version="${{ needs.extract-version.outputs.version }}" stem="mikusays-macos-${{ matrix.arch }}-$version" extract_dir="$(mktemp -d)" tar -tzf "artifacts/$stem.tar.gz" | grep -Fx "$stem" tar -xzf "artifacts/$stem.tar.gz" -C "$extract_dir" test -x "$extract_dir/$stem" - name: Upload artifact uses: actions/upload-artifact@v4 with: name: mikusays-macos-${{ matrix.arch }} path: artifacts/* create-release: name: Create Release runs-on: ubuntu-latest needs: [extract-version, build-windows, build-linux, build-macos] if: startsWith(github.ref, 'refs/tags/v') && needs.extract-version.outputs.version_valid == 'true' steps: - uses: actions/checkout@v4 - name: Download all artifacts uses: actions/download-artifact@v4 with: path: artifacts merge-multiple: true - name: Display structure of downloaded files run: ls -R artifacts - name: Create Release uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3 with: files: artifacts/mikusays-* draft: false prerelease: false generate_release_notes: true body: | ## Downloads ### Windows - **x64**: `mikusays-windows-x86_64-${{ needs.extract-version.outputs.version }}.exe` - **ARM64**: `mikusays-windows-aarch64-${{ needs.extract-version.outputs.version }}.exe` ### Windows Installer - **Any**: `mikusays-windows-inst-any-${{ needs.extract-version.outputs.version }}.exe` ### Linux (tar.gz) - **x64**: `mikusays-linux-x64-${{ needs.extract-version.outputs.version }}.tar.gz` - **ARM64**: `mikusays-linux-arm64-${{ needs.extract-version.outputs.version }}.tar.gz` ### Debian Package - **x64 Debian Package**: `mikusays-debian-x86_64-${{ needs.extract-version.outputs.version }}.deb` - **ARM64 Debian Package**: `mikusays-debian-aarch64-${{ needs.extract-version.outputs.version }}.deb` ### RedHat (RPM) Package - **x64 RPM Package**: `mikusays-redhat-x86_64-${{ needs.extract-version.outputs.version }}.rpm` - **ARM64 RPM Package**: `mikusays-redhat-aarch64-${{ needs.extract-version.outputs.version }}.rpm` ### Repository for Debian / Ubuntu / Fedora / RHEL / CentOS Information on how to add it available here https://apt.xxanqw.pp.ua ### macOS (tar.gz) - **Intel (x64)**: `mikusays-macos-x64-${{ needs.extract-version.outputs.version }}.tar.gz` - **Apple Silicon (ARM64)**: `mikusays-macos-arm64-${{ needs.extract-version.outputs.version }}.tar.gz` ## Usage ```bash # Download and extract the appropriate archive on Linux/macOS: archive="mikusays-linux-x64-${{ needs.extract-version.outputs.version }}.tar.gz" tar -xzf "$archive" # Run the program: "./${archive%.tar.gz}" "Hello, World!" # If Gatekeeper blocks an unsigned macOS binary: xattr -d com.apple.quarantine "./${archive%.tar.gz}" ``` env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Notify package managers env: GH_TOKEN: ${{ secrets.RELEASE_TOKEN }} RELEASE_TAG: v${{ needs.extract-version.outputs.version }} run: | for repository in xxanqw/scoop-bucket xxanqw/homebrew-mikusays; do if ! gh api --method POST "repos/$repository/dispatches" \ --raw-field event_type=new-release \ --field "client_payload[tag_name]=$RELEASE_TAG"; then echo "::warning::Could not dispatch $repository; its four-hour schedule will pick up the release. Check RELEASE_TOKEN if immediate updates are required." fi done