diff --git a/.github/workflows/build_all.yml b/.github/workflows/build_all.yml index 2f378b3777..195c2ee6c8 100644 --- a/.github/workflows/build_all.yml +++ b/.github/workflows/build_all.yml @@ -58,23 +58,41 @@ jobs: os: ubuntu-24.04 build-deps-only: ${{ inputs.build-deps-only || false }} secrets: inherit - build_all: - strategy: - fail-fast: false - matrix: - include: - - os: windows-latest - - os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-14' }} - arch: arm64 + build_windows: # Don't run scheduled builds on forks: if: ${{ !cancelled() && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }} uses: ./.github/workflows/build_check_cache.yml with: - os: ${{ matrix.os }} + os: windows-latest + build-deps-only: ${{ inputs.build-deps-only || false }} + force-build: ${{ github.event_name == 'schedule' }} + secrets: inherit + build_macos_arch: + strategy: + fail-fast: false + matrix: + arch: + - arm64 + - x86_64 + # Don't run scheduled builds on forks: + if: ${{ !cancelled() && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }} + uses: ./.github/workflows/build_check_cache.yml + with: + os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-14' }} arch: ${{ matrix.arch }} build-deps-only: ${{ inputs.build-deps-only || false }} force-build: ${{ github.event_name == 'schedule' }} secrets: inherit + build_macos_universal: + name: Build macOS Universal + needs: build_macos_arch + if: ${{ !cancelled() && needs.build_macos_arch.result == 'success' && !inputs.build-deps-only && (github.event_name != 'schedule' || github.repository == 'OrcaSlicer/OrcaSlicer') }} + uses: ./.github/workflows/build_orca.yml + with: + os: ${{ vars.SELF_HOSTED && 'orca-macos-arm64' || 'macos-14' }} + arch: universal + macos-combine-only: true + secrets: inherit unit_tests: name: Unit Tests runs-on: ubuntu-24.04 diff --git a/.github/workflows/build_check_cache.yml b/.github/workflows/build_check_cache.yml index 3d6d6ac35a..cbed96824b 100644 --- a/.github/workflows/build_check_cache.yml +++ b/.github/workflows/build_check_cache.yml @@ -30,16 +30,16 @@ jobs: with: lfs: 'true' - - name: set outputs - id: set_outputs - env: - # Normalize macOS runner names so self-hosted and GitHub-hosted produce the same cache key - cache-os: ${{ contains(inputs.os, 'macos') && 'macos-arm64' || inputs.os }} - dep-folder-name: ${{ !contains(inputs.os, 'macos') && '/OrcaSlicer_dep' || '' }} - output-cmd: ${{ inputs.os == 'windows-latest' && '$env:GITHUB_OUTPUT' || '"$GITHUB_OUTPUT"'}} - run: | - echo cache-key=${{ env.cache-os }}-cache-orcaslicer_deps-build-${{ hashFiles('deps/**') }} >> ${{ env.output-cmd }} - echo cache-path=${{ github.workspace }}/deps/build${{ env.dep-folder-name }} >> ${{ env.output-cmd }} + - name: set outputs + id: set_outputs + env: + # Keep macOS cache keys and paths architecture-specific. + cache-os: ${{ contains(inputs.os, 'macos') && format('macos-{0}', inputs.arch) || inputs.os }} + dep-folder-name: ${{ contains(inputs.os, 'macos') && format('/{0}', inputs.arch) || '/OrcaSlicer_dep' }} + output-cmd: ${{ inputs.os == 'windows-latest' && '$env:GITHUB_OUTPUT' || '"$GITHUB_OUTPUT"'}} + run: | + echo cache-key=${{ env.cache-os }}-cache-orcaslicer_deps-build-${{ hashFiles('deps/**') }} >> ${{ env.output-cmd }} + echo cache-path=${{ github.workspace }}/deps/build${{ env.dep-folder-name }} >> ${{ env.output-cmd }} - name: load cache id: cache_deps diff --git a/.github/workflows/build_deps.yml b/.github/workflows/build_deps.yml index 7756a52d7b..0e21091be4 100644 --- a/.github/workflows/build_deps.yml +++ b/.github/workflows/build_deps.yml @@ -80,11 +80,9 @@ jobs: if [ -z "${{ vars.SELF_HOSTED }}" ]; then brew install automake texinfo libtool fi - ./build_release_macos.sh -dx ${{ !vars.SELF_HOSTED && '-1' || '' }} -a universal -t 10.15 - for arch in arm64 x86_64; do - (cd "${{ github.workspace }}/deps/build/${arch}" && \ - find . -mindepth 1 -maxdepth 1 ! -name 'OrcaSlicer_dep' -exec rm -rf {} +) - done + ./build_release_macos.sh -dx ${{ !vars.SELF_HOSTED && '-1' || '' }} -a ${{ inputs.arch }} -t 10.15 + (cd "${{ github.workspace }}/deps/build/${{ inputs.arch }}" && \ + find . -mindepth 1 -maxdepth 1 ! -name 'OrcaSlicer_dep' -exec rm -rf {} +) - name: Apt-Install Dependencies diff --git a/.github/workflows/build_orca.yml b/.github/workflows/build_orca.yml index 4a8528bf11..3cc89d30a7 100644 --- a/.github/workflows/build_orca.yml +++ b/.github/workflows/build_orca.yml @@ -2,10 +2,10 @@ on: workflow_call: inputs: cache-key: - required: true + required: false type: string cache-path: - required: true + required: false type: string os: required: true @@ -13,6 +13,10 @@ on: arch: required: false type: string + macos-combine-only: + required: false + type: boolean + default: false jobs: build_orca: @@ -31,6 +35,7 @@ jobs: lfs: 'true' - name: load cached deps + if: ${{ !(contains(inputs.os, 'macos') && inputs.macos-combine-only) }} uses: actions/cache@v5 with: path: ${{ inputs.cache-path }} @@ -86,16 +91,16 @@ jobs: # Mac - name: Install tools mac - if: contains(inputs.os, 'macos') + if: contains(inputs.os, 'macos') && !inputs.macos-combine-only run: | if [ -z "${{ vars.SELF_HOSTED }}" ]; then brew install libtool brew list fi - mkdir -p ${{ github.workspace }}/deps/build + mkdir -p ${{ github.workspace }}/deps/build/${{ inputs.arch }} - name: Free disk space - if: contains(inputs.os, 'macos') && !vars.SELF_HOSTED + if: contains(inputs.os, 'macos') && !inputs.macos-combine-only && !vars.SELF_HOSTED run: | df -hI /dev/disk3s1s1 sudo find /Applications -maxdepth 1 -type d -name "Xcode_*.app" ! -name "Xcode_15.4.app" -exec rm -rf {} + @@ -103,14 +108,65 @@ jobs: df -hI /dev/disk3s1s1 - name: Build slicer mac - if: contains(inputs.os, 'macos') + if: contains(inputs.os, 'macos') && !inputs.macos-combine-only working-directory: ${{ github.workspace }} run: | - ./build_release_macos.sh -s -n -x ${{ !vars.SELF_HOSTED && '-1' || '' }} -a universal -t 10.15 + ./build_release_macos.sh -s -n -x ${{ !vars.SELF_HOSTED && '-1' || '' }} -a ${{ inputs.arch }} -t 10.15 + + - name: Pack macOS app bundle ${{ inputs.arch }} + if: contains(inputs.os, 'macos') && !inputs.macos-combine-only + working-directory: ${{ github.workspace }} + run: | + tar -czvf OrcaSlicer_Mac_bundle_${{ inputs.arch }}_${{ github.sha }}.tar.gz -C build/${{ inputs.arch }} OrcaSlicer + + - name: Upload macOS app bundle ${{ inputs.arch }} + if: contains(inputs.os, 'macos') && !inputs.macos-combine-only + uses: actions/upload-artifact@v6 + with: + name: OrcaSlicer_Mac_bundle_${{ inputs.arch }}_${{ github.sha }} + path: ${{ github.workspace }}/OrcaSlicer_Mac_bundle_${{ inputs.arch }}_${{ github.sha }}.tar.gz + + - name: Download macOS arm64 app bundle + if: contains(inputs.os, 'macos') && inputs.macos-combine-only + uses: actions/download-artifact@v7 + with: + name: OrcaSlicer_Mac_bundle_arm64_${{ github.sha }} + path: ${{ github.workspace }}/mac_bundles/arm64 + + - name: Download macOS x86_64 app bundle + if: contains(inputs.os, 'macos') && inputs.macos-combine-only + uses: actions/download-artifact@v7 + with: + name: OrcaSlicer_Mac_bundle_x86_64_${{ github.sha }} + path: ${{ github.workspace }}/mac_bundles/x86_64 + + - name: Extract macOS app bundles + if: contains(inputs.os, 'macos') && inputs.macos-combine-only + working-directory: ${{ github.workspace }} + run: | + mkdir -p build/arm64 build/x86_64 + arm_bundle=$(find "${{ github.workspace }}/mac_bundles/arm64" -name '*.tar.gz' -print -quit) + x86_bundle=$(find "${{ github.workspace }}/mac_bundles/x86_64" -name '*.tar.gz' -print -quit) + tar -xzvf "$arm_bundle" -C "${{ github.workspace }}/build/arm64" + tar -xzvf "$x86_bundle" -C "${{ github.workspace }}/build/x86_64" + + - name: Build universal mac app bundle + if: contains(inputs.os, 'macos') && inputs.macos-combine-only + working-directory: ${{ github.workspace }} + run: | + ./build_release_macos.sh -u -x ${{ !vars.SELF_HOSTED && '-1' || '' }} -a universal -t 10.15 + + - name: Delete intermediate per-arch artifacts + if: contains(inputs.os, 'macos') && inputs.macos-combine-only + uses: geekyeggo/delete-artifact@v5 + with: + name: | + OrcaSlicer_Mac_bundle_arm64_${{ github.sha }} + OrcaSlicer_Mac_bundle_x86_64_${{ github.sha }} # 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/')) && contains(inputs.os, 'macos') + if: github.repository == 'OrcaSlicer/OrcaSlicer' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) && contains(inputs.os, 'macos') && inputs.macos-combine-only working-directory: ${{ github.workspace }} env: BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }} @@ -164,7 +220,7 @@ jobs: fi - name: Create DMG without notary - if: github.ref != 'refs/heads/main' && contains(inputs.os, 'macos') + if: github.ref != 'refs/heads/main' && contains(inputs.os, 'macos') && inputs.macos-combine-only working-directory: ${{ github.workspace }} run: | mkdir -p ${{ github.workspace }}/build/universal/OrcaSlicer_dmg @@ -183,14 +239,14 @@ jobs: fi - name: Upload artifacts mac - if: contains(inputs.os, 'macos') + if: contains(inputs.os, 'macos') && inputs.macos-combine-only uses: actions/upload-artifact@v6 with: name: OrcaSlicer_Mac_universal_${{ env.ver }} path: ${{ github.workspace }}/OrcaSlicer_Mac_universal_${{ env.ver }}.dmg - name: Upload OrcaSlicer_profile_validator DMG mac - if: contains(inputs.os, 'macos') + if: contains(inputs.os, 'macos') && inputs.macos-combine-only uses: actions/upload-artifact@v6 with: name: OrcaSlicer_profile_validator_Mac_universal_DMG_${{ env.ver }} @@ -198,7 +254,7 @@ jobs: if-no-files-found: ignore - name: Deploy Mac release - if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main' && contains(inputs.os, 'macos') + if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main' && contains(inputs.os, 'macos') && inputs.macos-combine-only uses: WebFreak001/deploy-nightly@v3.2.0 with: upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label} @@ -209,7 +265,7 @@ jobs: max_releases: 1 # optional, if there are more releases than this matching the asset_name, the oldest ones are going to be deleted - name: Deploy Mac OrcaSlicer_profile_validator DMG release - if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main' && contains(inputs.os, 'macos') + if: github.repository == 'OrcaSlicer/OrcaSlicer' && github.ref == 'refs/heads/main' && contains(inputs.os, 'macos') && inputs.macos-combine-only uses: WebFreak001/deploy-nightly@v3.2.0 with: upload_url: https://uploads.github.com/repos/OrcaSlicer/OrcaSlicer/releases/137995723/assets{?name,label} diff --git a/build_release_macos.sh b/build_release_macos.sh index 1999c62b92..dffeb3d4b1 100755 --- a/build_release_macos.sh +++ b/build_release_macos.sh @@ -3,7 +3,7 @@ set -e set -o pipefail -while getopts ":dpa:snt:xbc:1Th" opt; do +while getopts ":dpa:snt:xbc:1Tuh" opt; do case "${opt}" in d ) export BUILD_TARGET="deps" @@ -40,10 +40,14 @@ while getopts ":dpa:snt:xbc:1Th" opt; do T ) export BUILD_TESTS="1" ;; + u ) + export BUILD_TARGET="universal" + ;; h ) echo "Usage: ./build_release_macos.sh [-d]" echo " -d: Build deps only" echo " -a: Set ARCHITECTURE (arm64 or x86_64 or universal)" echo " -s: Build slicer only" + echo " -u: Build universal app only (requires existing arm64 and x86_64 app bundles)" echo " -n: Nightly build" echo " -t: Specify minimum version of the target platform, default is 11.3" echo " -x: Use Ninja Multi-Config CMake generator, default is Xcode" @@ -249,48 +253,54 @@ function build_slicer() { done } +function lipo_dir() { + local universal_dir="$1" + local x86_64_dir="$2" + + # Find all Mach-O files in the universal (arm64-based) copy and lipo them + while IFS= read -r -d '' f; do + local rel="${f#"$universal_dir"/}" + local x86="$x86_64_dir/$rel" + if [ -f "$x86" ]; then + echo " lipo: $rel" + lipo -create "$f" "$x86" -output "$f.tmp" + mv "$f.tmp" "$f" + else + echo " warning: no x86_64 counterpart for $rel, keeping arm64 only" + fi + done < <(find "$universal_dir" -type f -print0 | while IFS= read -r -d '' candidate; do + if file "$candidate" | grep -q "Mach-O"; then + printf '%s\0' "$candidate" + fi + done) +} + function build_universal() { echo "Building universal binary..." PROJECT_BUILD_DIR="$PROJECT_DIR/build/$ARCH" - - # Create universal binary - echo "Creating universal binary..." - # PROJECT_BUILD_DIR="$PROJECT_DIR/build_Universal" + ARM64_APP="$PROJECT_DIR/build/arm64/OrcaSlicer/OrcaSlicer.app" + X86_64_APP="$PROJECT_DIR/build/x86_64/OrcaSlicer/OrcaSlicer.app" + mkdir -p "$PROJECT_BUILD_DIR/OrcaSlicer" UNIVERSAL_APP="$PROJECT_BUILD_DIR/OrcaSlicer/OrcaSlicer.app" rm -rf "$UNIVERSAL_APP" - cp -R "$PROJECT_DIR/build/arm64/OrcaSlicer/OrcaSlicer.app" "$UNIVERSAL_APP" - - # Get the binary path inside the .app bundle - BINARY_PATH="Contents/MacOS/OrcaSlicer" - - # Create universal binary using lipo - lipo -create \ - "$PROJECT_DIR/build/x86_64/OrcaSlicer/OrcaSlicer.app/$BINARY_PATH" \ - "$PROJECT_DIR/build/arm64/OrcaSlicer/OrcaSlicer.app/$BINARY_PATH" \ - -output "$UNIVERSAL_APP/$BINARY_PATH" - - echo "Universal binary created at $UNIVERSAL_APP" - + cp -R "$ARM64_APP" "$UNIVERSAL_APP" + + echo "Creating universal binaries for OrcaSlicer.app..." + lipo_dir "$UNIVERSAL_APP" "$X86_64_APP" + echo "Universal OrcaSlicer.app created at $UNIVERSAL_APP" + # Create universal binary for profile validator if it exists - if [ -f "$PROJECT_DIR/build/arm64/OrcaSlicer/OrcaSlicer_profile_validator.app/Contents/MacOS/OrcaSlicer_profile_validator" ] && \ - [ -f "$PROJECT_DIR/build/x86_64/OrcaSlicer/OrcaSlicer_profile_validator.app/Contents/MacOS/OrcaSlicer_profile_validator" ]; then - echo "Creating universal binary for OrcaSlicer_profile_validator..." + ARM64_VALIDATOR="$PROJECT_DIR/build/arm64/OrcaSlicer/OrcaSlicer_profile_validator.app" + X86_64_VALIDATOR="$PROJECT_DIR/build/x86_64/OrcaSlicer/OrcaSlicer_profile_validator.app" + if [ -d "$ARM64_VALIDATOR" ] && [ -d "$X86_64_VALIDATOR" ]; then + echo "Creating universal binaries for OrcaSlicer_profile_validator.app..." UNIVERSAL_VALIDATOR_APP="$PROJECT_BUILD_DIR/OrcaSlicer/OrcaSlicer_profile_validator.app" rm -rf "$UNIVERSAL_VALIDATOR_APP" - cp -R "$PROJECT_DIR/build/arm64/OrcaSlicer/OrcaSlicer_profile_validator.app" "$UNIVERSAL_VALIDATOR_APP" - - # Get the binary path inside the profile validator .app bundle - VALIDATOR_BINARY_PATH="Contents/MacOS/OrcaSlicer_profile_validator" - - # Create universal binary using lipo - lipo -create \ - "$PROJECT_DIR/build/x86_64/OrcaSlicer/OrcaSlicer_profile_validator.app/$VALIDATOR_BINARY_PATH" \ - "$PROJECT_DIR/build/arm64/OrcaSlicer/OrcaSlicer_profile_validator.app/$VALIDATOR_BINARY_PATH" \ - -output "$UNIVERSAL_VALIDATOR_APP/$VALIDATOR_BINARY_PATH" - - echo "Universal binary for OrcaSlicer_profile_validator created at $UNIVERSAL_VALIDATOR_APP" + cp -R "$ARM64_VALIDATOR" "$UNIVERSAL_VALIDATOR_APP" + lipo_dir "$UNIVERSAL_VALIDATOR_APP" "$X86_64_VALIDATOR" + echo "Universal OrcaSlicer_profile_validator.app created at $UNIVERSAL_VALIDATOR_APP" fi } @@ -305,13 +315,16 @@ case "${BUILD_TARGET}" in slicer) build_slicer ;; + universal) + build_universal + ;; *) - echo "Unknown target: $BUILD_TARGET. Available targets: deps, slicer, all." + echo "Unknown target: $BUILD_TARGET. Available targets: deps, slicer, universal, all." exit 1 ;; esac -if [ "$ARCH" = "universal" ] && [ "$BUILD_TARGET" != "deps" ]; then +if [ "$ARCH" = "universal" ] && { [ "$BUILD_TARGET" = "all" ] || [ "$BUILD_TARGET" = "slicer" ]; }; then build_universal fi