How VPhoneFirmwarePicker Pairs iPhone and CloudOS Builds in vphone-cli
VPhoneFirmwarePicker pairs iPhone and cloudOS builds by validating input URLs, interactively prompting for missing components using catalog data, and returning a VPhoneFirmwareSources struct containing download URLs for both images.
The VPhoneFirmwarePicker class in the Lakr233/vphone-cli repository serves as the central coordinator for firmware selection. It transforms partial or empty user input into concrete download URLs, ensuring compatibility between iPhone hardware and the cloudOS virtualization layer.
Overview of the Firmware Pairing Process
The Three-Stage Resolution Logic
The pairing mechanism operates through three distinct stages within the resolve method located in sources/VPhoneCore/VPhoneFirmwarePicker.swift (lines 38-65).
First, the method normalizes empty strings to nil and checks if both iphone and cloudos URLs are already provided. If both exist, or if the session is non-interactive (isInteractive: false), it immediately returns a VPhoneFirmwareSources struct with the supplied values.
Second, if neither URL is provided, the picker invokes pickPairing (lines 69-82) to display a full pairing menu. This menu is constructed from VPhoneFirmwareCatalog.pairings, a static array of VPhoneFirmwarePairing objects that map specific iPhone models to compatible cloudOS releases.
Third, if only one side is missing, the picker calls pickIPhone (lines 84-95) or pickCloudOS (lines 97-108) to prompt for the missing component individually. Both methods delegate to the choose helper for rendering numbered menus and validating user input against the catalog.
The resolve Method Implementation
The entry point for all pairing operations is the static resolve method. It accepts optional URL strings, an interactivity flag, and injected I/O closures for testability.
public static func resolve(
iphone: String?, cloudos: String?,
isInteractive: Bool,
maxRetries: Int = 5,
read: () -> String?,
write: (String) -> Void
) throws -> VPhoneFirmwareSources {
// Normalise empty strings to nil
let iphone = (iphone?.isEmpty == true) ? nil : iphone
let cloudos = (cloudos?.isEmpty == true) ? nil : cloudos
// 1️⃣ If we already have both URLs or cannot prompt → return as‑is
if (iphone != nil && cloudos != nil) || !isInteractive {
return VPhoneFirmwareSources(iphoneSource: iphone,
cloudosSource: cloudos)
}
// 2️⃣ Neither supplied → show a full pairing menu
if iphone == nil, cloudos == nil {
let p = try pickPairing(maxRetries: maxRetries,
read: read, write: write)
return VPhoneFirmwareSources(iphoneSource: p.iosURL,
cloudosSource: p.cloudosURL)
}
// 3️⃣ One side missing → prompt only for the missing half
if iphone == nil {
let p = try pickIPhone(maxRetries: maxRetries,
read: read, write: write)
return VPhoneFirmwareSources(iphoneSource: p.iosURL,
cloudosSource: cloudos)
}
// iPhone already known → ask for CloudOS
let c = try pickCloudOS(maxRetries: maxRetries,
read: read, write: write)
return VPhoneFirmwareSources(iphoneSource: iphone,
cloudosSource: c.url)
}
The method throws VPhoneFirmwarePickerError.invalidSelection if the user exceeds the retry limit, or VPhoneFirmwarePickerError.aborted if they submit empty input.
Interactive Selection Mechanics
Full Pairing Menu (pickPairing)
When both firmware URLs are absent, pickPairing presents a consolidated menu drawn from VPhoneFirmwareCatalog.pairings. Each entry displays a friendly pairing, such as "iPhone13,4 (17.5)" matched with "cloudOS-17.5-release". Selecting an entry returns the complete VPhoneFirmwarePairing object containing both iosURL and cloudosURL strings.
Single Component Selection (pickIPhone and pickCloudOS)
If only the cloudOS URL is known, pickIPhone displays iPhone-only options from the catalog. Conversely, pickCloudOS prompts for the cloudOS image when the iPhone URL is already specified. These single-column menus filter the available data to show only the missing component's options.
The choose Helper Function
All menus delegate to choose, which prints a numbered list with right-aligned indices, reads from the injected read closure (typically readLine()), trims whitespace, and validates numeric input against available options. The maxRetries parameter controls how many attempts the user has before the function throws an error.
Data Source: VPhoneFirmwareCatalog
The pairing data originates in sources/VPhoneCore/VPhoneFirmwareCatalog.swift. This file defines two primary collections used by the picker:
VPhoneFirmwareCatalog.pairings: An array ofVPhoneFirmwarePairingstructs containingiosName,cloudosName,iosURL, andcloudosURLproperties.VPhoneFirmwareCatalog.cloudOSOptions: An array of standalone cloudOS options used bypickCloudOS.
Each VPhoneFirmwarePairing statically maps specific iPhone hardware identifiers to compatible cloudOS release images, ensuring version alignment between the two firmware components.
Usage Examples
Non-Interactive Resolution
For automated scripts where you want the picker to accept provided values or return nils without prompting:
let sources = try VPhoneFirmwarePicker.resolve(
iphone: nil,
cloudos: nil,
isInteractive: false,
read: { nil },
write: { _ in }
)
// sources.iphoneSource and sources.cloudosSource may be nil;
// fw_prepare.sh will use its internal defaults.
Interactive Full Pairing
To present the complete pairing menu when starting fresh:
let sources = try VPhoneFirmwarePicker.resolve(
iphone: nil,
cloudos: nil,
isInteractive: true,
maxRetries: 5,
read: { readLine() },
write: { print($0) }
)
// sources now contains selected URLs for both firmware images
Supplying One Known URL
When you know the cloudOS URL but need to select a compatible iPhone build:
let sources = try VPhoneFirmwarePicker.resolve(
iphone: nil,
cloudos: "https://example.com/cloudos-17.5.img",
isInteractive: true,
read: { readLine() },
write: { print($0) }
)
// Interactive menu shows only iPhone options; cloudOS URL is preserved
Consuming Results in Build Scripts
The returned structure feeds directly into the firmware preparation pipeline executed by VPhoneFirmwareSelection.swift:
let fwSources = try VPhoneFirmwarePicker.resolve(...)
let args = [
"--iphone", fwSources.iphoneSource ?? "",
"--cloudos", fwSources.cloudosSource ?? ""
]
Process.run("/usr/local/bin/fw_prepare.sh", args)
Summary
- VPhoneFirmwarePicker in
Lakr233/vphone-clihandles pairing logic through the staticresolvemethod insources/VPhoneCore/VPhoneFirmwarePicker.swift. - The process validates existing URLs, presents full pairing menus when both are missing, or prompts for single components when one is provided.
- Data originates from
VPhoneFirmwareCatalog.pairings, which statically defines compatible iPhone and cloudOS combinations with their download URLs. - Interactive menus use the
choosehelper with configurable retry logic and injected I/O for testability. - The method returns a
VPhoneFirmwareSourcesstruct consumable by downstream components likeVPhoneFirmwareSelection.swiftand thefw_prepare.shscript.
Frequently Asked Questions
What happens if I provide both URLs to VPhoneFirmwarePicker?
If you provide non-nil values for both the iphone and cloudos parameters, the resolve method returns immediately with a VPhoneFirmwareSources struct containing your supplied URLs. This bypasses all interactive menus regardless of the isInteractive flag, allowing scripted workflows to override catalog selections.
Where does the pairing data come from?
The picker reads from VPhoneFirmwareCatalog.pairings, a static array defined in sources/VPhoneCore/VPhoneFirmwareCatalog.swift. Each VPhoneFirmwarePairing object contains human-readable names (iosName, cloudosName) and direct download URLs (iosURL, cloudosURL) for specific iPhone hardware and cloudOS version combinations.
How does the picker handle invalid user input?
The choose helper validates that input is a valid index within the menu bounds. If the user enters an invalid number or non-numeric text, the picker decrements the retry counter. After exceeding maxRetries (default 5), it throws VPhoneFirmwarePickerError.invalidSelection. An empty line immediately throws VPhoneFirmwarePickerError.aborted.
Can I use VPhoneFirmwarePicker in non-interactive mode?
Yes. Set isInteractive: false when calling resolve. In this mode, the method returns whatever URLs you provide (including nil) without displaying menus or reading from stdin. This is essential for CI/CD pipelines and automated scripts that rely on fw_prepare.sh defaults or environment-provided URLs.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →