Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Select Xcode
run: scripts/ci-select-xcode.sh
- name: Install the firmware helpers' build tools
run: brew install autoconf automake libtool pkg-config cmake
- name: Reuse the firmware helpers built from the same pinned sources
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: build-output/restore-helpers
key: restore-helpers-${{ hashFiles('scripts/build-restore-helpers.sh') }}-${{ env.XCODE_VERSION }}
- name: Build the app and UI tests with zero warnings
# project.yml makes warnings errors, but Swift does not promote every diagnostic (for
# example actor-isolation warnings involving XCTest), so also fail on any warning in
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- name: Select Xcode
run: scripts/ci-select-xcode.sh
- name: Install the firmware helpers' build tools
run: brew install autoconf automake libtool pkg-config cmake
- name: Reuse the firmware helpers built from the same pinned sources
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
with:
path: build-output/restore-helpers
key: restore-helpers-${{ hashFiles('scripts/build-restore-helpers.sh') }}-${{ env.XCODE_VERSION }}
- uses: github/codeql-action/init@24c54180a607b1449ed407dd24f251e4e9147c8d # v4
with:
languages: swift
Expand Down
29 changes: 26 additions & 3 deletions App/UITests/SmokeUITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ final class SmokeUITests: XCTestCase {
continueAfterFailure = false
app = XCUIApplication()
app.launchArguments = ["-ui-testing", "YES", "-demo-mode", "YES", "-ApplePersistenceIgnoreState", "YES"]
app.launchEnvironment["IDT_RESTORE_HELPERS"] = ""
app.launch()
app.activate()
XCTAssertTrue(app.wait(for: .runningForeground, timeout: 5), "The test app must be in the foreground before native UI interaction")
Expand Down Expand Up @@ -38,6 +39,13 @@ final class SmokeUITests: XCTestCase {
XCTFail("Missing workspace sidebar")
return
}
// Native outline containers can report unhittable while their controls are
// reachable. Check a visible sidebar button before scrolling, then the target row.
guard app.buttons["action-palette"].isHittable else {
attachScreenshot("unreachable-sidebar-\(identifier)")
XCTFail("The sidebar Action Palette button is not reachable. Make the test app window visible before running native UI tests.")
return
}
for _ in 0..<8 {
let viewport = sidebar.frame
let itemFrame = item.frame
Expand Down Expand Up @@ -98,6 +106,23 @@ final class SmokeUITests: XCTestCase {
XCTAssertTrue(app.descendants(matching: .any)["demo-banner"].waitForExistence(timeout: 10))
}

func testFirmwareToolsAreBundled() throws {
// The native UI test runner and app are sibling products in this build directory.
let appBundle = Bundle.main.bundleURL.deletingLastPathComponent()
.appendingPathComponent("iOS Developer Toolkit (Swift).app", isDirectory: true)
let helpers = appBundle.appendingPathComponent("Contents/Helpers", isDirectory: true)
for name in ["idevicerestore", "irecovery"] {
let helper = helpers.appendingPathComponent(name)
XCTAssertTrue(FileManager.default.isExecutableFile(atPath: helper.path), "Missing executable firmware helper: \(helper.path)")
}
let licenses = appBundle.appendingPathComponent("Contents/Resources/Licenses/restore-helpers", isDirectory: true)
for relativePath in ["SOURCES.txt", "idevicerestore/COPYING", "libirecovery/COPYING"] {
XCTAssertTrue(FileManager.default.isReadableFile(atPath: licenses.appendingPathComponent(relativePath).path), "Missing firmware license or source manifest: \(relativePath)")
}
openWorkspace("firmware")
XCTAssertFalse(app.descendants(matching: .any)["firmware.helper-problem"].exists, "The Firmware page must locate its bundled tools without an environment override")
}

func testEveryWorkspaceOpens() throws {
let workspaces = ["overview", "device", "readiness", "location", "liveLogs", "actions", "apps", "backup", "installApp", "evidence", "externalTools", "help", "safety", "firmware", "securityAnalysis"]
for workspace in workspaces {
Expand Down Expand Up @@ -274,9 +299,7 @@ final class SmokeUITests: XCTestCase {
}

func testFirmwareRequiresValidationAndBlocksDemoInstall() throws {
let sidebar = app.descendants(matching: .any)["sidebar-firmware"]
XCTAssertTrue(sidebar.waitForExistence(timeout: 5))
sidebar.click()
openWorkspace("firmware")
let install = app.buttons["firmware.install"]
XCTAssertTrue(install.waitForExistence(timeout: 5))
XCTAssertFalse(install.isEnabled, "A demo device cannot supply a validated install plan")
Expand Down
1 change: 1 addition & 0 deletions App/iOSDeveloperToolkit/Views/FirmwareView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ struct FirmwareView: View {
WorkspacePage(workspace: .firmware) {
if let problem = firmware.helperProblem {
Label(problem, systemImage: "exclamationmark.triangle")
.accessibilityIdentifier("firmware.helper-problem")
.font(.callout)
.foregroundStyle(.orange)
.fixedSize(horizontal: false, vertical: true)
Expand Down
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -372,16 +372,17 @@ xcodebuild -project iOSDeveloperToolkit.xcodeproj -scheme iOSDeveloperToolkit \
scripts/build-release.sh # universal, ad-hoc-signed release ZIP, SBOM, checksums in build-output/release/
```

The release includes the firmware helpers, which `scripts/build-release.sh` builds with
`scripts/build-restore-helpers.sh` from pinned sources. That needs
`brew install autoconf automake libtool pkg-config cmake`. To try the Firmware page from a
development build, build the helpers once and point the app at them:
Xcode Debug and Release builds include the firmware helpers. The `FirmwareHelpers` target
builds `idevicerestore` and `irecovery` from pinned sources, and the app target embeds them,
their licenses, and the source manifest. The first build needs the Homebrew build tools:

```bash
scripts/build-restore-helpers.sh
open --env IDT_RESTORE_HELPERS="$PWD/build-output/restore-helpers/out/bin" "iOS Developer Toolkit (Swift).app"
brew install autoconf automake libtool pkg-config cmake
```

Later builds reuse the helpers while the build script and source pins are unchanged.
`scripts/build-restore-helpers.sh` also builds them independently for command-line tests.

The app icon and logo come from one image, `docs/brand/logo-source.png`:
`xcrun swift scripts/generate-icons.swift` writes the app icon at every size, the in-app logo, and
`docs/brand/` (`icon-1024.png`, `iOSDeveloperToolkitSwift.icns`, `logo-1024.png`, `logo-512.png`,
Expand Down
2 changes: 1 addition & 1 deletion Sources/ToolkitFeatures/Firmware/RestoreHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public enum RestoreHelper: String, CaseIterable, Sendable {
for candidate in candidates where FileManager.default.isExecutableFile(atPath: candidate.path) {
return candidate
}
throw ToolkitError(.toolMissing, message: "The firmware tools are not included in this build.", recovery: "Use a release build of the app, or run scripts/build-restore-helpers.sh and set IDT_RESTORE_HELPERS to its bin folder.", technicalDetail: "Checked: " + candidates.map(\.path).joined(separator: ", "))
throw ToolkitError(.toolMissing, message: "The firmware tools are not included in this build.", recovery: "Build the app with Xcode to embed the firmware tools, or run scripts/build-restore-helpers.sh and set IDT_RESTORE_HELPERS to its bin folder.", technicalDetail: "Checked: " + candidates.map(\.path).joined(separator: ", "))
}
}

Expand Down
Loading
Loading