Compare commits

..

3 Commits

Author SHA1 Message Date
mahee96
85ff9b09ca ci: more fixes 2026-02-24 10:23:31 +05:30
mahee96
4100e8b1b9 ci: more fixes 2026-02-24 10:10:22 +05:30
mahee96
42fae569ca ci: more fixes 2026-02-24 10:04:22 +05:30
34 changed files with 2037 additions and 1542 deletions

63
.github/maintenance/cache.py vendored Normal file
View File

@@ -0,0 +1,63 @@
import requests
import sys
import os
# Your GitHub Personal Access Token
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
# Repository details
REPO_OWNER = "SideStore"
REPO_NAME = "SideStore"
API_URL = f"https://api.github.com/repos/{REPO_OWNER}/{REPO_NAME}/actions/caches"
# Common headers for GitHub API calls
HEADERS = {
"Accept": "application/vnd.github+json",
"Authorization": f"Bearer {GITHUB_TOKEN}"
}
def list_caches():
response = requests.get(API_URL, headers=HEADERS)
if response.status_code != 200:
print(f"Failed to list caches. HTTP {response.status_code}")
print("Response:", response.text)
sys.exit(1)
data = response.json()
return data.get("actions_caches", [])
def delete_cache(cache_id):
delete_url = f"{API_URL}/{cache_id}"
response = requests.delete(delete_url, headers=HEADERS)
return response.status_code
def main():
caches = list_caches()
if not caches:
print("No caches found.")
return
print("Found caches:")
for cache in caches:
print(f"ID: {cache.get('id')}, Key: {cache.get('key')}")
print("\nDeleting caches...")
for cache in caches:
cache_id = cache.get("id")
status = delete_cache(cache_id)
if status == 204:
print(f"Successfully deleted cache with ID: {cache_id}")
else:
print(f"Failed to delete cache with ID: {cache_id}. HTTP status code: {status}")
print("All caches processed.")
if __name__ == "__main__":
main()
### How to use
'''
just export the GITHUB_TOKEN and then run this script via `python3 cache.py' to delete the caches
'''

View File

@@ -2,7 +2,7 @@ name: Alpha SideStore Build
on:
push:
branches: [staging]
branches: [alpha]
workflow_dispatch:
concurrency:
@@ -13,8 +13,6 @@ jobs:
build:
runs-on: macos-26
env:
DEPLOY_KEY: ${{ secrets.CROSS_REPO_PUSH_KEY }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_NAME: Alpha
CHANNEL: alpha
UPSTREAM_CHANNEL: "nightly"
@@ -25,33 +23,36 @@ jobs:
submodules: recursive
fetch-depth: 0
- name: Find Last Successful commit
run: |
LAST_SUCCESSFUL_COMMIT=$(python3 scripts/ci/workflow.py last-successful-commit \
"false" "${{ env.CHANNEL }}" || echo "")
echo "LAST_SUCCESSFUL_COMMIT=$LAST_SUCCESSFUL_COMMIT" | tee -a $GITHUB_ENV
- run: brew install ldid xcbeautify
# --------------------------------------------------
# runtime env setup
# --------------------------------------------------
- uses: actions/checkout@v4
with:
repository: "SideStore/beta-build-num"
ref: ${{ env.CHANNEL }}
token: ${{ secrets.CROSS_REPO_PUSH_KEY }}
path: "Dependencies/beta-build-num"
fetch-depth: 1
- name: Setup Env
run: |
BUILD_NUM="${{ github.run_number }}"
BUILD_NUM=$(python3 scripts/ci/workflow.py reserve_build_number 'Dependencies/beta-build-num')
MARKETING_VERSION=$(python3 scripts/ci/workflow.py get-marketing-version)
SHORT_COMMIT=$(python3 scripts/ci/workflow.py commit-id)
SHORT_COMMIT=$(python3 scripts/ci/workflow.py commid-id)
NORMALIZED_VERSION=$(python3 scripts/ci/workflow.py compute-normalized \
QUALIFIED_VERSION=$(python3 scripts/ci/workflow.py compute-qualified \
"$MARKETING_VERSION" \
"$BUILD_NUM" \
"${{ env.CHANNEL }}" \
"$SHORT_COMMIT")
python3 scripts/ci/workflow.py set-marketing-version "$NORMALIZED_VERSION"
python3 scripts/ci/workflow.py set-marketing-version "$QUALIFIED_VERSION"
echo "BUILD_NUM=$BUILD_NUM" | tee -a $GITHUB_ENV
echo "SHORT_COMMIT=$SHORT_COMMIT" | tee -a $GITHUB_ENV
echo "MARKETING_VERSION=$NORMALIZED_VERSION" | tee -a $GITHUB_ENV
echo "MARKETING_VERSION=$QUALIFIED_VERSION" | tee -a $GITHUB_ENV
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.6.0
@@ -88,9 +89,7 @@ jobs:
python3 scripts/ci/workflow.py clean-spm-cache
- name: Boot simulator (async)
if: >
vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_RUN == '1'
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }}
run: |
mkdir -p build/logs
python3 scripts/ci/workflow.py boot-sim-async "iPhone 17 Pro"
@@ -107,9 +106,7 @@ jobs:
- name: Tests Build
id: test-build
if: >
vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_BUILD == '1'
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_BUILD == '1' }}
env:
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
run: |
@@ -128,9 +125,7 @@ jobs:
- name: Tests Run
id: test-run
if: >
vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_RUN == '1'
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }}
env:
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
run: |
@@ -143,24 +138,20 @@ jobs:
# --------------------------------------------------
- uses: actions/upload-artifact@v4
with:
name: build-logs-${{ env.MARKETING_VERSION }}.zip
path: build-logs.zip
name: encrypted-build-logs-${{ env.MARKETING_VERSION }}.zip
path: encrypted-build-logs.zip
- uses: actions/upload-artifact@v4
if: >
vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_BUILD == '1'
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_BUILD == '1' }}
with:
name: tests-build-logs-${{ env.SHORT_COMMIT }}.zip
path: tests-build-logs.zip
name: encrypted-tests-build-logs-${{ env.SHORT_COMMIT }}.zip
path: encrypted-tests-build-logs.zip
- uses: actions/upload-artifact@v4
if: >
vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_RUN == '1'
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }}
with:
name: tests-run-logs-${{ env.SHORT_COMMIT }}.zip
path: tests-run-logs.zip
name: encrypted-tests-run-logs-${{ env.SHORT_COMMIT }}.zip
path: encrypted-tests-run-logs.zip
- uses: actions/upload-artifact@v4
with:
@@ -172,21 +163,30 @@ jobs:
path: SideStore.dSYMs.zip
- uses: actions/checkout@v4
if: env.DEPLOY_KEY != ''
with:
repository: "SideStore/apps-v2.json"
ref: "main"
token: ${{ secrets.CROSS_REPO_PUSH_KEY }}
path: "SideStore/apps-v2.json"
- name: Generate Metadata
# --------------------------------------------------
# deploy
# --------------------------------------------------
- name: Deploy
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
python3 scripts/ci/workflow.py dump-project-settings
PRODUCT_NAME=$(python3 scripts/ci/workflow.py read-product-name)
BUNDLE_ID=$(python3 scripts/ci/workflow.py read-bundle-id)
PRODUCT_NAME=$(python3 scripts/ci/workflow.py get-product-name)
BUNDLE_ID=$(python3 scripts/ci/workflow.py get-bundle-id)
SOURCE_JSON="_includes/source.json"
IPA_NAME="$PRODUCT_NAME.ipa"
python3 scripts/ci/workflow.py generate-metadata \
LAST_SUCCESSFUL_COMMIT=$(python3 scripts/ci/workflow.py last-successful-commit \
"${{ github.workflow }}" "$CHANNEL")
python3 scripts/ci/workflow.py deploy \
SideStore/apps-v2.json \
"$SOURCE_JSON" \
"$CHANNEL" \
"$SHORT_COMMIT" \
"$MARKETING_VERSION" \
@@ -195,23 +195,8 @@ jobs:
"$IPA_NAME" \
"$LAST_SUCCESSFUL_COMMIT"
- name: Deploy
if: env.DEPLOY_KEY != ''
run: |
SOURCE_JSON="_includes/source.json"
RELEASE_NOTES=$(python3 scripts/ci/workflow.py retrieve-release-notes "$CHANNEL")
python3 scripts/ci/workflow.py deploy \
SideStore/apps-v2.json \
"$SOURCE_JSON" \
"$CHANNEL" \
"$MARKETING_VERSION"
# --------------------------------------------------
# upload release to GH
# --------------------------------------------------
- name: Upload Release
run: |
python3 scripts/ci/workflow.py upload-release \
"$RELEASE_NAME" \
"$CHANNEL" \

View File

@@ -15,8 +15,6 @@ jobs:
build:
runs-on: macos-26
env:
DEPLOY_KEY: ${{ secrets.CROSS_REPO_PUSH_KEY }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_NAME: Nightly
CHANNEL: nightly
UPSTREAM_CHANNEL: ""
@@ -27,63 +25,43 @@ jobs:
submodules: recursive
fetch-depth: 0
- name: Find Last Successful commit
run: |
LAST_SUCCESSFUL_COMMIT=$(python3 scripts/ci/workflow.py last-successful-commit \
"false" "${{ env.CHANNEL }}" || echo "")
echo "LAST_SUCCESSFUL_COMMIT=$LAST_SUCCESSFUL_COMMIT" | tee -a $GITHUB_ENV
- name: Check for new changes (on schedule)
id: check_changes
if: github.event_name == 'schedule'
run: |
NEW_COMMITS=$(python3 scripts/ci/workflow.py count-new-commits "$LAST_SUCCESSFUL_COMMIT")
SHOULD_BUILD=$([ "${NEW_COMMITS:-0}" -ge 1 ] && echo true || echo false)
echo "should_build=$SHOULD_BUILD" >> $GITHUB_OUTPUT
echo "NEW_COMMITS=$NEW_COMMITS" | tee -a $GITHUB_ENV
- name: Should Skip Building (on schedule)
id: build_gate
run: |
SHOULD_SKIP=$(
{ [ "${{ github.event_name }}" = "schedule" ] && \
[ "${{ steps.check_changes.outputs.should_build }}" != "true" ]; \
} && echo true || echo false
)
echo "should_skip=$SHOULD_SKIP" >> $GITHUB_OUTPUT
- run: brew install ldid xcbeautify
if: steps.build_gate.outputs.should_skip != 'true'
# --------------------------------------------------
# runtime env setup
# --------------------------------------------------
- name: Setup Env
if: steps.build_gate.outputs.should_skip != 'true'
run: |
BUILD_NUM="${{ github.run_number }}"
MARKETING_VERSION=$(python3 scripts/ci/workflow.py get-marketing-version)
SHORT_COMMIT=$(python3 scripts/ci/workflow.py commit-id)
- uses: actions/checkout@v4
with:
repository: "SideStore/beta-build-num"
ref: ${{ env.CHANNEL }}
token: ${{ secrets.CROSS_REPO_PUSH_KEY }}
path: "Dependencies/beta-build-num"
fetch-depth: 1
NORMALIZED_VERSION=$(python3 scripts/ci/workflow.py compute-normalized \
- name: Setup Env
run: |
BUILD_NUM=$(python3 scripts/ci/workflow.py reserve_build_number 'Dependencies/beta-build-num')
MARKETING_VERSION=$(python3 scripts/ci/workflow.py get-marketing-version)
SHORT_COMMIT=$(python3 scripts/ci/workflow.py commid-id)
QUALIFIED_VERSION=$(python3 scripts/ci/workflow.py compute-qualified \
"$MARKETING_VERSION" \
"$BUILD_NUM" \
"${{ env.CHANNEL }}" \
"$SHORT_COMMIT")
python3 scripts/ci/workflow.py set-marketing-version "$NORMALIZED_VERSION"
python3 scripts/ci/workflow.py set-marketing-version "$QUALIFIED_VERSION"
echo "BUILD_NUM=$BUILD_NUM" | tee -a $GITHUB_ENV
echo "SHORT_COMMIT=$SHORT_COMMIT" | tee -a $GITHUB_ENV
echo "MARKETING_VERSION=$NORMALIZED_VERSION" | tee -a $GITHUB_ENV
echo "MARKETING_VERSION=$QUALIFIED_VERSION" | tee -a $GITHUB_ENV
- name: Setup Xcode
if: steps.build_gate.outputs.should_skip != 'true'
uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: "26.2"
- name: Restore Cache (exact)
if: steps.build_gate.outputs.should_skip != 'true'
id: xcode-cache-exact
uses: actions/cache/restore@v3
with:
@@ -93,9 +71,7 @@ jobs:
key: xcode-build-cache-${{ github.ref_name }}-${{ github.sha }}
- name: Restore Cache (last)
if: >
steps.build_gate.outputs.should_skip != 'true' &&
steps.xcode-cache-exact.outputs.cache-hit != 'true'
if: steps.xcode-cache-exact.outputs.cache-hit != 'true'
id: xcode-cache-fallback
uses: actions/cache/restore@v3
with:
@@ -108,23 +84,19 @@ jobs:
# build and test
# --------------------------------------------------
- name: Clean
if: steps.build_gate.outputs.should_skip != 'true' && contains(github.event.head_commit.message, '[--clean-build]')
if: contains(github.event.head_commit.message, '[--clean-build]')
run: |
python3 scripts/ci/workflow.py clean
python3 scripts/ci/workflow.py clean-derived-data
python3 scripts/ci/workflow.py clean-spm-cache
- name: Boot simulator (async)
if: >
steps.build_gate.outputs.should_skip != 'true' &&
vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_RUN == '1'
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }}
run: |
mkdir -p build/logs
python3 scripts/ci/workflow.py boot-sim-async "iPhone 17 Pro"
- name: Build
if: steps.build_gate.outputs.should_skip != 'true'
id: build
env:
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
@@ -135,11 +107,8 @@ jobs:
exit $STATUS
- name: Tests Build
if: >
steps.build_gate.outputs.should_skip != 'true' &&
vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_BUILD == '1'
id: test-build
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_BUILD == '1' }}
env:
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
run: |
@@ -148,9 +117,7 @@ jobs:
exit $STATUS
- name: Save Cache
if: >
steps.build_gate.outputs.should_skip != 'true' &&
steps.xcode-cache-fallback.outputs.cache-hit != 'true'
if: ${{ steps.xcode-cache-fallback.outputs.cache-hit != 'true' }}
uses: actions/cache/save@v3
with:
path: |
@@ -159,11 +126,8 @@ jobs:
key: xcode-build-cache-${{ github.ref_name }}-${{ github.sha }}
- name: Tests Run
if: >
steps.build_gate.outputs.should_skip != 'true' &&
vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_RUN == '1'
id: test-run
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }}
env:
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
run: |
@@ -175,57 +139,56 @@ jobs:
# artifacts
# --------------------------------------------------
- uses: actions/upload-artifact@v4
if: steps.build_gate.outputs.should_skip != 'true'
with:
name: build-logs-${{ env.MARKETING_VERSION }}.zip
path: build-logs.zip
name: encrypted-build-logs-${{ env.MARKETING_VERSION }}.zip
path: encrypted-build-logs.zip
- uses: actions/upload-artifact@v4
if: >
steps.build_gate.outputs.should_skip != 'true' &&
vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_BUILD == '1'
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_BUILD == '1' }}
with:
name: tests-build-logs-${{ env.SHORT_COMMIT }}.zip
path: tests-build-logs.zip
name: encrypted-tests-build-logs-${{ env.SHORT_COMMIT }}.zip
path: encrypted-tests-build-logs.zip
- uses: actions/upload-artifact@v4
if: >
steps.build_gate.outputs.should_skip != 'true' &&
vars.ENABLE_TESTS == '1' &&
vars.ENABLE_TESTS_RUN == '1'
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }}
with:
name: tests-run-logs-${{ env.SHORT_COMMIT }}.zip
path: tests-run-logs.zip
name: encrypted-tests-run-logs-${{ env.SHORT_COMMIT }}.zip
path: encrypted-tests-run-logs.zip
- uses: actions/upload-artifact@v4
if: steps.build_gate.outputs.should_skip != 'true'
with:
name: SideStore-${{ env.MARKETING_VERSION }}.ipa
path: SideStore.ipa
- uses: actions/upload-artifact@v4
if: steps.build_gate.outputs.should_skip != 'true'
with:
name: SideStore-${{ env.MARKETING_VERSION }}-dSYMs.zip
path: SideStore.dSYMs.zip
- uses: actions/checkout@v4
if: steps.build_gate.outputs.should_skip != 'true' && env.DEPLOY_KEY != ''
with:
repository: "SideStore/apps-v2.json"
ref: "main"
token: ${{ secrets.CROSS_REPO_PUSH_KEY }}
path: "SideStore/apps-v2.json"
- name: Generate Metadata
if: steps.build_gate.outputs.should_skip != 'true'
# --------------------------------------------------
# deploy
# --------------------------------------------------
- name: Deploy
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
python3 scripts/ci/workflow.py dump-project-settings
PRODUCT_NAME=$(python3 scripts/ci/workflow.py read-product-name)
BUNDLE_ID=$(python3 scripts/ci/workflow.py read-bundle-id)
PRODUCT_NAME=$(python3 scripts/ci/workflow.py get-product-name)
BUNDLE_ID=$(python3 scripts/ci/workflow.py get-bundle-id)
SOURCE_JSON="_includes/source.json"
IPA_NAME="$PRODUCT_NAME.ipa"
python3 scripts/ci/workflow.py generate-metadata \
LAST_SUCCESSFUL_COMMIT=$(python3 scripts/ci/workflow.py last-successful-commit \
"${{ github.workflow }}" "$CHANNEL")
python3 scripts/ci/workflow.py deploy \
SideStore/apps-v2.json \
"$SOURCE_JSON" \
"$CHANNEL" \
"$SHORT_COMMIT" \
"$MARKETING_VERSION" \
@@ -234,24 +197,8 @@ jobs:
"$IPA_NAME" \
"$LAST_SUCCESSFUL_COMMIT"
- name: Deploy
if: steps.build_gate.outputs.should_skip != 'true' && env.DEPLOY_KEY != ''
run: |
SOURCE_JSON="_includes/source.json"
RELEASE_NOTES=$(python3 scripts/ci/workflow.py retrieve-release-notes "$CHANNEL")
python3 scripts/ci/workflow.py deploy \
SideStore/apps-v2.json \
"$SOURCE_JSON" \
"$CHANNEL" \
"$MARKETING_VERSION"
# --------------------------------------------------
# upload release to GH
# --------------------------------------------------
- name: Upload Release
if: steps.build_gate.outputs.should_skip != 'true'
run: |
python3 scripts/ci/workflow.py upload-release \
"$RELEASE_NAME" \
"$CHANNEL" \

