diff --git a/.github/workflows/stable.yml b/.github/workflows/stable.yml index b8edd762..978d03a2 100644 --- a/.github/workflows/stable.yml +++ b/.github/workflows/stable.yml @@ -1,242 +1,116 @@ name: Stable SideStore build + on: push: tags: - - '[0-9]+.[0-9]+.[0-9]+' # example: 1.0.0 + - '[0-9]+.[0-9]+.[0-9]+' workflow_dispatch: +concurrency: + group: ${{ github.ref }} + cancel-in-progress: true + jobs: build: - name: Build SideStore - stable (on tag push) - strategy: - fail-fast: false - matrix: - include: - - os: 'macos-26' - version: '26.0' - runs-on: ${{ matrix.os }} + name: Build SideStore - stable + runs-on: macos-26 + + env: + RELEASE_NAME: Stable + CHANNEL: stable + UPSTREAM_CHANNEL: "" steps: - - name: Checkout code - uses: actions/checkout@v4 + - uses: actions/checkout@v4 with: submodules: recursive + fetch-depth: 0 - - name: Echo Build.xcconfig + - run: brew install ldid xcbeautify + + - name: Setup Env run: | - echo "cat Build.xcconfig" - cat Build.xcconfig - shell: bash + MARKETING_VERSION=$(python3 scripts/ci/workflow.py get-marketing-version) + SHORT_COMMIT=$(python3 scripts/ci/workflow.py commit-id) - # - name: Change MARKETING_VERSION to the pushed tag that triggered this build - # run: sed -e '/MARKETING_VERSION = .*/s/= .*/= ${{ github.ref_name }}/' -i '' Build.xcconfig - - - name: Echo Updated Build.xcconfig - run: | - cat Build.xcconfig - 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" - - echo "MARKETING_VERSION=$version" >> $GITHUB_ENV - echo "MARKETING_VERSION=$version" >> $GITHUB_OUTPUT - echo "MARKETING_VERSION=$version" - - shell: bash - - - name: Fail the build if pushed tag and embedded MARKETING_VERSION in Build.xcconfig are mismatching - run: | if [ "$MARKETING_VERSION" != "${{ github.ref_name }}" ]; then - echo 'Version mismatch: $tag != $marketing_version ... ' - echo " expected-tag : $MARKETING_VERSION" - echo " pushed-tag : ${{ github.ref_name }}" + echo "Version mismatch" + echo "Build.xcconfig: $MARKETING_VERSION" + echo "Tag: ${{ github.ref_name }}" exit 1 fi - echo 'Version matches: $tag == $marketing_version ... ' - echo " expected-tag : $MARKETING_VERSION" - echo " pushed-tag : ${{ github.ref_name }}" - shell: bash - - name: Install dependencies - ldid & xcbeautify - run: | - brew install ldid xcbeautify + echo "MARKETING_VERSION=$MARKETING_VERSION" | tee -a $GITHUB_ENV + echo "SHORT_COMMIT=$SHORT_COMMIT" | tee -a $GITHUB_ENV - name: Setup Xcode uses: maxim-lobanov/setup-xcode@v1.6.0 with: - xcode-version: ${{ matrix.version }} + xcode-version: "26.0" - - name: (Build) Restore Xcode & SwiftPM Cache (Exact match) - id: xcode-cache-restore + - name: Restore Cache (exact) + id: xcode-cache-exact uses: actions/cache/restore@v3 with: path: | ~/Library/Developer/Xcode/DerivedData ~/Library/Caches/org.swift.swiftpm - key: xcode-cache-build-stable-${{ github.sha }} + key: xcode-build-cache-stable-${{ github.sha }} - - name: (Build) Restore Xcode & SwiftPM Cache (Last Available) - id: xcode-cache-restore-recent + - name: Restore Cache (last) + if: steps.xcode-cache-exact.outputs.cache-hit != 'true' + id: xcode-cache-fallback uses: actions/cache/restore@v3 with: path: | ~/Library/Developer/Xcode/DerivedData ~/Library/Caches/org.swift.swiftpm - key: xcode-cache-build-stable- + key: xcode-build-cache-stable- - - name: (Build) Clean previous build artifacts + - name: Clean + run: python3 scripts/ci/workflow.py clean + + - name: Build + id: build + env: + BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }} run: | - make clean - mkdir -p build/logs - shell: bash + python3 scripts/ci/workflow.py build; STATUS=$? + python3 scripts/ci/workflow.py encrypt-build + exit $STATUS - - name: (Build) List Files and derived data - if: always() - shell: bash - run: | - echo ">>>>>>>>> Workdir <<<<<<<<<<" - ls -la . - 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.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' }} + - name: Save Cache + if: ${{ steps.xcode-cache-fallback.outputs.cache-hit != 'true' }} uses: actions/cache/save@v3 with: path: | ~/Library/Developer/Xcode/DerivedData ~/Library/Caches/org.swift.swiftpm - key: xcode-cache-build-stable-${{ github.sha }} - - - name: (Build) List Files and Build artifacts - run: | - echo ">>>>>>>>> Workdir <<<<<<<<<<" - ls -la . - echo "" + key: xcode-build-cache-stable-${{ github.sha }} - 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 + - uses: actions/upload-artifact@v4 with: - name: encrypted-build-logs-${{ steps.version.outputs.version }}.zip + name: encrypted-build-logs-${{ env.MARKETING_VERSION }}.zip path: encrypted-build-logs.zip - - name: Upload SideStore.ipa Artifact - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v4 with: - name: SideStore-${{ steps.version.outputs.version }}.ipa + name: SideStore-${{ env.MARKETING_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 + - uses: actions/upload-artifact@v4 with: - name: SideStore-${{ steps.version.outputs.version }}-dSYMs.zip + name: SideStore-${{ env.MARKETING_VERSION }}-dSYMs.zip path: SideStore.dSYMs.zip - - 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 }} - draft: true - release: ${{ github.ref_name }} # name - tag: ${{ github.ref_name }} - # stick with what the user pushed, do not use latest commit or anything, - # ex: if we want to go back to previous release due to hot issue, dev can create a new tag pointing to that older working tag/commit so as to keep it as an update (to revert major issue) - # in this case we do not want the tag to be auto-updated to latest - updateTag: false - prerelease: false - files: > - SideStore.ipa - SideStore.dSYMs.zip - encrypted-build-logs.zip - body: | - - ## Changelog - - - TODO - - ## Build Info - - Built at (UTC): `${{ steps.date.outputs.date }}` - Built at (UTC date): `${{ steps.date_altstore.outputs.date }}` - Commit SHA: `${{ github.sha }}` - Version: `${{ steps.version.outputs.version }}` + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + python3 scripts/ci/workflow.py upload-release \ + "$RELEASE_NAME" \ + "${{ github.ref_name }}" \ + "$GITHUB_SHA" \ + "$GITHUB_REPOSITORY" \ + "$UPSTREAM_CHANNEL" \ + "true" diff --git a/scripts/ci/workflow.py b/scripts/ci/workflow.py index 439d1856..78874d50 100644 --- a/scripts/ci/workflow.py +++ b/scripts/ci/workflow.py @@ -396,7 +396,18 @@ def last_successful_commit(workflow, branch): return None -def upload_release(release_name, release_tag, commit_sha, repo, upstream_tag_recommended): +def upload_release(release_name, release_tag, commit_sha, repo, upstream_tag_recommended, is_stable=False): + is_stable = str(is_stable).lower() in ("1", "true", "yes") + + if is_stable: + draft = True # always create a draft for stable and let user publish release + update_tag = False + prerelease = False + else: + draft = False + update_tag = True # update existing + prerelease = True + token = getenv("GH_TOKEN") if token: os.environ["GH_TOKEN"] = token @@ -423,7 +434,7 @@ def upload_release(release_name, release_tag, commit_sha, repo, upstream_tag_rec f"--retrieve {release_tag} " f"--output-dir {ROOT}" ) - # normalize section header + release_notes = re.sub( r'^\s*#{1,6}\s*what(?:\'?s|\s+is)?\s+(?:new|changed).*', "## What's Changed", @@ -440,19 +451,27 @@ def upload_release(release_name, release_tag, commit_sha, repo, upstream_tag_rec f"(https://github.com/{repo}/releases?q={tag}).\n\n" ) - header = getFormattedUploadMsg(release_name, release_tag, commit_sha, repo, upstream_block, built_time, built_date, marketing_version) - body = header + "\n\n" + release_notes.lstrip() + "\n" + header = getFormattedUploadMsg( + release_name, commit_sha, repo, upstream_block, + built_time, built_date, marketing_version, is_stable, + ) + + body = header + release_notes.lstrip() + "\n" body_file = ROOT / "release_body.md" body_file.write_text(body, encoding="utf-8") prerelease_flag = "--prerelease" if is_beta else "" + draft_flag = "--draft" if draft else "" + prerelease_flag = "--prerelease" if prerelease else "" + latest_flag = "" if update_tag else "--latest=false" + run( f'gh release edit "{release_tag}" ' f'--title "{release_name}" ' f'--notes-file "{body_file}" ' - f'{prerelease_flag}' + f'{draft_flag} {prerelease_flag} {latest_flag}' ) run( @@ -461,19 +480,26 @@ def upload_release(release_name, release_tag, commit_sha, repo, upstream_tag_rec f'--clobber' ) -def getFormattedUploadMsg(release_name, release_tag, commit_sha, repo, upstream_block, built_time, built_date, marketing_version): - return f""" + +def getFormattedUploadMsg(release_name, commit_sha, repo, upstream_block, built_time, built_date, marketing_version, is_stable): + experimental_header = "" + if not is_stable: + experimental_header = f""" This is an ⚠️ **EXPERIMENTAL** ⚠️ {release_name} build for commit [{commit_sha}](https://github.com/{repo}/commit/{commit_sha}). {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!** -{upstream_block}## Build Info +""".lstrip("\n") + + header = f""" +{experimental_header}{upstream_block}## Build Info Built at (UTC): `{built_time}` Built at (UTC date): `{built_date}` Commit SHA: `{commit_sha}` Version: `{marketing_version}` """.lstrip("\n") + return header # ---------------------------------------------------------- # ENTRYPOINT @@ -534,7 +560,7 @@ COMMANDS = { "retrieve-release-notes" : (retrieve_release_notes, 1, ""), "deploy" : (deploy, 9, " [last_successful_commit]"), - "upload-release" : (upload_release, 5, " "), + "upload-release" : (upload_release, 5, " [is_stable]"), } def main():