File: README.md
xtool
Cross-platform Xcode replacement. Build and deploy iOS apps with SwiftPM on Linux, Windows, and macOS.
Overview
xtool is a cross-platform (Linux/WSL/macOS) tool that replicates Xcode functionality with open standards.
โ Build a SwiftPM package into an iOS app
โ Sign and install iOS apps
โ Interact with Apple Developer Services programmatically
Getting Started
- Follow the guide to install
xtool - Create and run your first xtool-powered app by following the tutorial!
Examples
Screenshot
Command line interface
$ xtool --help
OVERVIEW: Cross-platform Xcode replacement
USAGE: xtool <subcommand>
OPTIONS:
-h, --help Show help information.
CONFIGURATION SUBCOMMANDS:
setup Set up xtool for iOS development
auth Manage Apple Developer Services authentication
sdk Manage the Darwin Swift SDK
DEVELOPMENT SUBCOMMANDS:
new Create a new xtool SwiftPM project
dev Build and run an xtool SwiftPM project
ds Interact with Apple Developer Services
DEVICE SUBCOMMANDS:
devices List devices
install Install an ipa file to your device
uninstall Uninstall an installed app
launch Launch an installed app
See 'xtool help <subcommand>' for detailed help.Library
xtool includes a library that you can use to interact with Apple Developer Services, iOS devices, and more from your own app. You can use this by adding XKit as a SwiftPM dependency.
// package dependency:
.package(url: "https://github.com/xtool-org/xtool", .upToNextMinor(from: "1.2.0"))
// target dependency:
.product(name: "XKit", package: "xtool")File: Documentation/xtool.docc/Appex.md
Create App Extensions
Add app extensions to your iOS app.
Overview
iOS includes several App Extension Points to deeply integrate with system functionality: such as Widgets, Share Extensions, and Safari Extensions. With a little setup, you can create App Extensions with xtool.
This guide will show you how to add a Widget Extension to your app. The steps are similar for other extensions as well.
Note: This guide assumes you already have an xtool-based application up and running. If you don't, create one first with doc:First-app.
Step 1: Add a new product
Start by adding a new product declaration to your Package.swift.
// swift-tools-version: 6.0
import PackageDescription
let package = Package(
name: "Hello",
platforms: [.iOS(.v17)],
products: [
.library(
name: "Hello",
targets: ["Hello"]
),
+ .library(
+ name: "HelloWidget",
+ targets: ["HelloWidget"]
+ ),
],
targets: [
.target(name: "Hello"),
+ .target(name: "HelloWidget"),
]
)Step 2: Update xtool.yml
Now that we have two products, we'll need to tell xtool which one corresponds to the application and which one corresponds to the extension.
You should already have a bare-bones xtool.yml file that describes metadata about your package (the bundle ID, at the bare minimum.) We'll add the new information to this file.
version: 1
bundleID: com.example.Hello
+ product: Hello
+ extensions:
+ - product: HelloWidget
+ infoPath: HelloWidget-Info.plistStep 3: Add an Info.plist
App extensions require an Info.plist file that tells the system what kind of extension they are, amongst other things. In the previous step, we promised xtool that this file will be located at ./HelloWidget-Info.plist in your project. We'll now create this file.
<?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">
<dict>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.widgetkit-extension</string>
</dict>
</dict>Note:
The value
com.apple.widgetkit-extensiontells the system that this is a Widget Extension. If you're creating a different type of Extension, yourInfo.plistmight look different.For a list of possible
NSExtensionPointIdentifiervalues, see Apple's documentation on the subject. You may also need other keys likeNSExtensionPrincipalClass: refer to Apple'sbetterlegacy documentation library for details.Finally, note that the above refers to the old paradigm of Foundation Extensions. Apple has started moving towards a newer framework called ExtensionKit for recent extension types. The main difference for extension consumers is that you replace
NSExtension -> EXAppExtensionAttributes, andNSExtensionPointIdentifier -> EXExtensionPointIdentifier. xtool doesn't support ExtensionKit yet, but it's planned.
Step 4: Code your widget
We can finally write the code for the widget. Create a new file at Sources/HelloWidget/Widget.swift with these contents:
import WidgetKit
import SwiftUI
@main struct Bundle: WidgetBundle {
var body: some Widget {
HelloWidget()
}
}
struct HelloWidget: Widget {
var body: some WidgetConfiguration {
StaticConfiguration(
kind: "HelloWidget",
provider: Provider()
) { entry in
VStack {
Text(entry.date, style: .date)
}
.containerBackground(.fill.tertiary, for: .widget)
}
.configurationDisplayName("HelloWidget")
.description("This is an example widget.")
}
struct Entry: TimelineEntry {
var date = Date()
}
struct Provider: TimelineProvider {
func placeholder(in context: Context) -> Entry {
Entry()
}
func getSnapshot(
in context: Context,
completion: @escaping (Entry) -> Void
) {
completion(Entry())
}
func getTimeline(
in context: Context,
completion: @escaping (Timeline<Entry>) -> Void
) {
completion(Timeline(
entries: [Entry()],
policy: .after(.now + 3600)
))
}
}
}Note: Blindly refreshing once an hour isn't a great strategy in practice but it makes for a short snippet. I never said this was a tutorial on writing good WidgetKit code; there's plenty of other resources online if that's your goal, including Apple's own documentation.
Finally, build and run with xtool dev. You should be able to locate the widget in your widget library and add it to your home screen!
File: Documentation/xtool.docc/Control.md
Control your app's structure
Add resources, modify your Info.plist, and more.
Overview
When building your app, xtool provides many knobs to add resources and modify files in its .app bundle. This article explains how to perform some common tasks along these lines.
Many of the tasks here involve modifying the xtool.yml file. The default xtool new template should generate this file for you. If you don't have it, make sure you create one first.
cat > xtool.yml << EOF
version: 1
bundleID: com.example.Hello
EOFChange your bundle ID
You can update your app's bundle ID by editing the bundleID field in xtool.yml.
Note:
xtool adds a prefix to your bundle ID when signing and installing the app on a real device. For example, it may update the above bundle id to
XTL-1234.com.example.Hello. This is because, at least with free accounts (those not enrolled in the Apple Developer Program), two accounts cannot use the same bundle ID. Prefixing ensures that if you share your app with someone else, they won't run into a bundle ID conflict when installing it.With additional engineering effort, this limitation could be lifted for paid Developer Program users by leveraging wildcard provisioning profiles. If you have a concrete use case for this and/or wish to contribute, please file an issue.
Customize Info.plist
xtool automatically generates a bare-bones Info.plist file for you. However, you may need to add new Info.plist keys or edit existing ones.
To do this, create an Info.plist file in your project containing just the keys you want to add/update, and tell xtool about it by adding the infoPath key:
infoPath: path/to/Info.plistTip:
After running
xtool dev [build], you can inspect the built app bundle at./xtool/YourApp.appto view itsInfo.plistand other resources.
For example, you might create an Info.plist file that looks something like
<?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">
<dict>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CFBundleDisplayName</key>
<string>My App</string>
</dict>- The default
Info.plisthas aUISupportedInterfaceOrientationsvalue of portrait. This will replace it with landscape-only. - We don't include a
CFBundleDisplayNameby default, so this will add the key and specify a display name of "My App".
Copy resources (SwiftPM)
If you need to include non-code resource files, like images, the easiest way to do so is to use SwiftPM Resources. These "just work" with xtool.
For example, if you have Sources/Hello/Blob.png, you can include it in your app by updating the targets in your Package.swift to
targets: [
.target(
name: "Hello",
resources: [.copy("Blob.png")]
),
]Then, to draw it on screen, you can use
Image("Blob", bundle: Bundle.module)Copy resources (top level)
SwiftPM resources are copied into .bundle directories nested inside your .app. Sometimes, you need to place a file in a specific location in the app directory instead.
To achieve this, you can include a resources array in your xtool.yml containing a list of files that should be directly included at the root level of the app bundle.
For example, if you use Firebase, you usually need to include a GoogleServices-Info.plist file in your root bundle. If you saved this file in your project directory at ./Resources/GoogleServices-Info.plist, you can update your xtool.yml with
resources:
- Resources/GoogleServices-Info.plistThe file will be copied to Hello.app/GoogleServices-Info.plist.
Add an app icon
Say your app icon is saved at Resources/AppIcon.png. You can make this your app icon by setting the iconPath key in xtool.yml:
iconPath: Resources/AppIcon.pngTip:
Your app icon must be a png, and it should ideally have a resolution of 1024x1024px.
Add entitlements
You may need to add special entitlements to your app, for example you need the com.apple.developer.homekit entitlement in order to connect to HomeKit devices.
First create an App.entitlements file with the necessary entitlements:
<?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">
<dict>
<key>com.apple.developer.homekit</key>
</dict>Then, tell xtool about it via xtool.yml:
entitlementsPath: App.entitlementsTroubleshooting:
There are a number of situations in which xtool may not be able to successfully apply an entitlement to your app.
- Some entitlements, such as Network Extension, don't work with free developer accounts. You might need to enrol in one of the Apple Developer Programs to use these.
- Other entitlements, like User Notifications Filtering, require special permission from Apple. If you do have the appropriate permission, these may or may not work with xtool.
- Many entitlements require specific "Capabilities" to be enabled on your app in the Developer Services portal. xtool handles this for you, but we need to add code to xtool for each Entitlement-Capability mapping. If you run into a Capability that we don't handle yet, please let us know.
Entitlements are a large surface area. If you find any bugs or missing functionality, please create a GitHub Issue.
File: Documentation/xtool.docc/Installation-Linux.md
Installation (Linux/Windows)
Set up xtool for iOS app development on Linux or Windows.
Overview
This article outlines the steps to install xtool and begin developing iOS apps on Linux (or Windows via WSL).
Prerequisites
WSL (for Windows users)
If you're on Windows, you can use xtool via Windows Subsystem for Linux (WSL).
Once you install WSL, you'll also need to set up USB passthrough. See Microsoft's instructions on installing USBIPD. Make sure you're able to bind your iOS device to WSL via USB.
Swift
Install the Swift 6.3 toolchain for your Linux distribution from https://swift.org/install/linux.
After following the steps there, confirm that Swift is installed correctly:
swift --version
# should say something like:
# Swift version 6.3 (swift-6.3-RELEASE)usbmuxd
xtool relies on usbmuxd to talk to your iOS device from Linux.
Your Linux distro probably offers this package, and it may be preinstalled. To check if it is, run
usbmuxd --help
# Usage: usbmuxd [OPTIONS]
# ...If instead you get "command not found", you need to install usbmuxd yourself. On Ubuntu/Debian, for example, you can do this with
sudo apt-get install usbmuxdOther useful tools:
usbmuxdis part of the libimobiledevice project. You may want to install other libimobiledevice tools, such asideviceinfo, that offer many ways to interact with your iOS device from the command line. On Ubuntu/Debian, you can runbashsudo apt-get install libimobiledevice-utils # The following NEW packages will be installed: # libimobiledevice-utils # 0 upgraded, 1 newly installed ideviceinfo # DeviceName: Kabir's iPhone # SerialNumber: ... # UniqueDeviceID: ... # ...
Xcode.xip
Download Xcode 26 from https://developer.apple.com/download/all/?q=Xcode. Note the path where Xcode.xip is saved.
Note:
The URL above requires authentication, so make sure to visit it in your browser rather than running
curl. You'll be asked to log in with your Apple ID and accept the license agreement to download Xcode.
Installation
1. Download xtool
Once you have the prerequisites, download the latest GitHub Release of xtool.AppImage for your architecture. Rename it to xtool and add it to a location in your PATH.
curl -fL \
"https://github.com/xtool-org/xtool/releases/latest/download/xtool-$(uname -m).AppImage" \
-o xtool
chmod +x xtool
sudo mv xtool /usr/local/bin/Confirm that xtool is installed correctly:
xtool --help
# OVERVIEW: Cross-platform Xcode replacement
# ...2. Configure xtool: log in
Perform one-time setup with
xtool setupYou'll be asked to log in:
Select login mode
0: API Key (requires paid Apple Developer Program membership)
1: Password (works with any Apple ID but uses private APIs)
Choice (0-1):Choosing a login mode:
API Key: If you have a paid Apple Developer Program membership, this is the recommended option. It relies on the public App Store Connect API. You'll want to follow the instructions to generate a Team Key with the App Manager role.
Password: If you aren't enrolled in the paid developer program, you'll want to use password-based authentication. This relies on private Apple APIs to authenticate, so you may want to create a throwaway Apple ID to be extra cautious.
Once you select a login mode, you'll be asked to provide the corresponding credentials (API key or email+password+2FA). Needless to say, your credentials are only sent to Apple and nobody else (feel free to build xtool from source and check!)
3. Configure xtool: SDK
After you're logged in, you'll be asked to provide the path to the Xcode.xip file you downloaded earlier.
Choice (0-1): 0
...
Path to Xcode.xip:Enter the path (for example ~/Downloads/Xcode_16.3.xip) and hit enter. xtool will extract the Xcode XIP to generate and install an iOS Swift SDK for you.
Confirm that it worked:
swift sdk list
# darwinNext steps
You're now ready to use xtool! See doc:First-app.
File: Documentation/xtool.docc/Installation-macOS.md
Installation (macOS)
Set up xtool for declarative, Xcode-free iOS app development on macOS.
Overview
This article outlines the steps to install xtool for declarative, SwiftPM-driven iOS app development on macOS.
Prerequisites
Xcode
Though we don't rely on the Xcode build system, you still need to install Xcode for the iOS SDK and toolchain. You'll want to install Xcode and launch it once, completing any installation prompts.
Confirm that Xcode is set up with the iOS SDK:
xcrun -sdk iphoneos -show-sdk-path
# /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.4.sdkAs well as Swift:
swift --version
# swift-driver version: 1.120.5 Apple Swift version 6.1 (swiftlang-6.1.0.110.21 clang-1700.0.13.3)
# Target: arm64-apple-macosx15.0Installation
1. Download xtool
If you have Homebrew, you can install xtool with
brew install xtool-org/tap/xtoolOtherwise, you can download xtool.app from the latest GitHub release. After moving it into /Applications, launch it and run the suggested script to add the xtool command to your PATH.
Note:
If you install xtool manually, you'll also need to check for new GitHub releases and update it yourself. If you're interested in contributing support for auto-updates through Sparkle, pull requests are appreciated. See issue #34 on GitHub.
Confirm that xtool is installed correctly:
xtool --help
# OVERVIEW: Cross-platform Xcode replacement
# ...2. Configure xtool: log in
Perform one-time setup with
xtool setupYou'll be asked to log in:
Select login mode
0: API Key (requires paid Apple Developer Program membership)
1: Password (works with any Apple ID but uses private APIs)
Choice (0-1):Choosing a login mode:
API Key: If you have a paid Apple Developer Program membership, this is the recommended option. It relies on the public App Store Connect API. You'll want to follow the instructions to generate a Team Key with the App Manager role.
Password: If you aren't enrolled in the paid developer program, you'll want to use password-based authentication. This relies on private Apple APIs to authenticate, so you may want to create a throwaway Apple ID to be extra cautious.
Once you select a login mode, you'll be asked to provide the corresponding credentials (API key or email+password+2FA). Needless to say, your credentials are only sent to Apple and nobody else (feel free to build xtool from source and check!)
Next steps
You're now ready to use xtool! See doc:First-app. (The tutorial is tailored to Linux, but it works the same on macOS.)
File: Documentation/xtool.docc/xtool.md
xtool
@Metadata {
@TechnologyRoot
@TitleHeading("Command-line Tool")
@CallToAction(url: "https://github.com/xtool-org/xtool", label: "View on GitHub")
}
Cross-platform Xcode replacement.
Overview
xtool is a cross-platform (Linux/WSL/macOS) tool that replicates Xcode functionality with open standards.
โ Build a SwiftPM package into an iOS app
โ Sign and install iOS apps
โ Interact with Apple Developer Services programmatically
Topics
Getting started
Common tasks
File: Linux/README.md
xtool + Linux = <3
xtool currently uses AppImage to build its Linux CLI. It is possible to build and run the AppImage on a native Linux host, as well as through Docker (natively or through Docker Desktop on macOS).
Setup
Make sure usbmuxd is installed on your host machine. On macOS, usbmuxd is preinstalled.
Using Docker
You can build and run a container with standard Docker Compose commands, for example:
docker compose run --rm xtoolThis will spawn a shell inside the container. The xtool root directory will be bind-mounted at /xtool.
To actually access iOS devices from inside Docker, you'll also need to forward the host's usbmuxd to a port which the Docker container's libusbmuxd will connect to. Keep this command running on your host machine:
socat -dd TCP-LISTEN:27015,range=127.0.0.1/32,reuseaddr,fork UNIX-CLIENT:/var/run/usbmuxdBuilding
Simply run ./build.sh in this directory. This will output an AppImage to packages/xtool.AppImage, which can directly be invoked.
File: macOS/README.md
macOS Packaging
xtool is packaged as a .app bundle on macOS, so that we can add entitlements and use the keychain.
Ideally we'd dogfood xtool to package xtool, but it only supports iOS apps right now. Someday, hopefully.
Building
- Install XcodeGen
- Run
xcodegeninside this (./macOS) directory - Open the Xcode project and build the
XToolMacscheme