28
.github/workflows/obsolete/alpha.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
name: Alpha SideStore build
on:
push:
branches:
- develop-alpha
# cancel duplicate run if from same branch
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
jobs:
Reusable-build:
uses: ./.github/workflows/reusable-sidestore-build.yml
with:
# bundle_id: "com.SideStore.SideStore.Alpha"
bundle_id: "com.SideStore.SideStore"
# bundle_id_suffix: ".Alpha"
is_beta: true
publish: ${{ vars.PUBLISH_ALPHA_UPDATES == 'true' }}
is_shared_build_num: false
release_tag: "alpha"
release_name: "Alpha"
upstream_tag: "nightly"
upstream_name: "Nightly"
secrets:
CROSS_REPO_PUSH_KEY: ${{ secrets.CROSS_REPO_PUSH_KEY }}
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}

103
.github/workflows/obsolete/beta.yml vendored Normal file
View File

@@ -0,0 +1,103 @@
name: Beta SideStore build
on:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+-beta.[0-9]+' # example: 1.0.0-beta.1
jobs:
build:
name: Build and upload SideStore Beta
strategy:
fail-fast: false
matrix:
include:
- os: 'macos-14'
version: '15.4'
runs-on: ${{ matrix.os }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Install dependencies
run: brew install ldid
- name: Change version to tag
run: sed -e '/MARKETING_VERSION = .*/s/= .*/= ${{ github.ref_name }}/' -i '' Build.xcconfig
- name: Get version
id: version
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
- name: Echo version
run: echo "${{ steps.version.outputs.version }}"
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: ${{ matrix.version }}
- name: Cache Build
uses: irgaly/xcode-cache@v1
with:
key: xcode-cache-deriveddata-${{ github.sha }}
restore-keys: xcode-cache-deriveddata
- name: Build SideStore
run: make build | xcpretty && exit ${PIPESTATUS[0]}
- name: Fakesign app
run: make fakesign
- name: Convert to IPA
run: make ipa
- name: Get current date
id: date
run: echo "date=$(date -u +'%c')" >> $GITHUB_OUTPUT
- name: Get current date in AltStore date form
id: date_altstore
run: echo "date=$(date -u +'%Y-%m-%d')" >> $GITHUB_OUTPUT
- name: Upload to new beta release
uses: softprops/action-gh-release@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
name: ${{ steps.version.outputs.version }}
tag_name: ${{ github.ref_name }}
draft: true
prerelease: true
files: SideStore.ipa
body: |
<!-- NOTE: to reset SideSource cache, go to `https://apps.sidestore.io/reset-cache/nightly/<sidesource key>`. This is not included in the GitHub Action since it makes draft releases so they can be edited and have a changelog. -->
Beta builds are hand-picked builds from development commits that will allow you to try out new features earlier than normal. However, **they might contain bugs and other issues. Use at your own risk!**
## 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 }}`
- name: Add version to IPA file name
run: mv SideStore.ipa SideStore-${{ steps.version.outputs.version }}.ipa
- name: Upload SideStore.ipa Artifact
uses: actions/upload-artifact@v4
with:
name: SideStore-${{ steps.version.outputs.version }}.ipa
path: SideStore-${{ steps.version.outputs.version }}.ipa
- name: Upload *.dSYM Artifact
uses: actions/upload-artifact@v4
with:
name: SideStore-${{ steps.version.outputs.version }}-dSYM
path: ./*.dSYM/

View File

@@ -0,0 +1,34 @@
#!/usr/bin/env bash
# Ensure we are in root directory
cd "$(dirname "$0")/../.."
DATE=`date -u +'%Y.%m.%d'`
BUILD_NUM=1
# Use RELEASE_CHANNEL from the environment variable or default to "beta"
RELEASE_CHANNEL=${RELEASE_CHANNEL:-"beta"}
write() {
sed -e "/MARKETING_VERSION = .*/s/$/-$RELEASE_CHANNEL.$DATE.$BUILD_NUM+$(git rev-parse --short HEAD)/" -i '' Build.xcconfig
echo "$DATE,$BUILD_NUM" > build_number.txt
}
if [ ! -f "build_number.txt" ]; then
write
exit 0
fi
LAST_DATE=`cat build_number.txt | perl -n -e '/([^,]*),([^ ]*)$/ && print $1'`
LAST_BUILD_NUM=`cat build_number.txt | perl -n -e '/([^,]*),([^ ]*)$/ && print $2'`
# if [[ "$DATE" != "$LAST_DATE" ]]; then
# write
# else
# BUILD_NUM=`expr $LAST_BUILD_NUM + 1`
# write
# fi
# Build number is always incremental
BUILD_NUM=`expr $LAST_BUILD_NUM + 1`
write

82
.github/workflows/obsolete/nightly.yml vendored Normal file
View File

@@ -0,0 +1,82 @@
name: Nightly SideStore Build
on:
push:
branches:
- develop
schedule:
- cron: '0 0 * * *' # Runs every night at midnight UTC
workflow_dispatch: # Allows manual trigger
# cancel duplicate run if from same branch
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
jobs:
check-changes:
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
outputs:
has_changes: ${{ steps.check.outputs.has_changes }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0 # Ensure full history
- name: Get last successful workflow run
id: get_last_success
run: |
LAST_SUCCESS=$(gh run list --workflow "Nightly SideStore Build" --json createdAt,conclusion \
--jq '[.[] | select(.conclusion=="success")][0].createdAt' || echo "")
echo "Last successful run: $LAST_SUCCESS"
echo "last_success=$LAST_SUCCESS" >> $GITHUB_ENV
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Check for new commits since last successful build
id: check
run: |
if [ -n "$LAST_SUCCESS" ]; then
NEW_COMMITS=$(git rev-list --count --since="$LAST_SUCCESS" origin/develop)
COMMIT_LOG=$(git log --since="$LAST_SUCCESS" --pretty=format:"%h %s" origin/develop)
else
NEW_COMMITS=1
COMMIT_LOG=$(git log -n 10 --pretty=format:"%h %s" origin/develop) # Show last 10 commits if no history
fi
echo "Has changes: $NEW_COMMITS"
echo "New commits since last successful build:"
echo "$COMMIT_LOG"
if [ "$NEW_COMMITS" -gt 0 ]; then
echo "has_changes=true" >> $GITHUB_OUTPUT
else
echo "has_changes=false" >> $GITHUB_OUTPUT
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
LAST_SUCCESS: ${{ env.last_success }}
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/reusable-sidestore-build.yml
with:
# bundle_id: "com.SideStore.SideStore.Nightly"
bundle_id: "com.SideStore.SideStore"
# bundle_id_suffix: ".Nightly"
is_beta: true
publish: ${{ vars.PUBLISH_NIGHTLY_UPDATES == 'true' }}
is_shared_build_num: false
release_tag: "nightly"
release_name: "Nightly"
upstream_tag: "0.5.10"
upstream_name: "Stable"
secrets:
CROSS_REPO_PUSH_KEY: ${{ secrets.CROSS_REPO_PUSH_KEY }}
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}

View File

@@ -0,0 +1,105 @@
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
# 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
jobs:
shared:
uses: ./.github/workflows/sidestore-shared.yml
secrets: inherit
build:
needs: shared
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 }}
short_commit: ${{ needs.shared.outputs.short-commit }}
bundle_id: ${{ inputs.bundle_id }}
bundle_id_suffix: ${{ inputs.bundle_id_suffix }}
secrets: inherit
# tests-build:
# if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_BUILD == '1' }}
# needs: shared
# uses: ./.github/workflows/sidestore-tests-build.yml
# with:
# release_tag: ${{ inputs.release_tag }}
# short_commit: ${{ needs.shared.outputs.short-commit }}
# secrets: inherit
# tests-run:
# if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }}
# needs: [shared, tests-build]
# uses: ./.github/workflows/sidestore-tests-run.yml
# with:
# release_tag: ${{ inputs.release_tag }}
# short_commit: ${{ needs.shared.outputs.short-commit }}
# secrets: inherit
deploy:
# needs: [shared, build, tests-build, tests-run] # Keep tests-run in needs
needs: [shared, build] # Keep tests-run in needs
if: ${{ always() && (needs.tests-run.result == 'skipped' || needs.tests-run.result == 'success') }}
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.shared.outputs.short-commit }}
release_channel: ${{ needs.build.outputs.release-channel }}
marketing_version: ${{ needs.build.outputs.marketing-version }}
bundle_id: ${{ inputs.bundle_id }}
secrets: inherit

View File

@@ -0,0 +1,358 @@
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-26'
version: '26.0'
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) 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 ">>>>>>>>> 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

View File

@@ -0,0 +1,281 @@
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
bundle_id:
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<<EOF" >> $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: List files to upload
id: list_uploads
run: |
echo ">>>>>>>>> Workdir <<<<<<<<<<"
find . -maxdepth 4 -exec ls -ld {} + || true # List contents if directory exists
echo ""
FILES="SideStore.ipa SideStore.dSYMs.zip encrypted-build-logs.zip"
if [[ "${{ vars.ENABLE_TESTS }}" == "1" && "${{ vars.ENABLE_TESTS_BUILD }}" == "1" ]]; then
FILES="$FILES encrypted-tests-build-logs.zip"
fi
if [[ "${{ vars.ENABLE_TESTS }}" == "1" && "${{ vars.ENABLE_TESTS_RUN }}" == "1" ]]; then
FILES="$FILES encrypted-tests-run-logs.zip test-results.zip tests-recording.mp4"
fi
echo "Final upload list:"
for f in $FILES; do
if [[ -f "$f" ]]; then
echo " ✓ $f"
else
echo " - $f (missing)"
fi
done
echo "files=$FILES" >> $GITHUB_OUTPUT
- name: Set Upstream Recommendation
id: upstream_recommendation
run: |
UPSTREAM_NAME=$(echo "${{ inputs.upstream_name }}" | tr '[:upper:]' '[:lower:]')
if [[ "$UPSTREAM_NAME" != "nightly" ]]; then
echo "content<<EOF" >> $GITHUB_OUTPUT
echo "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 }})." >> $GITHUB_OUTPUT
echo "" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
else
echo "content=" >> $GITHUB_OUTPUT
fi
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: ${{ steps.list_uploads.outputs.files }}
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!**
${{ steps.upstream_recommendation.outputs.content }}
## 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: |
echo "IS_BETA=${{ inputs.is_beta }}" >> $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
# Format localized description
get_description() {
cat <<EOF
This is release for:
- version: "${{ inputs.version }}"
- revision: "${{ inputs.short_commit }}"
- timestamp: "${{ steps.date.outputs.date }}"
Release Notes:
${{ steps.release_notes.outputs.content }}
EOF
}
LOCALIZED_DESCRIPTION=$(get_description)
echo "$LOCALIZED_DESCRIPTION"
# multiline strings
echo "LOCALIZED_DESCRIPTION<<EOF" >> $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

