mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-04-06 00:32:05 +02:00
WIP: auto update
This commit is contained in:
248
.github/workflows/build_all.yml
vendored
248
.github/workflows/build_all.yml
vendored
@@ -5,6 +5,7 @@ on:
|
||||
branches:
|
||||
- main
|
||||
- release/*
|
||||
- feature/auto-update # TODO: Remove after auto-update testing is complete
|
||||
paths:
|
||||
- 'deps/**'
|
||||
- 'src/**'
|
||||
@@ -48,25 +49,28 @@ concurrency:
|
||||
|
||||
|
||||
jobs:
|
||||
build_linux: # Separate so unit tests can wait on just Linux builds to complete.
|
||||
name: Build Linux
|
||||
strategy:
|
||||
fail-fast: false
|
||||
# Don't run scheduled builds on forks:
|
||||
if: ${{ !cancelled() && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
|
||||
uses: ./.github/workflows/build_deps.yml
|
||||
with:
|
||||
os: ubuntu-24.04
|
||||
build-deps-only: ${{ inputs.build-deps-only || false }}
|
||||
force-build: ${{ github.event_name == 'schedule' }}
|
||||
secrets: inherit
|
||||
# TODO: Re-enable after auto-update testing is complete
|
||||
# build_linux: # Separate so unit tests can wait on just Linux builds to complete.
|
||||
# name: Build Linux
|
||||
# strategy:
|
||||
# fail-fast: false
|
||||
# # Don't run scheduled builds on forks:
|
||||
# if: ${{ !cancelled() && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
|
||||
# uses: ./.github/workflows/build_deps.yml
|
||||
# with:
|
||||
# os: ubuntu-24.04
|
||||
# build-deps-only: ${{ inputs.build-deps-only || false }}
|
||||
# force-build: ${{ github.event_name == 'schedule' }}
|
||||
# secrets: inherit
|
||||
|
||||
build_all:
|
||||
name: Build Non-Linux
|
||||
name: Build macOS (testing auto-update)
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: windows-latest
|
||||
# TODO: Re-enable Windows after auto-update testing is complete
|
||||
# - os: windows-latest
|
||||
- os: macos-14
|
||||
arch: arm64
|
||||
# Don't run scheduled builds on forks:
|
||||
@@ -78,109 +82,113 @@ jobs:
|
||||
build-deps-only: ${{ inputs.build-deps-only || false }}
|
||||
force-build: ${{ github.event_name == 'schedule' }}
|
||||
secrets: inherit
|
||||
unit_tests:
|
||||
name: Unit Tests
|
||||
runs-on: ubuntu-24.04
|
||||
needs: build_linux
|
||||
if: ${{ !cancelled() && success() }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
sparse-checkout: |
|
||||
.github
|
||||
scripts
|
||||
tests
|
||||
- name: Apt-Install Dependencies
|
||||
uses: ./.github/actions/apt-install-deps
|
||||
- name: Restore Test Artifact
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: ${{ github.sha }}-tests
|
||||
- uses: lukka/get-cmake@latest
|
||||
with:
|
||||
cmakeVersion: "~3.28.0" # use most recent 3.28.x version
|
||||
- name: Unpackage and Run Unit Tests
|
||||
timeout-minutes: 20
|
||||
run: |
|
||||
tar -xvf build_tests.tar
|
||||
scripts/run_unit_tests.sh
|
||||
- name: Upload Test Logs
|
||||
uses: actions/upload-artifact@v5
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
name: unit-test-logs
|
||||
path: build/tests/**/*.log
|
||||
- name: Publish Test Results
|
||||
if: always()
|
||||
uses: EnricoMi/publish-unit-test-result-action/linux@v2
|
||||
with:
|
||||
files: "ctest_results.xml"
|
||||
flatpak:
|
||||
name: "Flatpak"
|
||||
container:
|
||||
image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-48
|
||||
options: --privileged
|
||||
volumes:
|
||||
- /usr/local/lib/android:/usr/local/lib/android
|
||||
- /usr/share/dotnet:/usr/share/dotnet
|
||||
- /opt/ghc:/opt/ghc1
|
||||
- /usr/local/share/boost:/usr/local/share/boost1
|
||||
- /opt/hostedtoolcache:/opt/hostedtoolcache1
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
variant:
|
||||
- arch: x86_64
|
||||
runner: ubuntu-24.04
|
||||
- arch: aarch64
|
||||
runner: ubuntu-24.04-arm
|
||||
# Don't run scheduled builds on forks:
|
||||
if: ${{ !cancelled() && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
|
||||
runs-on: ${{ matrix.variant.runner }}
|
||||
env:
|
||||
date:
|
||||
ver:
|
||||
ver_pure:
|
||||
steps:
|
||||
- name: "Remove unneeded stuff to free disk space"
|
||||
run:
|
||||
rm -rf /usr/local/lib/android/* /usr/share/dotnet/* /opt/ghc1/* "/usr/local/share/boost1/*" /opt/hostedtoolcache1/*
|
||||
- uses: actions/checkout@v6
|
||||
- name: Get the version and date
|
||||
run: |
|
||||
ver_pure=$(grep 'set(SoftFever_VERSION' version.inc | cut -d '"' -f2)
|
||||
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||
ver="PR-${{ github.event.number }}"
|
||||
git_commit_hash="${{ github.event.pull_request.head.sha }}"
|
||||
else
|
||||
ver=V$ver_pure
|
||||
git_commit_hash=""
|
||||
fi
|
||||
echo "ver=$ver" >> $GITHUB_ENV
|
||||
echo "ver_pure=$ver_pure" >> $GITHUB_ENV
|
||||
echo "date=$(date +'%Y%m%d')" >> $GITHUB_ENV
|
||||
echo "git_commit_hash=$git_commit_hash" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
- uses: flatpak/flatpak-github-actions/flatpak-builder@master
|
||||
with:
|
||||
bundle: OrcaSlicer-Linux-flatpak_${{ env.ver }}_${{ matrix.variant.arch }}.flatpak
|
||||
manifest-path: scripts/flatpak/io.github.softfever.OrcaSlicer.yml
|
||||
cache: true
|
||||
arch: ${{ matrix.variant.arch }}
|
||||
upload-artifact: false
|
||||
- name: Upload artifacts Flatpak
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: OrcaSlicer-Linux-flatpak_${{ env.ver }}_${{ matrix.variant.arch }}.flatpak
|
||||
path: '/__w/OrcaSlicer/OrcaSlicer/OrcaSlicer-Linux-flatpak_${{ env.ver }}_${{ matrix.variant.arch }}.flatpak'
|
||||
- name: Deploy Flatpak to nightly release
|
||||
if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main'
|
||||
uses: WebFreak001/deploy-nightly@v3.2.0
|
||||
with:
|
||||
upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
|
||||
release_id: 137995723
|
||||
asset_path: /__w/OrcaSlicer/OrcaSlicer/OrcaSlicer-Linux-flatpak_${{ env.ver }}_${{ matrix.variant.arch }}.flatpak
|
||||
asset_name: OrcaSlicer-Linux-flatpak_nightly_${{ matrix.variant.arch }}.flatpak
|
||||
asset_content_type: application/octet-stream
|
||||
max_releases: 1 # optional, if there are more releases than this matching the asset_name, the oldest ones are going to be deleted
|
||||
|
||||
# TODO: Re-enable after auto-update testing is complete (depends on build_linux)
|
||||
# unit_tests:
|
||||
# name: Unit Tests
|
||||
# runs-on: ubuntu-24.04
|
||||
# needs: build_linux
|
||||
# if: ${{ !cancelled() && success() }}
|
||||
# steps:
|
||||
# - name: Checkout
|
||||
# uses: actions/checkout@v6
|
||||
# with:
|
||||
# sparse-checkout: |
|
||||
# .github
|
||||
# scripts
|
||||
# tests
|
||||
# - name: Apt-Install Dependencies
|
||||
# uses: ./.github/actions/apt-install-deps
|
||||
# - name: Restore Test Artifact
|
||||
# uses: actions/download-artifact@v6
|
||||
# with:
|
||||
# name: ${{ github.sha }}-tests
|
||||
# - uses: lukka/get-cmake@latest
|
||||
# with:
|
||||
# cmakeVersion: "~3.28.0" # use most recent 3.28.x version
|
||||
# - name: Unpackage and Run Unit Tests
|
||||
# timeout-minutes: 20
|
||||
# run: |
|
||||
# tar -xvf build_tests.tar
|
||||
# scripts/run_unit_tests.sh
|
||||
# - name: Upload Test Logs
|
||||
# uses: actions/upload-artifact@v5
|
||||
# if: ${{ failure() }}
|
||||
# with:
|
||||
# name: unit-test-logs
|
||||
# path: build/tests/**/*.log
|
||||
# - name: Publish Test Results
|
||||
# if: always()
|
||||
# uses: EnricoMi/publish-unit-test-result-action/linux@v2
|
||||
# with:
|
||||
# files: "ctest_results.xml"
|
||||
|
||||
# TODO: Re-enable after auto-update testing is complete
|
||||
# flatpak:
|
||||
# name: "Flatpak"
|
||||
# container:
|
||||
# image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-48
|
||||
# options: --privileged
|
||||
# volumes:
|
||||
# - /usr/local/lib/android:/usr/local/lib/android
|
||||
# - /usr/share/dotnet:/usr/share/dotnet
|
||||
# - /opt/ghc:/opt/ghc1
|
||||
# - /usr/local/share/boost:/usr/local/share/boost1
|
||||
# - /opt/hostedtoolcache:/opt/hostedtoolcache1
|
||||
# strategy:
|
||||
# fail-fast: false
|
||||
# matrix:
|
||||
# variant:
|
||||
# - arch: x86_64
|
||||
# runner: ubuntu-24.04
|
||||
# - arch: aarch64
|
||||
# runner: ubuntu-24.04-arm
|
||||
# # Don't run scheduled builds on forks:
|
||||
# if: ${{ !cancelled() && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }}
|
||||
# runs-on: ${{ matrix.variant.runner }}
|
||||
# env:
|
||||
# date:
|
||||
# ver:
|
||||
# ver_pure:
|
||||
# steps:
|
||||
# - name: "Remove unneeded stuff to free disk space"
|
||||
# run:
|
||||
# rm -rf /usr/local/lib/android/* /usr/share/dotnet/* /opt/ghc1/* "/usr/local/share/boost1/*" /opt/hostedtoolcache1/*
|
||||
# - uses: actions/checkout@v6
|
||||
# - name: Get the version and date
|
||||
# run: |
|
||||
# ver_pure=$(grep 'set(SoftFever_VERSION' version.inc | cut -d '"' -f2)
|
||||
# if [[ "${{ github.event_name }}" == "pull_request" ]]; then
|
||||
# ver="PR-${{ github.event.number }}"
|
||||
# git_commit_hash="${{ github.event.pull_request.head.sha }}"
|
||||
# else
|
||||
# ver=V$ver_pure
|
||||
# git_commit_hash=""
|
||||
# fi
|
||||
# echo "ver=$ver" >> $GITHUB_ENV
|
||||
# echo "ver_pure=$ver_pure" >> $GITHUB_ENV
|
||||
# echo "date=$(date +'%Y%m%d')" >> $GITHUB_ENV
|
||||
# echo "git_commit_hash=$git_commit_hash" >> $GITHUB_ENV
|
||||
# shell: bash
|
||||
# - uses: flatpak/flatpak-github-actions/flatpak-builder@master
|
||||
# with:
|
||||
# bundle: OrcaSlicer-Linux-flatpak_${{ env.ver }}_${{ matrix.variant.arch }}.flatpak
|
||||
# manifest-path: scripts/flatpak/io.github.softfever.OrcaSlicer.yml
|
||||
# cache: true
|
||||
# arch: ${{ matrix.variant.arch }}
|
||||
# upload-artifact: false
|
||||
# - name: Upload artifacts Flatpak
|
||||
# uses: actions/upload-artifact@v5
|
||||
# with:
|
||||
# name: OrcaSlicer-Linux-flatpak_${{ env.ver }}_${{ matrix.variant.arch }}.flatpak
|
||||
# path: '/__w/OrcaSlicer/OrcaSlicer/OrcaSlicer-Linux-flatpak_${{ env.ver }}_${{ matrix.variant.arch }}.flatpak'
|
||||
# - name: Deploy Flatpak to nightly release
|
||||
# if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main'
|
||||
# uses: WebFreak001/deploy-nightly@v3.2.0
|
||||
# with:
|
||||
# upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label}
|
||||
# release_id: 137995723
|
||||
# asset_path: /__w/OrcaSlicer/OrcaSlicer/OrcaSlicer-Linux-flatpak_${{ env.ver }}_${{ matrix.variant.arch }}.flatpak
|
||||
# asset_name: OrcaSlicer-Linux-flatpak_nightly_${{ matrix.variant.arch }}.flatpak
|
||||
# asset_content_type: application/octet-stream
|
||||
# max_releases: 1 # optional, if there are more releases than this matching the asset_name, the oldest ones are going to be deleted
|
||||
|
||||
115
.github/workflows/build_orca.yml
vendored
115
.github/workflows/build_orca.yml
vendored
@@ -103,11 +103,13 @@ jobs:
|
||||
if: inputs.os == 'macos-14'
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
./build_release_macos.sh -s -n -x -a universal -t 10.15 -1
|
||||
# TODO: Change back to -a universal after auto-update testing is complete
|
||||
./build_release_macos.sh -s -n -x -a arm64 -t 10.15 -1
|
||||
|
||||
# Thanks to RaySajuuk, it's working now
|
||||
- name: Sign app and notary
|
||||
if: github.repository == 'OrcaSlicer/OrcaSlicer' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) && inputs.os == 'macos-14'
|
||||
# TODO: Remove feature/auto-update after testing is complete
|
||||
if: github.repository == 'OrcaSlicer/OrcaSlicer' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/') || github.ref == 'refs/heads/feature/auto-update') && inputs.os == 'macos-14'
|
||||
working-directory: ${{ github.workspace }}
|
||||
env:
|
||||
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
|
||||
@@ -124,27 +126,28 @@ jobs:
|
||||
security import $CERTIFICATE_PATH -P $P12_PASSWORD -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
|
||||
security list-keychain -d user -s $KEYCHAIN_PATH
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $P12_PASSWORD $KEYCHAIN_PATH
|
||||
codesign --deep --force --verbose --options runtime --timestamp --entitlements ${{ github.workspace }}/scripts/disable_validation.entitlements --sign "$CERTIFICATE_ID" ${{ github.workspace }}/build/universal/OrcaSlicer/OrcaSlicer.app
|
||||
# TODO: Change build/arm64 back to build/universal after auto-update testing is complete
|
||||
codesign --deep --force --verbose --options runtime --timestamp --entitlements ${{ github.workspace }}/scripts/disable_validation.entitlements --sign "$CERTIFICATE_ID" ${{ github.workspace }}/build/arm64/OrcaSlicer/OrcaSlicer.app
|
||||
# Sign OrcaSlicer_profile_validator.app if it exists
|
||||
if [ -f "${{ github.workspace }}/build/universal/OrcaSlicer/OrcaSlicer_profile_validator.app/Contents/MacOS/OrcaSlicer_profile_validator" ]; then
|
||||
codesign --deep --force --verbose --options runtime --timestamp --entitlements ${{ github.workspace }}/scripts/disable_validation.entitlements --sign "$CERTIFICATE_ID" ${{ github.workspace }}/build/universal/OrcaSlicer/OrcaSlicer_profile_validator.app
|
||||
if [ -f "${{ github.workspace }}/build/arm64/OrcaSlicer/OrcaSlicer_profile_validator.app/Contents/MacOS/OrcaSlicer_profile_validator" ]; then
|
||||
codesign --deep --force --verbose --options runtime --timestamp --entitlements ${{ github.workspace }}/scripts/disable_validation.entitlements --sign "$CERTIFICATE_ID" ${{ github.workspace }}/build/arm64/OrcaSlicer/OrcaSlicer_profile_validator.app
|
||||
fi
|
||||
|
||||
|
||||
# Create main OrcaSlicer DMG without the profile validator helper
|
||||
mkdir -p ${{ github.workspace }}/build/universal/OrcaSlicer_dmg
|
||||
rm -rf ${{ github.workspace }}/build/universal/OrcaSlicer_dmg/*
|
||||
cp -R ${{ github.workspace }}/build/universal/OrcaSlicer/OrcaSlicer.app ${{ github.workspace }}/build/universal/OrcaSlicer_dmg/
|
||||
ln -sfn /Applications ${{ github.workspace }}/build/universal/OrcaSlicer_dmg/Applications
|
||||
hdiutil create -volname "OrcaSlicer" -srcfolder ${{ github.workspace }}/build/universal/OrcaSlicer_dmg -ov -format UDZO OrcaSlicer_Mac_universal_${{ env.ver }}.dmg
|
||||
mkdir -p ${{ github.workspace }}/build/arm64/OrcaSlicer_dmg
|
||||
rm -rf ${{ github.workspace }}/build/arm64/OrcaSlicer_dmg/*
|
||||
cp -R ${{ github.workspace }}/build/arm64/OrcaSlicer/OrcaSlicer.app ${{ github.workspace }}/build/arm64/OrcaSlicer_dmg/
|
||||
ln -sfn /Applications ${{ github.workspace }}/build/arm64/OrcaSlicer_dmg/Applications
|
||||
hdiutil create -volname "OrcaSlicer" -srcfolder ${{ github.workspace }}/build/arm64/OrcaSlicer_dmg -ov -format UDZO OrcaSlicer_Mac_universal_${{ env.ver }}.dmg
|
||||
codesign --deep --force --verbose --options runtime --timestamp --entitlements ${{ github.workspace }}/scripts/disable_validation.entitlements --sign "$CERTIFICATE_ID" OrcaSlicer_Mac_universal_${{ env.ver }}.dmg
|
||||
|
||||
|
||||
# Create separate OrcaSlicer_profile_validator DMG if the app exists
|
||||
if [ -f "${{ github.workspace }}/build/universal/OrcaSlicer/OrcaSlicer_profile_validator.app/Contents/MacOS/OrcaSlicer_profile_validator" ]; then
|
||||
mkdir -p ${{ github.workspace }}/build/universal/OrcaSlicer_profile_validator_dmg
|
||||
rm -rf ${{ github.workspace }}/build/universal/OrcaSlicer_profile_validator_dmg/*
|
||||
cp -R ${{ github.workspace }}/build/universal/OrcaSlicer/OrcaSlicer_profile_validator.app ${{ github.workspace }}/build/universal/OrcaSlicer_profile_validator_dmg/
|
||||
ln -sfn /Applications ${{ github.workspace }}/build/universal/OrcaSlicer_profile_validator_dmg/Applications
|
||||
hdiutil create -volname "OrcaSlicer Profile Validator" -srcfolder ${{ github.workspace }}/build/universal/OrcaSlicer_profile_validator_dmg -ov -format UDZO OrcaSlicer_profile_validator_Mac_universal_${{ env.ver }}.dmg
|
||||
if [ -f "${{ github.workspace }}/build/arm64/OrcaSlicer/OrcaSlicer_profile_validator.app/Contents/MacOS/OrcaSlicer_profile_validator" ]; then
|
||||
mkdir -p ${{ github.workspace }}/build/arm64/OrcaSlicer_profile_validator_dmg
|
||||
rm -rf ${{ github.workspace }}/build/arm64/OrcaSlicer_profile_validator_dmg/*
|
||||
cp -R ${{ github.workspace }}/build/arm64/OrcaSlicer/OrcaSlicer_profile_validator.app ${{ github.workspace }}/build/arm64/OrcaSlicer_profile_validator_dmg/
|
||||
ln -sfn /Applications ${{ github.workspace }}/build/arm64/OrcaSlicer_profile_validator_dmg/Applications
|
||||
hdiutil create -volname "OrcaSlicer Profile Validator" -srcfolder ${{ github.workspace }}/build/arm64/OrcaSlicer_profile_validator_dmg -ov -format UDZO OrcaSlicer_profile_validator_Mac_universal_${{ env.ver }}.dmg
|
||||
codesign --deep --force --verbose --options runtime --timestamp --entitlements ${{ github.workspace }}/scripts/disable_validation.entitlements --sign "$CERTIFICATE_ID" OrcaSlicer_profile_validator_Mac_universal_${{ env.ver }}.dmg
|
||||
fi
|
||||
|
||||
@@ -159,23 +162,65 @@ jobs:
|
||||
xcrun stapler staple OrcaSlicer_profile_validator_Mac_universal_${{ env.ver }}.dmg
|
||||
fi
|
||||
|
||||
- name: Sign DMG for Sparkle auto-update
|
||||
# TODO: Remove feature/auto-update after testing is complete
|
||||
if: github.repository == 'OrcaSlicer/OrcaSlicer' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/') || github.ref == 'refs/heads/feature/auto-update') && inputs.os == 'macos-14'
|
||||
working-directory: ${{ github.workspace }}
|
||||
env:
|
||||
SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }}
|
||||
run: |
|
||||
# Get the Sparkle sign_update tool from deps (installed to OrcaSlicer_dep/bin)
|
||||
SIGN_UPDATE="${{ github.workspace }}/deps/build/arm64/OrcaSlicer_dep/bin/sign_update"
|
||||
|
||||
# Fallback to x86_64 if arm64 not found
|
||||
if [ ! -f "$SIGN_UPDATE" ]; then
|
||||
SIGN_UPDATE="${{ github.workspace }}/deps/build/x86_64/OrcaSlicer_dep/bin/sign_update"
|
||||
fi
|
||||
|
||||
if [ -f "$SIGN_UPDATE" ] && [ -n "$SPARKLE_PRIVATE_KEY" ]; then
|
||||
# Write the private key to a temp file
|
||||
echo "$SPARKLE_PRIVATE_KEY" > /tmp/sparkle_private_key
|
||||
chmod 600 /tmp/sparkle_private_key
|
||||
|
||||
# Sign the DMG and capture the signature
|
||||
SIGNATURE=$("$SIGN_UPDATE" "OrcaSlicer_Mac_universal_${{ env.ver }}.dmg" -f /tmp/sparkle_private_key)
|
||||
|
||||
# Clean up the key file
|
||||
rm -f /tmp/sparkle_private_key
|
||||
|
||||
# Save signature to a file for later use in appcast generation
|
||||
echo "$SIGNATURE" > OrcaSlicer_Mac_universal_${{ env.ver }}.dmg.sig
|
||||
echo "Sparkle signature generated: $SIGNATURE"
|
||||
|
||||
# Also output as GitHub Actions output
|
||||
echo "sparkle_signature=$SIGNATURE" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "Warning: Sparkle sign_update tool not found at $SIGN_UPDATE or private key not set, skipping signature generation"
|
||||
if [ ! -f "$SIGN_UPDATE" ]; then
|
||||
echo "sign_update not found. Available files:"
|
||||
ls -la "${{ github.workspace }}/deps/build/arm64/OrcaSlicer_dep/" || true
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Create DMG without notary
|
||||
if: github.ref != 'refs/heads/main' && inputs.os == 'macos-14'
|
||||
# TODO: Remove feature/auto-update exclusion after testing is complete
|
||||
if: github.ref != 'refs/heads/main' && github.ref != 'refs/heads/feature/auto-update' && !startsWith(github.ref, 'refs/heads/release/') && inputs.os == 'macos-14'
|
||||
working-directory: ${{ github.workspace }}
|
||||
run: |
|
||||
mkdir -p ${{ github.workspace }}/build/universal/OrcaSlicer_dmg
|
||||
rm -rf ${{ github.workspace }}/build/universal/OrcaSlicer_dmg/*
|
||||
cp -R ${{ github.workspace }}/build/universal/OrcaSlicer/OrcaSlicer.app ${{ github.workspace }}/build/universal/OrcaSlicer_dmg/
|
||||
ln -sfn /Applications ${{ github.workspace }}/build/universal/OrcaSlicer_dmg/Applications
|
||||
hdiutil create -volname "OrcaSlicer" -srcfolder ${{ github.workspace }}/build/universal/OrcaSlicer_dmg -ov -format UDZO OrcaSlicer_Mac_universal_${{ env.ver }}.dmg
|
||||
|
||||
# TODO: Change build/arm64 back to build/universal after auto-update testing is complete
|
||||
mkdir -p ${{ github.workspace }}/build/arm64/OrcaSlicer_dmg
|
||||
rm -rf ${{ github.workspace }}/build/arm64/OrcaSlicer_dmg/*
|
||||
cp -R ${{ github.workspace }}/build/arm64/OrcaSlicer/OrcaSlicer.app ${{ github.workspace }}/build/arm64/OrcaSlicer_dmg/
|
||||
ln -sfn /Applications ${{ github.workspace }}/build/arm64/OrcaSlicer_dmg/Applications
|
||||
hdiutil create -volname "OrcaSlicer" -srcfolder ${{ github.workspace }}/build/arm64/OrcaSlicer_dmg -ov -format UDZO OrcaSlicer_Mac_universal_${{ env.ver }}.dmg
|
||||
|
||||
# Create separate OrcaSlicer_profile_validator DMG if the app exists
|
||||
if [ -f "${{ github.workspace }}/build/universal/OrcaSlicer/OrcaSlicer_profile_validator.app/Contents/MacOS/OrcaSlicer_profile_validator" ]; then
|
||||
mkdir -p ${{ github.workspace }}/build/universal/OrcaSlicer_profile_validator_dmg
|
||||
rm -rf ${{ github.workspace }}/build/universal/OrcaSlicer_profile_validator_dmg/*
|
||||
cp -R ${{ github.workspace }}/build/universal/OrcaSlicer/OrcaSlicer_profile_validator.app ${{ github.workspace }}/build/universal/OrcaSlicer_profile_validator_dmg/
|
||||
ln -sfn /Applications ${{ github.workspace }}/build/universal/OrcaSlicer_profile_validator_dmg/Applications
|
||||
hdiutil create -volname "OrcaSlicer Profile Validator" -srcfolder ${{ github.workspace }}/build/universal/OrcaSlicer_profile_validator_dmg -ov -format UDZO OrcaSlicer_profile_validator_Mac_universal_${{ env.ver }}.dmg
|
||||
if [ -f "${{ github.workspace }}/build/arm64/OrcaSlicer/OrcaSlicer_profile_validator.app/Contents/MacOS/OrcaSlicer_profile_validator" ]; then
|
||||
mkdir -p ${{ github.workspace }}/build/arm64/OrcaSlicer_profile_validator_dmg
|
||||
rm -rf ${{ github.workspace }}/build/arm64/OrcaSlicer_profile_validator_dmg/*
|
||||
cp -R ${{ github.workspace }}/build/arm64/OrcaSlicer/OrcaSlicer_profile_validator.app ${{ github.workspace }}/build/arm64/OrcaSlicer_profile_validator_dmg/
|
||||
ln -sfn /Applications ${{ github.workspace }}/build/arm64/OrcaSlicer_profile_validator_dmg/Applications
|
||||
hdiutil create -volname "OrcaSlicer Profile Validator" -srcfolder ${{ github.workspace }}/build/arm64/OrcaSlicer_profile_validator_dmg -ov -format UDZO OrcaSlicer_profile_validator_Mac_universal_${{ env.ver }}.dmg
|
||||
fi
|
||||
|
||||
- name: Upload artifacts mac
|
||||
@@ -185,6 +230,14 @@ jobs:
|
||||
name: OrcaSlicer_Mac_universal_${{ env.ver }}
|
||||
path: ${{ github.workspace }}/OrcaSlicer_Mac_universal_${{ env.ver }}.dmg
|
||||
|
||||
- name: Upload Sparkle signature mac
|
||||
if: inputs.os == 'macos-14'
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: OrcaSlicer_Mac_universal_${{ env.ver }}_sig
|
||||
path: ${{ github.workspace }}/OrcaSlicer_Mac_universal_${{ env.ver }}.dmg.sig
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Upload OrcaSlicer_profile_validator DMG mac
|
||||
if: inputs.os == 'macos-14'
|
||||
uses: actions/upload-artifact@v5
|
||||
|
||||
138
.github/workflows/generate_appcast.yml
vendored
Normal file
138
.github/workflows/generate_appcast.yml
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
name: Generate Appcast
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Version to generate appcast for (e.g., 2.3.2)'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
generate_appcast:
|
||||
name: Generate and Deploy Appcast
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Get version from release or input
|
||||
id: version
|
||||
run: |
|
||||
if [ "${{ github.event_name }}" == "release" ]; then
|
||||
VERSION="${{ github.event.release.tag_name }}"
|
||||
VERSION="${VERSION#v}" # Remove 'v' prefix if present
|
||||
else
|
||||
VERSION="${{ github.event.inputs.version }}"
|
||||
fi
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Version: $VERSION"
|
||||
|
||||
- name: Download release assets info
|
||||
id: assets
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
TAG="v$VERSION"
|
||||
|
||||
# Get release info
|
||||
RELEASE_URL="https://github.com/${{ github.repository }}/releases/tag/$TAG"
|
||||
echo "release_url=$RELEASE_URL" >> $GITHUB_OUTPUT
|
||||
|
||||
# Get macOS DMG URL and size
|
||||
# Use browser_download_url for public access (not .url which is the API endpoint)
|
||||
MAC_ASSET=$(gh release view "$TAG" --json assets -q '.assets[] | select(.name | contains("Mac_universal")) | select(.name | endswith(".dmg"))')
|
||||
if [ -n "$MAC_ASSET" ]; then
|
||||
MAC_URL=$(echo "$MAC_ASSET" | jq -r '.browser_download_url // .url')
|
||||
MAC_SIZE=$(echo "$MAC_ASSET" | jq -r '.size')
|
||||
echo "mac_url=$MAC_URL" >> $GITHUB_OUTPUT
|
||||
echo "mac_size=$MAC_SIZE" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
# Get Windows installer URL and size
|
||||
# Use browser_download_url for public access (not .url which is the API endpoint)
|
||||
WIN_ASSET=$(gh release view "$TAG" --json assets -q '.assets[] | select(.name | contains("Windows_Installer")) | select(.name | endswith(".exe"))')
|
||||
if [ -n "$WIN_ASSET" ]; then
|
||||
WIN_URL=$(echo "$WIN_ASSET" | jq -r '.browser_download_url // .url')
|
||||
WIN_SIZE=$(echo "$WIN_ASSET" | jq -r '.size')
|
||||
echo "win_url=$WIN_URL" >> $GITHUB_OUTPUT
|
||||
echo "win_size=$WIN_SIZE" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Download signatures
|
||||
id: signatures
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
TAG="v$VERSION"
|
||||
|
||||
# Try to download macOS signature artifact
|
||||
MAC_SIG_ARTIFACT="OrcaSlicer_Mac_universal_V${VERSION}_sig"
|
||||
if gh run download --name "$MAC_SIG_ARTIFACT" -D /tmp/mac_sig 2>/dev/null; then
|
||||
MAC_SIG=$(cat /tmp/mac_sig/*.sig)
|
||||
echo "mac_signature=$MAC_SIG" >> $GITHUB_OUTPUT
|
||||
echo "Found macOS signature: $MAC_SIG"
|
||||
else
|
||||
echo "No macOS signature artifact found"
|
||||
fi
|
||||
|
||||
# For Windows, signature would come from WinSparkle signing (if implemented)
|
||||
# echo "win_signature=$WIN_SIG" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Generate appcast.xml
|
||||
run: |
|
||||
python scripts/generate_appcast.py \
|
||||
--version "${{ steps.version.outputs.version }}" \
|
||||
--release-notes-url "${{ steps.assets.outputs.release_url }}" \
|
||||
--mac-url "${{ steps.assets.outputs.mac_url }}" \
|
||||
--mac-signature "${{ steps.signatures.outputs.mac_signature }}" \
|
||||
--mac-length "${{ steps.assets.outputs.mac_size }}" \
|
||||
--output appcast.xml
|
||||
|
||||
echo "Generated appcast.xml:"
|
||||
cat appcast.xml
|
||||
|
||||
- name: Upload appcast artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: appcast
|
||||
path: appcast.xml
|
||||
|
||||
# Deploy to Cloudflare KV (for check-version.orcaslicer.com Worker)
|
||||
- name: Deploy appcast to Cloudflare KV
|
||||
if: github.event_name == 'release'
|
||||
env:
|
||||
CF_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
|
||||
CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
|
||||
KV_NAMESPACE_ID: ${{ secrets.CF_KV_NAMESPACE_ID }}
|
||||
run: |
|
||||
if [ -n "$CF_API_TOKEN" ] && [ -n "$CF_ACCOUNT_ID" ] && [ -n "$KV_NAMESPACE_ID" ]; then
|
||||
# Deploy appcast.xml
|
||||
curl -X PUT \
|
||||
"https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/storage/kv/namespaces/$KV_NAMESPACE_ID/values/appcast.xml" \
|
||||
-H "Authorization: Bearer $CF_API_TOKEN" \
|
||||
-H "Content-Type: text/plain" \
|
||||
--data-binary @appcast.xml
|
||||
echo "Appcast deployed to Cloudflare KV"
|
||||
|
||||
# Deploy macOS signature file (for verification/auditing)
|
||||
if [ -n "${{ steps.signatures.outputs.mac_signature }}" ]; then
|
||||
echo "${{ steps.signatures.outputs.mac_signature }}" > mac_signature.txt
|
||||
curl -X PUT \
|
||||
"https://api.cloudflare.com/client/v4/accounts/$CF_ACCOUNT_ID/storage/kv/namespaces/$KV_NAMESPACE_ID/values/signatures/${{ steps.version.outputs.version }}/mac.sig" \
|
||||
-H "Authorization: Bearer $CF_API_TOKEN" \
|
||||
-H "Content-Type: text/plain" \
|
||||
--data-binary @mac_signature.txt
|
||||
echo "macOS signature deployed to Cloudflare KV"
|
||||
fi
|
||||
else
|
||||
echo "Cloudflare credentials not configured, skipping deployment"
|
||||
fi
|
||||
@@ -811,6 +811,11 @@ function(orcaslicer_copy_dlls target config postfix output_dlls)
|
||||
${TOP_LEVEL_PROJECT_DIR}/deps/WebView2/lib/win-${_arch}/WebView2Loader.dll
|
||||
DESTINATION ${_out_dir})
|
||||
|
||||
# WinSparkle for auto-updates
|
||||
if(EXISTS "${CMAKE_PREFIX_PATH}/bin/WinSparkle.dll")
|
||||
file(COPY ${CMAKE_PREFIX_PATH}/bin/WinSparkle.dll DESTINATION ${_out_dir})
|
||||
endif()
|
||||
|
||||
file(COPY ${CMAKE_PREFIX_PATH}/bin/occt/TKBO.dll
|
||||
${CMAKE_PREFIX_PATH}/bin/occt/TKBRep.dll
|
||||
${CMAKE_PREFIX_PATH}/bin/occt/TKCAF.dll
|
||||
|
||||
17
deps/CMakeLists.txt
vendored
17
deps/CMakeLists.txt
vendored
@@ -386,6 +386,16 @@ endif ()
|
||||
include(OCCT/OCCT.cmake)
|
||||
include(OpenCV/OpenCV.cmake)
|
||||
|
||||
# WinSparkle for Windows auto-updates
|
||||
if(WIN32)
|
||||
include(WinSparkle/WinSparkle.cmake)
|
||||
endif()
|
||||
|
||||
# Sparkle 2 for macOS auto-updates
|
||||
if(APPLE)
|
||||
include(Sparkle/Sparkle.cmake)
|
||||
endif()
|
||||
|
||||
set(_dep_list
|
||||
dep_Boost
|
||||
dep_TBB
|
||||
@@ -410,12 +420,19 @@ set(_dep_list
|
||||
if (MSVC)
|
||||
# Experimental
|
||||
#list(APPEND _dep_list "dep_qhull")
|
||||
# WinSparkle for auto-updates
|
||||
list(APPEND _dep_list "dep_WinSparkle")
|
||||
else()
|
||||
list(APPEND _dep_list "dep_Qhull")
|
||||
# Not working, static build has different Eigen
|
||||
#list(APPEND _dep_list "dep_libigl")
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
# Sparkle 2 for auto-updates
|
||||
list(APPEND _dep_list "dep_Sparkle")
|
||||
endif()
|
||||
|
||||
add_custom_target(deps ALL DEPENDS ${_dep_list})
|
||||
|
||||
# Note: I'm not using any of the LOG_xxx options in ExternalProject_Add() commands
|
||||
|
||||
27
deps/Sparkle/Sparkle.cmake
vendored
Normal file
27
deps/Sparkle/Sparkle.cmake
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
# Sparkle 2 - Auto-update framework for macOS
|
||||
# https://sparkle-project.org/
|
||||
# https://github.com/sparkle-project/Sparkle
|
||||
#
|
||||
# Sparkle is distributed as a pre-built framework, so we just download and extract.
|
||||
|
||||
if(APPLE)
|
||||
set(SPARKLE_VERSION "2.8.1")
|
||||
|
||||
ExternalProject_Add(
|
||||
dep_Sparkle
|
||||
EXCLUDE_FROM_ALL ON
|
||||
URL "https://github.com/sparkle-project/Sparkle/releases/download/${SPARKLE_VERSION}/Sparkle-${SPARKLE_VERSION}.tar.xz"
|
||||
URL_HASH SHA256=5cddb7695674ef7704268f38eccaee80e3accbf19e61c1689efff5b6116d85be
|
||||
DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/Sparkle
|
||||
# No build step needed - just install pre-built framework and tools
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ${CMAKE_COMMAND} -E make_directory ${DESTDIR}/Frameworks
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
<SOURCE_DIR>/Sparkle.framework ${DESTDIR}/Frameworks/Sparkle.framework
|
||||
# Also install the Sparkle CLI tools (sign_update, generate_appcast) for CI/CD signing
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${DESTDIR}/bin
|
||||
COMMAND ${CMAKE_COMMAND} -E copy <SOURCE_DIR>/bin/sign_update ${DESTDIR}/bin/sign_update
|
||||
COMMAND ${CMAKE_COMMAND} -E copy <SOURCE_DIR>/bin/generate_appcast ${DESTDIR}/bin/generate_appcast
|
||||
)
|
||||
endif()
|
||||
33
deps/WinSparkle/WinSparkle.cmake
vendored
Normal file
33
deps/WinSparkle/WinSparkle.cmake
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
# WinSparkle - Auto-update framework for Windows
|
||||
# https://winsparkle.org/
|
||||
# https://github.com/vslavik/winsparkle
|
||||
#
|
||||
# WinSparkle is distributed as pre-built binaries, so we just download and extract.
|
||||
|
||||
if(WIN32)
|
||||
set(WINSPARKLE_VERSION "0.8.3")
|
||||
|
||||
# Determine architecture
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
set(WINSPARKLE_ARCH "x64")
|
||||
else()
|
||||
set(WINSPARKLE_ARCH "x86")
|
||||
endif()
|
||||
|
||||
ExternalProject_Add(
|
||||
dep_WinSparkle
|
||||
EXCLUDE_FROM_ALL ON
|
||||
URL "https://github.com/vslavik/winsparkle/releases/download/v${WINSPARKLE_VERSION}/WinSparkle-${WINSPARKLE_VERSION}.zip"
|
||||
URL_HASH SHA256=5ff4a4604c78d57e01d83e22f79f5ffea0c4969defd48b45c69ccbd6b1a71e94
|
||||
DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/WinSparkle
|
||||
# No build step needed - just install pre-built binaries
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
<SOURCE_DIR>/include ${DESTDIR}/include
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
<SOURCE_DIR>/${WINSPARKLE_ARCH}/Release/WinSparkle.dll ${DESTDIR}/bin/WinSparkle.dll
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
<SOURCE_DIR>/${WINSPARKLE_ARCH}/Release/WinSparkle.lib ${DESTDIR}/lib/WinSparkle.lib
|
||||
)
|
||||
endif()
|
||||
208
scripts/generate_appcast.py
Executable file
208
scripts/generate_appcast.py
Executable file
@@ -0,0 +1,208 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Generate appcast.xml for Sparkle/WinSparkle auto-updates.
|
||||
|
||||
This script generates an appcast XML file that can be used by both
|
||||
Sparkle (macOS) and WinSparkle (Windows) for auto-update functionality.
|
||||
|
||||
Usage:
|
||||
python generate_appcast.py --version 2.1.0 \
|
||||
--win-url https://github.com/.../OrcaSlicer_Windows.exe \
|
||||
--win-signature "BASE64_SIGNATURE" \
|
||||
--win-length 150000000 \
|
||||
--mac-url https://github.com/.../OrcaSlicer_Mac.dmg \
|
||||
--mac-signature "BASE64_SIGNATURE" \
|
||||
--mac-length 200000000 \
|
||||
--release-notes-url https://github.com/.../releases/tag/v2.1.0 \
|
||||
--output appcast.xml
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from xml.etree import ElementTree as ET
|
||||
from xml.dom import minidom
|
||||
|
||||
|
||||
SPARKLE_NS = "http://www.andymatuschak.org/xml-namespaces/sparkle"
|
||||
DC_NS = "http://purl.org/dc/elements/1.1/"
|
||||
|
||||
|
||||
def create_appcast(
|
||||
version: str,
|
||||
release_notes_url: str,
|
||||
win_url: str = None,
|
||||
win_signature: str = None,
|
||||
win_length: int = None,
|
||||
mac_url: str = None,
|
||||
mac_signature: str = None,
|
||||
mac_length: int = None,
|
||||
title: str = "OrcaSlicer Updates",
|
||||
description: str = "Most recent updates to OrcaSlicer",
|
||||
link: str = "https://github.com/OrcaSlicer/OrcaSlicer",
|
||||
) -> str:
|
||||
"""
|
||||
Create an appcast XML string.
|
||||
|
||||
Args:
|
||||
version: Version string (e.g., "2.1.0")
|
||||
release_notes_url: URL to release notes HTML page
|
||||
win_url: Download URL for Windows installer
|
||||
win_signature: EdDSA signature for Windows installer
|
||||
win_length: File size in bytes for Windows installer
|
||||
mac_url: Download URL for macOS DMG
|
||||
mac_signature: EdDSA signature for macOS DMG
|
||||
mac_length: File size in bytes for macOS DMG
|
||||
title: Feed title
|
||||
description: Feed description
|
||||
link: Link to project homepage
|
||||
|
||||
Returns:
|
||||
XML string of the appcast
|
||||
"""
|
||||
# Register namespaces
|
||||
ET.register_namespace("sparkle", SPARKLE_NS)
|
||||
ET.register_namespace("dc", DC_NS)
|
||||
|
||||
# Create root RSS element
|
||||
rss = ET.Element("rss")
|
||||
rss.set("version", "2.0")
|
||||
rss.set(f"xmlns:sparkle", SPARKLE_NS)
|
||||
rss.set(f"xmlns:dc", DC_NS)
|
||||
|
||||
# Create channel
|
||||
channel = ET.SubElement(rss, "channel")
|
||||
ET.SubElement(channel, "title").text = title
|
||||
ET.SubElement(channel, "link").text = link
|
||||
ET.SubElement(channel, "description").text = description
|
||||
ET.SubElement(channel, "language").text = "en"
|
||||
|
||||
# Create item for this release
|
||||
item = ET.SubElement(channel, "item")
|
||||
ET.SubElement(item, "title").text = f"Version {version}"
|
||||
|
||||
# Publication date in RFC 2822 format
|
||||
pub_date = datetime.now(timezone.utc).strftime("%a, %d %b %Y %H:%M:%S +0000")
|
||||
ET.SubElement(item, "pubDate").text = pub_date
|
||||
|
||||
# Release notes link
|
||||
release_notes = ET.SubElement(item, f"{{{SPARKLE_NS}}}releaseNotesLink")
|
||||
release_notes.text = release_notes_url
|
||||
|
||||
# Windows enclosure
|
||||
if win_url and win_signature:
|
||||
win_enclosure = ET.SubElement(item, "enclosure")
|
||||
win_enclosure.set("url", win_url)
|
||||
win_enclosure.set(f"{{{SPARKLE_NS}}}version", version)
|
||||
win_enclosure.set(f"{{{SPARKLE_NS}}}os", "windows")
|
||||
win_enclosure.set(f"{{{SPARKLE_NS}}}edSignature", win_signature)
|
||||
if win_length:
|
||||
win_enclosure.set("length", str(win_length))
|
||||
win_enclosure.set("type", "application/octet-stream")
|
||||
|
||||
# macOS enclosure
|
||||
if mac_url and mac_signature:
|
||||
mac_enclosure = ET.SubElement(item, "enclosure")
|
||||
mac_enclosure.set("url", mac_url)
|
||||
mac_enclosure.set(f"{{{SPARKLE_NS}}}version", version)
|
||||
mac_enclosure.set(f"{{{SPARKLE_NS}}}os", "macos")
|
||||
mac_enclosure.set(f"{{{SPARKLE_NS}}}edSignature", mac_signature)
|
||||
if mac_length:
|
||||
mac_enclosure.set("length", str(mac_length))
|
||||
mac_enclosure.set("type", "application/octet-stream")
|
||||
|
||||
# Convert to pretty-printed string
|
||||
rough_string = ET.tostring(rss, encoding="unicode", method="xml")
|
||||
reparsed = minidom.parseString(rough_string)
|
||||
pretty_xml = reparsed.toprettyxml(indent=" ", encoding=None)
|
||||
|
||||
# Remove extra blank lines
|
||||
lines = [line for line in pretty_xml.split("\n") if line.strip()]
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate appcast.xml for OrcaSlicer auto-updates",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog=__doc__,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--version", "-v", required=True, help="Version string (e.g., 2.1.0)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--release-notes-url",
|
||||
"-r",
|
||||
required=True,
|
||||
help="URL to release notes page",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--win-url", help="Download URL for Windows installer"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--win-signature", help="EdDSA signature for Windows installer"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--win-length", type=int, help="File size in bytes for Windows installer"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--mac-url", help="Download URL for macOS DMG"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--mac-signature", help="EdDSA signature for macOS DMG"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--mac-length", type=int, help="File size in bytes for macOS DMG"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output", "-o", default="appcast.xml", help="Output file path"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--title", default="OrcaSlicer Updates", help="Feed title"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--link",
|
||||
default="https://github.com/OrcaSlicer/OrcaSlicer",
|
||||
help="Project homepage URL",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Validate that at least one platform is specified
|
||||
has_windows = args.win_url and args.win_signature
|
||||
has_macos = args.mac_url and args.mac_signature
|
||||
|
||||
if not has_windows and not has_macos:
|
||||
print(
|
||||
"Error: At least one platform (Windows or macOS) must have both URL and signature",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# Generate appcast
|
||||
xml_content = create_appcast(
|
||||
version=args.version,
|
||||
release_notes_url=args.release_notes_url,
|
||||
win_url=args.win_url,
|
||||
win_signature=args.win_signature,
|
||||
win_length=args.win_length,
|
||||
mac_url=args.mac_url,
|
||||
mac_signature=args.mac_signature,
|
||||
mac_length=args.mac_length,
|
||||
title=args.title,
|
||||
link=args.link,
|
||||
)
|
||||
|
||||
# Write output
|
||||
if args.output == "-":
|
||||
print(xml_content)
|
||||
else:
|
||||
with open(args.output, "w", encoding="utf-8") as f:
|
||||
f.write(xml_content)
|
||||
print(f"Appcast written to: {args.output}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -131,6 +131,11 @@ if (APPLE)
|
||||
# add_definitions(-DBOOST_THREAD_DONT_USE_CHRONO -DBOOST_NO_CXX11_RVALUE_REFERENCES -DBOOST_THREAD_USES_MOVE)
|
||||
# -liconv: boost links to libiconv by default
|
||||
target_link_libraries(OrcaSlicer "-liconv -framework IOKit" "-framework CoreFoundation" "-framework AVFoundation" "-framework AVKit" "-framework CoreMedia" "-framework VideoToolbox" -lc++)
|
||||
# Set rpath for embedded frameworks (Sparkle)
|
||||
set_target_properties(OrcaSlicer PROPERTIES
|
||||
INSTALL_RPATH "@executable_path/../Frameworks"
|
||||
BUILD_WITH_INSTALL_RPATH TRUE
|
||||
)
|
||||
elseif (MSVC)
|
||||
# Manifest is provided through OrcaSlicer.rc, don't generate your own.
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /MANIFEST:NO")
|
||||
@@ -252,6 +257,24 @@ else ()
|
||||
COMMAND ln -sfn "${SLIC3R_RESOURCES_DIR}" "${BIN_RESOURCES_DIR}"
|
||||
COMMENT "Symlinking the resources directory into the build tree"
|
||||
VERBATIM)
|
||||
|
||||
# Embed Sparkle framework for auto-updates
|
||||
if (CMAKE_MACOSX_BUNDLE)
|
||||
find_library(SPARKLE_FRAMEWORK Sparkle PATHS ${CMAKE_PREFIX_PATH}/Frameworks)
|
||||
if(SPARKLE_FRAMEWORK)
|
||||
if (CMAKE_CONFIGURATION_TYPES)
|
||||
set(BUNDLE_FRAMEWORKS_DIR "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/OrcaSlicer.app/Contents/Frameworks")
|
||||
else()
|
||||
set(BUNDLE_FRAMEWORKS_DIR "${CMAKE_CURRENT_BINARY_DIR}/OrcaSlicer.app/Contents/Frameworks")
|
||||
endif()
|
||||
add_custom_command(TARGET OrcaSlicer POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${BUNDLE_FRAMEWORKS_DIR}"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory "${SPARKLE_FRAMEWORK}" "${BUNDLE_FRAMEWORKS_DIR}/Sparkle.framework"
|
||||
COMMENT "Embedding Sparkle.framework into app bundle"
|
||||
VERBATIM)
|
||||
message(STATUS "Sparkle framework will be embedded: ${SPARKLE_FRAMEWORK}")
|
||||
endif()
|
||||
endif()
|
||||
endif ()
|
||||
|
||||
# Slic3r binary install target. Default build type is release in case no CMAKE_BUILD_TYPE is provided.
|
||||
|
||||
@@ -135,5 +135,14 @@
|
||||
<key>ASAN_OPTIONS</key>
|
||||
<string>detect_container_overflow=0</string>
|
||||
</dict>
|
||||
<!-- Sparkle 2 Auto-Update Configuration -->
|
||||
<key>SUFeedURL</key>
|
||||
<string>https://check-version.orcaslicer.com/appcast.xml</string>
|
||||
<key>SUPublicEDKey</key>
|
||||
<string>eLFARgt9i0VZQR4FtXiTL6jdwjkGr2RMPjfYCCfBWeM=</string>
|
||||
<key>SUEnableAutomaticChecks</key>
|
||||
<true/>
|
||||
<key>SUAllowsAutomaticUpdates</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -456,6 +456,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/UnsavedChangesDialog.hpp
|
||||
GUI/UpdateDialogs.cpp
|
||||
GUI/UpdateDialogs.hpp
|
||||
GUI/UpdateManager.hpp
|
||||
GUI/UpdateManager.cpp
|
||||
GUI/UpgradePanel.cpp
|
||||
GUI/UpgradePanel.hpp
|
||||
GUI/UserManager.cpp
|
||||
@@ -648,6 +650,7 @@ if (APPLE)
|
||||
GUI/InstanceCheckMac.mm
|
||||
GUI/InstanceCheckMac.h
|
||||
GUI/GUI_UtilsMac.mm
|
||||
GUI/UpdateManagerMac.mm
|
||||
GUI/wxMediaCtrl2.mm
|
||||
GUI/wxMediaCtrl2.h
|
||||
)
|
||||
@@ -672,11 +675,6 @@ string(REPLACE "\r" "" ORCA_UPDATER_SIG_KEY_B64 "${ORCA_UPDATER_SIG_KEY_B64}")
|
||||
string(REPLACE "\t" "" ORCA_UPDATER_SIG_KEY_B64 "${ORCA_UPDATER_SIG_KEY_B64}")
|
||||
string(REPLACE " " "" ORCA_UPDATER_SIG_KEY_B64 "${ORCA_UPDATER_SIG_KEY_B64}")
|
||||
|
||||
set(ORCA_UPDATER_SIG_KEY_AVAILABLE 0)
|
||||
if(ORCA_UPDATER_SIG_KEY_B64)
|
||||
set(ORCA_UPDATER_SIG_KEY_AVAILABLE 1)
|
||||
endif()
|
||||
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/GeneratedConfig.hpp.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/GeneratedConfig.hpp
|
||||
@ONLY)
|
||||
@@ -707,6 +705,16 @@ target_link_libraries(libslic3r_gui libslic3r cereal::cereal imgui imguizmo mini
|
||||
|
||||
if (MSVC)
|
||||
target_link_libraries(libslic3r_gui Setupapi.lib)
|
||||
# WinSparkle for auto-updates
|
||||
find_library(WINSPARKLE_LIB WinSparkle PATHS ${CMAKE_PREFIX_PATH}/lib)
|
||||
if(WINSPARKLE_LIB)
|
||||
target_link_libraries(libslic3r_gui ${WINSPARKLE_LIB})
|
||||
target_include_directories(libslic3r_gui PRIVATE ${CMAKE_PREFIX_PATH}/include)
|
||||
target_compile_definitions(libslic3r_gui PRIVATE ORCA_HAS_WINSPARKLE)
|
||||
message(STATUS "WinSparkle found: ${WINSPARKLE_LIB}")
|
||||
else()
|
||||
message(STATUS "WinSparkle not found, auto-update disabled on Windows")
|
||||
endif()
|
||||
elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
FIND_LIBRARY(WAYLAND_SERVER_LIBRARIES NAMES wayland-server)
|
||||
FIND_LIBRARY(WAYLAND_EGL_LIBRARIES NAMES wayland-egl)
|
||||
@@ -722,6 +730,15 @@ elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
)
|
||||
elseif (APPLE)
|
||||
target_link_libraries(libslic3r_gui ${DISKARBITRATION_LIBRARY} "-framework Security")
|
||||
# Sparkle 2 for auto-updates
|
||||
find_library(SPARKLE_FRAMEWORK Sparkle PATHS ${CMAKE_PREFIX_PATH}/Frameworks)
|
||||
if(SPARKLE_FRAMEWORK)
|
||||
target_link_libraries(libslic3r_gui ${SPARKLE_FRAMEWORK})
|
||||
target_compile_definitions(libslic3r_gui PRIVATE ORCA_HAS_SPARKLE)
|
||||
message(STATUS "Sparkle found: ${SPARKLE_FRAMEWORK}")
|
||||
else()
|
||||
message(STATUS "Sparkle not found, auto-update disabled on macOS")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (SLIC3R_STATIC)
|
||||
|
||||
@@ -91,6 +91,7 @@
|
||||
#include "Tab.hpp"
|
||||
#include "SysInfoDialog.hpp"
|
||||
#include "UpdateDialogs.hpp"
|
||||
#include "UpdateManager.hpp"
|
||||
#include "Mouse3DController.hpp"
|
||||
#include "RemovableDriveManager.hpp"
|
||||
#include "InstanceCheck.hpp"
|
||||
@@ -984,6 +985,16 @@ void GUI_App::post_init()
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " sync_user_preset: false";
|
||||
}
|
||||
|
||||
// Initialize the auto-update manager
|
||||
#if defined(ORCA_HAS_SPARKLE) || defined(ORCA_HAS_WINSPARKLE)
|
||||
{
|
||||
// Use the appcast URL for Sparkle/WinSparkle (different from version_check_url)
|
||||
std::string appcast_url = "https://check-version.orcaslicer.com/appcast.xml";
|
||||
UpdateManager::init(appcast_url, "");
|
||||
BOOST_LOG_TRIVIAL(info) << "UpdateManager initialized with appcast URL: " << appcast_url;
|
||||
}
|
||||
#endif
|
||||
|
||||
// The extra CallAfter() is needed because of Mac, where this is the only way
|
||||
// to popup a modal dialog on start without screwing combo boxes.
|
||||
// This is ugly but I honestly found no better way to do it.
|
||||
@@ -998,7 +1009,12 @@ void GUI_App::post_init()
|
||||
bool sys_preset = app_config->get("sync_system_preset") == "true";
|
||||
this->preset_updater->sync(http_url, language, network_ver, sys_preset ? preset_bundle : nullptr);
|
||||
|
||||
// Check for application updates
|
||||
#if defined(ORCA_HAS_SPARKLE) || defined(ORCA_HAS_WINSPARKLE)
|
||||
UpdateManager::check_for_updates_background();
|
||||
#else
|
||||
this->check_new_version_sf();
|
||||
#endif
|
||||
if (is_user_login() && !app_config->get_stealth_mode()) {
|
||||
// this->check_privacy_version(0);
|
||||
request_user_handle(0);
|
||||
@@ -2240,6 +2256,11 @@ bool GUI_App::OnInit()
|
||||
|
||||
int GUI_App::OnExit()
|
||||
{
|
||||
// Shutdown the auto-update manager
|
||||
#if defined(ORCA_HAS_SPARKLE) || defined(ORCA_HAS_WINSPARKLE)
|
||||
UpdateManager::shutdown();
|
||||
#endif
|
||||
|
||||
stop_sync_user_preset();
|
||||
|
||||
if (m_device_manager) {
|
||||
@@ -4630,7 +4651,7 @@ std::string base64url_encode(const unsigned char* data, std::size_t length)
|
||||
|
||||
std::optional<std::vector<unsigned char>> load_signature_key()
|
||||
{
|
||||
#if ORCA_UPDATER_SIG_KEY_AVAILABLE
|
||||
#ifdef ORCA_UPDATER_SIG_KEY_B64
|
||||
std::string key = ORCA_UPDATER_SIG_KEY_B64;
|
||||
boost::algorithm::trim(key);
|
||||
if (key.empty())
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
#include "MarkdownTip.hpp"
|
||||
#include "NetworkTestDialog.hpp"
|
||||
#include "ConfigWizard.hpp"
|
||||
#include "UpdateManager.hpp"
|
||||
#include "Widgets/WebView.hpp"
|
||||
#include "DailyTips.hpp"
|
||||
#include "FilamentMapDialog.hpp"
|
||||
@@ -2332,7 +2333,11 @@ static wxMenu* generate_help_menu()
|
||||
// Check New Version
|
||||
append_menu_item(helpMenu, wxID_ANY, _L("Check for Update"), _L("Check for Update"),
|
||||
[](wxCommandEvent&) {
|
||||
#if defined(ORCA_HAS_SPARKLE) || defined(ORCA_HAS_WINSPARKLE)
|
||||
UpdateManager::check_for_updates_interactive();
|
||||
#else
|
||||
wxGetApp().check_new_version_sf(true, 1);
|
||||
#endif
|
||||
}, "", nullptr, []() {
|
||||
return true;
|
||||
});
|
||||
|
||||
174
src/slic3r/GUI/UpdateManager.cpp
Normal file
174
src/slic3r/GUI/UpdateManager.cpp
Normal file
@@ -0,0 +1,174 @@
|
||||
#include "UpdateManager.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
// Windows implementation uses WinSparkle
|
||||
#if defined(_WIN32) && defined(ORCA_HAS_WINSPARKLE)
|
||||
#include <winsparkle.h>
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
// Static member definitions (defined in UpdateManagerMac.mm for macOS)
|
||||
#if !defined(__APPLE__)
|
||||
bool UpdateManager::s_initialized = false;
|
||||
std::string UpdateManager::s_appcast_url;
|
||||
std::string UpdateManager::s_public_key;
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32) && defined(ORCA_HAS_WINSPARKLE)
|
||||
// ============================================================================
|
||||
// Windows Implementation (WinSparkle)
|
||||
// ============================================================================
|
||||
|
||||
void UpdateManager::init(const std::string& appcast_url, const std::string& public_key)
|
||||
{
|
||||
if (s_initialized) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "UpdateManager::init called multiple times";
|
||||
return;
|
||||
}
|
||||
|
||||
s_appcast_url = appcast_url;
|
||||
s_public_key = public_key;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "UpdateManager: Initializing WinSparkle with appcast URL: " << appcast_url;
|
||||
|
||||
// Set application details for registry storage
|
||||
win_sparkle_set_app_details(L"SoftFever", L"OrcaSlicer", L"" SLIC3R_VERSION);
|
||||
|
||||
// Set appcast URL
|
||||
win_sparkle_set_appcast_url(appcast_url.c_str());
|
||||
|
||||
// Set EdDSA public key for signature verification
|
||||
if (!public_key.empty()) {
|
||||
win_sparkle_set_dsa_pub_pem(public_key.c_str());
|
||||
BOOST_LOG_TRIVIAL(info) << "UpdateManager: EdDSA public key configured";
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(warning) << "UpdateManager: No public key provided, signature verification disabled";
|
||||
}
|
||||
|
||||
// Initialize WinSparkle (starts background thread)
|
||||
win_sparkle_init();
|
||||
s_initialized = true;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "UpdateManager: WinSparkle initialized successfully";
|
||||
}
|
||||
|
||||
void UpdateManager::check_for_updates_interactive()
|
||||
{
|
||||
if (!s_initialized) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "UpdateManager::check_for_updates_interactive called before init";
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "UpdateManager: User-triggered update check";
|
||||
win_sparkle_check_update_with_ui();
|
||||
}
|
||||
|
||||
void UpdateManager::check_for_updates_background()
|
||||
{
|
||||
if (!s_initialized) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "UpdateManager::check_for_updates_background called before init";
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "UpdateManager: Background update check";
|
||||
win_sparkle_check_update_without_ui();
|
||||
}
|
||||
|
||||
void UpdateManager::shutdown()
|
||||
{
|
||||
if (!s_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "UpdateManager: Shutting down WinSparkle";
|
||||
win_sparkle_cleanup();
|
||||
s_initialized = false;
|
||||
}
|
||||
|
||||
void UpdateManager::set_automatic_check_enabled(bool enabled)
|
||||
{
|
||||
// WinSparkle manages automatic checks via registry settings
|
||||
// The user can configure this through WinSparkle's own preferences dialog
|
||||
BOOST_LOG_TRIVIAL(info) << "UpdateManager: Automatic check enabled: " << enabled;
|
||||
}
|
||||
|
||||
#elif defined(__linux__)
|
||||
// ============================================================================
|
||||
// Linux Implementation (Stub - AppImageUpdate deferred)
|
||||
// ============================================================================
|
||||
|
||||
void UpdateManager::init(const std::string& appcast_url, const std::string& public_key)
|
||||
{
|
||||
s_appcast_url = appcast_url;
|
||||
s_public_key = public_key;
|
||||
s_initialized = true;
|
||||
BOOST_LOG_TRIVIAL(info) << "UpdateManager: Linux auto-update not yet implemented (stub)";
|
||||
}
|
||||
|
||||
void UpdateManager::check_for_updates_interactive()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "UpdateManager: Linux interactive update check not implemented";
|
||||
// TODO: Implement AppImageUpdate integration
|
||||
// For now, fall back to the old behavior (handled by caller)
|
||||
}
|
||||
|
||||
void UpdateManager::check_for_updates_background()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "UpdateManager: Linux background update check not implemented";
|
||||
// TODO: Implement AppImageUpdate integration
|
||||
}
|
||||
|
||||
void UpdateManager::shutdown()
|
||||
{
|
||||
s_initialized = false;
|
||||
}
|
||||
|
||||
void UpdateManager::set_automatic_check_enabled(bool enabled)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "UpdateManager: Linux automatic check not implemented";
|
||||
}
|
||||
|
||||
#elif !defined(__APPLE__)
|
||||
// ============================================================================
|
||||
// Fallback Implementation (No auto-update support)
|
||||
// ============================================================================
|
||||
|
||||
void UpdateManager::init(const std::string& appcast_url, const std::string& public_key)
|
||||
{
|
||||
s_appcast_url = appcast_url;
|
||||
s_public_key = public_key;
|
||||
s_initialized = true;
|
||||
BOOST_LOG_TRIVIAL(info) << "UpdateManager: No auto-update support on this platform";
|
||||
}
|
||||
|
||||
void UpdateManager::check_for_updates_interactive()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "UpdateManager: Interactive update check not available";
|
||||
}
|
||||
|
||||
void UpdateManager::check_for_updates_background()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "UpdateManager: Background update check not available";
|
||||
}
|
||||
|
||||
void UpdateManager::shutdown()
|
||||
{
|
||||
s_initialized = false;
|
||||
}
|
||||
|
||||
void UpdateManager::set_automatic_check_enabled(bool enabled)
|
||||
{
|
||||
// No-op
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Note: macOS implementation is in UpdateManagerMac.mm
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
41
src/slic3r/GUI/UpdateManager.hpp
Normal file
41
src/slic3r/GUI/UpdateManager.hpp
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
/// Cross-platform auto-update manager abstraction.
|
||||
/// Uses WinSparkle on Windows, Sparkle 2 on macOS.
|
||||
/// Linux support deferred (stub implementation).
|
||||
class UpdateManager {
|
||||
public:
|
||||
/// Initialize the platform-specific updater.
|
||||
/// Must be called once during application startup (GUI_App::on_init_inner).
|
||||
/// @param appcast_url URL to the appcast XML feed
|
||||
/// @param public_key Base64-encoded Ed25519 public key for signature verification
|
||||
static void init(const std::string& appcast_url, const std::string& public_key);
|
||||
|
||||
/// Manual check triggered by user (Help -> Check for Updates).
|
||||
/// Shows UI regardless of whether an update is available.
|
||||
static void check_for_updates_interactive();
|
||||
|
||||
/// Background check called on application startup.
|
||||
/// Only shows UI if an update is available.
|
||||
static void check_for_updates_background();
|
||||
|
||||
/// Cleanup on application exit.
|
||||
static void shutdown();
|
||||
|
||||
/// Enable or disable automatic update checks.
|
||||
/// @param enabled If true, automatically check for updates periodically
|
||||
static void set_automatic_check_enabled(bool enabled);
|
||||
|
||||
private:
|
||||
static bool s_initialized;
|
||||
static std::string s_appcast_url;
|
||||
static std::string s_public_key;
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
204
src/slic3r/GUI/UpdateManagerMac.mm
Normal file
204
src/slic3r/GUI/UpdateManagerMac.mm
Normal file
@@ -0,0 +1,204 @@
|
||||
#include "UpdateManager.hpp"
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
#ifdef ORCA_HAS_SPARKLE
|
||||
#import <Sparkle/Sparkle.h>
|
||||
#endif
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
// ============================================================================
|
||||
// macOS Implementation (Sparkle 2)
|
||||
// ============================================================================
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
// Static member definitions (defined in UpdateManager.cpp for other platforms)
|
||||
// For macOS, we need to define them here since UpdateManagerMac.mm is compiled instead
|
||||
bool UpdateManager::s_initialized = false;
|
||||
std::string UpdateManager::s_appcast_url;
|
||||
std::string UpdateManager::s_public_key;
|
||||
|
||||
#ifdef ORCA_HAS_SPARKLE
|
||||
|
||||
// Sparkle updater delegate for custom behavior
|
||||
@interface OrcaSparkleDelegate : NSObject <SPUUpdaterDelegate>
|
||||
@end
|
||||
|
||||
@implementation OrcaSparkleDelegate
|
||||
|
||||
// Optional: Add custom parameters to the appcast request
|
||||
- (NSArray<NSDictionary<NSString *, NSString *> *> *)feedParametersForUpdater:(SPUUpdater *)updater
|
||||
sendingSystemProfile:(BOOL)sendingProfile
|
||||
{
|
||||
// Add OrcaSlicer-specific parameters to the update check request
|
||||
NSString *version = [NSString stringWithUTF8String:SLIC3R_VERSION];
|
||||
NSString *osVersion = [[NSProcessInfo processInfo] operatingSystemVersionString];
|
||||
|
||||
return @[
|
||||
@{@"key": @"app_version", @"value": version ?: @"unknown"},
|
||||
@{@"key": @"os_version", @"value": osVersion ?: @"unknown"}
|
||||
];
|
||||
}
|
||||
|
||||
// Optional: Handle update errors
|
||||
- (void)updater:(SPUUpdater *)updater didAbortWithError:(NSError *)error
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << "UpdateManager: Sparkle update aborted with error: "
|
||||
<< [[error localizedDescription] UTF8String];
|
||||
}
|
||||
|
||||
// Optional: Called when an update is found
|
||||
- (void)updater:(SPUUpdater *)updater didFindValidUpdate:(SUAppcastItem *)item
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "UpdateManager: Found update to version "
|
||||
<< [[item displayVersionString] UTF8String];
|
||||
}
|
||||
|
||||
// Optional: Called when no update is available
|
||||
- (void)updaterDidNotFindUpdate:(SPUUpdater *)updater
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "UpdateManager: No update available";
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
// Static Sparkle controller and delegate instances
|
||||
static SPUStandardUpdaterController *s_updater_controller = nil;
|
||||
static OrcaSparkleDelegate *s_updater_delegate = nil;
|
||||
|
||||
void UpdateManager::init(const std::string& appcast_url, const std::string& public_key)
|
||||
{
|
||||
if (s_initialized) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "UpdateManager::init called multiple times";
|
||||
return;
|
||||
}
|
||||
|
||||
s_appcast_url = appcast_url;
|
||||
s_public_key = public_key;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "UpdateManager: Initializing Sparkle 2";
|
||||
|
||||
@autoreleasepool {
|
||||
// Create the delegate
|
||||
s_updater_delegate = [[OrcaSparkleDelegate alloc] init];
|
||||
|
||||
// Create the standard updater controller
|
||||
// This reads SUFeedURL and SUPublicEDKey from Info.plist
|
||||
s_updater_controller = [[SPUStandardUpdaterController alloc]
|
||||
initWithStartingUpdater:YES
|
||||
updaterDelegate:s_updater_delegate
|
||||
userDriverDelegate:nil];
|
||||
|
||||
if (s_updater_controller) {
|
||||
s_initialized = true;
|
||||
BOOST_LOG_TRIVIAL(info) << "UpdateManager: Sparkle 2 initialized successfully";
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(error) << "UpdateManager: Failed to initialize Sparkle 2";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateManager::check_for_updates_interactive()
|
||||
{
|
||||
if (!s_initialized || !s_updater_controller) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "UpdateManager::check_for_updates_interactive called before init";
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "UpdateManager: User-triggered update check (Sparkle)";
|
||||
|
||||
@autoreleasepool {
|
||||
[s_updater_controller checkForUpdates:nil];
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateManager::check_for_updates_background()
|
||||
{
|
||||
if (!s_initialized || !s_updater_controller) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "UpdateManager::check_for_updates_background called before init";
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "UpdateManager: Background update check (Sparkle)";
|
||||
|
||||
@autoreleasepool {
|
||||
SPUUpdater *updater = s_updater_controller.updater;
|
||||
if (updater) {
|
||||
[updater checkForUpdatesInBackground];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateManager::shutdown()
|
||||
{
|
||||
if (!s_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "UpdateManager: Shutting down Sparkle";
|
||||
|
||||
@autoreleasepool {
|
||||
// Sparkle handles cleanup automatically when the controller is released
|
||||
s_updater_controller = nil;
|
||||
s_updater_delegate = nil;
|
||||
}
|
||||
|
||||
s_initialized = false;
|
||||
}
|
||||
|
||||
void UpdateManager::set_automatic_check_enabled(bool enabled)
|
||||
{
|
||||
if (!s_initialized || !s_updater_controller) {
|
||||
return;
|
||||
}
|
||||
|
||||
@autoreleasepool {
|
||||
SPUUpdater *updater = s_updater_controller.updater;
|
||||
if (updater) {
|
||||
updater.automaticallyChecksForUpdates = enabled;
|
||||
BOOST_LOG_TRIVIAL(info) << "UpdateManager: Automatic check enabled: " << enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#else // !ORCA_HAS_SPARKLE
|
||||
|
||||
// Stub implementation when Sparkle is not available
|
||||
|
||||
void UpdateManager::init(const std::string& appcast_url, const std::string& public_key)
|
||||
{
|
||||
s_appcast_url = appcast_url;
|
||||
s_public_key = public_key;
|
||||
s_initialized = true;
|
||||
BOOST_LOG_TRIVIAL(info) << "UpdateManager: Sparkle not available (stub)";
|
||||
}
|
||||
|
||||
void UpdateManager::check_for_updates_interactive()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "UpdateManager: Interactive update check not available (no Sparkle)";
|
||||
}
|
||||
|
||||
void UpdateManager::check_for_updates_background()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << "UpdateManager: Background update check not available (no Sparkle)";
|
||||
}
|
||||
|
||||
void UpdateManager::shutdown()
|
||||
{
|
||||
s_initialized = false;
|
||||
}
|
||||
|
||||
void UpdateManager::set_automatic_check_enabled(bool enabled)
|
||||
{
|
||||
// No-op
|
||||
}
|
||||
|
||||
#endif // ORCA_HAS_SPARKLE
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // __APPLE__
|
||||
@@ -1,5 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
// Ed25519 public key for verifying update signatures (used by load_signature_key)
|
||||
#define ORCA_UPDATER_SIG_KEY_B64 "@ORCA_UPDATER_SIG_KEY_B64@"
|
||||
#define ORCA_UPDATER_SIG_KEY_AVAILABLE @ORCA_UPDATER_SIG_KEY_AVAILABLE@
|
||||
|
||||
|
||||
Reference in New Issue
Block a user