Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
5729c07
build: link swift-dependencies into PackLib
mrackwitz Oct 14, 2026
a1032fb
packer: define macOS build contracts
mrackwitz Oct 13, 2026
b88a200
packer: build selected products for macOS targets
mrackwitz Oct 13, 2026
99a3080
planner: handle platform-specific products and resources
mrackwitz Oct 13, 2026
9219de7
packer: assemble macOS product bundles
mrackwitz Oct 13, 2026
9aff500
dev: expose deferred macOS signing handoff
mrackwitz Oct 13, 2026
56fb195
packer: validate macOS link metadata and runtime paths
mrackwitz Oct 13, 2026
cf1c7eb
dev: configure editor tooling for the selected macOS target
mrackwitz Oct 13, 2026
3292856
packer: coalesce shared helper dependencies
mrackwitz Oct 13, 2026
9ad216d
packer: make macOS resource bundles signable
mrackwitz Oct 13, 2026
cb0aad3
docs: document macOS build contract
mrackwitz Oct 13, 2026
1c35855
packer: resolve static XCFramework archives by library path
mrackwitz Oct 13, 2026
a8d9cf6
build: link swift-crypto into PackLib
mrackwitz Oct 14, 2026
9efa985
build: reference SwiftyMobileDevice by package product
mrackwitz Oct 14, 2026
838c9f7
dev: add macOS app execution and verified build handoffs
mrackwitz Oct 13, 2026
bb4df06
dev: add verified macOS artifact signing
mrackwitz Oct 13, 2026
456a428
dev: add resumable macOS notarization
mrackwitz Oct 13, 2026
be38fb8
packer: honour versioned macOS triples with Linux Swift SDKs
mrackwitz Oct 13, 2026
2459c89
packer: accept universal static archives
mrackwitz Oct 14, 2026
bf7e57c
packer: stop system rpaths from hiding missing embedded libraries
mrackwitz Oct 14, 2026
05c1645
packer: copy legacy macOS resources into Contents/Resources
mrackwitz Oct 14, 2026
c5e978a
packer: find static archives of local XCFramework binary targets
mrackwitz Oct 14, 2026
e8dd3d0
dev: reject unknown `dev run` arguments before `--`
mrackwitz Oct 14, 2026
63f55ed
packer: compile Darwin products for the planned deployment target
mrackwitz Oct 14, 2026
94585da
packer: keep raw assets in iOS SwiftPM resource bundles
mrackwitz Oct 14, 2026
083d04c
packer: raise old package minimums to xtool's platform minimum
mrackwitz Oct 14, 2026
06bb18e
dev: start the editor build server without package inspection
mrackwitz Oct 14, 2026
c0ba7d0
packer: pass per-product link options through the toolset linker
mrackwitz Oct 14, 2026
0e98f8b
packer: compile macOS resource accessors once for all products
mrackwitz Oct 14, 2026
b58060e
dev: let dev run --help reach the argument parser
mrackwitz Oct 14, 2026
adc753f
test: stub the Mac host explicitly in the macOS run rejection test
mrackwitz Oct 14, 2026
47cb4d3
ci: run the macOS bats suite on the macOS host
mrackwitz Oct 14, 2026
cc9b211
docs: describe macOS signing and notarization in the macOS guide
mrackwitz Oct 14, 2026
f3907f9
packer: compile each package for its own deployment target
mrackwitz Oct 14, 2026
1142970
packer: keep raw assets in macOS SwiftPM resource bundles
mrackwitz Oct 14, 2026
1508a72
packer: keep the requested triple for SwiftBuild builds
mrackwitz Oct 16, 2026
88a2cd2
packer: lower the macOS deployment floor to 13.0
mrackwitz Oct 16, 2026
912d12e
test: expect the requested triple for SwiftBuild build servers
mrackwitz Oct 17, 2026
1d39d20
packer: build macOS resource accessors in a single pass
mrackwitz Oct 16, 2026
8d29bad
packer: plan before building so clean builds compile accessors once
mrackwitz Oct 19, 2026
6e96adf
docs: list the artifact manifest among the macOS handoff files
mrackwitz Oct 19, 2026
dda4c7b
packer: keep the Swift SDK cache in the root package
mrackwitz Oct 19, 2026
6bb8437
dev: audit existing apps against their own target in `dev run --app`
mrackwitz Oct 19, 2026
3175d37
dev: pass the loaded schema to the iOS pack path
mrackwitz Oct 20, 2026
1ca1811
packer: format SDK identities and compiler triples in one place
mrackwitz Oct 20, 2026
c99f09e
packer: share one macOS Swift SDK lookup
mrackwitz Oct 20, 2026
73f06f9
packer: validate single path components in one place
mrackwitz Oct 20, 2026
177f3c0
mac: reuse MachOBinary's CPU and platform constants in the bundle audit
mrackwitz Oct 20, 2026
d9faa36
dev: reject provisioning-dependent entitlements when recording the ha…
mrackwitz Oct 20, 2026
553cdb8
packer: accept internal symlinks under a symlinked package path
mrackwitz Oct 20, 2026
e1dfd87
mac: run host tools through one MacHostClient
mrackwitz Oct 20, 2026
19055be
packer: document both purposes of the -platform_version link option
mrackwitz Oct 20, 2026
875538a
packer: bypass SwiftPM's cached build manifest in the shared scratch …
mrackwitz Oct 20, 2026
cda0676
packer: query the build output directory once per package
mrackwitz Oct 20, 2026
e799262
packer: build all wrapped products in one SwiftPM invocation
mrackwitz Oct 20, 2026
3e694d5
packer: parse Mach-O files from a mapped buffer without copying
mrackwitz Oct 20, 2026
d93f754
packer: bound concurrent package inspection to the core count
mrackwitz Oct 20, 2026
e496197
utils: share one sorted, pretty-printed JSONEncoder setup
mrackwitz Oct 20, 2026
9683d82
planner: reuse inspections of version-pinned dependencies
mrackwitz Oct 20, 2026
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
10 changes: 10 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ jobs:
- name: Run tests
run: |
swift test
- name: Run macOS integration tests
run: |
for lib in bats-core@v1.14.0 bats-support@v0.3.0 bats-assert@v2.2.4; do
git -c advice.detachedHead=false clone -q --depth 1 --branch "${lib#*@}" "https://github.com/bats-core/${lib%@*}" ".build/bats/${lib%@*}"
done
mkdir -p .build/integration-bin
ln -fs "$PWD/.build/debug/xtool" .build/integration-bin/xtool
ln -fs "$(xcrun -f llvm-objdump)" .build/integration-bin/llvm-objdump
PATH="$PWD/.build/integration-bin:$PATH" BATS_LIB_PATH="$PWD/.build/bats" \
.build/bats/bats-core/bin/bats integration-tests/suite/macos.bats
build-ios:
runs-on: xcode-27
steps:
Expand Down
129 changes: 129 additions & 0 deletions Documentation/xtool.docc/Configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Configure packaging

Describe a SwiftPM application's target, products and prepared inputs in `xtool.yml`.

## Version 2

Version 1 retains the existing iOS configuration. Version 2 adds `target`,
`deploymentTarget`, `source`, `resourceCopies` and `nestedProducts`; using these
fields in version 1 is rejected. See <doc:MacOS> for target precedence, supported
backends and build commands.

The example below packages an app with an extension, XPC service and helper.
Supply the referenced plist, entitlement and icon files alongside your package.

```yaml
version: 2
bundleID: com.example.app
product: MyApp
target: arm64-apple-macosx15.0
deploymentTarget: '15.0'
source: {kind: executable, product: MyApp}
infoPath: Support/App-Info.plist
entitlementsPath: Support/App.entitlements
iconPath: Support/App.icns
nestedProducts:
- product: MyExtension
kind: appExtension
bundleID: com.example.extension
source: {kind: library, product: MyExtension}
infoPath: Support/Extension-Info.plist
entitlementsPath: Support/Extension.entitlements
- product: MyService
kind: xpcService
bundleID: com.example.xpc
source: {kind: executable, product: MyService}
infoPath: Support/XPC-Info.plist
entitlementsPath: Support/XPC.entitlements
- product: MyHelper
kind: helperExecutable
bundleID: com.example.helper
source: {kind: executable, product: MyHelper}
```

## Fields and product sources

| Field | Contract |
| --- | --- |
| `version` | Required schema version, `1` or `2`. |
| `bundleID` / `orgID` | At least one is required. `bundleID` takes precedence; `orgID` derives `<orgID>.<product>`. |
| `product` | Output/application product name. Use an explicit name when the package has multiple candidates. |
| `target` | Apple target triple, independent of host OS; CLI `--triple` takes precedence. |
| `deploymentTarget` | Numeric OS version; cannot lower the package or platform minimum. |
| `source` | Tagged product source described below; explicit in this example. |
| `infoPath` | Caller plist dictionary merged over generated defaults; caller identifiers and extension/XPC metadata are preserved. |
| `entitlementsPath` | Caller plist dictionary associated with this product for the later signer. |
| `iconPath` | Prepared `.png` or `.icns` in version 2; ICNS requires macOS. |
| `resources` | Legacy list of files/directories copied to the product's resources directory: the bundle root on iOS, `Contents/Resources` on macOS. |
| `resourceCopies` | Explicit copies with `source` and `destination: {location, path}`. |
| `nestedProducts` | Products with required `product`, `kind` and `source`; optional `bundleID`, `infoPath`, `entitlementsPath`, `resourceCopies`. |
| `extensions` | Existing extension list with required `product`/`infoPath`; version 2 also accepts `source`/`resourceCopies`. |
| `skipLSP` | Skip automatic creation of a missing LSP support file. |

Paths to input files resolve from the package directory. Product names must be
safe single path components; nested names must be unique. Extension and XPC
products require `infoPath`; helpers are bare executables without their own
Info.plist. `xpcService` and `helperExecutable` require a macOS target.

| `source.kind` | Required value | Build behavior |
| --- | --- | --- |
| `executable` | `product` | Build that SwiftPM executable directly; do not create a target depending on an executable product. |
| `library` | `product` | Create an executable wrapper linking that SwiftPM library; extensions use the extension entry point. |
| `prebuilt` | `path` | Validate and copy an existing target executable; do not compile it. |

SwiftPM sources cannot carry `path`; prebuilt sources cannot carry `product`.
The requested kind must match the selected package product. Omitted source
retains legacy product selection; explicit sources make multi-product packages
unambiguous. For prebuilt XPC services/helpers use `source.kind: prebuilt` with
a `path` to a compatible macOS Mach-O executable, while retaining the nested
product's `kind` and metadata. This form is covered by planner/builder tests;
the example above builds its XPC service/helper from source.
Prebuilt products with automatically discovered SwiftPM resources are rejected
because xtool cannot rewrite their compiled accessors. Supply explicit copies
and resource lookup implemented by that binary instead.

## macOS bundle and resource destinations

| Product kind | Executable relative to the outer app |
| --- | --- |
| `application` | `Contents/MacOS/<product>` |
| `appExtension` | `Contents/PlugIns/<product>.appex/Contents/MacOS/<product>` |
| `xpcService` | `Contents/XPCServices/<product>.xpc/Contents/MacOS/<product>` |
| `helperExecutable` | `Contents/Helpers/<product>` |

The outer application is selected at the schema root; `nestedProducts` cannot
contain `application`. App, extension and XPC plists live in their respective
`Contents/Info.plist`. Helpers have no plist. Dependency traversal follows
platform-conditional SwiftPM dependencies, collecting resource bundles, dynamic
libraries and compatible binary targets. Frameworks/dylibs are placed in each
owner's `Contents/Frameworks`; helper-owned frameworks use
`Contents/Helpers/Frameworks`, with a matching helper rpath.

`destination.location` is required in YAML and accepts:

| Location | Base for app/extension/XPC | Base for helper |
| --- | --- | --- |
| `resources` | The owner's `Contents/Resources` | Outer app's `Contents/Resources` |
| `contents` | The owner's `Contents` | Outer app's `Contents/Helpers` |
| `bundle` | The owner's bundle root | Outer app's `Contents/Helpers` |

