xtool Documentation

Cross-platform Xcode replacement. Build and deploy iOS apps with SwiftPM on Linux, Windows, macOS.

4,824 stars Swift Swift 1 file ยท ~5,903 tokens #ios#linux#swift#swift-package-manager#windows#xcode
RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy xtool on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name xtool --restart always xtool-org/xtool:latest
Configured Services & Ports
โš™๏ธ xtool
image: build locally
โš™๏ธ xtool-test
image: build locally
docker-compose.yml
yaml (docker-compose.yml)
services:
  xtool:
    build:
      context: .
      target: dev
    volumes:
      - .:/xtool
      - .build/linux:/xtool/.build
    container_name: xtool
    stdin_open: true
    tty: true
    cap_add:
      - sys_ptrace
    security_opt:
      - seccomp:unconfined
    extra_hosts:
      # host.docker.internal doesn't exist by default on Linux hosts
      - "host.docker.internal:host-gateway"
  xtool-test:
    extends:
      service: xtool
    build:
      target: dev-test

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

  1. Follow the guide to install xtool
  2. Create and run your first xtool-powered app by following the tutorial!

Examples

Screenshot

Command line interface

bash
$ 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.

swift
// 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.

diff
// 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.

diff
version: 1
  bundleID: com.example.Hello
+ product: Hello
+ extensions:
+   - product: HelloWidget
+     infoPath: HelloWidget-Info.plist

Step 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
<?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-extension tells the system that this is a Widget Extension. If you're creating a different type of Extension, your Info.plist might look different.

For a list of possible NSExtensionPointIdentifier values, see Apple's documentation on the subject. You may also need other keys like NSExtensionPrincipalClass: refer to Apple's better legacy 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, and NSExtensionPointIdentifier -> 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:

swift
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.

bash
cat > xtool.yml << EOF
version: 1
bundleID: com.example.Hello
EOF

Change 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:

yaml
infoPath: path/to/Info.plist

Tip:

After running xtool dev [build], you can inspect the built app bundle at ./xtool/YourApp.app to view its Info.plist and other resources.

For example, you might create an Info.plist file that looks something like

plist
<?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.plist has a UISupportedInterfaceOrientations value of portrait. This will replace it with landscape-only.
  • We don't include a CFBundleDisplayName by 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

swift
targets: [
    .target(
        name: "Hello",
        resources: [.copy("Blob.png")]
    ),
]

Then, to draw it on screen, you can use

swift
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

yaml
resources:
- Resources/GoogleServices-Info.plist

The 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:

yaml
iconPath: Resources/AppIcon.png

Tip:

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:

plist
<?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:

yaml
entitlementsPath: App.entitlements

Troubleshooting:

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:

bash
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

bash
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

bash
sudo apt-get install usbmuxd

Other useful tools:

usbmuxd is part of the libimobiledevice project. You may want to install other libimobiledevice tools, such as ideviceinfo, that offer many ways to interact with your iOS device from the command line. On Ubuntu/Debian, you can run

bash
sudo 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.

bash
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:

bash
xtool --help
# OVERVIEW: Cross-platform Xcode replacement
# ...

2. Configure xtool: log in

Perform one-time setup with

bash
xtool setup

You'll be asked to log in:

text
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.

text
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:

bash
swift sdk list
# darwin

Next 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:

bash
xcrun -sdk iphoneos -show-sdk-path
# /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.4.sdk

As well as Swift:

bash
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.0

Installation

1. Download xtool

If you have Homebrew, you can install xtool with

bash
brew install xtool-org/tap/xtool

Otherwise, 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:

bash
xtool --help
# OVERVIEW: Cross-platform Xcode replacement
# ...

2. Configure xtool: log in

Perform one-time setup with

bash
xtool setup

You'll be asked to log in:

text
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:

text
docker compose run --rm xtool

This 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:

text
socat -dd TCP-LISTEN:27015,range=127.0.0.1/32,reuseaddr,fork UNIX-CLIENT:/var/run/usbmuxd

Building

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

  1. Install XcodeGen
  2. Run xcodegen inside this (./macOS) directory
  3. Open the Xcode project and build the XToolMac scheme