diff --git a/.cargo/config.toml b/.cargo/config.toml index 8d1e3890..ae406fda 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,3 +1,9 @@ [env] TMPDIR = "/tmp" + +[target.x86_64-unknown-linux-musl] +linker = "musl-gcc" + +[target.x86_64-unknown-linux-musl.openssl-sys] +rustc-link-lib = ["static=ssl", "static=crypto"] diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 9378a440..cc49eadd 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -1,10 +1,11 @@ -name: CD +name: Build & Release on: push: branches: - main workflow_dispatch: + pull_request: concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -20,6 +21,7 @@ env: GH_TOKEN: ${{ secrets.GH_TOKEN }} ACTIONS_RUNTIME_TOKEN: dummy CARGO_TERM_COLOR: always + RUST_CACHE_VERSION: 1 jobs: artifact: @@ -37,35 +39,128 @@ jobs: - os: ubuntu-latest target: x86_64-unknown-linux-musl steps: + - name: Check if should skip this matrix combination + id: check_skip + run: | + # Skip macOS targets when running in act (Linux containers) + if [[ -n "${ACT}" && "${{ matrix.os }}" == "macos-latest" ]]; then + echo "skip=true" >> $GITHUB_OUTPUT + echo "Skipping macOS target in act environment" + else + echo "skip=false" >> $GITHUB_OUTPUT + fi + - uses: actions/checkout@v4 + if: steps.check_skip.outputs.skip != 'true' + + - name: Install Node.js for act + if: steps.check_skip.outputs.skip != 'true' && env.ACT == 'true' + run: | + # Install Node.js manually in act container + curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - + sudo apt-get install -y nodejs + # Verify installation + node --version + npm --version + + - name: Cache cargo registry + if: steps.check_skip.outputs.skip != 'true' + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry/index + ~/.cargo/registry/cache + ~/.cargo/git/db + key: ${{ runner.os }}-${{ matrix.target }}-cargo-registry-${{ env.RUST_CACHE_VERSION }}-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.target }}-cargo-registry-${{ env.RUST_CACHE_VERSION }}- + ${{ runner.os }}-cargo-registry-${{ env.RUST_CACHE_VERSION }}- + + - name: Cache cargo target + if: steps.check_skip.outputs.skip != 'true' + uses: actions/cache@v4 + with: + path: target + key: ${{ runner.os }}-${{ matrix.target }}-cargo-target-${{ env.RUST_CACHE_VERSION }}-${{ hashFiles('**/Cargo.lock') }}-${{ hashFiles('**/*.rs') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.target }}-cargo-target-${{ env.RUST_CACHE_VERSION }}-${{ hashFiles('**/Cargo.lock') }}- + ${{ runner.os }}-${{ matrix.target }}-cargo-target-${{ env.RUST_CACHE_VERSION }}- + + - name: Cache Rust toolchain + if: steps.check_skip.outputs.skip != 'true' + uses: actions/cache@v4 + with: + path: | + ~/.rustup/toolchains + ~/.rustup/update-hashes + ~/.rustup/settings.toml + key: ${{ runner.os }}-rust-toolchain-nightly-${{ matrix.target }} + restore-keys: | + ${{ runner.os }}-rust-toolchain-nightly- + ${{ runner.os }}-rust-toolchain- + + - name: Install Node + if: steps.check_skip.outputs.skip != 'true' + uses: actions/setup-node@v4 - name: Setup Rust + if: steps.check_skip.outputs.skip != 'true' uses: dtolnay/rust-toolchain@nightly with: targets: ${{ matrix.target }} + - name: Check if running in act + id: check_act + if: steps.check_skip.outputs.skip != 'true' + run: | + if [[ -n "${ACT}" ]]; then + echo "running_in_act=true" >> $GITHUB_OUTPUT + else + echo "running_in_act=false" >> $GITHUB_OUTPUT + fi + + - name: Install node + uses: actions/setup-node@v4 + with: + node-version: 22 + - name: Add x86_64-unknown-linux-musl target - if: matrix.target == 'x86_64-unknown-linux-musl' + if: matrix.target == 'x86_64-unknown-linux-musl' && steps.check_act.outputs.running_in_act != 'true' && steps.check_skip.outputs.skip != 'true' run: | rustup target add x86_64-unknown-linux-musl sudo apt-get update && sudo apt-get install -y musl-tools - name: Install Dependencies for musl Target - if: matrix.target == 'x86_64-unknown-linux-musl' + if: matrix.target == 'x86_64-unknown-linux-musl' && steps.check_act.outputs.running_in_act != 'true' && steps.check_skip.outputs.skip != 'true' run: | sudo apt-get update - sudo apt-get install -y musl-tools musl-dev perl make pkg-config libssl-dev - # Set up environment for musl compilation + sudo apt-get install -y musl-tools musl-dev perl make pkg-config + # Set up environment for musl cross-compilation echo "CC_x86_64_unknown_linux_musl=musl-gcc" >> $GITHUB_ENV echo "CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER=musl-gcc" >> $GITHUB_ENV + # Configure OpenSSL build to avoid -m64 flag + echo "OPENSSL_STATIC=1" >> $GITHUB_ENV + echo "OPENSSL_LIB_DIR=/usr/lib/x86_64-linux-musl" >> $GITHUB_ENV + echo "OPENSSL_INCLUDE_DIR=/usr/include" >> $GITHUB_ENV + + - name: Skip musl build in act + if: matrix.target == 'x86_64-unknown-linux-musl' && steps.check_act.outputs.running_in_act == 'true' && steps.check_skip.outputs.skip != 'true' + run: | + echo "Skipping musl build in act due to cross-compilation issues" + mkdir -p bin + echo '#!/bin/sh' > bin/git-ai + echo 'echo "Placeholder for musl build in act"' >> bin/git-ai + chmod +x bin/git-ai + cp bin/git-ai bin/git-ai-hook - name: Install Dependencies for Linux Target - if: matrix.target == 'x86_64-unknown-linux-gnu' + if: matrix.target == 'x86_64-unknown-linux-gnu' && steps.check_skip.outputs.skip != 'true' run: | sudo apt-get update sudo apt-get install -y pkg-config libssl-dev - name: Build for target + if: ${{ !(matrix.target == 'x86_64-unknown-linux-musl' && steps.check_act.outputs.running_in_act == 'true') && steps.check_skip.outputs.skip != 'true' }} run: | cargo build \ -Z unstable-options \ @@ -73,76 +168,159 @@ jobs: --artifact-dir bin \ --target ${{ matrix.target }} - - name: Upload and compress artifacts + - name: Package artifacts + if: steps.check_skip.outputs.skip != 'true' + run: | + mkdir -p dist + if [[ "${{ matrix.target }}" == *"-windows-"* ]]; then + cp bin/git-ai.exe dist/ + cd dist + zip -r ../git-ai-${{ matrix.target }}.zip . + cd .. + else + cp bin/git-ai dist/ + cd dist + tar -czf ../git-ai-${{ matrix.target }}.tar.gz . + cd .. + fi + + - name: Upload packaged artifacts + if: steps.check_skip.outputs.skip != 'true' + uses: actions/upload-artifact@v4 + with: + name: git-ai-${{ matrix.target }}-package + if-no-files-found: error + path: git-ai-${{ matrix.target }}.* + + - name: Upload raw artifacts + if: steps.check_skip.outputs.skip != 'true' uses: actions/upload-artifact@v4 with: - name: git-ai-${{ matrix.target }} + name: git-ai-${{ matrix.target }}-raw if-no-files-found: error path: bin/git-* release: - runs-on: ubuntu-latest + runs-on: macos-latest needs: artifact steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: main - - name: Setup Rust - uses: dtolnay/rust-toolchain@nightly - - - name: Configure git user name - run: git config user.name ${{ github.actor }} + - name: Cache cargo registry + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry/index + ~/.cargo/registry/cache + ~/.cargo/git/db + key: ${{ runner.os }}-cargo-registry-${{ env.RUST_CACHE_VERSION }}-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-registry-${{ env.RUST_CACHE_VERSION }}- - - name: Configure git email - run: git config user.email ${{ github.actor }}@users.noreply.github.com + - name: Cache cargo target + uses: actions/cache@v4 + with: + path: target + key: ${{ runner.os }}-cargo-target-release-${{ env.RUST_CACHE_VERSION }}-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-target-release-${{ env.RUST_CACHE_VERSION }}- - - name: Install cargo-bump - run: cargo install cargo-bump + - name: Cache cargo-binstall + uses: actions/cache@v4 + with: + path: ~/.cargo/bin/cargo-binstall + key: ${{ runner.os }}-cargo-binstall-${{ env.RUST_CACHE_VERSION }} - - name: Bump version - run: cargo bump patch --git-tag + - uses: dtolnay/rust-toolchain@stable - - name: Commit Version Bump - run: git commit -a --amend --no-edit + - name: Install cargo-binstall + uses: taiki-e/install-action@v2 + with: + tool: cargo-binstall - - name: New version - id: app - run: echo "version=$(git describe --tags --abbrev=0 HEAD)" >> $GITHUB_OUTPUT + - name: Install cargo-release + run: cargo binstall -y cargo-release - - name: Delete old tag - run: git tag -d ${{ steps.app.outputs.version }} + - name: Git config + run: | + git config user.name ${{ github.actor }} + git config user.email ${{ github.actor }}@users.noreply.github.com - - name: Create new tag - run: git tag v${{ steps.app.outputs.version }} + - name: Checkout main + run: git checkout main - - name: Publish to crates.io - if: github.ref == 'refs/heads/main' - run: cargo publish --allow-dirty + - name: Build & publish + run: cargo release minor --no-confirm --execute --allow-branch main - - name: Test publish to crates.io (dry run) - if: github.ref != 'refs/heads/main' - run: cargo publish --dry-run --allow-dirty + - name: Create temporary directory for artifacts + run: mkdir -p ${{ runner.temp }}/artifacts - - name: Push to origin - if: github.ref == 'refs/heads/main' - run: git push origin HEAD --tags + - name: Download all packaged artifacts + uses: actions/download-artifact@v4 + with: + pattern: git-ai-*-package + path: ${{ runner.temp }}/artifacts + merge-multiple: true - - name: Test push to origin (dry run) - if: github.ref != 'refs/heads/main' - run: git push origin HEAD --tags --dry-run + - name: Move artifacts to packages directory + run: | + mkdir -p packages + # Move all artifacts from temp to packages directory + mv ${{ runner.temp }}/artifacts/* packages/ - - name: Download all artifacts - run: gh run download ${{ github.run_id }} + - name: Get version + id: app + run: echo "version=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[0].version')" >> $GITHUB_OUTPUT - - name: Zip each downloaded directory + - name: Generate cargo-binstall metadata.json run: | - for dir in $(ls -d git-ai-*); do - tar -czf ${dir}.tar.gz ${dir} - done + cat > metadata.json << EOF + { + "name": "git-ai", + "version": "${{ steps.app.outputs.version }}", + "package": { + "targets": [ + { + "name": "git-ai", + "bins": ["git-ai"] + } + ], + "platforms": { + "x86_64-unknown-linux-gnu": { + "bins": { + "git-ai": "https://github.com/${{ github.repository }}/releases/download/v${{ steps.app.outputs.version }}/git-ai-x86_64-unknown-linux-gnu.tar.gz" + } + }, + "x86_64-unknown-linux-musl": { + "bins": { + "git-ai": "https://github.com/${{ github.repository }}/releases/download/v${{ steps.app.outputs.version }}/git-ai-x86_64-unknown-linux-musl.tar.gz" + } + }, + "x86_64-apple-darwin": { + "bins": { + "git-ai": "https://github.com/${{ github.repository }}/releases/download/v${{ steps.app.outputs.version }}/git-ai-x86_64-apple-darwin.tar.gz" + } + }, + "aarch64-apple-darwin": { + "bins": { + "git-ai": "https://github.com/${{ github.repository }}/releases/download/v${{ steps.app.outputs.version }}/git-ai-aarch64-apple-darwin.tar.gz" + } + } + } + } + } + EOF - - name: Uploads compressed artifacts - if: github.ref == 'refs/heads/main' + - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: tag_name: v${{ steps.app.outputs.version }} - fail_on_unmatched_files: true - files: git-ai-*.tar.gz + fail_on_unmatched_files: false + files: | + packages/git-ai-*.tar.gz + packages/git-ai-*.zip + metadata.json + generate_release_notes: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 944b5dab..8a0dfb13 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,8 +1,9 @@ name: CI on: - pull_request: - types: [opened, synchronize, reopened] + workflow_dispatch: + # pull_request: + # types: [opened, synchronize, reopened] concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/.github/workflows/test-event.json b/.github/workflows/test-event.json new file mode 100644 index 00000000..46895ae8 --- /dev/null +++ b/.github/workflows/test-event.json @@ -0,0 +1 @@ +{"ref":"refs/tags/v0.0.0-test"} diff --git a/Cargo.lock b/Cargo.lock index 3c186a9a..31672d81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -674,7 +674,7 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "git-ai" -version = "1.0.7" +version = "1.3.0" dependencies = [ "anyhow", "async-openai", diff --git a/Cargo.toml b/Cargo.toml index 08a09b90..c9b2dcfa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "git-ai" -version = "1.0.8" +version = "1.3.0" edition = "2021" description = "Git AI: Automates commit messages using ChatGPT. Stage your files, and Git AI generates the messages." license = "MIT" diff --git a/docs/LOCAL_TESTING.md b/docs/LOCAL_TESTING.md new file mode 100644 index 00000000..ace7bc7b --- /dev/null +++ b/docs/LOCAL_TESTING.md @@ -0,0 +1,96 @@ +# Local Testing with Act + +## Overview + +This document explains how to test GitHub Actions workflows locally using `act`. + +## Running Specific Matrix Combinations + +### Option 1: Using act filters (Recommended) + +To run only specific matrix combinations locally, use act's job filtering: + +```bash +# Run only the macOS x86_64 build +act -j artifact -W .github/workflows/cd.yml --matrix os:macos-latest --matrix target:x86_64-apple-darwin + +# Run only the Linux GNU build +act -j artifact -W .github/workflows/cd.yml --matrix os:ubuntu-latest --matrix target:x86_64-unknown-linux-gnu +``` + +### Option 2: Create a custom event file + +Create a file `.act.json` with specific matrix values: + +```json +{ + "workflow_dispatch": { + "inputs": { + "matrix_filter": "macos" + } + } +} +``` + +Then run: +```bash +act workflow_dispatch -e .act.json +``` + +## Known Issues + +### Cross-compilation for musl targets + +When running workflows locally with `act` on macOS, cross-compilation for `x86_64-unknown-linux-musl` targets will fail. This is because: + +1. `act` runs Linux containers on macOS +2. The build process tries to compile OpenSSL from source for musl +3. The OpenSSL build system incorrectly uses `-m64` flag with musl-gcc, which doesn't support it + +### Cross-compilation for macOS targets + +When running in `act` (Linux containers), you cannot build macOS targets (`x86_64-apple-darwin`, `aarch64-apple-darwin`) because: + +1. `act` runs in Linux containers +2. Cross-compiling from Linux to macOS requires Apple's SDK and toolchain +3. The compiler flags like `-arch` and `-mmacosx-version-min` are not recognized by Linux gcc + +### Solutions + +#### 1. Run only Linux targets locally + +The safest approach is to only test Linux targets locally: + +```bash +# Test Linux GNU target +act -j artifact -W .github/workflows/cd.yml --matrix os:ubuntu-latest --matrix target:x86_64-unknown-linux-gnu +``` + +#### 2. Skip problematic builds + +The main workflow automatically detects when running in `act` and skips musl builds. It will create placeholder binaries instead. + +#### 3. Test on real GitHub Actions + +Push your changes to a branch and let the real GitHub Actions runners handle the cross-compilation: + +```bash +git push origin feature/your-branch +``` + +## Configuration + +The `.actrc` file is configured to: +- Use appropriate container images +- Set up caching +- Enable the `ACT` environment variable for detection + +## Troubleshooting + +If you encounter issues: + +1. Ensure Docker is running +2. Update act to the latest version: `brew upgrade act` +3. Clear the cache: `rm -rf tmp/cache` +4. Check the `.actrc` configuration +5. Use `-v` flag for verbose output: `act -v` diff --git a/scripts/act-local.sh b/scripts/act-local.sh new file mode 100755 index 00000000..f5195ef0 --- /dev/null +++ b/scripts/act-local.sh @@ -0,0 +1,49 @@ +#!/bin/bash +# Helper script to run specific matrix combinations with act + +set -e + +# Default to Linux GNU target +TARGET="${1:-linux-gnu}" + +case "$TARGET" in + "linux-gnu") + echo "Running Linux GNU build..." + act -j artifact -W .github/workflows/cd.yml \ + --matrix os:ubuntu-latest \ + --matrix target:x86_64-unknown-linux-gnu + ;; + "linux-musl") + echo "Running Linux musl build (will be skipped due to cross-compilation issues)..." + act -j artifact -W .github/workflows/cd.yml \ + --matrix os:ubuntu-latest \ + --matrix target:x86_64-unknown-linux-musl + ;; + "macos-x86") + echo "Running macOS x86_64 build (will be skipped in act)..." + act -j artifact -W .github/workflows/cd.yml \ + --matrix os:macos-latest \ + --matrix target:x86_64-apple-darwin + ;; + "macos-arm") + echo "Running macOS ARM64 build (will be skipped in act)..." + act -j artifact -W .github/workflows/cd.yml \ + --matrix os:macos-latest \ + --matrix target:aarch64-apple-darwin + ;; + "all") + echo "Running all builds (macOS targets will be skipped)..." + act -j artifact -W .github/workflows/cd.yml + ;; + *) + echo "Usage: $0 [linux-gnu|linux-musl|macos-x86|macos-arm|all]" + echo "" + echo "Available targets:" + echo " linux-gnu - Build for x86_64-unknown-linux-gnu (works in act)" + echo " linux-musl - Build for x86_64-unknown-linux-musl (skipped in act)" + echo " macos-x86 - Build for x86_64-apple-darwin (skipped in act)" + echo " macos-arm - Build for aarch64-apple-darwin (skipped in act)" + echo " all - Run all builds (only Linux GNU will actually build)" + exit 1 + ;; +esac diff --git a/scripts/comprehensive-tests b/scripts/comprehensive-tests old mode 100755 new mode 100644 diff --git a/scripts/current-version b/scripts/current-version old mode 100755 new mode 100644 diff --git a/scripts/hook-stress-test b/scripts/hook-stress-test old mode 100755 new mode 100644 diff --git a/scripts/integration-tests b/scripts/integration-tests old mode 100755 new mode 100644