diff --git a/.github/workflows/reusable-build-workflow.yml b/.github/.obsolete/reusable-build-workflow.yml similarity index 100% rename from .github/workflows/reusable-build-workflow.yml rename to .github/.obsolete/reusable-build-workflow.yml diff --git a/.github/workflows/alpha.yml b/.github/workflows/alpha.yml index 3998eb50..9066ff6a 100644 --- a/.github/workflows/alpha.yml +++ b/.github/workflows/alpha.yml @@ -10,9 +10,8 @@ concurrency: cancel-in-progress: true jobs: - Reuseable-build: - # uses: ./.github/workflows/reuseable-workflows/reusable-build-workflow.yml - uses: ./.github/workflows/reusable-build-workflow.yml + Reusable-build: + uses: ./.github/workflows/reusable-sidestore-build.yml with: # bundle_id: "com.SideStore.SideStore.Alpha" bundle_id: "com.SideStore.SideStore" diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index c248e730..e33968f3 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -58,14 +58,13 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} LAST_SUCCESS: ${{ env.last_success }} - Reuseable-build: + Reusable-build: if: | always() && (github.event_name == 'push' || (github.event_name == 'schedule' && needs.check-changes.result == 'success' && needs.check-changes.outputs.has_changes == 'true')) needs: check-changes - # uses: ./.github/workflows/reuseable-workflows/reusable-build-workflow.yml - uses: ./.github/workflows/reusable-build-workflow.yml + uses: ./.github/workflows/reusable-sidestore-build.yml with: # bundle_id: "com.SideStore.SideStore.Nightly" bundle_id: "com.SideStore.SideStore" diff --git a/.github/workflows/reusable-sidestore-build.yml b/.github/workflows/reusable-sidestore-build.yml new file mode 100644 index 00000000..f0be4bcb --- /dev/null +++ b/.github/workflows/reusable-sidestore-build.yml @@ -0,0 +1,96 @@ +name: Reusable SideStore Build + +on: + workflow_call: + inputs: + is_beta: + required: false + default: false + type: boolean + publish: + required: false + default: false + type: boolean + is_shared_build_num: + required: false + default: true + type: boolean + release_name: + required: true + type: string + release_tag: + required: true + type: string + upstream_tag: + required: true + type: string + upstream_name: + required: true + type: string + bundle_id: + default: com.SideStore.SideStore + required: true + type: string + bundle_id_suffix: + default: '' + required: false + type: string + + secrets: + # GITHUB_TOKEN: + # required: true + CROSS_REPO_PUSH_KEY: + required: true + BUILD_LOG_ZIP_PASSWORD: + required: false + +jobs: + serialize: + uses: ./.github/workflows/sidestore-serialize.yml + secrets: inherit + + build: + needs: serialize + uses: ./.github/workflows/sidestore-build.yml + with: + is_beta: ${{ inputs.is_beta }} + is_shared_build_num: ${{ inputs.is_shared_build_num }} + release_tag: ${{ inputs.release_tag }} + bundle_id: ${{ inputs.bundle_id }} + short_commit: ${{ needs.serialize.outputs.short-commit }} + bundle_id_suffix: ${{ inputs.bundle_id_suffix }} + secrets: inherit + + tests-build: + if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_BUILD == '1' }} + needs: serialize + uses: ./.github/workflows/sidestore-tests-build.yml + with: + release_tag: ${{ inputs.release_tag }} + short_commit: ${{ needs.serialize.outputs.short-commit }} + secrets: inherit + + tests-run: + if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }} + needs: [serialize, tests-build] + uses: ./.github/workflows/sidestore-tests-run.yml + with: + release_tag: ${{ inputs.release_tag }} + short_commit: ${{ needs.serialize.outputs.short-commit }} + secrets: inherit + + deploy: + needs: [serialize, build, tests-build, tests-run] + uses: ./.github/workflows/sidestore-deploy.yml + with: + is_beta: ${{ inputs.is_beta }} + publish: ${{ inputs.publish }} + release_name: ${{ inputs.release_name }} + release_tag: ${{ inputs.release_tag }} + upstream_tag: ${{ inputs.upstream_tag }} + upstream_name: ${{ inputs.upstream_name }} + version: ${{ needs.build.outputs.version }} + short_commit: ${{ needs.serialize.outputs.short-commit }} + release_channel: ${{ needs.build.outputs.release-channel }} + marketing_version: ${{ needs.build.outputs.marketing-version }} + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/sidestore-build.yml b/.github/workflows/sidestore-build.yml new file mode 100644 index 00000000..d030bc19 --- /dev/null +++ b/.github/workflows/sidestore-build.yml @@ -0,0 +1,401 @@ +name: SideStore Build + +on: + workflow_call: + inputs: + is_beta: + type: boolean + is_shared_build_num: + type: boolean + release_tag: + type: string + bundle_id: + type: string + bundle_id_suffix: + type: string + short_commit: + type: string + secrets: + CROSS_REPO_PUSH_KEY: + required: true + BUILD_LOG_ZIP_PASSWORD: + required: false + outputs: + version: + value: ${{ jobs.build.outputs.version }} + marketing-version: + value: ${{ jobs.build.outputs.marketing-version }} + release-channel: + value: ${{ jobs.build.outputs.release-channel }} + +jobs: + build: + name: Build SideStore - ${{ inputs.release_tag }} + strategy: + fail-fast: false + matrix: + include: + - os: 'macos-15' + version: '16.2' + runs-on: ${{ matrix.os }} + outputs: + version: ${{ steps.version.outputs.version }} + marketing-version: ${{ steps.marketing-version.outputs.MARKETING_VERSION }} + release-channel: ${{ steps.release-channel.outputs.RELEASE_CHANNEL }} + + steps: + - name: Set beta status + run: echo "IS_BETA=${{ inputs.is_beta }}" >> $GITHUB_ENV + shell: bash + + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + fetch-depth: 0 + + - name: Install dependencies - ldid & xcbeautify + run: | + brew install ldid xcbeautify + + - name: Set ref based on is_shared_build_num + if: ${{ inputs.is_beta }} + id: set_ref + run: | + if [ "${{ inputs.is_shared_build_num }}" == "true" ]; then + echo "ref=main" >> $GITHUB_ENV + else + echo "ref=${{ inputs.release_tag }}" >> $GITHUB_ENV + fi + shell: bash + + - name: Checkout SideStore/beta-build-num repo + if: ${{ inputs.is_beta }} + uses: actions/checkout@v4 + with: + repository: 'SideStore/beta-build-num' + ref: ${{ env.ref }} + token: ${{ secrets.CROSS_REPO_PUSH_KEY }} + path: 'SideStore/beta-build-num' + + - name: Copy build_number.txt to repo root + if: ${{ inputs.is_beta }} + run: | + cp SideStore/beta-build-num/build_number.txt . + echo "cat build_number.txt" + cat build_number.txt + shell: bash + + - name: Echo Build.xcconfig + run: | + echo "cat Build.xcconfig" + cat Build.xcconfig + shell: bash + + - name: Set Release Channel info for build number bumper + id: release-channel + run: | + RELEASE_CHANNEL="${{ inputs.release_tag }}" + echo "RELEASE_CHANNEL=${RELEASE_CHANNEL}" >> $GITHUB_ENV + echo "RELEASE_CHANNEL=${RELEASE_CHANNEL}" >> $GITHUB_OUTPUT + echo "RELEASE_CHANNEL=${RELEASE_CHANNEL}" + shell: bash + + - name: Increase build number for beta builds + if: ${{ inputs.is_beta }} + run: | + bash .github/workflows/increase-beta-build-num.sh + shell: bash + + - name: Extract MARKETING_VERSION from Build.xcconfig + id: version + run: | + version=$(grep MARKETING_VERSION Build.xcconfig | sed -e 's/MARKETING_VERSION = //g') + echo "version=$version" >> $GITHUB_OUTPUT + echo "version=$version" + shell: bash + + - name: Set MARKETING_VERSION + if: ${{ inputs.is_beta }} + id: marketing-version + run: | + # Extract version number (e.g., "0.6.0") + version=$(echo "${{ steps.version.outputs.version }}" | sed -E 's/^[^0-9]*([0-9]+\.[0-9]+\.[0-9]+).*/\1/') + # Extract date (YYYYMMDD) (e.g., "20250205") + date=$(echo "${{ steps.version.outputs.version }}" | sed -E 's/.*\.([0-9]{4})\.([0-9]{2})\.([0-9]{2})\..*/\1\2\3/') + # Extract build number (e.g., "2") + build_num=$(echo "${{ steps.version.outputs.version }}" | sed -E 's/.*\.([0-9]+)\+.*/\1/') + + # Combine them into the final output + MARKETING_VERSION="${version}-${date}.${build_num}+${{ inputs.short_commit }}" + + echo "MARKETING_VERSION=$MARKETING_VERSION" >> $GITHUB_ENV + echo "MARKETING_VERSION=$MARKETING_VERSION" >> $GITHUB_OUTPUT + echo "MARKETING_VERSION=$MARKETING_VERSION" + shell: bash + + - name: Echo Updated Build.xcconfig, build_number.txt + if: ${{ inputs.is_beta }} + run: | + cat Build.xcconfig + cat build_number.txt + shell: bash + + - name: Setup Xcode + uses: maxim-lobanov/setup-xcode@v1.6.0 + with: + xcode-version: ${{ matrix.version }} + + - name: (Build) Restore Xcode & SwiftPM Cache (Exact match) + id: xcode-cache-restore + uses: actions/cache/restore@v3 + with: + path: | + ~/Library/Developer/Xcode/DerivedData + ~/Library/Caches/org.swift.swiftpm + key: xcode-cache-build-${{ github.ref_name }}-${{ github.sha }} + + - name: (Build) Restore Xcode & SwiftPM Cache (Last Available) + id: xcode-cache-restore-recent + uses: actions/cache/restore@v3 + with: + path: | + ~/Library/Developer/Xcode/DerivedData + ~/Library/Caches/org.swift.swiftpm + key: xcode-cache-build-${{ github.ref_name }}- + + # - name: (Build) Cache Build + # uses: irgaly/xcode-cache@v1.8.1 + # with: + # key: xcode-cache-deriveddata-build-${{ github.ref_name }}-${{ github.sha }} + # restore-keys: xcode-cache-deriveddata-build-${{ github.ref_name }}- + # swiftpm-cache-key: xcode-cache-sourcedata-build-${{ github.ref_name }}-${{ github.sha }} + # swiftpm-cache-restore-keys: | + # xcode-cache-sourcedata-build-${{ github.ref_name }}- + + - name: (Build) Restore Pods from Cache (Exact match) + id: pods-restore + uses: actions/cache/restore@v3 + with: + path: | + ./Podfile.lock + ./Pods/ + ./AltStore.xcworkspace/ + key: pods-cache-build-${{ github.ref_name }}-${{ hashFiles('Podfile') }} + # restore-keys: | # commented out to strictly check cache for this particular podfile + # pods-cache- + + - name: (Build) Restore Pods from Cache (Last Available) + if: ${{ steps.pods-restore.outputs.cache-hit != 'true' }} + id: pods-restore-recent + uses: actions/cache/restore@v3 + with: + path: | + ./Podfile.lock + ./Pods/ + ./AltStore.xcworkspace/ + key: pods-cache-build-${{ github.ref_name }}- + + + - name: (Build) Install CocoaPods + run: pod install + shell: bash + + - name: (Build) Save Pods to Cache + id: save-pods + if: ${{ steps.pods-restore.outputs.cache-hit != 'true' }} + uses: actions/cache/save@v3 + with: + path: | + ./Podfile.lock + ./Pods/ + ./AltStore.xcworkspace/ + key: pods-cache-build-${{ github.ref_name }}-${{ hashFiles('Podfile') }} + + - name: (Build) Clean previous build artifacts + # using 'tee' to intercept stdout and log for detailed build-log + run: | + make clean + mkdir -p build/logs + shell: bash + + - name: (Build) List Files and derived data + if: always() + shell: bash + run: | + echo ">>>>>>>>> Workdir <<<<<<<<<<" + ls -la . + echo "" + + echo ">>>>>>>>> Pods <<<<<<<<<<" + find Pods -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists + echo "" + + echo ">>>>>>>>> SideStore <<<<<<<<<<" + find SideStore -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists + echo "" + + echo ">>>>>>>>> Dependencies <<<<<<<<<<" + find Dependencies -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists + echo "" + + echo ">>>>>>>>> Xcode-Derived-Data <<<<<<<<<<" + ls -la ~/Library/Developer/Xcode/DerivedData || true # List contents if directory exists + echo "" + + - name: Set BundleID Suffix for Sidestore build + run: | + echo "BUNDLE_ID_SUFFIX=${{ inputs.bundle_id_suffix }}" >> $GITHUB_ENV + shell: bash + + + - name: Build SideStore.xcarchive + # using 'tee' to intercept stdout and log for detailed build-log + run: | + NSUnbufferedIO=YES make -B build 2>&1 | tee -a build/logs/build.log | xcbeautify --renderer github-actions && exit ${PIPESTATUS[0]} + shell: bash + + - name: Fakesign app + run: make fakesign | tee -a build/logs/build.log + shell: bash + + - name: Convert to IPA + run: make ipa | tee -a build/logs/build.log + shell: bash + + - name: (Build) Save Xcode & SwiftPM Cache + id: cache-save + if: ${{ steps.xcode-cache-restore.outputs.cache-hit != 'true' }} + uses: actions/cache/save@v3 + with: + path: | + ~/Library/Developer/Xcode/DerivedData + ~/Library/Caches/org.swift.swiftpm + key: xcode-cache-build-${{ github.ref_name }}-${{ github.sha }} + + - name: (Build) List Files and Build artifacts + run: | + echo ">>>>>>>>> Workdir <<<<<<<<<<" + ls -la . + echo "" + + echo ">>>>>>>>> Build <<<<<<<<<<" + find build -maxdepth 3 -exec ls -ld {} + || true # List contents if directory exists + echo "" + + echo ">>>>>>>>> SideStore <<<<<<<<<<" + find SideStore -maxdepth 3 -exec ls -ld {} + || true # List contents if directory exists + echo "" + + echo ">>>>>>>>> SideStore.xcarchive <<<<<<<<<<" + find SideStore.xcarchive -maxdepth 3 -exec ls -ld {} + || true # List contents if directory exists + echo "" + + echo ">>>>>>>>> Xcode-Derived-Data <<<<<<<<<<" + ls -la ~/Library/Developer/Xcode/DerivedData || true # List contents if directory exists + echo "" + shell: bash + + - name: Encrypt build-logs for upload + id: encrypt-build-log + run: | + DEFAULT_BUILD_LOG_PASSWORD=12345 + + BUILD_LOG_ZIP_PASSWORD=${{ secrets.BUILD_LOG_ZIP_PASSWORD }} + BUILD_LOG_ZIP_PASSWORD=${BUILD_LOG_ZIP_PASSWORD:-$DEFAULT_BUILD_LOG_PASSWORD} + + if [ "$BUILD_LOG_ZIP_PASSWORD" == "$DEFAULT_BUILD_LOG_PASSWORD" ]; then + echo "Warning: BUILD_LOG_ZIP_PASSWORD is not set. Defaulting to '${DEFAULT_BUILD_LOG_PASSWORD}'." + fi + + pushd build/logs && zip -e -P "$BUILD_LOG_ZIP_PASSWORD" ../../encrypted-build-logs.zip * || popd + echo "::set-output name=encrypted::true" + shell: bash + + - name: Upload encrypted-build-logs.zip + id: attach-encrypted-build-log + if: ${{ always() && steps.encrypt-build-log.outputs.encrypted == 'true' }} + uses: actions/upload-artifact@v4 + with: + name: encrypted-build-logs-${{ steps.version.outputs.version }}.zip + path: encrypted-build-logs.zip + + - name: Upload SideStore.ipa Artifact + uses: actions/upload-artifact@v4 + with: + name: SideStore-${{ steps.version.outputs.version }}.ipa + path: SideStore.ipa + + - name: Zip dSYMs + run: zip -r -9 ./SideStore.dSYMs.zip ./SideStore.xcarchive/dSYMs + shell: bash + + - name: Upload *.dSYM Artifact + uses: actions/upload-artifact@v4 + with: + name: SideStore-${{ steps.version.outputs.version }}-dSYMs.zip + path: SideStore.dSYMs.zip + + - name: Keep rolling the build numbers for each successful build + if: ${{ inputs.is_beta }} + run: | + pushd SideStore/beta-build-num/ + + echo "Configure Git user (committer details)" + git config user.name "GitHub Actions" + git config user.email "github-actions@github.com" + + echo "Adding files to commit" + git add --verbose build_number.txt + git commit -m " - updated for ${{ inputs.release_tag }} - ${{ inputs.short_commit }} deployment" || echo "No changes to commit" + + echo "Pushing to remote repo" + git push --verbose + popd + shell: bash + + - name: Get last successful commit + id: get_last_commit + run: | + # Try to get the last successful workflow run commit + LAST_SUCCESS_SHA=$(gh run list --branch "${{ github.ref_name }}" --status success --json headSha --jq '.[0].headSha') + echo "LAST_SUCCESS_SHA=$LAST_SUCCESS_SHA" >> $GITHUB_OUTPUT + echo "LAST_SUCCESS_SHA=$LAST_SUCCESS_SHA" >> $GITHUB_ENV + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + shell: bash + + - name: Create release notes + run: | + LAST_SUCCESS_SHA=${{ steps.get_last_commit.outputs.LAST_SUCCESS_SHA}} + echo "Last successful commit SHA: $LAST_SUCCESS_SHA" + + FROM_COMMIT=$LAST_SUCCESS_SHA + # Check if we got a valid SHA + if [ -z "$LAST_SUCCESS_SHA" ] || [ "$LAST_SUCCESS_SHA" = "null" ]; then + echo "No successful run found, using initial commit of branch" + # Get the first commit of the branch (initial commit) + FROM_COMMIT=$(git rev-list --max-parents=0 HEAD) + fi + + python3 update_release_notes.py $FROM_COMMIT ${{ inputs.release_tag }} ${{ github.ref_name }} + # cat release-notes.md + shell: bash + + - name: Upload release-notes.md + uses: actions/upload-artifact@v4 + with: + name: release-notes-${{ inputs.short_commit }}.md + path: release-notes.md + + - name: Upload update_release_notes.py + uses: actions/upload-artifact@v4 + with: + name: update_release_notes-${{ inputs.short_commit }}.py + path: update_release_notes.py + + - name: Upload update_apps.py + uses: actions/upload-artifact@v4 + with: + name: update_apps-${{ inputs.short_commit }}.py + path: update_apps.py \ No newline at end of file diff --git a/.github/workflows/sidestore-deploy.yml b/.github/workflows/sidestore-deploy.yml new file mode 100644 index 00000000..a3b4b67f --- /dev/null +++ b/.github/workflows/sidestore-deploy.yml @@ -0,0 +1,233 @@ +name: SideStore Deploy + +on: + workflow_call: + inputs: + is_beta: + type: boolean + publish: + type: boolean + release_name: + type: string + release_tag: + type: string + upstream_tag: + type: string + upstream_name: + type: string + version: + type: string + short_commit: + type: string + marketing_version: + type: string + release_channel: + type: string + secrets: + CROSS_REPO_PUSH_KEY: + required: true + # GITHUB_TOKEN: + # required: true + +jobs: + deploy: + name: Deploy SideStore - ${{ inputs.release_tag }} + runs-on: macos-15 + steps: + - name: Download IPA artifact + uses: actions/download-artifact@v4 + with: + name: SideStore-${{ inputs.version }}.ipa + + - name: Download dSYM artifact + uses: actions/download-artifact@v4 + with: + name: SideStore-${{ inputs.version }}-dSYMs.zip + + - name: Download encrypted-build-logs artifact + uses: actions/download-artifact@v4 + with: + name: encrypted-build-logs-${{ inputs.version }}.zip + + - name: Download encrypted-tests-build-logs artifact + if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_BUILD == '1' }} + uses: actions/download-artifact@v4 + with: + name: encrypted-tests-build-logs-${{ inputs.short_commit }}.zip + + - name: Download encrypted-tests-run-logs artifact + if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }} + uses: actions/download-artifact@v4 + with: + name: encrypted-tests-run-logs-${{ inputs.short_commit }}.zip + + - name: Download tests-recording artifact + if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }} + uses: actions/download-artifact@v4 + with: + name: tests-recording-${{ inputs.short_commit }}.mp4 + + - name: Download test-results artifact + if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }} + uses: actions/download-artifact@v4 + with: + name: test-results-${{ inputs.short_commit }}.zip + + - name: Download release-notes.md + uses: actions/download-artifact@v4 + with: + name: release-notes-${{ inputs.short_commit }}.md + + - name: Download update_release_notes.py + uses: actions/download-artifact@v4 + with: + name: update_release_notes-${{ inputs.short_commit }}.py + + - name: Download update_apps.py + uses: actions/download-artifact@v4 + with: + name: update_apps-${{ inputs.short_commit }}.py + + - name: Read release notes + id: release_notes + run: | + CONTENT=$(python3 update_release_notes.py --retrieve ${{ inputs.release_tag }}) + echo "content<> $GITHUB_OUTPUT + echo "$CONTENT" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + shell: bash + + - name: List files before upload + run: | + echo ">>>>>>>>> Workdir <<<<<<<<<<" + find . -maxdepth 4 -exec ls -ld {} + || true # List contents if directory exists + echo "" + shell: bash + + - name: Get current date + id: date + run: echo "date=$(date -u +'%c')" >> $GITHUB_OUTPUT + shell: bash + + - name: Get current date in AltStore date form + id: date_altstore + run: echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT + shell: bash + + - name: Upload to releases + uses: IsaacShelton/update-existing-release@v1.3.1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + release: ${{ inputs.release_name }} + tag: ${{ inputs.release_tag }} + prerelease: ${{ inputs.is_beta }} + files: SideStore.ipa SideStore.dSYMs.zip encrypted-build-logs.zip encrypted-tests-build-logs.zip encrypted-tests-run-logs.zip test-results.zip tests-recording.mp4 + body: | + This is an ⚠️ **EXPERIMENTAL** ⚠️ ${{ inputs.release_name }} build for commit [${{ github.sha }}](https://github.com/${{ github.repository }}/commit/${{ github.sha }}). + + ${{ inputs.release_name }} builds are **extremely experimental builds only meant to be used by developers and beta testers. They often contain bugs and experimental features. Use at your own risk!** + + If you want to try out new features early but want a lower chance of bugs, you can look at [SideStore ${{ inputs.upstream_name }}](https://github.com/${{ github.repository }}/releases?q=${{ inputs.upstream_tag }}). + + ## Build Info + + Built at (UTC): `${{ steps.date.outputs.date }}` + Built at (UTC date): `${{ steps.date_altstore.outputs.date }}` + Commit SHA: `${{ github.sha }}` + Version: `${{ inputs.version }}` + + ${{ steps.release_notes.outputs.content }} + + - name: Get formatted date + run: | + FORMATTED_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + echo "Formatted date: $FORMATTED_DATE" + echo "FORMATTED_DATE=$FORMATTED_DATE" >> $GITHUB_ENV + shell: bash + + - name: Get size of IPA in bytes (macOS/Linux) + run: | + if [[ "$(uname)" == "Darwin" ]]; then + # macOS + IPA_SIZE=$(stat -f %z SideStore.ipa) + else + # Linux + IPA_SIZE=$(stat -c %s SideStore.ipa) + fi + echo "IPA size in bytes: $IPA_SIZE" + echo "IPA_SIZE=$IPA_SIZE" >> $GITHUB_ENV + shell: bash + + - name: Compute SHA-256 of IPA + run: | + SHA256_HASH=$(shasum -a 256 SideStore.ipa | awk '{ print $1 }') + echo "SHA-256 Hash: $SHA256_HASH" + echo "SHA256_HASH=$SHA256_HASH" >> $GITHUB_ENV + shell: bash + + - name: Set Release Info variables + run: | + # Format localized description + LOCALIZED_DESCRIPTION=$(cat <> $GITHUB_ENV + echo "BUNDLE_IDENTIFIER=${{ inputs.bundle_id }}" >> $GITHUB_ENV + echo "VERSION_IPA=${{ inputs.marketing_version }}" >> $GITHUB_ENV + echo "VERSION_DATE=$FORMATTED_DATE" >> $GITHUB_ENV + echo "RELEASE_CHANNEL=${{ inputs.release_channel }}" >> $GITHUB_ENV + echo "SIZE=$IPA_SIZE" >> $GITHUB_ENV + echo "SHA256=$SHA256_HASH" >> $GITHUB_ENV + echo "DOWNLOAD_URL=https://github.com/SideStore/SideStore/releases/download/${{ inputs.release_tag }}/SideStore.ipa" >> $GITHUB_ENV + + # multiline strings + echo "LOCALIZED_DESCRIPTION<> $GITHUB_ENV + echo "$LOCALIZED_DESCRIPTION" >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + shell: bash + + - name: Check if Publish updates is set + id: check_publish + run: | + echo "Publish updates to source.json = ${{ inputs.publish }}" + shell: bash + + - name: Checkout SideStore/apps-v2.json + if: ${{ inputs.is_beta && inputs.publish }} + uses: actions/checkout@v4 + with: + repository: 'SideStore/apps-v2.json' + ref: 'main' # this branch is shared by all beta builds, so beta build workflows are serialized + token: ${{ secrets.CROSS_REPO_PUSH_KEY }} + path: 'SideStore/apps-v2.json' + + # for stable builds, let the user manually edit the source.json + - name: Publish to SideStore/apps-v2.json + if: ${{ inputs.is_beta && inputs.publish }} + id: publish-release + shell: bash + run: | + # Copy and execute the update script + pushd SideStore/apps-v2.json/ + + # Configure Git user (committer details) + git config user.name "GitHub Actions" + git config user.email "github-actions@github.com" + + # update the source.json + python3 ../../update_apps.py "./_includes/source.json" + + # Commit changes and push using SSH + git add --verbose ./_includes/source.json + git commit -m " - updated for ${{ inputs.short_commit }} deployment" || echo "No changes to commit" + + git push --verbose + popd diff --git a/.github/workflows/sidestore-serialize.yml b/.github/workflows/sidestore-serialize.yml new file mode 100644 index 00000000..b5df50a7 --- /dev/null +++ b/.github/workflows/sidestore-serialize.yml @@ -0,0 +1,29 @@ +name: SideStore Serialize + +on: + workflow_call: + outputs: + short-commit: + value: ${{ jobs.serialize.outputs.short-commit }} + +jobs: + serialize: + name: Wait for other jobs + # since build cache, test-build cache, test-run cache are involved, out of order exec if serialization is on individual jobs will wreak all sorts of havoc + # so we serialize on the entire workflow + concurrency: + group: serialize-workflow + strategy: + fail-fast: false + runs-on: 'macos-15' + steps: + - run: echo "No other contending jobs are running now..." + - name: Set short commit hash + id: commit-id + run: | + # SHORT_COMMIT="${{ github.sha }}" + SHORT_COMMIT=${GITHUB_SHA:0:7} + echo "Short commit hash: $SHORT_COMMIT" + echo "SHORT_COMMIT=$SHORT_COMMIT" >> $GITHUB_OUTPUT + outputs: + short-commit: ${{ steps.commit-id.outputs.SHORT_COMMIT }} \ No newline at end of file diff --git a/.github/workflows/sidestore-tests-build.yml b/.github/workflows/sidestore-tests-build.yml new file mode 100644 index 00000000..a0643366 --- /dev/null +++ b/.github/workflows/sidestore-tests-build.yml @@ -0,0 +1,204 @@ +name: SideStore Tests Build + +on: + workflow_call: + inputs: + release_tag: + type: string + short_commit: + type: string + secrets: + BUILD_LOG_ZIP_PASSWORD: + required: false + +jobs: + tests-build: + name: Tests-Build SideStore - ${{ inputs.release_tag }} + if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_BUILD == '1' }} + strategy: + fail-fast: false + matrix: + include: + - os: 'macos-15' + version: '16.2' + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install dependencies - xcbeautify + run: | + brew install xcbeautify + shell: bash + + - name: Setup Xcode + uses: maxim-lobanov/setup-xcode@v1.6.0 + with: + xcode-version: '16.2' + + # - name: (Tests-Build) Cache Build + # uses: irgaly/xcode-cache@v1.8.1 + # with: + # key: xcode-cache-deriveddata-test-${{ github.ref_name }}-${{ github.sha }} + # # tests shouldn't restore cache unless it is same build + # # restore-keys: xcode-cache-deriveddata-test-${{ github.ref_name }}- + # swiftpm-cache-key: xcode-cache-sourcedata-test-${{ github.ref_name }}-${{ github.sha }} + # swiftpm-cache-restore-keys: | + # xcode-cache-sourcedata-test-${{ github.ref_name }}- + # delete-used-deriveddata-cache: true + + - name: (Tests-Build) Restore Xcode & SwiftPM Cache (Exact match) + id: xcode-cache-restore + uses: actions/cache/restore@v3 + with: + path: | + ~/Library/Developer/Xcode/DerivedData + ~/Library/Caches/org.swift.swiftpm + key: xcode-cache-tests-${{ github.ref_name }}-${{ github.sha }} + + - name: (Tests-Build) Restore Xcode & SwiftPM Cache (Last Available) + id: xcode-cache-restore-recent + uses: actions/cache/restore@v3 + with: + path: | + ~/Library/Developer/Xcode/DerivedData + ~/Library/Caches/org.swift.swiftpm + key: xcode-cache-tests-${{ github.ref_name }}- + + - name: (Tests-Build) Restore Pods from Cache (Exact match) + id: pods-restore + uses: actions/cache/restore@v3 + with: + path: | + ./Podfile.lock + ./Pods/ + ./AltStore.xcworkspace/ + key: pods-cache-test-${{ github.ref_name }}-${{ hashFiles('Podfile') }} + + - name: (Tests-Build) Restore Pods from Cache (Last Available) + if: ${{ steps.pods-restore.outputs.cache-hit != 'true' }} + id: pods-restore-recent + uses: actions/cache/restore@v3 + with: + path: | + ./Podfile.lock + ./Pods/ + ./AltStore.xcworkspace/ + key: pods-cache-test-${{ github.ref_name }}- + + - name: (Tests-Build) Install CocoaPods + run: pod install + shell: bash + + - name: (Tests-Build) Save Pods to Cache + if: ${{ steps.pods-restore.outputs.cache-hit != 'true' }} + uses: actions/cache/save@v3 + with: + path: | + ./Podfile.lock + ./Pods/ + ./AltStore.xcworkspace/ + key: pods-cache-test-${{ github.ref_name }}-${{ hashFiles('Podfile') }} + + - name: Clean Derived Data (if required) + if: ${{ vars.PERFORM_CLEAN_TESTS_BUILD == '1' }} + run: | + rm -rf ~/Library/Developer/Xcode/DerivedData/ + make clean + xcodebuild clean + shell: bash + + - name: (Tests-Build) Clean previous build artifacts + run: | + make clean + mkdir -p build/logs + shell: bash + + - name: (Tests-Build) List Files and derived data + shell: bash + run: | + echo ">>>>>>>>> Workdir <<<<<<<<<<" + ls -la . + echo "" + + echo ">>>>>>>>> Pods <<<<<<<<<<" + find Pods -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists + echo "" + + echo ">>>>>>>>> SideStore <<<<<<<<<<" + find SideStore -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists + echo "" + + echo ">>>>>>>>> Dependencies <<<<<<<<<<" + find Dependencies -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists + echo "" + + echo ">>>>>>>>> Xcode-Derived-Data <<<<<<<<<<" + ls -la ~/Library/Developer/Xcode/DerivedData || true # List contents if directory exists + echo "" + + - name: Build SideStore Tests + # using 'tee' to intercept stdout and log for detailed build-log + shell: bash + run: | + NSUnbufferedIO=YES make -B build-tests 2>&1 | tee -a build/logs/tests-build.log | xcbeautify --renderer github-actions && exit ${PIPESTATUS[0]} + + - name: (Tests-Build) Save Xcode & SwiftPM Cache + id: cache-save + if: ${{ steps.xcode-cache-restore.outputs.cache-hit != 'true' }} + uses: actions/cache/save@v3 + with: + path: | + ~/Library/Developer/Xcode/DerivedData + ~/Library/Caches/org.swift.swiftpm + key: xcode-cache-tests-${{ github.ref_name }}-${{ github.sha }} + + - name: (Tests-Build) List Files and Build artifacts + if: always() + shell: bash + run: | + echo ">>>>>>>>> Workdir <<<<<<<<<<" + ls -la . + echo "" + + echo ">>>>>>>>> Build <<<<<<<<<<" + find build -maxdepth 3 -exec ls -ld {} + || true # List contents if directory exists + echo "" + + echo ">>>>>>>>> Xcode-Derived-Data <<<<<<<<<<" + find ~/Library/Developer/Xcode/DerivedData -maxdepth 8 -exec ls -ld {} + | grep "Build/Products" >> tests-build-deriveddata.txt || true + echo "" + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: tests-build-deriveddata-${{ inputs.short_commit }}.txt + path: tests-build-deriveddata.txt + + - name: Encrypt tests-build-logs for upload + id: encrypt-test-log + if: always() + shell: bash + run: | + DEFAULT_BUILD_LOG_PASSWORD=12345 + + BUILD_LOG_ZIP_PASSWORD=${{ secrets.BUILD_LOG_ZIP_PASSWORD }} + BUILD_LOG_ZIP_PASSWORD=${BUILD_LOG_ZIP_PASSWORD:-$DEFAULT_BUILD_LOG_PASSWORD} + + if [ "$BUILD_LOG_ZIP_PASSWORD" == "$DEFAULT_BUILD_LOG_PASSWORD" ]; then + echo "Warning: BUILD_LOG_ZIP_PASSWORD is not set. Defaulting to '${DEFAULT_BUILD_LOG_PASSWORD}'." + fi + + pushd build/logs && zip -e -P "$BUILD_LOG_ZIP_PASSWORD" ../../encrypted-tests-build-logs.zip * || popd + echo "::set-output name=encrypted::true" + + - name: Upload encrypted-tests-build-logs.zip + id: attach-encrypted-test-log + if: always() && steps.encrypt-test-log.outputs.encrypted == 'true' + uses: actions/upload-artifact@v4 + with: + name: encrypted-tests-build-logs-${{ inputs.short_commit }}.zip + path: encrypted-tests-build-logs.zip \ No newline at end of file diff --git a/.github/workflows/sidestore-tests-run.yml b/.github/workflows/sidestore-tests-run.yml new file mode 100644 index 00000000..bbb0640e --- /dev/null +++ b/.github/workflows/sidestore-tests-run.yml @@ -0,0 +1,235 @@ +name: SideStore Tests Run + +on: + workflow_call: + inputs: + release_tag: + type: string + short_commit: + type: string + secrets: + BUILD_LOG_ZIP_PASSWORD: + required: false + +jobs: + tests-run: + name: Tests-Run SideStore - ${{ inputs.release_tag }} + if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }} + strategy: + fail-fast: false + matrix: + include: + - os: 'macos-15' + version: '16.2' + runs-on: ${{ matrix.os }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Boot Simulator async(nohup) for testing + run: | + mkdir -p build/logs + nohup make -B boot-sim-async > build/logs/tests-run.log 2>&1 & + shell: bash + + - name: Setup Xcode + uses: maxim-lobanov/setup-xcode@v1.6.0 + with: + xcode-version: '16.2' + + # - name: (Tests-Run) Cache Build + # uses: irgaly/xcode-cache@v1.8.1 + # with: + # # This comes from + # key: xcode-cache-deriveddata-test-${{ github.ref_name }}-${{ github.sha }} + # swiftpm-cache-key: xcode-cache-sourcedata-test-${{ github.ref_name }}-${{ github.sha }} + + - name: (Tests-Build) Restore Xcode & SwiftPM Cache (Exact match) [from tests-build job] + id: xcode-cache-restore + uses: actions/cache/restore@v3 + with: + path: | + ~/Library/Developer/Xcode/DerivedData + ~/Library/Caches/org.swift.swiftpm + key: xcode-cache-tests-${{ github.ref_name }}-${{ github.sha }} + + - name: (Tests-Run) Restore Pods from Cache (Exact match) + id: pods-restore + uses: actions/cache/restore@v3 + with: + path: | + ./Podfile.lock + ./Pods/ + ./AltStore.xcworkspace/ + key: pods-cache-test-${{ github.ref_name }}-${{ hashFiles('Podfile') }} + + - name: (Tests-Run) Restore Pods from Cache (Last Available) + if: ${{ steps.pods-restore.outputs.cache-hit != 'true' }} + id: pods-restore-recent + uses: actions/cache/restore@v3 + with: + path: | + ./Podfile.lock + ./Pods/ + ./AltStore.xcworkspace/ + key: pods-cache-test-${{ github.ref_name }}- + + - name: (Tests-Run) Install CocoaPods + run: pod install + shell: bash + + - name: (Tests-Run) Save Pods to Cache + if: ${{ steps.pods-restore.outputs.cache-hit != 'true' }} + uses: actions/cache/save@v3 + with: + path: | + ./Podfile.lock + ./Pods/ + ./AltStore.xcworkspace/ + key: pods-cache-test-${{ github.ref_name }}-${{ hashFiles('Podfile') }} + + - name: (Tests-Run) Clean previous build artifacts + run: | + make clean + mkdir -p build/logs + shell: bash + + - name: (Tests-Run) List Files and derived data + shell: bash + run: | + echo ">>>>>>>>> Workdir <<<<<<<<<<" + ls -la . + echo "" + + echo ">>>>>>>>> Pods <<<<<<<<<<" + find Pods -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists + echo "" + + echo ">>>>>>>>> SideStore <<<<<<<<<<" + find SideStore -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists + echo "" + + echo ">>>>>>>>> Dependencies <<<<<<<<<<" + find Dependencies -maxdepth 2 -exec ls -ld {} + || true # List contents if directory exists + echo "" + + echo ">>>>>>>>> Xcode-Derived-Data <<<<<<<<<<" + find ~/Library/Developer/Xcode/DerivedData -maxdepth 8 -exec ls -ld {} + | grep "Build/Products" >> tests-run-deriveddata.txt || true + echo "" + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: tests-run-deriveddata-${{ inputs.short_commit }}.txt + path: tests-run-deriveddata.txt + + # we expect simulator to have been booted by now, so exit otherwise + - name: Simulator Boot Check + run: | + mkdir -p build/logs + make -B sim-boot-check | tee -a build/logs/tests-run.log + exit ${PIPESTATUS[0]} + shell: bash + + - name: Start Recording UI tests (if DEBUG_RECORD_TESTS is set to 1) + if: ${{ vars.DEBUG_RECORD_TESTS == '1' }} + run: | + nohup xcrun simctl io booted recordVideo -f tests-recording.mp4 --codec h264 tests-recording.log 2>&1 & + RECORD_PID=$! + echo "RECORD_PID=$RECORD_PID" >> $GITHUB_ENV + shell: bash + + - name: Run SideStore Tests + # using 'tee' to intercept stdout and log for detailed build-log + run: | + make run-tests 2>&1 | tee -a build/logs/tests-run.log && exit ${PIPESTATUS[0]} + # NSUnbufferedIO=YES make -B run-tests 2>&1 | tee build/logs/tests-run.log | xcpretty -r junit --output ./build/tests/test-results.xml && exit ${PIPESTATUS[0]} + shell: bash + + - name: Stop Recording tests + if: ${{ always() && env.RECORD_PID != '' }} + run: | + kill -INT ${{ env.RECORD_PID }} + shell: bash + + - name: (Tests-Run) List Files and Build artifacts + if: always() + run: | + echo ">>>>>>>>> Workdir <<<<<<<<<<" + ls -la . + echo "" + + echo ">>>>>>>>> Build <<<<<<<<<<" + find build -maxdepth 3 -exec ls -ld {} + || true # List contents if directory exists + echo "" + shell: bash + + - name: Encrypt tests-run-logs for upload + id: encrypt-test-log + if: always() + run: | + DEFAULT_BUILD_LOG_PASSWORD=12345 + + BUILD_LOG_ZIP_PASSWORD=${{ secrets.BUILD_LOG_ZIP_PASSWORD }} + BUILD_LOG_ZIP_PASSWORD=${BUILD_LOG_ZIP_PASSWORD:-$DEFAULT_BUILD_LOG_PASSWORD} + + if [ "$BUILD_LOG_ZIP_PASSWORD" == "$DEFAULT_BUILD_LOG_PASSWORD" ]; then + echo "Warning: BUILD_LOG_ZIP_PASSWORD is not set. Defaulting to '${DEFAULT_BUILD_LOG_PASSWORD}'." + fi + + pushd build/logs && zip -e -P "$BUILD_LOG_ZIP_PASSWORD" ../../encrypted-tests-run-logs.zip * || popd + echo "::set-output name=encrypted::true" + shell: bash + + - name: Upload encrypted-tests-run-logs.zip + id: attach-encrypted-test-log + if: always() && steps.encrypt-test-log.outputs.encrypted == 'true' + uses: actions/upload-artifact@v4 + with: + name: encrypted-tests-run-logs-${{ inputs.short_commit }}.zip + path: encrypted-tests-run-logs.zip + + - name: Print tests-recording.log contents (if exists) + if: ${{ always() && env.RECORD_PID != '' }} + run: | + if [ -f tests-recording.log ]; then + echo "tests-recording.log found. Its contents:" + cat tests-recording.log + else + echo "tests-recording.log not found." + fi + shell: bash + + - name: Check for tests-recording.mp4 presence + id: check-recording + if: ${{ always() && env.RECORD_PID != '' }} + run: | + if [ -f tests-recording.mp4 ]; then + echo "::set-output name=found::true" + echo "tests-recording.mp4 found." + else + echo "tests-recording.mp4 not found, skipping upload." + echo "::set-output name=found::false" + fi + shell: bash + + - name: Upload tests-recording.mp4 + id: upload-recording + if: ${{ always() && steps.check-recording.outputs.found == 'true' }} + uses: actions/upload-artifact@v4 + with: + name: tests-recording-${{ inputs.short_commit }}.mp4 + path: tests-recording.mp4 + + - name: Zip test-results + run: zip -r -9 ./test-results.zip ./build/tests + shell: bash + + - name: Upload Test Artifacts + uses: actions/upload-artifact@v4 + with: + name: test-results-${{ inputs.short_commit }}.zip + path: test-results.zip diff --git a/CodeSigning.xcconfig.sample b/CodeSigning.xcconfig.sample index a1806277..f3eb16f5 100644 --- a/CodeSigning.xcconfig.sample +++ b/CodeSigning.xcconfig.sample @@ -4,7 +4,7 @@ DEVELOPMENT_TEAM = XYZ0123456 // Set this for dev-local xcode builds -MARKETING_VERSION_SUFFIX = -local +//MARKETING_VERSION_SUFFIX = -local // Prefix of unique bundle IDs registered to you in Apple Developer Portal. // You need to register: