cargo lib missing fix #9
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| tags: | |
| - "v*.*.*" | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: "Tag to build artifacts for (e.g. v0.3.21). Must already exist." | |
| required: true | |
| permissions: | |
| contents: write # for creating GitHub releases + uploading assets | |
| env: | |
| CARGO_TERM_COLOR: always | |
| jobs: | |
| verify: | |
| name: verify build | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: Swatinem/rust-cache@v2 | |
| - run: cargo fmt --all -- --check | |
| - run: cargo clippy --all-targets --all-features -- -D warnings | |
| - run: cargo test --all-features | |
| package-source: | |
| name: package source + cargo .crate | |
| needs: verify | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: dtolnay/rust-toolchain@stable | |
| - uses: Swatinem/rust-cache@v2 | |
| - name: Determine tag | |
| id: tag | |
| run: | | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| echo "tag=${{ github.event.inputs.tag }}" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "tag=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT" | |
| fi | |
| # `cargo package` builds the exact .crate file that would be uploaded | |
| # to crates.io if/when we publish manually. Shipping it lets downstream | |
| # consumers verify what's on crates.io matches the tagged source. | |
| - name: Build cargo package | |
| run: cargo package --allow-dirty --no-verify | |
| - name: Stage source artifacts | |
| run: | | |
| tag="${{ steps.tag.outputs.tag }}" | |
| mkdir -p release | |
| cp target/package/capa-*.crate "release/capa-${tag}.crate" | |
| src_name="capa-${tag}-src" | |
| git archive --format=tar.gz --prefix="${src_name}/" \ | |
| -o "release/${src_name}.tar.gz" HEAD | |
| (cd release && \ | |
| for f in *.crate *.tar.gz; do | |
| sha256sum "$f" > "$f.sha256" | |
| done) | |
| ls -la release | |
| - name: Upload source artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: capa-source-artifacts | |
| path: | | |
| release/*.crate | |
| release/*.tar.gz | |
| release/*.sha256 | |
| if-no-files-found: error | |
| # 0.4.3: separate `flirt-sigs.tar.gz` artifact shipped alongside the | |
| # CLI binaries. The `flirt-sigs/` directory in the repo is ~70 MB | |
| # raw (~25-35 MB compressed) — too big for crates.io, but a | |
| # one-time download for CLI users. Single tarball is platform- | |
| # independent; users extract once and point `--signatures` at the | |
| # extracted path. | |
| package-flirt-sigs: | |
| name: package flirt-sigs tarball | |
| needs: verify | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Determine tag | |
| id: tag | |
| run: | | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| echo "tag=${{ github.event.inputs.tag }}" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "tag=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Build flirt-sigs tarball | |
| run: | | |
| tag="${{ steps.tag.outputs.tag }}" | |
| mkdir -p release | |
| if [ ! -d flirt-sigs ]; then | |
| echo "flirt-sigs/ directory missing from checkout — refusing to ship empty bundle" | |
| exit 1 | |
| fi | |
| # Tar from the parent so the archive extracts to a clean | |
| # `flirt-sigs/` directory at the user's chosen path. | |
| tar -czf "release/flirt-sigs-${tag}.tar.gz" flirt-sigs/ | |
| (cd release && sha256sum "flirt-sigs-${tag}.tar.gz" > "flirt-sigs-${tag}.tar.gz.sha256") | |
| ls -lh release | |
| - name: Upload flirt-sigs artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: flirt-sigs | |
| path: | | |
| release/flirt-sigs-*.tar.gz | |
| release/flirt-sigs-*.sha256 | |
| if-no-files-found: error | |
| build-cli: | |
| name: build capa_cli — ${{ matrix.target }} | |
| needs: verify | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - target: x86_64-unknown-linux-gnu | |
| os: ubuntu-latest | |
| cross: false | |
| - target: aarch64-unknown-linux-gnu | |
| os: ubuntu-latest | |
| cross: true | |
| - target: x86_64-apple-darwin | |
| os: macos-latest | |
| cross: false | |
| - target: aarch64-apple-darwin | |
| os: macos-latest | |
| cross: false | |
| - target: x86_64-pc-windows-msvc | |
| os: windows-latest | |
| cross: false | |
| - target: aarch64-pc-windows-msvc | |
| os: windows-latest | |
| cross: false | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install Rust + target | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.target }} | |
| - uses: Swatinem/rust-cache@v2 | |
| with: | |
| key: ${{ matrix.target }} | |
| # Linux aarch64 needs a cross linker / sysroot. The simplest way that | |
| # doesn't pull in `cross` (and Docker-in-Docker on CI) is the apt | |
| # gcc-aarch64-linux-gnu toolchain + a cargo config that uses it as | |
| # the linker for the target. | |
| - name: Install aarch64-linux cross toolchain | |
| if: matrix.target == 'aarch64-unknown-linux-gnu' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y gcc-aarch64-linux-gnu | |
| mkdir -p .cargo | |
| cat >> .cargo/config.toml <<'EOF' | |
| [target.aarch64-unknown-linux-gnu] | |
| linker = "aarch64-linux-gnu-gcc" | |
| EOF | |
| - name: Determine tag | |
| id: tag | |
| shell: bash | |
| run: | | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| echo "tag=${{ github.event.inputs.tag }}" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "tag=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Build capa_cli (release) | |
| run: cargo build --release --example capa_cli --target ${{ matrix.target }} --all-features | |
| - name: Stage binary | |
| shell: bash | |
| run: | | |
| tag="${{ steps.tag.outputs.tag }}" | |
| target="${{ matrix.target }}" | |
| mkdir -p release | |
| # The example binary lives at target/<triple>/release/examples/<name>(.exe). | |
| if [[ "$target" == *windows* ]]; then | |
| src="target/${target}/release/examples/capa_cli.exe" | |
| dst="release/capa_cli-${tag}-${target}.exe" | |
| else | |
| src="target/${target}/release/examples/capa_cli" | |
| dst="release/capa_cli-${tag}-${target}" | |
| fi | |
| cp "$src" "$dst" | |
| # SHA-256 — `sha256sum` exists on Linux/macOS, `certutil` on Windows. | |
| if command -v sha256sum >/dev/null 2>&1; then | |
| (cd release && sha256sum "$(basename "$dst")" > "$(basename "$dst").sha256") | |
| else | |
| (cd release && certutil -hashfile "$(basename "$dst")" SHA256 | \ | |
| grep -v ':' | head -1 | tr -d ' \r' > "$(basename "$dst").sha256") | |
| fi | |
| ls -la release | |
| - name: Upload binary | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: capa_cli-${{ matrix.target }} | |
| path: | | |
| release/capa_cli-* | |
| release/capa_cli-*.sha256 | |
| if-no-files-found: error | |
| github-release: | |
| name: create GitHub release | |
| needs: [package-source, package-flirt-sigs, build-cli] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Determine tag | |
| id: tag | |
| run: | | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| echo "tag=${{ github.event.inputs.tag }}" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "tag=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Generate release notes | |
| id: notes | |
| run: | | |
| tag="${{ steps.tag.outputs.tag }}" | |
| previous=$(git describe --tags --abbrev=0 "${tag}^" 2>/dev/null || echo "") | |
| { | |
| echo "notes<<EOF" | |
| if [ -n "$previous" ]; then | |
| echo "## Changes since $previous" | |
| echo | |
| git log --pretty=format:"- %s (%h)" "${previous}..${tag}" | |
| else | |
| echo "## Initial release" | |
| echo | |
| git log --pretty=format:"- %s (%h)" "${tag}" | |
| fi | |
| echo | |
| echo | |
| echo "## Source" | |
| echo | |
| echo "- \`capa-${tag}.crate\` — the cargo package archive (same bytes as crates.io)" | |
| echo "- \`capa-${tag}-src.tar.gz\` — plain git source tarball" | |
| echo | |
| echo "## CLI binaries" | |
| echo | |
| echo "Pre-built \`capa_cli\` binaries for each supported platform:" | |
| echo | |
| echo "| Platform | Binary |" | |
| echo "|---------------------------|----------------------------------------------------|" | |
| echo "| Linux x86_64 | \`capa_cli-${tag}-x86_64-unknown-linux-gnu\` |" | |
| echo "| Linux aarch64 | \`capa_cli-${tag}-aarch64-unknown-linux-gnu\` |" | |
| echo "| macOS Intel (x86_64) | \`capa_cli-${tag}-x86_64-apple-darwin\` |" | |
| echo "| macOS Apple Silicon | \`capa_cli-${tag}-aarch64-apple-darwin\` |" | |
| echo "| Windows x86_64 | \`capa_cli-${tag}-x86_64-pc-windows-msvc.exe\` |" | |
| echo "| Windows aarch64 | \`capa_cli-${tag}-aarch64-pc-windows-msvc.exe\` |" | |
| echo | |
| echo "Every artifact ships with a matching \`*.sha256\` checksum." | |
| echo | |
| echo "## FLIRT signatures (optional, for library-function recognition)" | |
| echo | |
| echo "- \`flirt-sigs-${tag}.tar.gz\` — Mandiant FLARE + Maktm FLIRTDB \`.sig\` corpora (~25-35 MB compressed, ~70 MB extracted)" | |
| echo | |
| echo "Extract anywhere and pass \`--signatures /path/to/flirt-sigs\` to \`capa_cli\`. Optional — analysis works without it, but stripped MSVC binaries produce cleaner output when FLIRT is wired up. Signature content remains the work of Mandiant FLARE and Michael Kiros (Maktm); see \`flirt-sigs/README.md\` for licensing and credits." | |
| echo | |
| echo "Publishing to crates.io is performed manually with \`cargo publish\` and is not part of this workflow." | |
| echo EOF | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts | |
| - name: Flatten artifact tree | |
| run: | | |
| mkdir -p release | |
| # Each artifacts/<name>/ folder contains files we want at release/ top-level. | |
| find artifacts -type f \( -name 'capa-*' -o -name 'capa_cli-*' -o -name 'flirt-sigs-*' \) -exec cp {} release/ \; | |
| ls -la release | |
| - name: Create release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ steps.tag.outputs.tag }} | |
| name: ${{ steps.tag.outputs.tag }} | |
| body: ${{ steps.notes.outputs.notes }} | |
| files: release/* | |
| draft: false | |
| prerelease: ${{ contains(steps.tag.outputs.tag, '-') }} |