`destination.path` is a nonempty relative path appended to that base. Absolute
paths, empty components, `.`, `..`, backslashes and NUL are rejected. Assembly
also rejects overlapping destinations, including case/Unicode-equivalent
collisions, and unsafe symlinks. Legacy `resources` copies into each product's
resources directory; use `resourceCopies` to choose another destination. Explicit extension resources
belong in the extension's Resources directory.

Source-built macOS SwiftPM resource accessors are adapted for relocated bundles;
separate relocation tests exercise the adapted `Bundle.module` accessor after
original build inputs are removed.
Prepared assets are copied without invoking `actool` or `ibtool`. Raw
`.xcassets`, `.iconset`, `.storyboard` and `.xib` inputs require preparation elsewhere;
SwiftPM resource bundles are copied as built, with any raw assets left uncompiled.

Caller plist values override generated defaults, including identifiers. Ensure
`CFBundleExecutable`, bundle kind and minimum OS agree with the actual product;
xtool does not translate an Xcode project's build settings or extension policy.
Deferred signing preserves entitlement dictionaries and their product associations
without applying them. It rejects provisioning-dependent entitlements, which the signer
cannot apply.
17 changes: 17 additions & 0 deletions Documentation/xtool.docc/Control.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,23 @@ bundleID: com.example.Hello
EOF
```

## Select the target platform

Version 1 configurations keep the `arm64-apple-ios` default. Version 2 adds explicit target and deployment settings:

```yaml
version: 2
bundleID: com.example.Hello
target: arm64-apple-macosx
deploymentTarget: "15.0"
```

`xtool dev build --triple` and `xtool dev build-server --triple` override `target` in `xtool.yml`. When neither is set, both use `arm64-apple-ios`. macOS targets support `arm64` and `x86_64`; `macos` is accepted as an alias for `macosx`. Build macOS apps with `xtool dev build --defer-signing` and sign the resulting app later on a Mac. On Linux, use `--configuration release`; macOS Debug builds require signing support available on macOS.

The selected triple's OS version takes precedence over `deploymentTarget`, then the package's matching platform minimum, then xtool's platform minimum (iOS 13.0 or macOS 13.0). A package minimum below xtool's platform minimum is raised to it. An explicit version cannot lower the schema, package, or platform minimum. For example, `arm64-apple-macosx16.0` selects macOS 16.0 while the SDK identity remains `arm64-apple-macosx`.

Editor support uses the same target resolution and package minimum as builds. Swift 6.4 and later use `.bsp/xtool.json`, whose build-server command pins the effective compiler triple (for example `arm64-apple-macosx15.0`), so starting the build server does not inspect the package. Earlier toolchains use `.sourcekit-lsp/config.json` with separate Swift SDK and compiler triple settings. Restart the editor after changing these files. xtool refreshes or migrates only configurations matching its complete generated content; customized files are preserved. Set `skipLSP: true` to disable automatic editor configuration.

## Change your bundle ID

You can update your app's bundle ID by editing the `bundleID` field in `xtool.yml`.
Expand Down
64 changes: 64 additions & 0 deletions Documentation/xtool.docc/MacOS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Build macOS applications

Cross-compile a SwiftPM application on Linux and hand its assembled bundle to a Mac for signing.

## Supported build contract

macOS builds use the SwiftPM backend and require explicit deferred signing.

The CLI requires xtool, a matching Swift toolchain and an installed Darwin Swift
SDK on Linux, or a selected Xcode installation on macOS.

```sh
# Run in the application's directory beside Package.swift and xtool.yml.
xtool dev build --configuration release --defer-signing
```

Set `version: 2` and a macOS `target` in `xtool.yml`; see <doc:Configuration>.
The optional CLI `--triple arm64-apple-macosx15.0` overrides the schema target.
Omitting both retains the existing `arm64-apple-ios` default, even on a Mac.
The deployment version comes from the selected triple, then the schema's
`deploymentTarget`, then the package's platform minimum, then the platform
minimum (macOS 13.0). A selected version below any explicit schema/package
minimum or the platform minimum is rejected. The deployment version is recorded in the
products' binaries; each package's code still compiles for its own declared platforms, as with
`swift build`, so declare `platforms` to use newer APIs without availability checks.

The parser accepts arm64 and x86_64 macOS triples. Linux macOS Debug builds are rejected because SwiftPM
requires unavailable `codesign` support. Use Release. SwiftBuild is rejected
for macOS; its existing iOS support does not establish macOS support.

The output is `xtool/<product>.app`, accompanied by `<product>.artifact.json`,
`<product>.signing.json` and, when supplied, `<product>.signing-inputs/`. The CLI preserves copies of
caller entitlement dictionaries outside the app and rebases their paths in
the manifest. Transfer all of them together; `xtool dev verify` and `xtool dev sign` read each one. The manifest has `version: 1`,
`status: deferred`, a resolved target, and product bundle/executable paths with
their entitlement associations. It records signing intent, not release approval.

This route performs no Apple account provisioning or identity signing. Linker
ad-hoc signatures may already be present. `--sign`, IPA output and Xcode
project generation are unavailable for macOS targets. `xtool dev run` builds
and launches a local Mac app; `--app` launches an existing app without rebuilding. On a Mac,
`xtool dev sign` signs the handoff into a new directory (ad-hoc, Apple Development or
Developer ID, with an explicit certificate fingerprint) and `xtool dev notarize` submits
Developer ID output with an existing `notarytool` keychain profile. The handoff does not
select certificates or publish releases.

## Tests

The Swift Testing suite in `Tests/XToolTests` covers product selection, target
validation, nested bundle assembly, resource relocation and signing handoffs.
On macOS it compiles and executes disposable packages to test generated
`Bundle.module` lookup after removing the original build directory.
The Bats suite in `integration-tests/suite` exercises the public CLI using the
installed Darwin SDK.

## Boundaries

This support excludes native execution of macOS applications on Linux, an asset
compiler replacement, release publishing, universal binary
production and full Xcode project ingestion. Supply prepared ICNS/PNG,
`Assets.car`, storyboards or nibs where needed; raw asset catalogs, iconsets, storyboards
and xibs you supply are rejected by assembly. SwiftPM resource bundles are copied as
built, so raw assets inside them stay uncompiled. Certificate creation, identity selection and
provisioning-dependent entitlements remain separate work.
4 changes: 4 additions & 0 deletions Documentation/xtool.docc/xtool.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ xtool is a cross-platform (Linux/WSL/macOS) tool that replicates Xcode functiona

✅ Build a SwiftPM package into an iOS app

✅ Build and assemble a macOS SwiftPM app with deferred signing

✅ Sign and install iOS apps

✅ Interact with Apple Developer Services programmatically
Expand All @@ -32,3 +34,5 @@ xtool is a cross-platform (Linux/WSL/macOS) tool that replicates Xcode functiona

- <doc:Control>
- <doc:Appex>
- <doc:MacOS>
- <doc:Configuration>
6 changes: 4 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ let package = Package(
.target(
name: "XToolSupport",
dependencies: [
"SwiftyMobileDevice",
.product(name: "SwiftyMobileDevice", package: "SwiftyMobileDevice"),
"XKit",
"PackLib",
.product(name: "ArgumentParser", package: "swift-argument-parser"),
Expand All @@ -196,6 +196,8 @@ let package = Package(
name: "PackLib",
dependencies: [
"XUtils",
.product(name: "Crypto", package: "swift-crypto"),
.product(name: "Dependencies", package: "swift-dependencies"),
"XcodeProjects",
.product(name: "Superutils", package: "xtool-core"),
.product(name: "Yams", package: "Yams"),
Expand All @@ -206,7 +208,7 @@ let package = Package(
.executableTarget(
name: "xtool",
dependencies: [
"SwiftyMobileDevice",
.product(name: "SwiftyMobileDevice", package: "SwiftyMobileDevice"),
"XKit",
"XToolSupport",
],
Expand Down
83 changes: 83 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,89 @@ DEVICE SUBCOMMANDS:
See 'xtool help <subcommand>' for detailed help.
```

### macOS development

For a version 2 project targeting `arm64-apple-macosx15.0`:

```sh
xtool dev build --configuration release --defer-signing
xtool dev verify xtool/MyApp.app
# On a Mac, build and run without adding signatures:
xtool dev run --configuration release -- --your-app-argument
# Or run an existing app, including one signed externally:
xtool dev run --app /path/to/MyApp.app -- --your-app-argument
```

The app runs in the foreground with terminal output and exit-status propagation. Ctrl-C
cancels the child process. Apps requiring entitlements or additional signing must be signed
first; the run command does not add signatures or install applications.

Copy the `.app`, `.artifact.json`, `.signing.json`, and `.signing-inputs/` together for a
portable handoff. `dev verify` checks the unchanged **pre-signing** bundle, resource files,
executable permissions, internal symlinks and entitlement inputs. Signing changes those bytes;
use `dev run --app` to launch an externally signed app without checking obsolete build hashes.
The inventory also records nested-code signing order and audits static Mach-O dependencies.
It detects changes, but does not authenticate who built the app or prove arbitrary `dlopen` paths.

Linux builds remain Release-only. A prepared SDK can be selected with `--swift-sdks-path`,
and `--scratch-path` selects a separate macOS build directory. macOS project generation
and provisioning remain separate work.

### macOS signing

Sign a verified build handoff on a Mac into a **new** output directory:

```sh
xtool dev sign xtool/MyApp.app --output signed --mode ad-hoc --dry-run
xtool dev sign xtool/MyApp.app --output signed --mode ad-hoc
xtool dev verify signed/MyApp.app --signed
# Certificate modes require an explicit certificate SHA-1 fingerprint:
xtool dev sign xtool/MyApp.app --output release-signed --mode developer-id --identity "$CERTIFICATE_SHA1"
```

`development` selects an Apple Development certificate; `developer-id` requires a
Developer ID Application certificate, hardened runtime and a secure timestamp. The signer
checks the actual certificate fingerprint and category. It does not select identities,
create certificates or provision capabilities. Provisioning-dependent entitlements are rejected
when `dev build --defer-signing` records the handoff, and again by the signer.

Signing works on a private copy, applies each product's own entitlements in nested-code order,
and verifies signatures and effective entitlements before publishing. Input files remain unchanged;
existing output directories are rejected. The adjacent `.signing-receipt.json` records the original
artifact digest, signed inventory and verified signing metadata. Keep it with the signed app.
`dev verify --signed` checks that receipt and native signatures on a Mac. Inventories cover file
contents, executable bits and symlinks, not extended attributes, and are local integrity records
rather than authenticated build attestations. Old content-addressed entitlement inputs are retained
when replacing a build handoff so an interrupted update cannot delete the previous inputs.

### macOS notarization

Use the Developer ID signed output and an existing, explicitly selected `notarytool`
keychain profile. These commands run on a Mac:

```sh
xtool dev notarize submit release-signed/MyApp.app --output notarized --keychain-profile "$NOTARY_PROFILE"
xtool dev notarize resume notarized --keychain-profile "$NOTARY_PROFILE" --timeout 60
xtool dev notarize log notarized --keychain-profile "$NOTARY_PROFILE"
# Once accepted:
xtool dev notarize staple notarized
xtool dev notarize verify notarized
```

`status` queries without waiting. `submit` never reuses an existing output directory.
An interrupted submission with no recorded UUID remains uncertain: recover with
`resume --submission-id UUID` only when Apple's completed log matches the original upload
SHA-256. Status, resume and log never upload again. A wait timeout leaves the recorded attempt
available for later queries. Credentials are neither created nor selected automatically.

The release directory retains the original signed snapshot and upload ZIP, append-only receipt
files, and the Apple log binding acceptance to that exact archive. Stapling publishes a separate
`distribution/` containing the app, `distribution.zip` and a recovery receipt. Verification checks
that final inventory, archive hash, native signatures and stapled ticket. Interrupted distribution
publication can be resumed with `staple`. Preserve the complete directory for later verification.
These local receipts are not authenticated attestations or a guarantee of Gatekeeper approval.
The existing Fastlane release of xtool itself remains independent.

### 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.
Expand Down
Loading