mirror of
https://github.com/SideStore/SideStore.git
synced 2026-03-29 06:45:39 +02:00
Compare commits
16 Commits
3927cbd438
...
cf0a174882
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf0a174882 | ||
|
|
8fc38300d5 | ||
|
|
5460fdfec5 | ||
|
|
ded65e30d6 | ||
|
|
b0fab7550f | ||
|
|
10154954f0 | ||
|
|
59892247a6 | ||
|
|
f350c9fa49 | ||
|
|
32fb3d706c | ||
|
|
232dec2c36 | ||
|
|
e9772afbf6 | ||
|
|
014d7b8346 | ||
|
|
8d90def5d8 | ||
|
|
e891b7ec93 | ||
|
|
e5dbf2b677 | ||
|
|
4ff35e36e4 |
59
.github/workflows/alpha.yml
vendored
59
.github/workflows/alpha.yml
vendored
@@ -1,28 +1,47 @@
|
||||
name: Alpha SideStore build
|
||||
name: Alpha SideStore Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop-alpha
|
||||
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 }}
|
||||
build:
|
||||
runs-on: macos-15
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
|
||||
- run: brew install ldid xcbeautify
|
||||
|
||||
- name: Shared
|
||||
id: shared
|
||||
run: python3 scripts/ci.py shared
|
||||
|
||||
- name: Beta bump
|
||||
env:
|
||||
RELEASE_CHANNEL: alpha
|
||||
run: python3 scripts/ci.py bump-beta
|
||||
|
||||
- name: Version
|
||||
id: version
|
||||
run: python3 scripts/ci.py version
|
||||
|
||||
- name: Build
|
||||
run: python3 scripts/ci.py build
|
||||
|
||||
- name: Encrypt logs
|
||||
env:
|
||||
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
|
||||
run: python3 scripts/ci.py encrypt-build
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: SideStore-${{ steps.version.outputs.version }}.ipa
|
||||
path: SideStore.ipa
|
||||
174
.github/workflows/nightly.yml
vendored
174
.github/workflows/nightly.yml
vendored
@@ -1,82 +1,128 @@
|
||||
name: Nightly SideStore Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
branches: [develop]
|
||||
schedule:
|
||||
- cron: '0 0 * * *' # Runs every night at midnight UTC
|
||||
workflow_dispatch: # Allows manual trigger
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
# 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 }}
|
||||
build:
|
||||
runs-on: macos-26
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Ensure full history
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
|
||||
- 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
|
||||
- run: brew install ldid xcbeautify
|
||||
|
||||
- name: 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: 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: Short Commit SHA
|
||||
id: shared
|
||||
run: python3 scripts/ci.py commid-id
|
||||
|
||||
- name: Nightly Version bump
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE_CHANNEL: nightly
|
||||
run: python3 scripts/ci.py bump-beta
|
||||
|
||||
- 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
|
||||
- name: Version
|
||||
id: version
|
||||
run: python3 scripts/ci.py version
|
||||
|
||||
- name: Clean previous build artifacts
|
||||
run: python3 scripts/ci.py clean
|
||||
|
||||
- name: Build
|
||||
run: python3 scripts/ci.py build
|
||||
|
||||
- name: Tests Build
|
||||
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_BUILD == '1' }}
|
||||
run: python3 scripts/ci.py tests-build
|
||||
|
||||
- name: 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: Tests Run
|
||||
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }}
|
||||
run: python3 scripts/ci.py tests-run
|
||||
|
||||
- name: Encrypt build logs
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
LAST_SUCCESS: ${{ env.last_success }}
|
||||
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
|
||||
run: python3 scripts/ci.py encrypt-build
|
||||
|
||||
Reusable-build:
|
||||
if: |
|
||||
always() &&
|
||||
(github.event_name == 'push' ||
|
||||
(github.event_name == 'schedule' && needs.check-changes.result == 'success' && needs.check-changes.outputs.has_changes == 'true'))
|
||||
needs: check-changes
|
||||
uses: ./.github/workflows/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 }}
|
||||
- name: Encrypt tests-build logs
|
||||
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_BUILD == '1' }}
|
||||
env:
|
||||
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
|
||||
run: python3 scripts/ci.py encrypt-tests-build
|
||||
|
||||
- name: Encrypt tests-run logs
|
||||
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }}
|
||||
env:
|
||||
BUILD_LOG_ZIP_PASSWORD: ${{ secrets.BUILD_LOG_ZIP_PASSWORD }}
|
||||
run: python3 scripts/ci.py encrypt-tests-run
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: encrypted-build-logs-${{ steps.version.outputs.version }}.zip
|
||||
path: encrypted-build-logs.zip
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_BUILD == '1' }}
|
||||
with:
|
||||
name: encrypted-tests-build-logs-${{ steps.shared.outputs.short_commit }}.zip
|
||||
path: encrypted-tests-build-logs.zip
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ vars.ENABLE_TESTS == '1' && vars.ENABLE_TESTS_RUN == '1' }}
|
||||
with:
|
||||
name: encrypted-tests-run-logs-${{ steps.shared.outputs.short_commit }}.zip
|
||||
path: encrypted-tests-run-logs.zip
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: SideStore-${{ steps.version.outputs.version }}.ipa
|
||||
path: SideStore.ipa
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: SideStore-${{ steps.version.outputs.version }}-dSYMs.zip
|
||||
path: SideStore.dSYMs.zip
|
||||
|
||||
- name: Deploy
|
||||
run: python3 scripts/ci.py deploy \
|
||||
--release-tag nightly \
|
||||
--release-name Nightly
|
||||
28
.github/workflows/obsolete/alpha.yml
vendored
Normal file
28
.github/workflows/obsolete/alpha.yml
vendored
Normal 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 }}
|
||||
82
.github/workflows/obsolete/nightly.yml
vendored
Normal file
82
.github/workflows/obsolete/nightly.yml
vendored
Normal 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 }}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -76,7 +76,7 @@ final class LaunchViewController: UIViewController, UIDocumentPickerDelegate {
|
||||
detectAndImportAccountFile()
|
||||
|
||||
if UserDefaults.standard.enableEMPforWireguard {
|
||||
start_em_proxy(bind_addr: AppConstants.Proxy.serverURL)
|
||||
startEMProxy(bind_addr: AppConstants.Proxy.serverURL)
|
||||
}
|
||||
guard let pf = fetchPairingFile() else {
|
||||
displayError("Device pairing file not found.")
|
||||
|
||||
@@ -300,99 +300,3 @@ struct OperationError: ALTLocalizedError {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension MinimuxerError: LocalizedError {
|
||||
public var failureReason: String? {
|
||||
switch self {
|
||||
case .NoDevice:
|
||||
return NSLocalizedString("Cannot fetch the device from the muxer", comment: "")
|
||||
case .NoConnection:
|
||||
return NSLocalizedString("Unable to connect to the device, make sure LocalDevVPN is enabled and you're connected to Wi-Fi. This could mean an invalid pairing.", comment: "")
|
||||
case .PairingFile:
|
||||
return NSLocalizedString("Invalid pairing file. Your pairing file either didn't have a UDID, or it wasn't a valid plist. Please use iloader to replace it.", comment: "")
|
||||
|
||||
case .CreateDebug:
|
||||
return self.createService(name: "debug")
|
||||
case .LookupApps:
|
||||
return self.getFromDevice(name: "installed apps")
|
||||
case .FindApp:
|
||||
return self.getFromDevice(name: "path to the app")
|
||||
case .BundlePath:
|
||||
return self.getFromDevice(name: "bundle path")
|
||||
case .MaxPacket:
|
||||
return self.setArgument(name: "max packet")
|
||||
case .WorkingDirectory:
|
||||
return self.setArgument(name: "working directory")
|
||||
case .Argv:
|
||||
return self.setArgument(name: "argv")
|
||||
case .LaunchSuccess:
|
||||
return self.getFromDevice(name: "launch success")
|
||||
case .Detach:
|
||||
return NSLocalizedString("Unable to detach from the app's process", comment: "")
|
||||
case .Attach:
|
||||
return NSLocalizedString("Unable to attach to the app's process", comment: "")
|
||||
|
||||
case .CreateInstproxy:
|
||||
return self.createService(name: "instproxy")
|
||||
case .CreateAfc:
|
||||
return self.createService(name: "AFC")
|
||||
case .RwAfc:
|
||||
return NSLocalizedString("AFC was unable to manage files on the device. Ensure Wi-Fi and LocalDevVPN are connected. If they both are, replace your pairing using iloader.", comment: "")
|
||||
case .InstallApp(let message):
|
||||
return NSLocalizedString("Unable to install the app: \(message.toString())", comment: "")
|
||||
case .UninstallApp:
|
||||
return NSLocalizedString("Unable to uninstall the app", comment: "")
|
||||
|
||||
case .CreateMisagent:
|
||||
return self.createService(name: "misagent")
|
||||
case .ProfileInstall:
|
||||
return NSLocalizedString("Unable to manage profiles on the device", comment: "")
|
||||
case .ProfileRemove:
|
||||
return NSLocalizedString("Unable to manage profiles on the device", comment: "")
|
||||
case .CreateLockdown:
|
||||
return NSLocalizedString("Unable to connect to lockdown", comment: "")
|
||||
case .CreateCoreDevice:
|
||||
return NSLocalizedString("Unable to connect to core device proxy", comment: "")
|
||||
case .CreateSoftwareTunnel:
|
||||
return NSLocalizedString("Unable to create software tunnel", comment: "")
|
||||
case .CreateRemoteServer:
|
||||
return NSLocalizedString("Unable to connect to remote server", comment: "")
|
||||
case .CreateProcessControl:
|
||||
return NSLocalizedString("Unable to connect to process control", comment: "")
|
||||
case .GetLockdownValue:
|
||||
return NSLocalizedString("Unable to get value from lockdown", comment: "")
|
||||
case .Connect:
|
||||
return NSLocalizedString("Unable to connect to TCP port", comment: "")
|
||||
case .Close:
|
||||
return NSLocalizedString("Unable to close TCP port", comment: "")
|
||||
case .XpcHandshake:
|
||||
return NSLocalizedString("Unable to get services from XPC", comment: "")
|
||||
case .NoService:
|
||||
return NSLocalizedString("Device did not contain service", comment: "")
|
||||
case .InvalidProductVersion:
|
||||
return NSLocalizedString("Service version was in an unexpected format", comment: "")
|
||||
case .CreateFolder:
|
||||
return NSLocalizedString("Unable to create DDI folder", comment: "")
|
||||
case .DownloadImage:
|
||||
return NSLocalizedString("Unable to download DDI", comment: "")
|
||||
case .ImageLookup:
|
||||
return NSLocalizedString("Unable to lookup DDI images", comment: "")
|
||||
case .ImageRead:
|
||||
return NSLocalizedString("Unable to read images to memory", comment: "")
|
||||
case .Mount:
|
||||
return NSLocalizedString("Mount failed", comment: "")
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func createService(name: String) -> String {
|
||||
return String(format: NSLocalizedString("Cannot start a %@ server on the device.", comment: ""), name)
|
||||
}
|
||||
|
||||
fileprivate func getFromDevice(name: String) -> String {
|
||||
return String(format: NSLocalizedString("Cannot fetch %@ from the device.", comment: ""), name)
|
||||
}
|
||||
|
||||
fileprivate func setArgument(name: String) -> String {
|
||||
return String(format: NSLocalizedString("Cannot set %@ on the device.", comment: ""), name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ final class InstallAppOperation: ResultOperation<InstalledApp>
|
||||
}
|
||||
|
||||
do {
|
||||
try install_ipa(installedApp.bundleIdentifier)
|
||||
try installIPA(installedApp.bundleIdentifier)
|
||||
installing = false
|
||||
installedApp.refreshedDate = Date()
|
||||
self.finish(.success(installedApp))
|
||||
|
||||
@@ -46,8 +46,8 @@ final class RefreshAppOperation: ResultOperation<InstalledApp>
|
||||
|
||||
for p in profiles {
|
||||
do {
|
||||
let bytes = p.value.data.toRustByteSlice()
|
||||
try install_provisioning_profile(bytes.forRust())
|
||||
let bytes =
|
||||
try installProvisioningProfiles(p.value.data)
|
||||
} catch {
|
||||
self.finish(.failure(MinimuxerError.ProfileInstall))
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ final class RemoveAppOperation: ResultOperation<InstalledApp>
|
||||
let resignedBundleIdentifier = installedApp.resignedBundleIdentifier
|
||||
|
||||
do {
|
||||
try remove_app(resignedBundleIdentifier)
|
||||
try removeApp(resignedBundleIdentifier)
|
||||
} catch {
|
||||
return self.finish(.failure(error))
|
||||
}
|
||||
|
||||
@@ -222,7 +222,7 @@ private extension ResignAppOperation
|
||||
// The embedded certificate + certificate identifier are already in app bundle, no need to update them.
|
||||
}
|
||||
}
|
||||
else if infoDictionary.keys.contains(Bundle.Info.deviceID), let udid = fetch_udid()?.toString() as? String
|
||||
else if infoDictionary.keys.contains(Bundle.Info.deviceID), let udid = fetchUDID()
|
||||
{
|
||||
// There is an ALTDeviceID entry, so assume the app is using AltKit and replace it with the device's UDID.
|
||||
additionalValues[Bundle.Info.deviceID] = udid
|
||||
|
||||
@@ -46,8 +46,8 @@ final class SendAppOperation: ResultOperation<()>
|
||||
|
||||
if let data = NSData(contentsOf: fileURL) {
|
||||
do {
|
||||
let bytes = Data(data).toRustByteSlice()
|
||||
try yeet_app_afc(app.bundleIdentifier, bytes.forRust())
|
||||
let bytes = Data(data)
|
||||
try yeetAppAFC(app.bundleIdentifier, bytes)
|
||||
self.progress.completedUnitCount += 1
|
||||
self.finish(.success(()))
|
||||
} catch {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
9987603429A4555300818586 /* em_proxy.h in Sources */ = {isa = PBXBuildFile; fileRef = 9999259129A45319005CF020 /* em_proxy.h */; };
|
||||
A8A5B08D2F4C387300572B4A /* em_proxy.h in Sources */ = {isa = PBXBuildFile; fileRef = 9999259129A45319005CF020 /* em_proxy.h */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXBuildRule section */
|
||||
@@ -23,6 +23,7 @@
|
||||
outputFiles = (
|
||||
"$(OBJECT_FILE_DIR)/$(CARGO_XCODE_TARGET_ARCH)-$(EXECUTABLE_NAME)",
|
||||
"$(TARGET_BUILD_DIR)/$(FULL_PRODUCT_NAME)",
|
||||
"$(SRCROOT)/Dependencies/em_proxy/em_proxy.h",
|
||||
);
|
||||
script = "# generated with cargo-xcode 1.5.0\n# modified to use prebuilt binaries\n\nset -eu;\n\nBUILT_SRC=\"./em_proxy/$LIB_FILE_NAME.a\"\necho Generating Static lib: $BUILT_SRC\nln -f -- \"$BUILT_SRC\" \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\" || cp \"$BUILT_SRC\" \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\necho \"$BUILT_SRC -> $TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\n\n# xcode generates dep file, but for its own path, so append our rename to it\n#DEP_FILE_SRC=\"minimuxer/target/${CARGO_XCODE_TARGET_TRIPLE}/release/${CARGO_XCODE_CARGO_DEP_FILE_NAME}\"\n#if [ -f \"$DEP_FILE_SRC\" ]; then\n# DEP_FILE_DST=\"${DERIVED_FILE_DIR}/${CARGO_XCODE_TARGET_ARCH}-${EXECUTABLE_NAME}.d\"\n# cp -f \"$DEP_FILE_SRC\" \"$DEP_FILE_DST\"\n# echo >> \"$DEP_FILE_DST\" \"$SCRIPT_OUTPUT_FILE_0: $BUILT_SRC\"\n#fi\n\n# lipo script needs to know all the platform-specific files that have been built\n# archs is in the file name, so that paths don't stay around after archs change\n# must match input for LipoScript\n#FILE_LIST=\"${DERIVED_FILE_DIR}/${ARCHS}-${EXECUTABLE_NAME}.xcfilelist\"\n#touch \"$FILE_LIST\"\n#if ! egrep -q \"$SCRIPT_OUTPUT_FILE_0\" \"$FILE_LIST\" ; then\n# echo >> \"$FILE_LIST\" \"$SCRIPT_OUTPUT_FILE_0\"\n#fi\n";
|
||||
};
|
||||
@@ -117,6 +118,9 @@
|
||||
);
|
||||
outputPaths = (
|
||||
./em_proxy/em_proxy.h,
|
||||
./em_proxy/em_proxy.swift,
|
||||
"./em_proxy/libem_proxy-ios.a",
|
||||
"./em_proxy/libem_proxy-sim.a",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
@@ -129,7 +133,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
9987603429A4555300818586 /* em_proxy.h in Sources */,
|
||||
A8A5B08D2F4C387300572B4A /* em_proxy.h in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
12
Dependencies/fetch-prebuilt.sh
vendored
12
Dependencies/fetch-prebuilt.sh
vendored
@@ -75,14 +75,16 @@ check_for_update() {
|
||||
if [ "$FORCE_DOWNLOAD" = true ] || [ "$NOT_UPTODATE" = true ] ;then
|
||||
echo "downloading binaries"
|
||||
echo
|
||||
wget -O "$1/lib$1-sim.a" "https://github.com/SideStore/$1/releases/latest/download/lib$1-sim.a"
|
||||
if [[ "$1" != "minimuxer" ]]; then
|
||||
wget -O "$1/lib$1-ios.a" "https://github.com/SideStore/$1/releases/latest/download/lib$1.a"
|
||||
wget -O "$1/$1.h" "https://github.com/SideStore/$1/releases/latest/download/$1.h"
|
||||
wget -O "$1/lib$1-sim.a" "https://github.com/SideStore/$1/releases/latest/download/lib$1-sim.a"
|
||||
wget -O "$1/lib$1-ios.a" "https://github.com/SideStore/$1/releases/latest/download/lib$1-ios.a"
|
||||
wget -O "$1/$1.h" "https://github.com/SideStore/$1/releases/latest/download/$1.h"
|
||||
wget -O "$1/$1.swift" "https://github.com/SideStore/$1/releases/latest/download/$1.swift"
|
||||
echo
|
||||
else
|
||||
wget -O "$1/lib$1-ios.a" "https://github.com/SideStore/$1/releases/latest/download/lib$1-ios.a"
|
||||
wget -O "$1/generated.zip" "https://github.com/SideStore/$1/releases/latest/download/generated.zip"
|
||||
wget -O "$1/lib$1-sim.a" "https://github.com/SideStore/$1/releases/latest/download/lib$1-sim.a"
|
||||
wget -O "$1/lib$1-ios.a" "https://github.com/SideStore/$1/releases/latest/download/lib$1-ios.a"
|
||||
wget -O "$1/generated.zip" "https://github.com/SideStore/$1/releases/latest/download/generated.zip"
|
||||
echo
|
||||
echo "Unzipping generated.zip"
|
||||
cd "$1"
|
||||
|
||||
30
Dependencies/minimuxer.xcodeproj/project.pbxproj
vendored
30
Dependencies/minimuxer.xcodeproj/project.pbxproj
vendored
@@ -7,8 +7,8 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
9961EC2829BE9C2000AF2C6F /* SwiftBridgeCore.h in Sources */ = {isa = PBXBuildFile; fileRef = 9961EC2729BE9C1200AF2C6F /* SwiftBridgeCore.h */; };
|
||||
9987603329A454B500818586 /* minimuxer.h in Sources */ = {isa = PBXBuildFile; fileRef = 9987603229A454B500818586 /* minimuxer.h */; };
|
||||
A8A5B07F2F4C355800572B4A /* minimuxer.h in Sources */ = {isa = PBXBuildFile; fileRef = A8A5B07D2F4C34FF00572B4A /* minimuxer.h */; };
|
||||
A8A5B0802F4C355800572B4A /* SwiftBridgeCore.h in Sources */ = {isa = PBXBuildFile; fileRef = A8A5B07E2F4C34FF00572B4A /* SwiftBridgeCore.h */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXBuildRule section */
|
||||
@@ -24,27 +24,20 @@
|
||||
outputFiles = (
|
||||
"$(OBJECT_FILE_DIR)/$(CARGO_XCODE_TARGET_ARCH)-$(EXECUTABLE_NAME)",
|
||||
"$(TARGET_BUILD_DIR)/$(FULL_PRODUCT_NAME)",
|
||||
"$(SRCROOT)/Dependencies/minimuxer/minimuxer.h",
|
||||
);
|
||||
script = "# generated with cargo-xcode 1.5.0\n# modified to use prebuilt binaries\n\nset -eu;\n\nBUILT_SRC=\"./minimuxer/${LIB_FILE_NAME}.a\"\necho Generating Static lib: $BUILT_SRC\nln -f -- \"$BUILT_SRC\" \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\" || cp \"$BUILT_SRC\" \"$TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\necho \"$BUILT_SRC -> $TARGET_BUILD_DIR/$EXECUTABLE_PATH\"\n\n# xcode generates dep file, but for its own path, so append our rename to it\n#DEP_FILE_SRC=\"minimuxer/target/${CARGO_XCODE_TARGET_TRIPLE}/release/${CARGO_XCODE_CARGO_DEP_FILE_NAME}\"\n#if [ -f \"$DEP_FILE_SRC\" ]; then\n# DEP_FILE_DST=\"${DERIVED_FILE_DIR}/${CARGO_XCODE_TARGET_ARCH}-${EXECUTABLE_NAME}.d\"\n# cp -f \"$DEP_FILE_SRC\" \"$DEP_FILE_DST\"\n# echo >> \"$DEP_FILE_DST\" \"$SCRIPT_OUTPUT_FILE_0: $BUILT_SRC\"\n#fi\n\n# lipo script needs to know all the platform-specific files that have been built\n# archs is in the file name, so that paths don't stay around after archs change\n# must match input for LipoScript\n#FILE_LIST=\"${DERIVED_FILE_DIR}/${ARCHS}-${EXECUTABLE_NAME}.xcfilelist\"\n#touch \"$FILE_LIST\"\n#if ! egrep -q \"$SCRIPT_OUTPUT_FILE_0\" \"$FILE_LIST\" ; then\n# echo >> \"$FILE_LIST\" \"$SCRIPT_OUTPUT_FILE_0\"\n#fi\n";
|
||||
};
|
||||
/* End PBXBuildRule section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
9961EC2729BE9C1200AF2C6F /* SwiftBridgeCore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SwiftBridgeCore.h; path = minimuxer/SwiftBridgeCore.h; sourceTree = "<group>"; };
|
||||
9987603229A454B500818586 /* minimuxer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = minimuxer.h; path = minimuxer/minimuxer.h; sourceTree = "<group>"; };
|
||||
A8A5B07D2F4C34FF00572B4A /* minimuxer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = minimuxer.h; path = minimuxer/minimuxer.h; sourceTree = "<group>"; };
|
||||
A8A5B07E2F4C34FF00572B4A /* SwiftBridgeCore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SwiftBridgeCore.h; path = minimuxer/SwiftBridgeCore.h; sourceTree = "<group>"; };
|
||||
A8A5B09C2F4C454900572B4A /* minimuxer-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "minimuxer-Bridging-Header.h"; path = "minimuxer/minimuxer-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
CA609C732349C7AAD9FA67C4 /* libminimuxer_static.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libminimuxer_static.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
99F87D1529D8E41100B40039 /* Generated */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
9961EC2729BE9C1200AF2C6F /* SwiftBridgeCore.h */,
|
||||
9987603229A454B500818586 /* minimuxer.h */,
|
||||
);
|
||||
name = Generated;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CA6012A875F922869D176AE5 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -56,7 +49,9 @@
|
||||
CA6012A875F9D65BC3C892A8 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
99F87D1529D8E41100B40039 /* Generated */,
|
||||
A8A5B07D2F4C34FF00572B4A /* minimuxer.h */,
|
||||
A8A5B07E2F4C34FF00572B4A /* SwiftBridgeCore.h */,
|
||||
A8A5B09C2F4C454900572B4A /* minimuxer-Bridging-Header.h */,
|
||||
CA6012A875F922869D176AE5 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
@@ -132,6 +127,9 @@
|
||||
./minimuxer/minimuxer.swift,
|
||||
./minimuxer/SwiftBridgeCore.swift,
|
||||
"./minimuxer/minimuxer-Bridging-Header.h",
|
||||
"./minimuxer/minimuxer-helpers.swift",
|
||||
"./minimuxer/libminimuxer-ios.a",
|
||||
"./minimuxer/libminimuxer-sim.a",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
@@ -144,8 +142,8 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
9961EC2829BE9C2000AF2C6F /* SwiftBridgeCore.h in Sources */,
|
||||
9987603329A454B500818586 /* minimuxer.h in Sources */,
|
||||
A8A5B07F2F4C355800572B4A /* minimuxer.h in Sources */,
|
||||
A8A5B0802F4C355800572B4A /* SwiftBridgeCore.h in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
//
|
||||
// EMProxyWrwapper.swift
|
||||
// AltStore
|
||||
// SideStore
|
||||
//
|
||||
// Created by Magesh K on 22/02/26.
|
||||
// Copyright © 2026 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import em_proxy
|
||||
private import em_proxy
|
||||
|
||||
public func startEMProxy(bind_addr: String) {
|
||||
#if targetEnvironment(simulator)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
//
|
||||
// MinimuxerWrapper.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Jackson Coxson on 10/26/22.
|
||||
// Created by Magesh K on 22/02/26.
|
||||
// Copyright © 2026 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import minimuxer
|
||||
private import minimuxer
|
||||
|
||||
var isMinimuxerReady: Bool {
|
||||
#if targetEnvironment(simulator)
|
||||
@@ -32,3 +32,146 @@ func targetMinimuxerAddress() {
|
||||
minimuxer.target_minimuxer_address()
|
||||
#endif
|
||||
}
|
||||
|
||||
func installProvisioningProfiles(_ profileData: Data) throws {
|
||||
#if targetEnvironment(simulator)
|
||||
print("installProvisioningProfiles(\(profileData)) is no-op on simulator")
|
||||
#else
|
||||
try minimuxer.install_provisioning_profile(profileData.toRustByteSlice().forRust())
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
func removeApp(_ bundleId: String) throws {
|
||||
#if targetEnvironment(simulator)
|
||||
print("removeApp(\(bundleId)) is no-op on simulator")
|
||||
#else
|
||||
try minimuxer.remove_app(bundleId)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
func yeetAppAFC(_ bundleId: String, _ rawBytes: Data) throws {
|
||||
#if targetEnvironment(simulator)
|
||||
print("yeetAppAFC(\(bundleId), \(rawBytes)) is no-op on simulator")
|
||||
#else
|
||||
try minimuxer.yeet_app_afc(bundleId, rawBytes.toRustByteSlice().forRust())
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
func installIPA(_ bundleId: String) throws {
|
||||
#if targetEnvironment(simulator)
|
||||
print("installIPA(\(bundleId)) is no-op on simulator")
|
||||
#else
|
||||
try minimuxer.install_ipa(bundleId)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
func fetchUDID() -> String? {
|
||||
#if targetEnvironment(simulator)
|
||||
print("fetchUDID() is no-op on simulator")
|
||||
return "XXXXX-XXXX-XXXXX-XXXX"
|
||||
#else
|
||||
return minimuxer.fetch_udid()?.toString()
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
extension MinimuxerError: @retroactive LocalizedError {
|
||||
public var failureReason: String? {
|
||||
switch self {
|
||||
case .NoDevice:
|
||||
return NSLocalizedString("Cannot fetch the device from the muxer", comment: "")
|
||||
case .NoConnection:
|
||||
return NSLocalizedString("Unable to connect to the device, make sure LocalDevVPN is enabled and you're connected to Wi-Fi. This could mean an invalid pairing.", comment: "")
|
||||
case .PairingFile:
|
||||
return NSLocalizedString("Invalid pairing file. Your pairing file either didn't have a UDID, or it wasn't a valid plist. Please use iloader to replace it.", comment: "")
|
||||
|
||||
case .CreateDebug:
|
||||
return self.createService(name: "debug")
|
||||
case .LookupApps:
|
||||
return self.getFromDevice(name: "installed apps")
|
||||
case .FindApp:
|
||||
return self.getFromDevice(name: "path to the app")
|
||||
case .BundlePath:
|
||||
return self.getFromDevice(name: "bundle path")
|
||||
case .MaxPacket:
|
||||
return self.setArgument(name: "max packet")
|
||||
case .WorkingDirectory:
|
||||
return self.setArgument(name: "working directory")
|
||||
case .Argv:
|
||||
return self.setArgument(name: "argv")
|
||||
case .LaunchSuccess:
|
||||
return self.getFromDevice(name: "launch success")
|
||||
case .Detach:
|
||||
return NSLocalizedString("Unable to detach from the app's process", comment: "")
|
||||
case .Attach:
|
||||
return NSLocalizedString("Unable to attach to the app's process", comment: "")
|
||||
|
||||
case .CreateInstproxy:
|
||||
return self.createService(name: "instproxy")
|
||||
case .CreateAfc:
|
||||
return self.createService(name: "AFC")
|
||||
case .RwAfc:
|
||||
return NSLocalizedString("AFC was unable to manage files on the device. Ensure Wi-Fi and LocalDevVPN are connected. If they both are, replace your pairing using iloader.", comment: "")
|
||||
case .InstallApp(let message):
|
||||
return NSLocalizedString("Unable to install the app: \(message.toString())", comment: "")
|
||||
case .UninstallApp:
|
||||
return NSLocalizedString("Unable to uninstall the app", comment: "")
|
||||
|
||||
case .CreateMisagent:
|
||||
return self.createService(name: "misagent")
|
||||
case .ProfileInstall:
|
||||
return NSLocalizedString("Unable to manage profiles on the device", comment: "")
|
||||
case .ProfileRemove:
|
||||
return NSLocalizedString("Unable to manage profiles on the device", comment: "")
|
||||
case .CreateLockdown:
|
||||
return NSLocalizedString("Unable to connect to lockdown", comment: "")
|
||||
case .CreateCoreDevice:
|
||||
return NSLocalizedString("Unable to connect to core device proxy", comment: "")
|
||||
case .CreateSoftwareTunnel:
|
||||
return NSLocalizedString("Unable to create software tunnel", comment: "")
|
||||
case .CreateRemoteServer:
|
||||
return NSLocalizedString("Unable to connect to remote server", comment: "")
|
||||
case .CreateProcessControl:
|
||||
return NSLocalizedString("Unable to connect to process control", comment: "")
|
||||
case .GetLockdownValue:
|
||||
return NSLocalizedString("Unable to get value from lockdown", comment: "")
|
||||
case .Connect:
|
||||
return NSLocalizedString("Unable to connect to TCP port", comment: "")
|
||||
case .Close:
|
||||
return NSLocalizedString("Unable to close TCP port", comment: "")
|
||||
case .XpcHandshake:
|
||||
return NSLocalizedString("Unable to get services from XPC", comment: "")
|
||||
case .NoService:
|
||||
return NSLocalizedString("Device did not contain service", comment: "")
|
||||
case .InvalidProductVersion:
|
||||
return NSLocalizedString("Service version was in an unexpected format", comment: "")
|
||||
case .CreateFolder:
|
||||
return NSLocalizedString("Unable to create DDI folder", comment: "")
|
||||
case .DownloadImage:
|
||||
return NSLocalizedString("Unable to download DDI", comment: "")
|
||||
case .ImageLookup:
|
||||
return NSLocalizedString("Unable to lookup DDI images", comment: "")
|
||||
case .ImageRead:
|
||||
return NSLocalizedString("Unable to read images to memory", comment: "")
|
||||
case .Mount:
|
||||
return NSLocalizedString("Mount failed", comment: "")
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func createService(name: String) -> String {
|
||||
return String(format: NSLocalizedString("Cannot start a %@ server on the device.", comment: ""), name)
|
||||
}
|
||||
|
||||
fileprivate func getFromDevice(name: String) -> String {
|
||||
return String(format: NSLocalizedString("Cannot fetch %@ from the device.", comment: ""), name)
|
||||
}
|
||||
|
||||
fileprivate func setArgument(name: String) -> String {
|
||||
return String(format: NSLocalizedString("Cannot set %@ on the device.", comment: ""), name)
|
||||
}
|
||||
}
|
||||
|
||||
255
scripts/ci.py
Normal file
255
scripts/ci.py
Normal file
@@ -0,0 +1,255 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import datetime
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# helpers
|
||||
# ----------------------------------------------------------
|
||||
|
||||
def run(cmd, check=True):
|
||||
print(f"$ {cmd}", flush=True)
|
||||
subprocess.run(cmd, shell=True, cwd=ROOT, check=check)
|
||||
|
||||
|
||||
def output(name, value):
|
||||
print(f"{name}={value}")
|
||||
out = os.environ.get("GITHUB_OUTPUT")
|
||||
if out:
|
||||
with open(out, "a") as f:
|
||||
f.write(f"{name}={value}\n")
|
||||
|
||||
|
||||
def getenv(name, default=""):
|
||||
return os.environ.get(name, default)
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# SHARED
|
||||
# ----------------------------------------------------------
|
||||
|
||||
def short_commit():
|
||||
sha = subprocess.check_output(
|
||||
"git rev-parse --short HEAD",
|
||||
shell=True,
|
||||
cwd=ROOT
|
||||
).decode().strip()
|
||||
|
||||
output("short_commit", sha)
|
||||
return sha
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# VERSION BUMP
|
||||
# ----------------------------------------------------------
|
||||
|
||||
def bump_beta():
|
||||
date = datetime.datetime.utcnow().strftime("%Y.%m.%d")
|
||||
release_channel = getenv("RELEASE_CHANNEL", "beta")
|
||||
|
||||
build_file = ROOT / "build_number.txt"
|
||||
xcconfig = ROOT / "Build.xcconfig"
|
||||
|
||||
short = subprocess.check_output(
|
||||
"git rev-parse --short HEAD",
|
||||
shell=True,
|
||||
cwd=ROOT
|
||||
).decode().strip()
|
||||
|
||||
def write(num):
|
||||
run(
|
||||
f"""sed -e "/MARKETING_VERSION = .*/s/$/-{release_channel}.{date}.{num}+{short}/" -i '' Build.xcconfig"""
|
||||
)
|
||||
build_file.write_text(f"{date},{num}")
|
||||
|
||||
if not build_file.exists():
|
||||
write(1)
|
||||
return
|
||||
|
||||
last = build_file.read_text().strip().split(",")[1]
|
||||
write(int(last) + 1)
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# VERSION EXTRACTION
|
||||
# ----------------------------------------------------------
|
||||
|
||||
def extract_version():
|
||||
v = subprocess.check_output(
|
||||
"grep MARKETING_VERSION Build.xcconfig | sed -e 's/MARKETING_VERSION = //g'",
|
||||
shell=True,
|
||||
cwd=ROOT
|
||||
).decode().strip()
|
||||
|
||||
output("version", v)
|
||||
return v
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# CLEAN
|
||||
# ----------------------------------------------------------
|
||||
def clean():
|
||||
run("make clean")
|
||||
clean_derived_data()
|
||||
|
||||
def clean_derived_data():
|
||||
run("rm -rf ~/Library/Developer/Xcode/DerivedData/*", check=False)
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# BUILD
|
||||
# ----------------------------------------------------------
|
||||
|
||||
def build():
|
||||
run("make clean")
|
||||
run("rm -rf ~/Library/Developer/Xcode/DerivedData/*", check=False)
|
||||
run("mkdir -p build/logs")
|
||||
|
||||
run(
|
||||
"set -o pipefail && "
|
||||
"NSUnbufferedIO=YES make -B build "
|
||||
"2>&1 | tee -a build/logs/build.log | xcbeautify --renderer github-actions"
|
||||
)
|
||||
|
||||
run("make fakesign | tee -a build/logs/build.log")
|
||||
run("make ipa | tee -a build/logs/build.log")
|
||||
|
||||
run("zip -r -9 ./SideStore.dSYMs.zip ./SideStore.xcarchive/dSYMs")
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# TESTS BUILD
|
||||
# ----------------------------------------------------------
|
||||
|
||||
def tests_build():
|
||||
run("mkdir -p build/logs")
|
||||
run(
|
||||
"NSUnbufferedIO=YES make -B build-tests "
|
||||
"2>&1 | tee -a build/logs/tests-build.log | xcbeautify --renderer github-actions"
|
||||
)
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# TESTS RUN
|
||||
# ----------------------------------------------------------
|
||||
|
||||
def tests_run():
|
||||
run("mkdir -p build/logs")
|
||||
run("nohup make -B boot-sim-async </dev/null >> build/logs/tests-run.log 2>&1 &")
|
||||
|
||||
run("make -B sim-boot-check | tee -a build/logs/tests-run.log")
|
||||
|
||||
run("make run-tests 2>&1 | tee -a build/logs/tests-run.log")
|
||||
|
||||
run("zip -r -9 ./test-results.zip ./build/tests")
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# LOG ENCRYPTION
|
||||
# ----------------------------------------------------------
|
||||
|
||||
def encrypt_logs(name):
|
||||
pwd = getenv("BUILD_LOG_ZIP_PASSWORD", "12345")
|
||||
run(
|
||||
f'cd build/logs && zip -e -P "{pwd}" ../../{name}.zip *'
|
||||
)
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# RELEASE NOTES
|
||||
# ----------------------------------------------------------
|
||||
|
||||
def release_notes(tag):
|
||||
last = subprocess.check_output(
|
||||
"gh run list --branch $(git branch --show-current) "
|
||||
"--status success --json headSha --jq '.[0].headSha'",
|
||||
shell=True,
|
||||
cwd=ROOT
|
||||
).decode().strip()
|
||||
|
||||
if not last or last == "null":
|
||||
last = subprocess.check_output(
|
||||
"git rev-list --max-parents=0 HEAD",
|
||||
shell=True,
|
||||
cwd=ROOT
|
||||
).decode().strip()
|
||||
|
||||
run(f"python3 update_release_notes.py {last} {tag}")
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# PUBLISH SOURCE.JSON
|
||||
# ----------------------------------------------------------
|
||||
|
||||
def publish_apps(short_commit):
|
||||
repo = ROOT / "SideStore/apps-v2.json"
|
||||
|
||||
run("git config user.name 'GitHub Actions'", check=False)
|
||||
run("git config user.email 'github-actions@github.com'", check=False)
|
||||
|
||||
run("python3 scripts/update_apps.py './_includes/source.json'", check=False)
|
||||
|
||||
run("git add ./_includes/source.json", check=False)
|
||||
run(
|
||||
f"git commit -m ' - updated for {short_commit} deployment' || true",
|
||||
check=False
|
||||
)
|
||||
run("git push", check=False)
|
||||
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# ENTRYPOINT
|
||||
# ----------------------------------------------------------
|
||||
|
||||
def main():
|
||||
cmd = sys.argv[1]
|
||||
|
||||
if cmd == "commid-id":
|
||||
short_commit()
|
||||
|
||||
elif cmd == "bump-beta":
|
||||
bump_beta()
|
||||
|
||||
elif cmd == "version":
|
||||
extract_version()
|
||||
|
||||
elif cmd == "clean":
|
||||
clean()
|
||||
|
||||
elif cmd == "cleanDerivedData":
|
||||
clean_derived_data()
|
||||
|
||||
elif cmd == "build":
|
||||
build()
|
||||
|
||||
elif cmd == "tests-build":
|
||||
tests_build()
|
||||
|
||||
elif cmd == "tests-run":
|
||||
tests_run()
|
||||
|
||||
elif cmd == "encrypt-build":
|
||||
encrypt_logs("encrypted-build-logs")
|
||||
|
||||
elif cmd == "encrypt-tests-build":
|
||||
encrypt_logs("encrypted-tests-build-logs")
|
||||
|
||||
elif cmd == "encrypt-tests-run":
|
||||
encrypt_logs("encrypted-tests-run-logs")
|
||||
|
||||
elif cmd == "release-notes":
|
||||
release_notes(sys.argv[2])
|
||||
|
||||
elif cmd == "publish":
|
||||
publish_apps(sys.argv[2])
|
||||
|
||||
else:
|
||||
raise SystemExit(f"Unknown command {cmd}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user