View File

@@ -0,0 +1,24 @@
name: SideStore Shared
on:
workflow_call:
outputs:
short-commit:
value: ${{ jobs.shared.outputs.short-commit }}
jobs:
shared:
name: Shared Steps
strategy:
fail-fast: false
runs-on: 'macos-15'
steps:
- 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 }}

View File

@@ -0,0 +1,165 @@
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-26'
version: '26.0'
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: '26.0'
# - 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: 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 ">>>>>>>>> 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

View File

@@ -0,0 +1,196 @@
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-26'
version: '26.0'
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 </dev/null >> build/logs/tests-run.log 2>&1 &
shell: bash
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: '26.0'
# - 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) 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 ">>>>>>>>> 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 </dev/null > 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

View File

@@ -1,90 +1,98 @@
name: Pull Request SideStore build
on:
pull_request:
# types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
types: [opened, synchronize, reopened, ready_for_review]
concurrency:
group: pr-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
build:
name: Build and upload SideStore
if: ${{ github.event.pull_request.draft == false }}
runs-on: macos-26
strategy:
fail-fast: false
matrix:
include:
- os: 'macos-14'
version: '16.1'
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 1 # shallow clone just for PR
- run: brew install ldid xcbeautify
- name: Install dependencies
run: brew install ldid
- name: Setup Env
run: |
MARKETING_VERSION=$(python3 scripts/ci/workflow.py get-marketing-version)
SHORT_COMMIT=$(git rev-parse --short ${{ github.event.pull_request.head.sha }})
NORMALIZED_VERSION="${MARKETING_VERSION}-pr.${{ github.event.pull_request.number }}+${SHORT_COMMIT}"
python3 scripts/ci/workflow.py set-marketing-version "$NORMALIZED_VERSION"
echo "SHORT_COMMIT=$SHORT_COMMIT" | tee -a $GITHUB_ENV
echo "MARKETING_VERSION=$NORMALIZED_VERSION" | tee -a $GITHUB_ENV
- name: Install xcbeautify
run: brew install xcbeautify
- name: Add PR suffix to version
run: sed -e "/MARKETING_VERSION = .*/s/\$/-pr.${{ github.event.pull_request.number }}+$(git rev-parse --short ${COMMIT:-HEAD})/" -i '' Build.xcconfig
env:
COMMIT: ${{ github.event.pull_request.head.sha }}
- name: Get version
id: version
run: echo "version=$(grep MARKETING_VERSION Build.xcconfig | sed -e "s/MARKETING_VERSION = //g")" >> $GITHUB_OUTPUT
- name: Echo version
run: echo "${{ steps.version.outputs.version }}"
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: "26.2"
xcode-version: ${{ matrix.version }}
- name: Restore Cache (exact)
id: xcode-cache-exact
uses: actions/cache/restore@v3
- name: Cache Build
uses: irgaly/xcode-cache@v1
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-build-cache-${{ github.ref_name }}-${{ github.sha }}
key: xcode-cache-deriveddata-${{ github.sha }}
restore-keys: xcode-cache-deriveddata-
swiftpm-cache-key: xcode-cache-sourcedata-${{ github.sha }}
swiftpm-cache-restore-keys: |
xcode-cache-sourcedata-
- 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-build-cache-${{ github.ref_name }}-
- name: Build
env:
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
- name: List Files and derived data
run: |
python3 scripts/ci/workflow.py build; STATUS=$?
python3 scripts/ci/workflow.py encrypt-build
mv SideStore.ipa SideStore-${{ env.MARKETING_VERSION }}.ipa
exit $STATUS
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: 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-build-cache-${{ github.ref_name }}-${{ github.sha }}
- name: Build SideStore
run: NSUnbufferedIO=YES make build 2>&1 | xcbeautify --renderer github-actions && exit ${PIPESTATUS[0]}
- uses: actions/upload-artifact@v4
with:
name: build-logs-${{ env.MARKETING_VERSION }}.zip
path: build-logs.zip
- name: Fakesign app
run: make fakesign
- uses: actions/upload-artifact@v4
with:
name: SideStore-${{ env.MARKETING_VERSION }}.ipa
path: SideStore-${{ env.MARKETING_VERSION }}.ipa
- name: Convert to IPA
run: make ipa
- uses: actions/upload-artifact@v4
- name: Add version to IPA file name
run: mv SideStore.ipa SideStore-${{ steps.version.outputs.version }}.ipa
- name: Upload SideStore.ipa Artifact
uses: actions/upload-artifact@v4
with:
name: SideStore-${{ env.MARKETING_VERSION }}-dSYMs.zip
path: SideStore.dSYMs.zip
name: SideStore-${{ steps.version.outputs.version }}.ipa
path: SideStore-${{ steps.version.outputs.version }}.ipa
- name: Upload *.dSYM Artifact
uses: actions/upload-artifact@v4
with:
name: SideStore-${{ steps.version.outputs.version }}-dSYM
path: ./SideStore.xcarchive/dSYMs/*

View File

@@ -1,135 +1,242 @@
name: Stable SideStore build
on:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+'
- '[0-9]+.[0-9]+.[0-9]+' # example: 1.0.0
workflow_dispatch:
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Build SideStore - stable
runs-on: macos-26
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CHANNEL: stable
UPSTREAM_CHANNEL: ""
name: Build SideStore - stable (on tag push)
strategy:
fail-fast: false
matrix:
include:
- os: 'macos-26'
version: '26.0'
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0
- name: Find Last Successful commit
- name: Echo Build.xcconfig
run: |
LAST_SUCCESSFUL_COMMIT=$(python3 scripts/ci/workflow.py last-successful-commit \
"true" || echo "")
echo "LAST_SUCCESSFUL_COMMIT=$LAST_SUCCESSFUL_COMMIT" | tee -a $GITHUB_ENV
echo "cat Build.xcconfig"
cat Build.xcconfig
shell: bash
- run: brew install ldid xcbeautify
# - 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: Setup Env
- name: Echo Updated Build.xcconfig
run: |
MARKETING_VERSION=$(python3 scripts/ci/workflow.py get-marketing-version)
SHORT_COMMIT=$(python3 scripts/ci/workflow.py commit-id)
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"
echo "Build.xcconfig: $MARKETING_VERSION"
echo "Tag: ${{ github.ref_name }}"
echo 'Version mismatch: $tag != $marketing_version ... '
echo " expected-tag : $MARKETING_VERSION"
echo " pushed-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
echo "MARKETING_VERSION=$MARKETING_VERSION" | tee -a $GITHUB_ENV
echo "SHORT_COMMIT=$SHORT_COMMIT" | tee -a $GITHUB_ENV
- name: Install dependencies - ldid & xcbeautify
run: |
brew install ldid xcbeautify
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1.6.0
with:
xcode-version: "26.0"
xcode-version: ${{ matrix.version }}
- name: Restore Cache (exact)
id: xcode-cache-exact
- 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-build-cache-stable-${{ github.sha }}
key: xcode-cache-build-stable-${{ github.sha }}
- name: Restore Cache (last)
if: steps.xcode-cache-exact.outputs.cache-hit != 'true'
id: xcode-cache-fallback
- 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-build-cache-stable-
key: xcode-cache-build-stable-
- name: Build
id: build
env:
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
- name: (Build) Clean previous build artifacts
run: |
python3 scripts/ci/workflow.py build; STATUS=$?
python3 scripts/ci/workflow.py encrypt-build
exit $STATUS
make clean
mkdir -p build/logs
shell: bash
- name: Save Cache
if: ${{ steps.xcode-cache-fallback.outputs.cache-hit != 'true' }}
- 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' }}
uses: actions/cache/save@v3
with:
path: |
~/Library/Developer/Xcode/DerivedData
~/Library/Caches/org.swift.swiftpm
key: xcode-build-cache-stable-${{ github.sha }}
key: xcode-cache-build-stable-${{ github.sha }}
- name: (Build) List Files and Build artifacts
run: |
echo ">>>>>>>>> Workdir <<<<<<<<<<"
ls -la .
echo ""
- uses: actions/upload-artifact@v4
with:
name: build-logs-${{ env.MARKETING_VERSION }}.zip
path: build-logs.zip
echo ">>>>>>>>> Build <<<<<<<<<<"
find build -maxdepth 3 -exec ls -ld {} + || true # List contents if directory exists
echo ""
- uses: actions/upload-artifact@v4
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: SideStore-${{ env.MARKETING_VERSION }}.ipa
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
- uses: actions/upload-artifact@v4
- 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-${{ env.MARKETING_VERSION }}-dSYMs.zip
name: SideStore-${{ steps.version.outputs.version }}-dSYMs.zip
path: SideStore.dSYMs.zip
- name: Generate Metadata
run: |
python3 scripts/ci/workflow.py dump-project-settings
PRODUCT_NAME=$(python3 scripts/ci/workflow.py read-product-name)
BUNDLE_ID=$(python3 scripts/ci/workflow.py read-bundle-id)
IPA_NAME="$PRODUCT_NAME.ipa"
- name: Get current date
id: date
run: echo "date=$(date -u +'%c')" >> $GITHUB_OUTPUT
shell: bash
python3 scripts/ci/workflow.py generate-metadata \
"${{ github.ref_name }}" \
"$SHORT_COMMIT" \
"$MARKETING_VERSION" \
"$CHANNEL" \
"$BUNDLE_ID" \
"$IPA_NAME" \
"$LAST_SUCCESSFUL_COMMIT"
- 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
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
python3 scripts/ci/workflow.py upload-release \
"${{ github.ref_name }}" \
"${{ github.ref_name }}" \
"$GITHUB_SHA" \
"$GITHUB_REPOSITORY" \
"$UPSTREAM_CHANNEL" \
"true"
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: |
<!-- NOTE: to reset SideSource cache, go to `https://apps.sidestore.io/reset-cache/nightly/<sidesource key>`. This is not included in the GitHub Action since it makes draft releases so they can be edited and have a changelog. -->
## 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 }}`

5
.gitignore vendored
View File

@@ -69,7 +69,4 @@ SideStore/.skip-prebuilt-fetch-em_proxy
test-recording.mp4
test-recording.log
altstore-sources.md
local-build.sh
source-metadata.json
release-notes.md
local-build.sh

View File

@@ -48,90 +48,6 @@
remoteGlobalIDString = BF58047A246A28F7008AE704;
remoteInfo = AltBackup;
};
A802C9F82F525C030049FE2B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A802C5DA2F525B910049FE2B /* libfragmentzip.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 87B8C3401E0E9C37002F817D;
remoteInfo = "fragmentzip-cli-macOS";
};
A802C9FA2F525C030049FE2B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A802C5DA2F525B910049FE2B /* libfragmentzip.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = B315FDB02866CCF8002E243C;
remoteInfo = "fragmentzip-cli-iOS";
};
A802C9FC2F525C030049FE2B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A802C5DA2F525B910049FE2B /* libfragmentzip.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = B315FDB52866CD91002E243C;
remoteInfo = "fragmentzip-macOS";
};
A802C9FE2F525C030049FE2B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A802C5DA2F525B910049FE2B /* libfragmentzip.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = B315FDCE2866CDD3002E243C;
remoteInfo = "fragmentzip-iOS";
};
A802CA012F525C030049FE2B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A802C5B62F525B910049FE2B /* libgeneral.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 87977F6F227C4B71004F31DA;
remoteInfo = libgeneral;
};
A802CA062F525C030049FE2B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A802C95A2F525B910049FE2B /* Roxas.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = BFADAFF819AE7BB70050CF31;
remoteInfo = Roxas;
};
A802CA082F525C030049FE2B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A802C95A2F525B910049FE2B /* Roxas.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = BF8624801BB742E700C12EEE;
remoteInfo = RoxasTV;
};
A802CA0A2F525C030049FE2B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A802C95A2F525B910049FE2B /* Roxas.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = BFADB00319AE7BB80050CF31;
remoteInfo = RoxasTests;
};
A802CA0E2F525C030049FE2B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A802C8B62F525B910049FE2B /* SampleApp.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 44B1EE7C23DB90D5004E2E29;
remoteInfo = SampleApp;
};
A802CA102F525C030049FE2B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A802C8B62F525B910049FE2B /* SampleApp.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 445A906A2400612800B487B4;
remoteInfo = "NSAttributedString+MarkdownTests";
};
A802CA132F525C030049FE2B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A802C8CF2F525B910049FE2B /* SampleApp.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 44E8FA8923D90632009E1D13;
remoteInfo = SampleApp;
};
A802CA162F525C030049FE2B /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A802C8DB2F525B910049FE2B /* SwiftSampleApp.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 44962FDA23E7A54A00E2A598;
remoteInfo = SwiftSampleApp;
};
A81173222F4B8DBD0013ABD0 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A811720B2F4B8BA80013ABD0 /* em_proxy.xcodeproj */;
@@ -1903,174 +1819,6 @@
remoteGlobalIDString = 44962FDA23E7A54A00E2A598;
remoteInfo = SwiftSampleApp;
};
A8F3C1312F4E37A5006BC252 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A8F3BD152F4E3794006BC252 /* libfragmentzip.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 87B8C3401E0E9C37002F817D;
remoteInfo = "fragmentzip-cli-macOS";
};
A8F3C1332F4E37A5006BC252 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A8F3BD152F4E3794006BC252 /* libfragmentzip.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = B315FDB02866CCF8002E243C;
remoteInfo = "fragmentzip-cli-iOS";
};
A8F3C1352F4E37A5006BC252 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A8F3BD152F4E3794006BC252 /* libfragmentzip.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = B315FDB52866CD91002E243C;
remoteInfo = "fragmentzip-macOS";
};
A8F3C1372F4E37A5006BC252 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A8F3BD152F4E3794006BC252 /* libfragmentzip.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = B315FDCE2866CDD3002E243C;
remoteInfo = "fragmentzip-iOS";
};
A8F3C13A2F4E37A5006BC252 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A8F3BCF12F4E3794006BC252 /* libgeneral.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 87977F6F227C4B71004F31DA;
remoteInfo = libgeneral;
};
A8F3C13F2F4E37A5006BC252 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A8F3C0952F4E3794006BC252 /* Roxas.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = BFADAFF819AE7BB70050CF31;
remoteInfo = Roxas;
};
A8F3C1412F4E37A5006BC252 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A8F3C0952F4E3794006BC252 /* Roxas.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = BF8624801BB742E700C12EEE;
remoteInfo = RoxasTV;
};
A8F3C1432F4E37A5006BC252 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A8F3C0952F4E3794006BC252 /* Roxas.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = BFADB00319AE7BB80050CF31;
remoteInfo = RoxasTests;
};
A8F3C1472F4E37A5006BC252 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A8F3BFF12F4E3794006BC252 /* SampleApp.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 44B1EE7C23DB90D5004E2E29;
remoteInfo = SampleApp;
};
A8F3C1492F4E37A5006BC252 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A8F3BFF12F4E3794006BC252 /* SampleApp.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 445A906A2400612800B487B4;
remoteInfo = "NSAttributedString+MarkdownTests";
};
A8F3C14C2F4E37A5006BC252 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A8F3C00A2F4E3794006BC252 /* SampleApp.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 44E8FA8923D90632009E1D13;
remoteInfo = SampleApp;
};
A8F3C14F2F4E37A5006BC252 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A8F3C0162F4E3794006BC252 /* SwiftSampleApp.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 44962FDA23E7A54A00E2A598;
remoteInfo = SwiftSampleApp;
};
A8F3FC062F4E998F006BC252 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A8F3F7E42F4E96D7006BC252 /* libfragmentzip.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 87B8C3401E0E9C37002F817D;
remoteInfo = "fragmentzip-cli-macOS";
};
A8F3FC082F4E998F006BC252 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A8F3F7E42F4E96D7006BC252 /* libfragmentzip.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = B315FDB02866CCF8002E243C;
remoteInfo = "fragmentzip-cli-iOS";
};
A8F3FC0A2F4E998F006BC252 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A8F3F7E42F4E96D7006BC252 /* libfragmentzip.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = B315FDB52866CD91002E243C;
remoteInfo = "fragmentzip-macOS";
};
A8F3FC0C2F4E998F006BC252 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A8F3F7E42F4E96D7006BC252 /* libfragmentzip.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = B315FDCE2866CDD3002E243C;
remoteInfo = "fragmentzip-iOS";
};
A8F3FC0F2F4E998F006BC252 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A8F3F7C02F4E96D7006BC252 /* libgeneral.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 87977F6F227C4B71004F31DA;
remoteInfo = libgeneral;
};
A8F3FC142F4E998F006BC252 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A8F3FB642F4E96D7006BC252 /* Roxas.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = BFADAFF819AE7BB70050CF31;
remoteInfo = Roxas;
};
A8F3FC162F4E998F006BC252 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A8F3FB642F4E96D7006BC252 /* Roxas.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = BF8624801BB742E700C12EEE;
remoteInfo = RoxasTV;
};
A8F3FC182F4E998F006BC252 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A8F3FB642F4E96D7006BC252 /* Roxas.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = BFADB00319AE7BB80050CF31;
remoteInfo = RoxasTests;
};
A8F3FC1B2F4E998F006BC252 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A8F3FAD92F4E96D7006BC252 /* SampleApp.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 44E8FA8923D90632009E1D13;
remoteInfo = SampleApp;
};
A8F3FC1F2F4E998F006BC252 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A8F3FAC02F4E96D7006BC252 /* SampleApp.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 44B1EE7C23DB90D5004E2E29;
remoteInfo = SampleApp;
};
A8F3FC212F4E998F006BC252 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A8F3FAC02F4E96D7006BC252 /* SampleApp.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 445A906A2400612800B487B4;
remoteInfo = "NSAttributedString+MarkdownTests";
};
A8F3FC242F4E998F006BC252 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A8F3FAE52F4E96D7006BC252 /* SwiftSampleApp.xcodeproj */;
proxyType = 2;
remoteGlobalIDString = 44962FDA23E7A54A00E2A598;
remoteInfo = SwiftSampleApp;
};
A8FAC1CE2F4B51980061A851 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = A8FAC0562F4B50D10061A851 /* em_proxy.xcodeproj */;
@@ -2439,12 +2187,6 @@
/* Begin PBXFileReference section */
191E5FAB290A5D92001A3B7C /* libminimuxer_swift.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libminimuxer_swift.a; sourceTree = BUILT_PRODUCTS_DIR; };
A802C5B62F525B910049FE2B /* libgeneral.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = libgeneral.xcodeproj; sourceTree = "<group>"; };
A802C5DA2F525B910049FE2B /* libfragmentzip.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = libfragmentzip.xcodeproj; sourceTree = "<group>"; };
A802C8B62F525B910049FE2B /* SampleApp.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = SampleApp.xcodeproj; sourceTree = "<group>"; };
A802C8CF2F525B910049FE2B /* SampleApp.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = SampleApp.xcodeproj; sourceTree = "<group>"; };
A802C8DB2F525B910049FE2B /* SwiftSampleApp.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = SwiftSampleApp.xcodeproj; sourceTree = "<group>"; };
A802C95A2F525B910049FE2B /* Roxas.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = Roxas.xcodeproj; sourceTree = "<group>"; };
A8116EAD2F4B8BA80013ABD0 /* libgeneral.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = libgeneral.xcodeproj; sourceTree = "<group>"; };
A8116ED12F4B8BA80013ABD0 /* libfragmentzip.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = libfragmentzip.xcodeproj; sourceTree = "<group>"; };
A81171432F4B8BA80013ABD0 /* SampleApp.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = SampleApp.xcodeproj; sourceTree = "<group>"; };
@@ -2633,18 +2375,6 @@
A8EEDA1E2F4B19B000F2436D /* Roxas.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = Roxas.xcodeproj; sourceTree = "<group>"; };
A8EEDA9B2F4B19B000F2436D /* em_proxy.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = em_proxy.xcodeproj; sourceTree = "<group>"; };
A8EEDA9D2F4B19B000F2436D /* minimuxer.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = minimuxer.xcodeproj; sourceTree = "<group>"; };
A8F3BCF12F4E3794006BC252 /* libgeneral.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = libgeneral.xcodeproj; sourceTree = "<group>"; };
A8F3BD152F4E3794006BC252 /* libfragmentzip.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = libfragmentzip.xcodeproj; sourceTree = "<group>"; };
A8F3BFF12F4E3794006BC252 /* SampleApp.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = SampleApp.xcodeproj; sourceTree = "<group>"; };
A8F3C00A2F4E3794006BC252 /* SampleApp.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = SampleApp.xcodeproj; sourceTree = "<group>"; };
A8F3C0162F4E3794006BC252 /* SwiftSampleApp.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = SwiftSampleApp.xcodeproj; sourceTree = "<group>"; };
A8F3C0952F4E3794006BC252 /* Roxas.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = Roxas.xcodeproj; sourceTree = "<group>"; };
A8F3F7C02F4E96D7006BC252 /* libgeneral.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = libgeneral.xcodeproj; sourceTree = "<group>"; };
A8F3F7E42F4E96D7006BC252 /* libfragmentzip.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = libfragmentzip.xcodeproj; sourceTree = "<group>"; };
A8F3FAC02F4E96D7006BC252 /* SampleApp.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = SampleApp.xcodeproj; sourceTree = "<group>"; };
A8F3FAD92F4E96D7006BC252 /* SampleApp.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = SampleApp.xcodeproj; sourceTree = "<group>"; };
A8F3FAE52F4E96D7006BC252 /* SwiftSampleApp.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = SwiftSampleApp.xcodeproj; sourceTree = "<group>"; };
A8F3FB642F4E96D7006BC252 /* Roxas.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = Roxas.xcodeproj; sourceTree = "<group>"; };
A8FABA492F4B50D00061A851 /* libimobiledevice.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libimobiledevice.a; sourceTree = BUILT_PRODUCTS_DIR; };
A8FABA4A2F4B50D00061A851 /* libem_proxy_swift.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libem_proxy_swift.a; sourceTree = BUILT_PRODUCTS_DIR; };
A8FABCC32F4B50D10061A851 /* libgeneral.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = libgeneral.xcodeproj; sourceTree = "<group>"; };
@@ -2683,13 +2413,6 @@
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
177EF33D2F4D8B8E008CAAE1 /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
"NSAttributedString+Markdown.m",
);
target = BFD247692284B9A500981D42 /* SideStore */;
};
A8A5AFBE2F4C33A300572B4A /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
@@ -2899,7 +2622,6 @@
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
EMProxyWrapper.swift,
IfManager.swift,
MinimuxerWrapper.swift,
Utils/common/AbstractClassError.swift,
Utils/common/BuildInfo.swift,
@@ -2948,7 +2670,7 @@
/* Begin PBXFileSystemSynchronizedRootGroup section */
A8A5AC9D2F4C338F00572B4A /* Roxas */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Roxas; sourceTree = "<group>"; };
A8A5ACE42F4C339400572B4A /* MarkdownAttributedString */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (177EF33D2F4D8B8E008CAAE1 /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = MarkdownAttributedString; sourceTree = "<group>"; };
A8A5ACE42F4C339400572B4A /* MarkdownAttributedString */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = MarkdownAttributedString; sourceTree = "<group>"; };
A8A5AE1B2F4C33A300572B4A /* libplist */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (A8A5AFC02F4C33A300572B4A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = libplist; sourceTree = "<group>"; };
A8A5AE662F4C33A300572B4A /* libimobiledevice-glue */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (A8A5AFBF2F4C33A300572B4A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = "libimobiledevice-glue"; sourceTree = "<group>"; };
A8A5AF5E2F4C33A300572B4A /* libimobiledevice */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (A8A5AFBE2F4C33A300572B4A /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = libimobiledevice; sourceTree = "<group>"; };
@@ -3035,60 +2757,6 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
A802C5E12F525B910049FE2B /* Products */ = {
isa = PBXGroup;
children = (
A802C9F92F525C030049FE2B /* libfragmentzip */,
A802C9FB2F525C030049FE2B /* libfragmentzip */,
A802C9FD2F525C030049FE2B /* libfragmentzip.a */,
A802C9FF2F525C030049FE2B /* libfragmentzip.a */,
);
name = Products;
sourceTree = "<group>";
};
A802C5E32F525B910049FE2B /* Products */ = {
isa = PBXGroup;
children = (
A802CA022F525C030049FE2B /* libgeneral */,
);
name = Products;
sourceTree = "<group>";
};
A802C8E32F525B910049FE2B /* Products */ = {
isa = PBXGroup;
children = (
A802CA142F525C030049FE2B /* SampleApp.app */,
);
name = Products;
sourceTree = "<group>";
};
A802C8E52F525B910049FE2B /* Products */ = {
isa = PBXGroup;
children = (
A802CA0F2F525C030049FE2B /* SampleApp.app */,
A802CA112F525C030049FE2B /* NSAttributedString+MarkdownTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
A802C8E72F525B910049FE2B /* Products */ = {
isa = PBXGroup;
children = (
A802CA172F525C030049FE2B /* SwiftSampleApp.app */,
);
name = Products;
sourceTree = "<group>";
};
A802C95B2F525B910049FE2B /* Products */ = {
isa = PBXGroup;
children = (
A802CA072F525C030049FE2B /* Roxas.framework */,
A802CA092F525C030049FE2B /* Roxas.framework */,
A802CA0B2F525C030049FE2B /* RoxasTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
A811720E2F4B8BA80013ABD0 /* Products */ = {
isa = PBXGroup;
children = (
@@ -4404,114 +4072,6 @@
name = Products;
sourceTree = "<group>";
};
A8F3BD1C2F4E3794006BC252 /* Products */ = {
isa = PBXGroup;
children = (
A8F3C1322F4E37A5006BC252 /* libfragmentzip */,
A8F3C1342F4E37A5006BC252 /* libfragmentzip */,
A8F3C1362F4E37A5006BC252 /* libfragmentzip.a */,
A8F3C1382F4E37A5006BC252 /* libfragmentzip.a */,
);
name = Products;
sourceTree = "<group>";
};
A8F3BD1E2F4E3794006BC252 /* Products */ = {
isa = PBXGroup;
children = (
A8F3C13B2F4E37A5006BC252 /* libgeneral */,
);
name = Products;
sourceTree = "<group>";
};
A8F3C01E2F4E3794006BC252 /* Products */ = {
isa = PBXGroup;
children = (
A8F3C1482F4E37A5006BC252 /* SampleApp.app */,
A8F3C14A2F4E37A5006BC252 /* NSAttributedString+MarkdownTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
A8F3C0202F4E3794006BC252 /* Products */ = {
isa = PBXGroup;
children = (
A8F3C1502F4E37A5006BC252 /* SwiftSampleApp.app */,
);
name = Products;
sourceTree = "<group>";
};
A8F3C0222F4E3794006BC252 /* Products */ = {
isa = PBXGroup;
children = (
A8F3C14D2F4E37A5006BC252 /* SampleApp.app */,
);
name = Products;
sourceTree = "<group>";
};
A8F3C0962F4E3794006BC252 /* Products */ = {
isa = PBXGroup;
children = (
A8F3C1402F4E37A5006BC252 /* Roxas.framework */,
A8F3C1422F4E37A5006BC252 /* Roxas.framework */,
A8F3C1442F4E37A5006BC252 /* RoxasTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
A8F3F7EB2F4E96D7006BC252 /* Products */ = {
isa = PBXGroup;
children = (
A8F3FC072F4E998F006BC252 /* libfragmentzip */,
A8F3FC092F4E998F006BC252 /* libfragmentzip */,
A8F3FC0B2F4E998F006BC252 /* libfragmentzip.a */,
A8F3FC0D2F4E998F006BC252 /* libfragmentzip.a */,
);
name = Products;
sourceTree = "<group>";
};
A8F3F7ED2F4E96D7006BC252 /* Products */ = {
isa = PBXGroup;
children = (
A8F3FC102F4E998F006BC252 /* libgeneral */,
);
name = Products;
sourceTree = "<group>";
};
A8F3FAED2F4E96D7006BC252 /* Products */ = {
isa = PBXGroup;
children = (
A8F3FC1C2F4E998F006BC252 /* SampleApp.app */,
);
name = Products;
sourceTree = "<group>";
};
A8F3FAEF2F4E96D7006BC252 /* Products */ = {
isa = PBXGroup;
children = (
A8F3FC252F4E998F006BC252 /* SwiftSampleApp.app */,
);
name = Products;
sourceTree = "<group>";
};
A8F3FAF12F4E96D7006BC252 /* Products */ = {
isa = PBXGroup;
children = (
A8F3FC202F4E998F006BC252 /* SampleApp.app */,
A8F3FC222F4E998F006BC252 /* NSAttributedString+MarkdownTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
A8F3FB652F4E96D7006BC252 /* Products */ = {
isa = PBXGroup;
children = (
A8F3FC152F4E998F006BC252 /* Roxas.framework */,
A8F3FC172F4E998F006BC252 /* Roxas.framework */,
A8F3FC192F4E998F006BC252 /* RoxasTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
A8FAC0592F4B50D10061A851 /* Products */ = {
isa = PBXGroup;
children = (
@@ -5081,10 +4641,6 @@
ProductGroup = A8A5B06A2F4C347700572B4A /* Products */;
ProjectRef = A8A5B0622F4C347700572B4A /* libfragmentzip.xcodeproj */;
},
{
ProductGroup = A8F3BD1C2F4E3794006BC252 /* Products */;
ProjectRef = A8F3BD152F4E3794006BC252 /* libfragmentzip.xcodeproj */;
},
{
ProductGroup = A8636E9B2F4CF74D00E66784 /* Products */;
ProjectRef = A8636E922F4CF74D00E66784 /* libfragmentzip.xcodeproj */;
@@ -5101,10 +4657,6 @@
ProductGroup = A85AEC6B2F4B22F6002E2E11 /* Products */;
ProjectRef = A85AE43E2F4B22F6002E2E11 /* libfragmentzip.xcodeproj */;
},
{
ProductGroup = A8F3F7EB2F4E96D7006BC252 /* Products */;
ProjectRef = A8F3F7E42F4E96D7006BC252 /* libfragmentzip.xcodeproj */;
},
{
ProductGroup = A8A5B3BA2F4C4C8600572B4A /* Products */;
ProjectRef = A8A5B3B32F4C4C8600572B4A /* libfragmentzip.xcodeproj */;
@@ -5133,10 +4685,6 @@
ProductGroup = A8CCC2482F4B654400B0089A /* Products */;
ProjectRef = A8CCBF072F4B654400B0089A /* libfragmentzip.xcodeproj */;
},
{
ProductGroup = A802C5E12F525B910049FE2B /* Products */;
ProjectRef = A802C5DA2F525B910049FE2B /* libfragmentzip.xcodeproj */;
},
{
ProductGroup = A811973F2F4C1C710013ABD0 /* Products */;
ProjectRef = A81194062F4C1C710013ABD0 /* libfragmentzip.xcodeproj */;
@@ -5189,10 +4737,6 @@
ProductGroup = A8A5B06C2F4C347700572B4A /* Products */;
ProjectRef = A8A5B03E2F4C347700572B4A /* libgeneral.xcodeproj */;
},
{
ProductGroup = A802C5E32F525B910049FE2B /* Products */;
ProjectRef = A802C5B62F525B910049FE2B /* libgeneral.xcodeproj */;
},
{
ProductGroup = A8CCD22A2F4B6B0000B0089A /* Products */;
ProjectRef = A8CCCEBF2F4B6B0000B0089A /* libgeneral.xcodeproj */;
@@ -5209,18 +4753,10 @@
ProductGroup = A8FAD7302F4B61310061A851 /* Products */;
ProjectRef = A8FAD38F2F4B61310061A851 /* libgeneral.xcodeproj */;
},
{
ProductGroup = A8F3F7ED2F4E96D7006BC252 /* Products */;
ProjectRef = A8F3F7C02F4E96D7006BC252 /* libgeneral.xcodeproj */;
},
{
ProductGroup = A8EEDA212F4B19B000F2436D /* Products */;
ProjectRef = A8EED1D72F4B19B000F2436D /* libgeneral.xcodeproj */;
},
{
ProductGroup = A8F3BD1E2F4E3794006BC252 /* Products */;
ProjectRef = A8F3BCF12F4E3794006BC252 /* libgeneral.xcodeproj */;
},
{
ProductGroup = A8636E992F4CF74D00E66784 /* Products */;
ProjectRef = A8636E6E2F4CF74D00E66784 /* libgeneral.xcodeproj */;
@@ -5345,10 +4881,6 @@
ProductGroup = A8A5BE7E2F4C4D6800572B4A /* Products */;
ProjectRef = A8A5BE7D2F4C4D6800572B4A /* Roxas.xcodeproj */;
},
{
ProductGroup = A8F3FB652F4E96D7006BC252 /* Products */;
ProjectRef = A8F3FB642F4E96D7006BC252 /* Roxas.xcodeproj */;
},
{
ProductGroup = A85AEC732F4B22F6002E2E11 /* Products */;
ProjectRef = A85AEC612F4B22F6002E2E11 /* Roxas.xcodeproj */;
@@ -5377,10 +4909,6 @@
ProductGroup = A8FACF9D2F4B5CD40061A851 /* Products */;
ProjectRef = A8FACF932F4B5CD40061A851 /* Roxas.xcodeproj */;
},
{
ProductGroup = A802C95B2F525B910049FE2B /* Products */;
ProjectRef = A802C95A2F525B910049FE2B /* Roxas.xcodeproj */;
},
{
ProductGroup = A85A27CA2F4B370D002E2E11 /* Products */;
ProjectRef = A85A27C02F4B370D002E2E11 /* Roxas.xcodeproj */;
@@ -5401,10 +4929,6 @@
ProductGroup = A86372122F4CF74D00E66784 /* Products */;
ProjectRef = A86372112F4CF74D00E66784 /* Roxas.xcodeproj */;
},
{
ProductGroup = A8F3C0962F4E3794006BC252 /* Products */;
ProjectRef = A8F3C0952F4E3794006BC252 /* Roxas.xcodeproj */;
},
{
ProductGroup = A8EEDA272F4B19B000F2436D /* Products */;
ProjectRef = A8EEDA1E2F4B19B000F2436D /* Roxas.xcodeproj */;
@@ -5425,10 +4949,6 @@
ProductGroup = A8CCE16B2F4B76CF00B0089A /* Products */;
ProjectRef = A8CCE15B2F4B76CF00B0089A /* Roxas.xcodeproj */;
},
{
ProductGroup = A8F3C01E2F4E3794006BC252 /* Products */;
ProjectRef = A8F3BFF12F4E3794006BC252 /* SampleApp.xcodeproj */;
},
{
ProductGroup = A811973D2F4C1C710013ABD0 /* Products */;
ProjectRef = A81196782F4C1C710013ABD0 /* SampleApp.xcodeproj */;
@@ -5497,10 +5017,6 @@
ProductGroup = A8A5C5EE2F4C4FEC00572B4A /* Products */;
ProjectRef = A8A5C5C12F4C4FEC00572B4A /* SampleApp.xcodeproj */;
},
{
ProductGroup = A8F3C0222F4E3794006BC252 /* Products */;
ProjectRef = A8F3C00A2F4E3794006BC252 /* SampleApp.xcodeproj */;
},
{
ProductGroup = A8118FCC2F4C1C250013ABD0 /* Products */;
ProjectRef = A8118F012F4C1C250013ABD0 /* SampleApp.xcodeproj */;
@@ -5545,14 +5061,6 @@
ProductGroup = A8FAC0632F4B50D10061A851 /* Products */;
ProjectRef = A8FABF722F4B50D10061A851 /* SampleApp.xcodeproj */;
},
{
ProductGroup = A802C8E52F525B910049FE2B /* Products */;
ProjectRef = A802C8B62F525B910049FE2B /* SampleApp.xcodeproj */;
},
{
ProductGroup = A8F3FAED2F4E96D7006BC252 /* Products */;
ProjectRef = A8F3FAD92F4E96D7006BC252 /* SampleApp.xcodeproj */;
},
{
ProductGroup = A8FACFA52F4B5CD40061A851 /* Products */;
ProjectRef = A8FACE982F4B5CD40061A851 /* SampleApp.xcodeproj */;
@@ -5573,10 +5081,6 @@
ProductGroup = A85A11E02F4B34EF002E2E11 /* Products */;
ProjectRef = A85A10DD2F4B34EF002E2E11 /* SampleApp.xcodeproj */;
},
{
ProductGroup = A8F3FAF12F4E96D7006BC252 /* Products */;
ProjectRef = A8F3FAC02F4E96D7006BC252 /* SampleApp.xcodeproj */;
},
{
ProductGroup = A8CCC24E2F4B654400B0089A /* Products */;
ProjectRef = A8CCC1792F4B654400B0089A /* SampleApp.xcodeproj */;
@@ -5597,10 +5101,6 @@
ProductGroup = A8CCD2222F4B6B0000B0089A /* Products */;
ProjectRef = A8CCD1552F4B6B0000B0089A /* SampleApp.xcodeproj */;
},
{
ProductGroup = A802C8E32F525B910049FE2B /* Products */;
ProjectRef = A802C8CF2F525B910049FE2B /* SampleApp.xcodeproj */;
},
{
ProductGroup = A8CCC2502F4B654400B0089A /* Products */;
ProjectRef = A8CCC1922F4B654400B0089A /* SampleApp.xcodeproj */;
@@ -5637,10 +5137,6 @@
ProductGroup = A81197392F4C1C710013ABD0 /* Products */;
ProjectRef = A811969D2F4C1C710013ABD0 /* SwiftSampleApp.xcodeproj */;
},
{
ProductGroup = A8F3C0202F4E3794006BC252 /* Products */;
ProjectRef = A8F3C0162F4E3794006BC252 /* SwiftSampleApp.xcodeproj */;
},
{
ProductGroup = A863719D2F4CF74D00E66784 /* Products */;
ProjectRef = A86371932F4CF74D00E66784 /* SwiftSampleApp.xcodeproj */;
@@ -5665,10 +5161,6 @@
ProductGroup = A85AEC752F4B22F6002E2E11 /* Products */;
ProjectRef = A85AE6D52F4B22F6002E2E11 /* SwiftSampleApp.xcodeproj */;
},
{
ProductGroup = A8F3FAEF2F4E96D7006BC252 /* Products */;
ProjectRef = A8F3FAE52F4E96D7006BC252 /* SwiftSampleApp.xcodeproj */;
},
{
ProductGroup = A8FACF9B2F4B5CD40061A851 /* Products */;
ProjectRef = A8FACEBD2F4B5CD40061A851 /* SwiftSampleApp.xcodeproj */;
@@ -5701,10 +5193,6 @@
ProductGroup = A8A5B6C02F4C4C8600572B4A /* Products */;
ProjectRef = A8A5B6B42F4C4C8600572B4A /* SwiftSampleApp.xcodeproj */;
},
{
ProductGroup = A802C8E72F525B910049FE2B /* Products */;
ProjectRef = A802C8DB2F525B910049FE2B /* SwiftSampleApp.xcodeproj */;
},
{
ProductGroup = A8A5ACE52F4C339400572B4A /* Products */;
ProjectRef = A8A5ACDC2F4C339400572B4A /* SwiftSampleApp.xcodeproj */;
@@ -5728,90 +5216,6 @@
/* End PBXProject section */
/* Begin PBXReferenceProxy section */
A802C9F92F525C030049FE2B /* libfragmentzip */ = {
isa = PBXReferenceProxy;
fileType = "compiled.mach-o.executable";
path = libfragmentzip;
remoteRef = A802C9F82F525C030049FE2B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A802C9FB2F525C030049FE2B /* libfragmentzip */ = {
isa = PBXReferenceProxy;
fileType = "compiled.mach-o.executable";
path = libfragmentzip;
remoteRef = A802C9FA2F525C030049FE2B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A802C9FD2F525C030049FE2B /* libfragmentzip.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libfragmentzip.a;
remoteRef = A802C9FC2F525C030049FE2B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A802C9FF2F525C030049FE2B /* libfragmentzip.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libfragmentzip.a;
remoteRef = A802C9FE2F525C030049FE2B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A802CA022F525C030049FE2B /* libgeneral */ = {
isa = PBXReferenceProxy;
fileType = "compiled.mach-o.executable";
path = libgeneral;
remoteRef = A802CA012F525C030049FE2B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A802CA072F525C030049FE2B /* Roxas.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = Roxas.framework;
remoteRef = A802CA062F525C030049FE2B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A802CA092F525C030049FE2B /* Roxas.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = Roxas.framework;
remoteRef = A802CA082F525C030049FE2B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A802CA0B2F525C030049FE2B /* RoxasTests.xctest */ = {
isa = PBXReferenceProxy;
fileType = wrapper.cfbundle;
path = RoxasTests.xctest;
remoteRef = A802CA0A2F525C030049FE2B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A802CA0F2F525C030049FE2B /* SampleApp.app */ = {
isa = PBXReferenceProxy;
fileType = wrapper.application;
path = SampleApp.app;
remoteRef = A802CA0E2F525C030049FE2B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A802CA112F525C030049FE2B /* NSAttributedString+MarkdownTests.xctest */ = {
isa = PBXReferenceProxy;
fileType = wrapper.cfbundle;
path = "NSAttributedString+MarkdownTests.xctest";
remoteRef = A802CA102F525C030049FE2B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A802CA142F525C030049FE2B /* SampleApp.app */ = {
isa = PBXReferenceProxy;
fileType = wrapper.application;
path = SampleApp.app;
remoteRef = A802CA132F525C030049FE2B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A802CA172F525C030049FE2B /* SwiftSampleApp.app */ = {
isa = PBXReferenceProxy;
fileType = wrapper.application;
path = SwiftSampleApp.app;
remoteRef = A802CA162F525C030049FE2B /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A81173232F4B8DBD0013ABD0 /* libem_proxy_static.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
@@ -7548,174 +6952,6 @@
remoteRef = A8EEDAF12F4B1A0700F2436D /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A8F3C1322F4E37A5006BC252 /* libfragmentzip */ = {
isa = PBXReferenceProxy;
fileType = "compiled.mach-o.executable";
path = libfragmentzip;
remoteRef = A8F3C1312F4E37A5006BC252 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A8F3C1342F4E37A5006BC252 /* libfragmentzip */ = {
isa = PBXReferenceProxy;
fileType = "compiled.mach-o.executable";
path = libfragmentzip;
remoteRef = A8F3C1332F4E37A5006BC252 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A8F3C1362F4E37A5006BC252 /* libfragmentzip.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libfragmentzip.a;
remoteRef = A8F3C1352F4E37A5006BC252 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A8F3C1382F4E37A5006BC252 /* libfragmentzip.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libfragmentzip.a;
remoteRef = A8F3C1372F4E37A5006BC252 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A8F3C13B2F4E37A5006BC252 /* libgeneral */ = {
isa = PBXReferenceProxy;
fileType = "compiled.mach-o.executable";
path = libgeneral;
remoteRef = A8F3C13A2F4E37A5006BC252 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A8F3C1402F4E37A5006BC252 /* Roxas.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = Roxas.framework;
remoteRef = A8F3C13F2F4E37A5006BC252 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A8F3C1422F4E37A5006BC252 /* Roxas.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = Roxas.framework;
remoteRef = A8F3C1412F4E37A5006BC252 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A8F3C1442F4E37A5006BC252 /* RoxasTests.xctest */ = {
isa = PBXReferenceProxy;
fileType = wrapper.cfbundle;
path = RoxasTests.xctest;
remoteRef = A8F3C1432F4E37A5006BC252 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A8F3C1482F4E37A5006BC252 /* SampleApp.app */ = {
isa = PBXReferenceProxy;
fileType = wrapper.application;
path = SampleApp.app;
remoteRef = A8F3C1472F4E37A5006BC252 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A8F3C14A2F4E37A5006BC252 /* NSAttributedString+MarkdownTests.xctest */ = {
isa = PBXReferenceProxy;
fileType = wrapper.cfbundle;
path = "NSAttributedString+MarkdownTests.xctest";
remoteRef = A8F3C1492F4E37A5006BC252 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A8F3C14D2F4E37A5006BC252 /* SampleApp.app */ = {
isa = PBXReferenceProxy;
fileType = wrapper.application;
path = SampleApp.app;
remoteRef = A8F3C14C2F4E37A5006BC252 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A8F3C1502F4E37A5006BC252 /* SwiftSampleApp.app */ = {
isa = PBXReferenceProxy;
fileType = wrapper.application;
path = SwiftSampleApp.app;
remoteRef = A8F3C14F2F4E37A5006BC252 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A8F3FC072F4E998F006BC252 /* libfragmentzip */ = {
isa = PBXReferenceProxy;
fileType = "compiled.mach-o.executable";
path = libfragmentzip;
remoteRef = A8F3FC062F4E998F006BC252 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A8F3FC092F4E998F006BC252 /* libfragmentzip */ = {
isa = PBXReferenceProxy;
fileType = "compiled.mach-o.executable";
path = libfragmentzip;
remoteRef = A8F3FC082F4E998F006BC252 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A8F3FC0B2F4E998F006BC252 /* libfragmentzip.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libfragmentzip.a;
remoteRef = A8F3FC0A2F4E998F006BC252 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A8F3FC0D2F4E998F006BC252 /* libfragmentzip.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;
path = libfragmentzip.a;
remoteRef = A8F3FC0C2F4E998F006BC252 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A8F3FC102F4E998F006BC252 /* libgeneral */ = {
isa = PBXReferenceProxy;
fileType = "compiled.mach-o.executable";
path = libgeneral;
remoteRef = A8F3FC0F2F4E998F006BC252 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A8F3FC152F4E998F006BC252 /* Roxas.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = Roxas.framework;
remoteRef = A8F3FC142F4E998F006BC252 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A8F3FC172F4E998F006BC252 /* Roxas.framework */ = {
isa = PBXReferenceProxy;
fileType = wrapper.framework;
path = Roxas.framework;
remoteRef = A8F3FC162F4E998F006BC252 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A8F3FC192F4E998F006BC252 /* RoxasTests.xctest */ = {
isa = PBXReferenceProxy;
fileType = wrapper.cfbundle;
path = RoxasTests.xctest;
remoteRef = A8F3FC182F4E998F006BC252 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A8F3FC1C2F4E998F006BC252 /* SampleApp.app */ = {
isa = PBXReferenceProxy;
fileType = wrapper.application;
path = SampleApp.app;
remoteRef = A8F3FC1B2F4E998F006BC252 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A8F3FC202F4E998F006BC252 /* SampleApp.app */ = {
isa = PBXReferenceProxy;
fileType = wrapper.application;
path = SampleApp.app;
remoteRef = A8F3FC1F2F4E998F006BC252 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A8F3FC222F4E998F006BC252 /* NSAttributedString+MarkdownTests.xctest */ = {
isa = PBXReferenceProxy;
fileType = wrapper.cfbundle;
path = "NSAttributedString+MarkdownTests.xctest";
remoteRef = A8F3FC212F4E998F006BC252 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A8F3FC252F4E998F006BC252 /* SwiftSampleApp.app */ = {
isa = PBXReferenceProxy;
fileType = wrapper.application;
path = SwiftSampleApp.app;
remoteRef = A8F3FC242F4E998F006BC252 /* PBXContainerItemProxy */;
sourceTree = BUILT_PRODUCTS_DIR;
};
A8FAC1CF2F4B51980061A851 /* libem_proxy_static.a */ = {
isa = PBXReferenceProxy;
fileType = archive.ar;

View File

@@ -92,6 +92,7 @@ final class AppDelegate: UIResponder, UIApplicationDelegate {
}
}
self.setTintColor()
self.setTintColor()
self.prepareImageCache()

View File

@@ -368,7 +368,7 @@ private extension FeaturedViewController
#keyPath(StoreApp._source._apps),
#keyPath(StoreApp.bundleIdentifier),
StoreApp.altstoreAppID,
#keyPath(StoreApp.installedApp)
#keyPath(StoreApp.installedApp),
)
let primaryFetchRequest = fetchRequest.copy() as! NSFetchRequest<StoreApp>

View File

@@ -997,7 +997,6 @@ extension AppManager
case .failure(let error): completionHandler(.failure(error))
case .success(let installedApp): completionHandler(.success(installedApp))
}
//UIApplication.shared.open(shortcutURLon, options: [:], completionHandler: nil)
}
installOperation.addDependency(sendAppOperation)

View File

@@ -11,8 +11,6 @@ import AltStoreCore
import AltSign
import Roxas
let shortcutURLonDelay = URL(string: "shortcuts://run-shortcut?name=TurnOnDataDelay")!
@objc(InstallAppOperation)
final class InstallAppOperation: ResultOperation<InstalledApp>
{
@@ -178,13 +176,6 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
var installing = true
if installedApp.storeApp?.bundleIdentifier.range(of: Bundle.Info.appbundleIdentifier) != nil {
do {
// we need to flush changes to the disk now in case the changes are lost when iOS kills current process
try installedApp.managedObjectContext?.save()
} catch {
print("Failed to flush installedApp to disk: \(error)")
}
// Reinstalling ourself will hang until we leave the app, so we need to exit it without force closing
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
if UIApplication.shared.applicationState != .active {
@@ -213,10 +204,6 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
let alert = UIAlertController(title: "Finish Refresh", message: "Please reopen SideStore after the process is finished.To finish refreshing, SideStore must be moved to the background. To do this, you can either go to the Home Screen manually or by hitting Continue. Please reopen SideStore after doing this.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("Continue", comment: ""), style: .default, handler: { _ in
print("Going home")
// Cell Shortcut
UIApplication.shared.open(shortcutURLonDelay, options: [:]) { _ in
print("Cell OFF Shortcut finished execution.")}
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
}))
@@ -233,8 +220,6 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
}
}
}
// Cell Shortcut
UIApplication.shared.open(shortcutURLonDelay, options: [:]) { _ in print("Cell OFF Shortcut finished execution.")}
UIApplication.shared.perform(#selector(NSXPCConnection.suspend))
}
}

View File

@@ -211,6 +211,7 @@ private extension PatchAppOperation
#if targetEnvironment(simulator)
throw PatchAppError.unsupportedOperatingSystemVersion(ProcessInfo.processInfo.operatingSystemVersion)
#else
let spotlightPath = "Applications/Spotlight.app/Spotlight"
let spotlightFileURL = self.patchDirectory.appendingPathComponent(spotlightPath)

View File

@@ -25,48 +25,39 @@ final class SendAppOperation: ResultOperation<()>
self.progress.totalUnitCount = 1
}
override func main() {
override func main()
{
super.main()
if let error = self.context.error {
if let error = self.context.error
{
return self.finish(.failure(error))
}
guard let resignedApp = self.context.resignedApp else {
return self.finish(.failure(OperationError.invalidParameters("SendAppOperation.main: self.resignedApp is nil")))
}
let shortcutURLoff = URL(string: "shortcuts://run-shortcut?name=TurnOffData")!
// self.context.resignedApp.fileURL points to the app bundle, but we want the .ipa.
let app = AnyApp(name: resignedApp.name, bundleIdentifier: self.context.bundleIdentifier, url: resignedApp.fileURL, storeApp: nil)
let fileURL = InstalledApp.refreshedIPAURL(for: app)
print("AFC App `fileURL`: \(fileURL.absoluteString)")
// Wait for Shortcut to Finish Before Proceeding
UIApplication.shared.open(shortcutURLoff, options: [:]) { _ in
print("Shortcut finished execution. Proceeding with file transfer.")
DispatchQueue.global().async {
self.processFile(at: fileURL, for: app.bundleIdentifier)
if let data = NSData(contentsOf: fileURL) {
do {
let bytes = Data(data)
try yeetAppAFC(app.bundleIdentifier, bytes)
self.progress.completedUnitCount += 1
self.finish(.success(()))
} catch {
self.finish(.failure(MinimuxerError.RwAfc))
self.progress.completedUnitCount += 1
self.finish(.success(()))
}
}
}
private func processFile(at fileURL: URL, for bundleIdentifier: String) {
guard let data = NSData(contentsOf: fileURL) else {
} else {
print("IPA doesn't exist????")
return self.finish(.failure(OperationError(.appNotFound(name: bundleIdentifier))))
}
do {
let bytes = Data(data)
try yeetAppAFC(bundleIdentifier, bytes)
self.progress.completedUnitCount += 1
self.finish(.success(()))
} catch {
self.finish(.failure(MinimuxerError.RwAfc))
self.progress.completedUnitCount += 1
self.finish(.success(()))
self.finish(.failure(OperationError(.appNotFound(name: resignedApp.name))))
}
}
}

View File

@@ -399,40 +399,6 @@ private extension DatabaseManager
// For backwards compatibility reasons, we cannot use localApp's buildVersion as storeBuildVersion,
// or else the latest update will _always_ be considered new because we don't use buildVersions in our source (yet).
installedApp = InstalledApp(resignedApp: localApp, originalBundleIdentifier: StoreApp.altstoreAppID, certificateSerialNumber: serialNumber, storeBuildVersion: nil, context: context)
// figure out if the current AltStoreApp is signed with "Use Main Profie" option
// by checking if the first extension's entitlement's application-identifier matches current one
repeat {
guard let pluginURL = Bundle.main.builtInPlugInsURL else {
installedApp.useMainProfile = true
break
}
guard let pluginFolders = try? FileManager.default.contentsOfDirectory(at: pluginURL, includingPropertiesForKeys: nil) else {
installedApp.useMainProfile = true
break
}
guard let pluginFolder = pluginFolders.first, let altPluginApp = ALTApplication(fileURL: pluginFolder) else {
installedApp.useMainProfile = true
break
}
let entitlements = altPluginApp.entitlements
guard let appId = entitlements[ALTEntitlement.applicationIdentifier] as? String else {
installedApp.useMainProfile = false
print("no ALTEntitlementApplicationIdentifier???")
break
}
if appId.hasSuffix(Bundle.main.bundleIdentifier!) {
installedApp.useMainProfile = true
} else {
installedApp.useMainProfile = false
}
} while(false)
installedApp.storeApp = storeApp
}

View File

@@ -1,132 +0,0 @@
//
// IfManager.swift
// AltStore
//
// Created by ny on 2/27/26.
// Copyright © 2026 SideStore. All rights reserved.
//
import Foundation
import Network
fileprivate func uti(_ uint: UInt32) -> String? {
var buf = [CChar](repeating: 0, count: Int(NI_MAXHOST))
var addr = in_addr(s_addr: uint.bigEndian)
guard inet_ntop(AF_INET, &addr, &buf, UInt32(INET_ADDRSTRLEN)) != nil,
let str = String(utf8String: buf) else { return nil }
return str
}
fileprivate func socktouint(_ sock: inout sockaddr) -> UInt32 {
var buf = [CChar](repeating: 0, count: Int(NI_MAXHOST))
guard getnameinfo(&sock, socklen_t(sock.sa_len), &buf, socklen_t(buf.count), nil, socklen_t(0), NI_NUMERICHOST) == 0,
let name = String(utf8String: buf) else {
return 0
}
var addr = in_addr()
guard name.withCString({ cString in
inet_pton(AF_INET, cString, &addr)
}) == 1 else { return 0 }
return addr.s_addr.bigEndian
}
public struct NetInfo: Hashable, CustomStringConvertible {
public let name: String
public let hostIP: String
public let destIP: String
public let maskIP: String
private let host: UInt32
private let dest: UInt32
private let mask: UInt32
init(name: String, host: UInt32, dest: UInt32, mask: UInt32) {
self.name = name
self.host = host
self.dest = dest
self.mask = mask
self.hostIP = uti(host) ?? "10.7.0.0"
self.destIP = uti(dest) ?? "10.7.0.1"
self.maskIP = uti(mask) ?? "255.255.255.0"
}
init?(_ ifaddr: ifaddrs) {
guard
let ianame = String(utf8String: ifaddr.ifa_name)
else { return nil }
let host = socktouint(&ifaddr.ifa_addr.pointee)
let dest = socktouint(&ifaddr.ifa_dstaddr.pointee)
let mask = socktouint(&ifaddr.ifa_netmask.pointee)
self.init(name: ianame, host: host, dest: dest, mask: mask)
}
// computed networking values (still numeric internally)
public var minIP: UInt32 { host & mask }
public var maxIP: UInt32 { host | ~mask }
public var minIPString: String { uti(minIP) ?? "nil" }
public var maxIPString: String { uti(maxIP) ?? "nil" }
public var description: String {
"\(name) | ip=\(hostIP) dest=\(destIP) mask=\(maskIP) range=\(minIPString)-\(maxIPString)"
}
}
final class IfManager: Sendable {
public static let shared = IfManager()
nonisolated(unsafe) private(set) var addrs: Set<NetInfo> = Set()
private init() {
self.addrs = IfManager.query()
}
public func query() {
addrs = IfManager.query()
}
private static func query() -> Set<NetInfo> {
var addrs = Set<NetInfo>()
var head: UnsafeMutablePointer<ifaddrs>? = nil
guard getifaddrs(&head) == 0, let first = head else { return addrs }
defer { freeifaddrs(head) }
var cursor: UnsafeMutablePointer<ifaddrs>? = first
while let current = cursor {
// we only want v4 interfaces that aren't loopback and aren't masked 255.255.255.255
let entry = current.pointee
let flags = Int32(entry.ifa_flags)
let isIPv4 = entry.ifa_addr.pointee.sa_family == UInt8(AF_INET)
let isActive = (flags & (IFF_UP | IFF_RUNNING | IFF_LOOPBACK)) == (IFF_UP | IFF_RUNNING)
if isIPv4, isActive, let info = NetInfo(entry), info.maskIP != "255.255.255.255" {
addrs.insert(info)
}
cursor = entry.ifa_next
}
return addrs
}
private var nextLAN: NetInfo? {
addrs.first { $0.name.starts(with: "en") }
}
var nextProbableSideVPN: NetInfo? {
// try old 10.7.0.1 first, then fallback to next v4
// user should only be connected to StosVPN/LocalDevVPN
addrs.first {
$0.hostIP == "10.7.0.1" ||
$0.name.starts(with: "utun")
}
}
var sideVPNPatched: Bool {
nextLAN?.maskIP == nextProbableSideVPN?.maskIP &&
nextLAN?.maxIP == nextProbableSideVPN?.maxIP
}
}

View File

@@ -13,21 +13,14 @@ var isMinimuxerReady: Bool {
print("isMinimuxerReady property is always true on simulator")
return true
#else
IfManager.shared.query()
if #available(iOS 26.4, *) {
print("Running patched check")
return minimuxer.ready() && IfManager.shared.sideVPNPatched
} else {
return minimuxer.ready()
}
return minimuxer.ready()
#endif
}
func minimuxerStartWithLogger(_ pairingFile: String, _ logPath: String, _ loggingEnabled: Bool) throws {
func minimuxerStartWithLogger(_ pairingFile: String,_ logPath: String,_ loggingEnabled: Bool) throws {
#if targetEnvironment(simulator)
print("minimuxerStartWithLogger(\(pairingFile), \(logPath), \(loggingEnabled)) is no-op on simulator")
print("minimuxerStartWithLogger(\(pairingFile), \(logPath), \(loggingEnabled) is no-op on simulator")
#else
print("minimuxerStartWithLogger(\(pairingFile), \(logPath), \(loggingEnabled))")
try minimuxer.startWithLogger(pairingFile, logPath, loggingEnabled)
#endif
}
@@ -44,8 +37,7 @@ func installProvisioningProfiles(_ profileData: Data) throws {
#if targetEnvironment(simulator)
print("installProvisioningProfiles(\(profileData)) is no-op on simulator")
#else
let slice = profileData.toRustByteSlice()
try minimuxer.install_provisioning_profile(slice.forRust())
try minimuxer.install_provisioning_profile(profileData.toRustByteSlice().forRust())
#endif
}
@@ -63,8 +55,7 @@ func yeetAppAFC(_ bundleId: String, _ rawBytes: Data) throws {
#if targetEnvironment(simulator)
print("yeetAppAFC(\(bundleId), \(rawBytes)) is no-op on simulator")
#else
let slice = rawBytes.toRustByteSlice()
try minimuxer.yeet_app_afc(bundleId, slice.forRust())
try minimuxer.yeet_app_afc(bundleId, rawBytes.toRustByteSlice().forRust())
#endif
}

37
release-notes.md Normal file
View File

@@ -0,0 +1,37 @@
### nightly
#### What's Changed
- CI: improve more ci worflow
- CI: improve more ci worflow
- CI: improve more ci worflow
- CI: improve more ci worflow
- CI: improve more ci worflow
- CI: improve more ci worflow
- CI: improve more ci worflow
- CI: improve more ci worflow
- CI: improve more ci worflow
- CI: improve more ci worflow
- CI: improve more ci worflow
- CI: improve more ci worflow
- altsign updated to latest
- CI: improve more ci worflow
- CI: improve more ci worflow
- CI: improve more ci worflow
- CI: improve more ci worflow
- CI: improve more ci worflow
- re added openSSL from new path
- updated altsign to use xcframework for openSSL which was causing huge download of 1.2 GB each time
- CI: improve more ci worflow
- CI: improve more ci worflow
- CI: improve more ci worflow
- CI: improve more ci worflow
- CI: improve more ci worflow
- CI: improve more ci worflow
- CI: improve more ci worflow
- CI: improve more ci worflow
- CI: improve more ci worflow
- CI: improve more ci worflow
- CI: full rewrite - moved logic into ci.py and kept workflow scripts mostly dummy
#### Full Changelog: [38715283...99712f00](https://github.com/SideStore/SideStore/compare/38715283073ea37949a462b889ce3cad403ea499...99712f0020a4f2ae57d8d781514fa735f893c23a)

View File

@@ -95,7 +95,6 @@ def resolve_start_commit(last_successful: str):
except Exception:
return first_commit()
def generate_release_notes(last_successful, tag, branch):
current = head_commit()
@@ -125,12 +124,7 @@ def generate_release_notes(last_successful, tag, branch):
for m in messages:
section += f"{fmt_msg(m)}\n"
if commit_exists(branch):
previous_range = branch
else:
previous_range = last_successful
prev_authors = authors(previous_range)
prev_authors = authors(branch)
recent_authors = authors(f"{last_successful}..{current}")
new_authors = recent_authors - prev_authors
@@ -143,26 +137,13 @@ def generate_release_notes(last_successful, tag, branch):
url = repo_url()
section += (
f"\n{HEADER_MARKER} Full Changelog: "
f"[{ref_display(last_successful)}...{ref_display(current)}]"
f"[{last_successful[:8]}...{current[:8]}]"
f"({url}/compare/{last_successful}...{current})\n"
)
return section
def ref_display(ref):
try:
tag = run(f'git describe --tags --exact-match "{ref}" 2>/dev/null || true').strip()
# allow only semantic version tags: X.Y.Z
if re.fullmatch(r'\d+\.\d+\.\d+', tag):
return tag
except Exception:
pass
return ref[:8]
# ----------------------------------------------------------
# markdown update
# ----------------------------------------------------------
@@ -235,7 +216,7 @@ def retrieve_tag(tag, file_path: Path):
fr"^{TAG_MARKER} {re.escape(tag)}$",
content,
re.MULTILINE | re.IGNORECASE,
)
)
if not match:
return ""
@@ -295,4 +276,4 @@ def main():
if __name__ == "__main__":
main()
main()

View File

@@ -5,6 +5,7 @@ import json
import subprocess
from pathlib import Path
import argparse
import textwrap
import sys
SCRIPT_DIR = Path(__file__).resolve().parent
@@ -107,13 +108,13 @@ def main():
gen_cmd = (
f"python3 {script} "
f"{args.last_successful_commit} {args.release_tag} "
f'--output-dir "{notes_dir}"'
f"--output-dir \"{notes_dir}\""
)
else:
gen_cmd = (
f"python3 {script} "
f"{args.short_commit} {args.release_tag} "
f'--output-dir "{notes_dir}"'
f"--output-dir \"{notes_dir}\""
)
sh(gen_cmd, cwd=repo_root)
@@ -139,7 +140,15 @@ def main():
formatted = now.strftime("%Y-%m-%dT%H:%M:%SZ")
human = now.strftime("%c")
localized_description = getFormattedLocalizedDescription(args.marketing_version, args.short_commit, human, notes)
localized_description = textwrap.dedent(f"""
This is release for:
- version: "{args.marketing_version}"
- revision: "{args.short_commit}"
- timestamp: "{human}"
Release Notes:
{notes}
""").strip()
metadata = {
"is_beta": bool(args.is_beta),
@@ -161,16 +170,6 @@ def main():
print(f"Wrote {out_file}")
def getFormattedLocalizedDescription(marketing_version, short_commit, human, notes):
return f"""
This is release for:
- version: "{marketing_version}"
- revision: "{short_commit}"
- timestamp: "{human}"
Release Notes:
{notes}
""".lstrip("\n")
if __name__ == "__main__":
main()
main()

View File

@@ -6,13 +6,12 @@ import datetime
from pathlib import Path
import time
import json
import inspect
import re
from posix import getcwd
# REPO ROOT relative to script dir
ROOT = Path(__file__).resolve().parents[2]
SCRIPTS = ROOT / 'scripts/ci'
BUILD_SETTINGS_OUTFILE = "project-build-settings.txt"
# ----------------------------------------------------------
# helpers
@@ -58,69 +57,101 @@ def getenv(name, default=""):
def short_commit():
return runAndGet("git rev-parse --short HEAD")
def count_new_commits(last_commit):
if not last_commit or not last_commit.strip():
return 0
# ----------------------------------------------------------
# BUILD NUMBER RESERVATION
# ----------------------------------------------------------
try:
total = int(runAndGet("git rev-list --count HEAD"))
if total == 1:
head = runAndGet("git rev-parse HEAD")
return 1 if head != last_commit else 0
def reserve_build_number(repo, max_attempts=5):
repo = Path(repo).resolve()
version_json = repo / "version.json"
out = runAndGet(f"git rev-list --count {last_commit}..HEAD")
return int(out)
except Exception:
return 0
def utc_now():
return datetime.datetime.now(datetime.UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
def read():
branch = runAndGet("git rev-parse --abbrev-ref HEAD", cwd=repo)
defaults = {
"build": 0,
"issued_at": utc_now(),
"tag": branch,
}
if version_json.exists():
data = json.loads(version_json.read_text())
else:
data = {}
# fill missing fields
for k, v in defaults.items():
data.setdefault(k, v)
# ensure tag always tracks current branch
data["tag"] = branch
version_json.write_text(json.dumps(data, indent=2) + "\n")
return data
def write(data):
version_json.write_text(json.dumps(data, indent=2) + "\n")
for attempt in range(max_attempts):
run("git fetch --depth=1 origin HEAD", check=False, cwd=repo)
run("git reset --hard FETCH_HEAD", check=False, cwd=repo)
data = read()
data["build"] += 1
data["issued_at"] = utc_now()
write(data)
run("git add version.json", check=False, cwd=repo)
run(f"git commit -m '{data['tag']} - build no: {data['build']}' || true", check=False, cwd=repo)
rc = subprocess.call("git push", shell=True, cwd=repo)
if rc == 0:
print(f"Reserved build #{data['build']}", file=sys.stderr)
return data["build"]
print("Push rejected, retrying...", file=sys.stderr)
time.sleep(2)
raise SystemExit("Failed reserving build number")
# ----------------------------------------------------------
# PROJECT INFO
# ----------------------------------------------------------
def dump_project_settings(outdir=None):
outfile = Path(outdir).resolve() / BUILD_SETTINGS_OUTFILE if outdir else BUILD_SETTINGS_OUTFILE
run(f"xcodebuild -showBuildSettings 2>&1 > '{outfile}'")
def _extract_setting(cmd):
out = runAndGet(cmd + " || true").strip() # prevent grep failure from aborting
return out if out else None
def _read_dumped_build_setting(name):
return _extract_setting(
f"cat '{BUILD_SETTINGS_OUTFILE}' "
f"| grep '{name} = ' "
def get_product_name():
return runAndGet(
"xcodebuild -showBuildSettings "
"| grep PRODUCT_NAME "
"| tail -1 "
"| sed -e 's/.*= //g'"
)
def query_build_setting(name):
return _extract_setting(
f"xcodebuild -showBuildSettings 2>&1 "
f"| grep '{name} = ' "
def get_bundle_id():
return runAndGet(
"xcodebuild -showBuildSettings 2>&1 "
"| grep 'PRODUCT_BUNDLE_IDENTIFIER = ' "
"| tail -1 "
"| sed -e 's/.*= //g'"
)
def get_product_name(): return query_build_setting("PRODUCT_NAME")
def get_bundle_id(): return query_build_setting("PRODUCT_BUNDLE_IDENTIFIER")
def read_product_name(): return _read_dumped_build_setting("PRODUCT_NAME")
def read_bundle_id(): return _read_dumped_build_setting("PRODUCT_BUNDLE_IDENTIFIER")
def get_marketing_version():
return runAndGet(f"grep MARKETING_VERSION {ROOT}/Build.xcconfig | sed -e 's/MARKETING_VERSION = //g'")
def set_marketing_version(version):
def set_marketing_version(qualified):
run(
f"sed -E -i '' "
f"'s/^MARKETING_VERSION = .*/MARKETING_VERSION = {version}/' "
f"'s/^MARKETING_VERSION = .*/MARKETING_VERSION = {qualified}/' "
f"{ROOT}/Build.xcconfig"
)
def compute_normalized_version(marketing, build_num, short):
now = datetime.datetime.now(datetime.UTC)
date = now.strftime("%Y%m%d") # normalized date
base = marketing.strip()
return f"{base}-{date}.{build_num}+{short}"
def compute_qualified_version(marketing, build_num, channel, short):
date = datetime.datetime.now(datetime.UTC).strftime("%Y.%m.%d")
return f"{marketing}-{channel}.{date}.{build_num}+{short}"
# ----------------------------------------------------------
# CLEAN
@@ -215,13 +246,13 @@ def tests_run(model):
# ----------------------------------------------------------
def encrypt_logs(name):
pwd = getenv("BUILD_LOG_ZIP_PASSWORD")
cwd = getcwd()
if not pwd or not pwd.strip():
print("BUILD_LOG_ZIP_PASSWORD not set — logs will be uploaded UNENCRYPTED", file=sys.stderr)
run(f'cd {cwd}/build/logs && zip -r {cwd}/{name}.zip *')
return
run(f'cd {cwd}/build/logs && zip -e -P "{pwd}" {cwd}/{name}.zip *')
default_pwd = "12345"
pwd = getenv("BUILD_LOG_ZIP_PASSWORD", default_pwd)
if pwd == default_pwd:
print("Warning: BUILD_LOG_ZIP_PASSWORD not set, using fallback password", file=sys.stderr)
run(f'cd build/logs && zip -e -P "{pwd}" ../../{name}.zip *')
# ----------------------------------------------------------
# RELEASE NOTES
@@ -245,13 +276,22 @@ def retrieve_release_notes(tag):
# ----------------------------------------------------------
# DEPLOY SOURCE.JSON
# ----------------------------------------------------------
def generate_metadata(release_tag, short_commit, marketing_version, channel, bundle_id, ipa_name, last_successful_commit=None):
def deploy(repo, source_json, release_tag, short_commit, marketing_version, channel, bundle_id, ipa_name, last_successful_commit=None):
repo = (ROOT / repo).resolve()
ipa_path = ROOT / ipa_name
source_json_path = repo / source_json
metadata = 'source-metadata.json'
if not repo.exists():
raise SystemExit(f"{repo} repo missing")
if not ipa_path.exists():
raise SystemExit(f"{ipa_path} missing")
if not source_json_path.exists():
raise SystemExit(f"{source_json} missing inside repo")
cmd = (
f"python3 {SCRIPTS}/generate_source_metadata.py "
f"--repo-root {ROOT} "
@@ -266,26 +306,12 @@ def generate_metadata(release_tag, short_commit, marketing_version, channel, bun
f"--bundle-id {bundle_id}"
)
# pass only if provided
if last_successful_commit:
cmd += f" --last-successful-commit {last_successful_commit}"
run(cmd)
def deploy(repo, source_json, release_tag, marketing_version):
repo = (ROOT / repo).resolve()
source_json_path = repo / source_json
metadata = 'source-metadata.json'
if not repo.exists():
raise SystemExit(f"{repo} repo missing")
if not (repo / ".git").exists():
print("Repo is not a git repository, skipping deploy", file=sys.stderr)
return
if not source_json_path.exists():
raise SystemExit(f"{source_json} missing inside repo")
run("git config user.name 'GitHub Actions'", check=False)
run("git config user.email 'github-actions@github.com'", check=False)
@@ -294,7 +320,7 @@ def deploy(repo, source_json, release_tag, marketing_version):
run("git switch main || git switch -c main origin/main", cwd=repo)
run("git reset --hard origin/main", cwd=repo)
# ------------------------------------------------------
max_attempts = 5
for attempt in range(1, max_attempts + 1):
if attempt > 1:
@@ -317,49 +343,28 @@ def deploy(repo, source_json, release_tag, marketing_version):
else:
raise SystemExit("Deploy push failed after retries")
def last_successful_commit(is_stable, tag=None):
is_stable = str(is_stable).lower() in ("1", "true", "yes")
def last_successful_commit(workflow, branch):
import json
try:
if is_stable:
prev_tag = runAndGet(
r'git tag --sort=-v:refname '
r'| grep -E "^[0-9]+\.[0-9]+\.[0-9]+$" '
r'| sed -n "2p" || true'
).strip()
out = runAndGet(
f'gh run list '
f'--workflow "{workflow}" '
f'--json headSha,conclusion,headBranch'
)
if prev_tag:
return runAndGet(f'git rev-parse "{prev_tag}^{{commit}}"')
runs = json.loads(out)
return None # ← changed
if tag:
exists = subprocess.call(
f'git rev-parse -q --verify "refs/tags/{tag}"',
shell=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
) == 0
if exists:
return runAndGet(f'git rev-parse "{tag}^{{commit}}"')
for r in runs:
if r.get("conclusion") == "success" and r.get("headBranch") == branch:
return r["headSha"]
except Exception:
pass
return None
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")
draft = False
prerelease = True
latest = False
if is_stable:
prerelease = False
latest = True
def upload_release(release_name, release_tag, commit_sha, repo, upstream_recommendation):
token = getenv("GH_TOKEN")
if token:
os.environ["GH_TOKEN"] = token
@@ -372,6 +377,7 @@ def upload_release(release_name, release_tag, commit_sha, repo, upstream_tag_rec
meta = json.loads(metadata_path.read_text())
marketing_version = meta.get("version_ipa")
is_beta = bool(meta.get("is_beta"))
build_datetime = meta.get("version_date")
dt = datetime.datetime.fromisoformat(
@@ -385,92 +391,52 @@ def upload_release(release_name, release_tag, commit_sha, repo, upstream_tag_rec
f"--retrieve {release_tag} "
f"--output-dir {ROOT}"
)
if is_stable:
release_notes = re.sub(
r'(?im)^[ \t]*#{1,6}[ \t]*what[\']?s[ \t]+changed[ \t]*$',
"## What's Changed",
release_notes,
flags=re.IGNORECASE | re.MULTILINE,
)
upstream_block = ""
if upstream_tag_recommended and upstream_tag_recommended.strip():
tag = upstream_tag_recommended.strip()
upstream_block = (
f"If you want to try out new features early but want a lower chance of bugs, "
f"you can look at [{repo} {tag}]"
f"(https://github.com/{repo}/releases?q={tag}).\n\n"
)
header = getFormattedUploadMsg(
release_name, commit_sha, repo, upstream_block,
built_time, built_date, marketing_version, is_stable,
# normalize section header
release_notes = re.sub(
r'^\s*#{1,6}\s*what(?:\'?s|\s+is)?\s+(?:new|changed).*',
"## What's Changed",
release_notes,
flags=re.IGNORECASE | re.MULTILINE,
)
body = header + release_notes.lstrip() + "\n"
upstream_block = ""
if upstream_recommendation and upstream_recommendation.strip():
upstream_block = upstream_recommendation.strip() + "\n\n"
raw_body = 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
Built at (UTC): `{built_time}`
Built at (UTC date): `{built_date}`
Commit SHA: `{commit_sha}`
Version: `{marketing_version}`
"""
header = inspect.cleandoc(raw_body)
body = header + "\n\n" + release_notes.lstrip() + "\n"
body_file = ROOT / "release_body.md"
body_file.write_text(body, encoding="utf-8")
draft_flag = "--draft" if draft else ""
prerelease_flag = "--prerelease" if prerelease else ""
latest_flag = "--latest=true" if latest else ""
prerelease_flag = "--prerelease" if is_beta else ""
# create release if it doesn't exist
exists = subprocess.call(
f'gh release view "{release_tag}"',
shell=True,
cwd=ROOT,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
) == 0
if exists:
run(
f'gh release edit "{release_tag}" '
f'--title "{release_name}" '
f'--notes-file "{body_file}" '
f'{draft_flag} {prerelease_flag} {latest_flag}'
)
else:
run(
f'gh release create "{release_tag}" '
f'--title "{release_name}" '
f'--notes-file "{body_file}" '
f'{draft_flag} {prerelease_flag} {latest_flag}'
)
run(
f'gh release edit "{release_tag}" '
f'--title "{release_name}" '
f'--notes-file "{body_file}" '
f'{prerelease_flag}'
)
run(
f'gh release upload "{release_tag}" '
f'SideStore.ipa SideStore.dSYMs.zip build-logs.zip '
f'SideStore.ipa SideStore.dSYMs.zip encrypted-build-logs.zip'
f'--clobber'
)
run(f'git tag -f "{release_tag}" "{commit_sha}"')
run(f'git push origin "refs/tags/{release_tag}" --force')
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!**
""".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
# ----------------------------------------------------------
@@ -479,20 +445,17 @@ COMMANDS = {
# ----------------------------------------------------------
# SHARED
# ----------------------------------------------------------
"commit-id" : (short_commit, 0, ""),
"count-new-commits" : (count_new_commits, 1, "<last_successful_commit>"),
"commid-id" : (short_commit, 0, ""),
# ----------------------------------------------------------
# PROJECT INFO
# ----------------------------------------------------------
"get-marketing-version" : (get_marketing_version, 0, ""),
"set-marketing-version" : (set_marketing_version, 1, "<normalized_version>"),
"compute-normalized" : (compute_normalized_version,3, "<marketing> <build_num> <short_commit>"),
"set-marketing-version" : (set_marketing_version, 1, "<qualified_version>"),
"compute-qualified" : (compute_qualified_version, 4, "<marketing> <build_num> <channel> <short_commit>"),
"reserve_build_number" : (reserve_build_number, 1, "<repo>"),
"get-product-name" : (get_product_name, 0, ""),
"get-bundle-id" : (get_bundle_id, 0, ""),
"dump-project-settings" : (dump_project_settings, 0, ""),
"read-product-name" : (read_product_name, 0, ""),
"read-bundle-id" : (read_bundle_id, 0, ""),
# ----------------------------------------------------------
# CLEAN
@@ -517,22 +480,20 @@ COMMANDS = {
# ----------------------------------------------------------
# LOG ENCRYPTION
# ----------------------------------------------------------
"encrypt-build" : (lambda: encrypt_logs("build-logs"), 0, ""),
"encrypt-tests-build" : (lambda: encrypt_logs("tests-build-logs"), 0, ""),
"encrypt-tests-run" : (lambda: encrypt_logs("tests-run-logs"), 0, ""),
"encrypt-build" : (lambda: encrypt_logs("encrypted-build-logs"), 0, ""),
"encrypt-tests-build" : (lambda: encrypt_logs("encrypted-tests-build-logs"), 0, ""),
"encrypt-tests-run" : (lambda: encrypt_logs("encrypted-tests-run-logs"), 0, ""),
# ----------------------------------------------------------
# RELEASE / DEPLOY
# ----------------------------------------------------------
"last-successful-commit" : (last_successful_commit, 1, "<is_stable> [tag]"),
"release-notes" : (release_notes, 1, "<tag>"),
"retrieve-release-notes" : (retrieve_release_notes, 1, "<tag>"),
"generate-metadata" : (generate_metadata, 7,
"<release_tag> <short_commit> <marketing_version> <channel> <bundle_id> <ipa_name> [last_successful_commit]"),
"deploy" : (deploy, 4,
"<repo> <source_json> <release_tag> <marketing_version>"),
"upload-release" : (upload_release, 5,
"<release_name> <release_tag> <commit_sha> <repo> <upstream_tag_recommended> [is_stable]"),}
"last-successful-commit" : (last_successful_commit, 2, "<workflow_name> <branch>"),
"release-notes" : (release_notes, 1, "<tag>"),
"retrieve-release-notes" : (retrieve_release_notes, 1, "<tag>"),
"deploy" : (deploy, 9,
"<repo> <source_json> <release_tag> <short_commit> <marketing_version> <channel> <bundle_id> <ipa_name> [last_successful_commit]"),
"upload-release" : (upload_release, 5, "<release_name> <release_tag> <commit_sha> <repo> <upstream_recommendation>"),
}
def main():
def usage():
@@ -558,14 +519,15 @@ def main():
suffix = f" {arg_usage}" if arg_usage else ""
raise SystemExit(f"Usage: workflow.py {cmd}{suffix}")
args = sys.argv[2:]
args = sys.argv[2:2 + argc]
result = func(*args) if args else func()
result = func(*args) if argc else func()
# ONLY real outputs go to stdout
if result is not None:
sys.stdout.write(str(result))
sys.stdout.flush()
if __name__ == "__main__":
main()
main()

11
source-metadata.json Normal file
View File

@@ -0,0 +1,11 @@
{
"is_beta": false,
"bundle_identifier": "com.SideStore.SideStore",
"version_ipa": "0.6.3",
"version_date": "2026-02-23T23:38:22Z",
"release_channel": "nightly",
"size": 29313346,
"sha256": "51ec327bca0b0056ccd4c2eb1a130cb7c5bb21de2f303251eea3e0a7336699c4",
"download_url": "https://github.com/SideStore/SideStore/releases/download/nightly/SideStore.ipa",
"localized_description": "This is release for:\n - version: \"0.6.3-nightly.2026.02.24.42+abc123de\"\n - revision: \"99712f00\"\n - timestamp: \"Mon Feb 23 23:38:22 2026\"\n\nRelease Notes:\n#### What's Changed\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- altsign updated to latest\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- re added openSSL from new path\n- updated altsign to use xcframework for openSSL which was causing huge download of 1.2 GB each time\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: improve more ci worflow\n- CI: full rewrite - moved logic into ci.py and kept workflow scripts mostly dummy\n\n#### Full Changelog: [38715283...99712f00](https://github.com/SideStore/SideStore/compare/38715283073ea37949a462b889ce3cad403ea499...99712f0020a4f2ae57d8d781514fa735f893c23a)"
}

View File

@@ -75,14 +75,6 @@
{
"identifier": "thatstel.la.altsource",
"sourceURL": "https://alt.thatstel.la/"
},
{
"identifier": "com.deliacheminot.mona",
"sourceURL": "https://raw.githubusercontent.com/delia-cheminot/mona-hrt/refs/heads/main/ios_source.json"
},
{
"identifier": "moe.ampersand.app.source",
"sourceURL": "https://github.com/NyaomiDEV/Ampersand/releases/latest/download/altstore.json"
}
],
"sources": [
@@ -156,14 +148,6 @@
{
"identifier": "thatstel.la.altsource",
"sourceURL": "https://alt.thatstel.la/"
},
{
"identifier": "com.deliacheminot.mona",
"sourceURL": "https://raw.githubusercontent.com/delia-cheminot/mona-hrt/refs/heads/main/ios_source.json"
},
{
"identifier": "moe.ampersand.app.source",
"sourceURL": "https://github.com/NyaomiDEV/Ampersand/releases/latest/download/altstore.json"
}
]
}