From 7c6cb01d7da21862a41dfbf634f4fc7cc653c7aa Mon Sep 17 00:00:00 2001 From: Gavin Peacock Date: Tue, 8 Jul 2025 10:00:57 -0700 Subject: [PATCH] feat: add automated release workflows and improve build system - Add unified release workflow with automatic and manual triggers - Update c2pa dependency management workflow with testing - Improve Makefile with separate debug/release build directories - Fix test file conflicts to enable repeated test runs - Update README documentation for new build targets - Move workflow files to proper .github/workflows directory This implements automatic semantic versioning, dependency updates, and comprehensive testing before any releases. --- .github/workflows/release.yml | 161 ++++++++++++++++++++++++ .github/{ => workflows}/update_c2pa.yml | 20 +++ Makefile | 50 ++++++-- README.md | 13 +- tests/test.c | 14 ++- 5 files changed, 240 insertions(+), 18 deletions(-) create mode 100644 .github/workflows/release.yml rename .github/{ => workflows}/update_c2pa.yml (55%) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..0817d818 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,161 @@ +name: Release + +on: + push: + branches: [main] + workflow_dispatch: + inputs: + version_type: + description: 'Version bump type' + required: true + default: 'auto' + type: choice + options: + - auto + - patch + - minor + - major + custom_version: + description: 'Custom version (optional, overrides version_type)' + required: false + type: string + +jobs: + release: + runs-on: ubuntu-latest + if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Determine version bump + id: version + run: | + # Get current version from CMakeLists.txt + CURRENT_VERSION=$(grep "project(c2pa-c VERSION" CMakeLists.txt | sed -n 's/.*VERSION \([0-9.]*\).*/\1/p') + echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT + + # Determine bump type based on trigger + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + # Manual trigger + if [ -n "${{ github.event.inputs.custom_version }}" ]; then + NEW_VERSION="${{ github.event.inputs.custom_version }}" + BUMP="custom" + else + MANUAL_BUMP="${{ github.event.inputs.version_type }}" + if [ "$MANUAL_BUMP" = "auto" ]; then + # Auto-detect from recent commits + COMMITS=$(git log HEAD~5..HEAD --oneline) + if echo "$COMMITS" | grep -qE "^[a-f0-9]+ (feat!|BREAKING CHANGE)"; then + BUMP="major" + elif echo "$COMMITS" | grep -qE "^[a-f0-9]+ feat"; then + BUMP="minor" + elif echo "$COMMITS" | grep -qE "^[a-f0-9]+ (fix|chore|docs)"; then + BUMP="patch" + else + BUMP="patch" # Default for manual + fi + else + BUMP="$MANUAL_BUMP" + fi + fi + else + # Automatic trigger from push + LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0") + COMMITS=$(git log $LAST_TAG..HEAD --oneline) + + if echo "$COMMITS" | grep -qE "^[a-f0-9]+ (feat!|BREAKING CHANGE)"; then + BUMP="major" + elif echo "$COMMITS" | grep -qE "^[a-f0-9]+ feat"; then + BUMP="minor" + elif echo "$COMMITS" | grep -qE "^[a-f0-9]+ (fix|chore|docs)"; then + BUMP="patch" + else + echo "No version bump needed" + exit 0 + fi + fi + + # Calculate new version if not custom + if [ "$BUMP" != "custom" ]; then + IFS='.' read -r major minor patch <<< "$CURRENT_VERSION" + case $BUMP in + major) NEW_VERSION="$((major + 1)).0.0" ;; + minor) NEW_VERSION="$major.$((minor + 1)).0" ;; + patch) NEW_VERSION="$major.$minor.$((patch + 1))" ;; + esac + fi + + echo "bump_type=$BUMP" >> $GITHUB_OUTPUT + echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT + echo "Version bump: $CURRENT_VERSION -> $NEW_VERSION ($BUMP)" + + - name: Update version in CMakeLists.txt + if: steps.version.outputs.new_version != '' + run: | + NEW_VERSION=${{ steps.version.outputs.new_version }} + sed -i "s/project(c2pa-c VERSION [0-9.]*)/project(c2pa-c VERSION $NEW_VERSION)/" CMakeLists.txt + echo "Updated version to $NEW_VERSION" + + - name: Install dependencies + if: steps.version.outputs.new_version != '' + run: | + sudo apt-get update + sudo apt-get install -y ninja-build + + - name: Run tests + if: steps.version.outputs.new_version != '' + run: | + echo "Running tests before release..." + make test # Debug build and test + make test-release # Release build and test + echo "✅ All tests passed!" + + - name: Commit version bump + if: steps.version.outputs.new_version != '' + run: | + NEW_VERSION=${{ steps.version.outputs.new_version }} + + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git add CMakeLists.txt + git commit -m "chore(release): $NEW_VERSION [skip ci]" + git push + + - name: Create tag and release + if: steps.version.outputs.new_version != '' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + NEW_VERSION=${{ steps.version.outputs.new_version }} + + # Create and push tag + git tag -a "v$NEW_VERSION" -m "Release v$NEW_VERSION" + git push origin "v$NEW_VERSION" + + # Generate release notes + LAST_TAG=$(git describe --tags --abbrev=0 HEAD~1 2>/dev/null || echo "") + if [ -n "$LAST_TAG" ]; then + COMMITS=$(git log $LAST_TAG..HEAD --oneline --no-merges) + COMPARE_URL="https://github.com/${{ github.repository }}/compare/$LAST_TAG...v$NEW_VERSION" + else + COMMITS=$(git log --oneline --no-merges -10) + COMPARE_URL="https://github.com/${{ github.repository }}/commits/main" + fi + + # Create release + cat > release_notes.md << EOF + ## What's Changed + + $COMMITS + + **Full Changelog**: $COMPARE_URL + EOF + + gh release create "v$NEW_VERSION" \ + --title "Release v$NEW_VERSION" \ + --notes-file release_notes.md diff --git a/.github/update_c2pa.yml b/.github/workflows/update_c2pa.yml similarity index 55% rename from .github/update_c2pa.yml rename to .github/workflows/update_c2pa.yml index f79d2030..9cca8ff7 100644 --- a/.github/update_c2pa.yml +++ b/.github/workflows/update_c2pa.yml @@ -18,6 +18,22 @@ jobs: run: | version="${{ github.event.inputs.c2pa_version }}" sed -i "s/set(C2PA_VERSION \".*\")/set(C2PA_VERSION \"${version}\")/" CMakeLists.txt + echo "Updated C2PA_VERSION to $version" + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y ninja-build + + - name: Test with new c2pa-rs version + run: | + echo "Testing with c2pa-rs version ${{ github.event.inputs.c2pa_version }}..." + + # Test using our Makefile targets + make test # Debug build and test + make test-release # Release build and test + + echo "✅ All tests passed with c2pa-rs ${{ github.event.inputs.c2pa_version }}" - name: Create Pull Request uses: peter-evans/create-pull-request@v6 @@ -27,3 +43,7 @@ jobs: title: "Bump C2PA_VERSION to ${{ github.event.inputs.c2pa_version }}" body: | This PR updates C2PA_VERSION in CMakeLists.txt to ${{ github.event.inputs.c2pa_version }}. + + ✅ **Tests Status**: All tests (debug and release builds) have passed with the new c2pa-rs version. + + The new version should be compatible and ready for integration. diff --git a/Makefile b/Makefile index ea2c36d1..8fc5f9e6 100644 --- a/Makefile +++ b/Makefile @@ -1,21 +1,45 @@ OS := $(shell uname) BUILD_DIR = build +DEBUG_BUILD_DIR = build/debug +RELEASE_BUILD_DIR = build/release +# Default debug build cmake: - cmake -S . -B $(BUILD_DIR) -G "Ninja" - cmake --build $(BUILD_DIR) + cmake -S . -B $(DEBUG_BUILD_DIR) -G "Ninja" -DCMAKE_BUILD_TYPE=Debug + cmake --build $(DEBUG_BUILD_DIR) -test: cmake - cd $(BUILD_DIR) && ctest --output-on-failure +# Debug build (explicit) +debug: + cmake -S . -B $(DEBUG_BUILD_DIR) -G "Ninja" -DCMAKE_BUILD_TYPE=Debug + cmake --build $(DEBUG_BUILD_DIR) -demo: cmake - cmake --build $(BUILD_DIR) --target demo - $(BUILD_DIR)/examples/demo +# Release build with separate directory +release: + cmake -S . -B $(RELEASE_BUILD_DIR) -G "Ninja" -DCMAKE_BUILD_TYPE=Release + cmake --build $(RELEASE_BUILD_DIR) -training: cmake - cmake --build $(BUILD_DIR) --target training - $(BUILD_DIR)/examples/training +test: debug + cd $(DEBUG_BUILD_DIR) && ctest --output-on-failure + +test-release: release + cd $(RELEASE_BUILD_DIR) && ctest --output-on-failure + +demo: debug + cmake --build $(DEBUG_BUILD_DIR) --target demo + $(DEBUG_BUILD_DIR)/examples/demo + +demo-release: release + cmake --build $(RELEASE_BUILD_DIR) --target demo + $(RELEASE_BUILD_DIR)/examples/demo + +training: debug + cmake --build $(DEBUG_BUILD_DIR) --target training + $(DEBUG_BUILD_DIR)/examples/training + +training-release: release + cmake --build $(RELEASE_BUILD_DIR) --target training + $(RELEASE_BUILD_DIR)/examples/training examples: training demo @@ -24,3 +48,9 @@ all: test examples clean: rm -rf $(BUILD_DIR) +clean-debug: + rm -rf $(DEBUG_BUILD_DIR) + +clean-release: + rm -rf $(RELEASE_BUILD_DIR) + diff --git a/README.md b/README.md index 33b0080c..dbc94133 100644 --- a/README.md +++ b/README.md @@ -61,18 +61,21 @@ make release ``` The Makefile has a number of other targets; for example: -- `unit-tests` to run C++ unit tests -- `examples` to build and run the C++ examples. -- `all` to run everything. +- `debug` to build in debug mode +- `release` to build in optimized release mode +- `test` to run all tests (debug build) +- `test-release` to run all tests (release build) +- `examples` to build and run the C++ examples +- `all` to run tests and examples -Results are saved in the `build` directory. +Results are saved in the `build/debug` and `build/release` directories. ### Testing Build the [unit tests](https://github.com/contentauth/c2pa-c/tree/main/tests) by entering this `make` command: ``` -make unit-test +make test ``` ## License diff --git a/tests/test.c b/tests/test.c index 603da113..dfa163ab 100644 --- a/tests/test.c +++ b/tests/test.c @@ -20,6 +20,14 @@ int main(void) { + // Clean up any existing test output files + remove("build/tmp/earth1.jpg"); + remove("build/tmp/earth2.jpg"); + remove("build/tmp/earth3.jpg"); + remove("build/tmp/earth1.pem"); + remove("build/tmp/archive.zip"); + remove("build/thumb_c.jpg"); + char *version = c2pa_version(); assert_contains("version", version, "c_api/0."); @@ -71,10 +79,10 @@ int main(void) // create a sign_info struct C2paSignerInfo sign_info = {.alg = "es256", .sign_cert = certs, .private_key = private_key, .ta_url = "http://timestamp.digicert.com"}; - result = c2pa_sign_file("tests/fixtures/C.jpg", "build/tmp/earth.jpg", manifest, &sign_info, "tests/fixtures"); + result = c2pa_sign_file("tests/fixtures/C.jpg", "build/tmp/earth1.jpg", manifest, &sign_info, "tests/fixtures"); assert_not_null("c2pa_sign_file_ok", result); - result = c2pa_sign_file("tests/fixtures/foo.jpg", "build/tmp/earth.jpg", manifest, &sign_info, "tests/fixtures"); + result = c2pa_sign_file("tests/fixtures/foo.jpg", "build/tmp/earth2.jpg", manifest, &sign_info, "tests/fixtures"); assert_null("c2pa_sign_file_not_found", result, "FileNotFound"); result = c2pa_sign_file("tests/fixtures/es256_certs.pem", "build/tmp/earth1.pem", manifest, &sign_info, "tests/fixtures"); @@ -97,7 +105,7 @@ int main(void) assert_not_null("c2pa_signer_create", signer); C2paStream *source = open_file_stream("tests/fixtures/C.jpg", "rb"); - C2paStream *dest = open_file_stream("build/tmp/earth.jpg", "wb"); + C2paStream *dest = open_file_stream("build/tmp/earth3.jpg", "wb"); const unsigned char *manifest_bytes = NULL; // todo: test passing NULL instead of a pointer int result2 = c2pa_builder_sign(builder2, "image/jpeg", source, dest, signer, &manifest_bytes);