Contributing to AT-IRC
AT-IRC is a SwiftUI iOS client with a small embedded Go library (GoLib). The
whole build/test/run loop works from the command line; Xcode is optional.
All commands below are run from the repository root.
Prerequisites
-
macOS with Xcode 27.2 or newer and an iOS 27.2 simulator runtime.
-
Xcode's command-line tools selected:
xcode-select -p # should point into /Applications/Xcode.app xcodebuild -version xcrun simctl list runtimes -
Go 1.27.3+ if you will change
GoLib:go version go install golang.org/x/mobile/cmd/gomobile@latest go install golang.org/x/mobile/cmd/gobind@latestInstall both tools.
gomobile bindshells out togobind; installing onlygomobilefails withgobind was not found.
The generated Go framework is not checked in. On a fresh clone, build it once before building the app:
./GoLib/build.sh
This writes GoLib/AtircGo.xcframework, which is gitignored. Skipping this step
makes the app fail to compile with No such module 'AtircGo' or fail to link
with framework not found AtircGo.
Layout
| Path | Contents |
|---|---|
AT-IRC/ | SwiftUI app sources |
AT-IRCTests/ | Swift Testing unit tests |
AT-IRCUITests/ | XCUITest tests |
GoLib/ | Go module, build.sh, and the generated xcframework |
docs/golang/ | Notes on the Go bridge |
There is one shared scheme, AT-IRC.
Set up the shell
The examples use these variables. Run this once in the shell you are working in;
change DEVICE to any available simulator.
DEVICE="iPhone 17"
DEST="platform=iOS Simulator,name=$DEVICE"
BUNDLE_ID="com.fedfork.irc.AT-IRC"
APP=".build/Build/Products/Debug-iphonesimulator/AT-IRC.app"
Using -derivedDataPath .build keeps build products inside the repository at a
predictable path. .build/ is gitignored.
List the available simulators and destinations with:
xcrun simctl list devices available
xcodebuild -showdestinations -project AT-IRC.xcodeproj -scheme AT-IRC
Build from the command line
Debug build for a simulator:
xcodebuild build \
-project AT-IRC.xcodeproj \
-scheme AT-IRC \
-configuration Debug \
-destination "$DEST" \
-derivedDataPath .build
For a CI build that does not need a specific simulator, use the generic destination:
xcodebuild build \
-project AT-IRC.xcodeproj \
-scheme AT-IRC \
-configuration Debug \
-destination 'generic/platform=iOS Simulator' \
-derivedDataPath .build
The app is always at:
.build/Build/Products/Debug-iphonesimulator/AT-IRC.app
Use -configuration Release for a release build.
Test from the command line
Fast loop: run only the unit tests.
xcodebuild test \
-project AT-IRC.xcodeproj \
-scheme AT-IRC \
-destination "$DEST" \
-derivedDataPath .build \
-only-testing:AT-IRCTests
Run only UI tests:
xcodebuild test \
-project AT-IRC.xcodeproj \
-scheme AT-IRC \
-destination "$DEST" \
-derivedDataPath .build \
-only-testing:AT-IRCUITests
Run everything (unit + UI):
xcodebuild test \
-project AT-IRC.xcodeproj \
-scheme AT-IRC \
-destination "$DEST" \
-derivedDataPath .build
Run a single Swift Testing test. The parentheses are part of the identifier, so quote the whole value:
xcodebuild test \
-project AT-IRC.xcodeproj \
-scheme AT-IRC \
-destination "$DEST" \
-derivedDataPath .build \
-only-testing:'AT-IRCTests/AT_IRCTests/nickHashMatchesWeeChat()'
To keep a machine-readable result bundle, add -resultBundlePath (the path must
not already exist):
rm -rf .build/TestResults.xcresult
xcodebuild test \
-project AT-IRC.xcodeproj \
-scheme AT-IRC \
-destination "$DEST" \
-derivedDataPath .build \
-resultBundlePath .build/TestResults.xcresult
xcrun xcresulttool get test-results summary \
--path .build/TestResults.xcresult
The UI suite includes a launch-performance test and takes about a minute. The unit suite is the fast pre-commit check.
Run and iterate in the simulator
Boot the simulator (and optionally open the Simulator UI):
xcrun simctl bootstatus "$DEVICE" -b
open -a Simulator
Build, install, and relaunch:
xcodebuild build \
-project AT-IRC.xcodeproj \
-scheme AT-IRC \
-configuration Debug \
-destination "$DEST" \
-derivedDataPath .build
xcrun simctl install "$DEVICE" "$APP"
xcrun simctl launch --terminate-running-process "$DEVICE" "$BUNDLE_ID"
--terminate-running-process replaces the already-running copy, so the same
two simctl commands work for every iteration.
Useful companion commands:
# Stream this app's unified log
xcrun simctl spawn "$DEVICE" log stream --style compact --level debug \
--predicate 'process == "AT-IRC"'
# Show recent log entries once
xcrun simctl spawn "$DEVICE" log show --last 2m --style compact \
--predicate 'process == "AT-IRC"'
# Blocking stdout/stderr console (Ctrl-C to stop)
xcrun simctl launch --console-pty --terminate-running-process \
"$DEVICE" "$BUNDLE_ID"
# Screenshot
xcrun simctl io "$DEVICE" screenshot /tmp/atirc.png
Reset the app without wiping the simulator:
xcrun simctl terminate "$DEVICE" "$BUNDLE_ID" 2>/dev/null || true
xcrun simctl uninstall "$DEVICE" "$BUNDLE_ID"
To clear Keychain and all other simulator state (this erases the whole simulator, so shut it down first):
xcrun simctl shutdown "$DEVICE"
xcrun simctl erase "$DEVICE"
Changing the Go library
Xcode does not track the Go sources and will not regenerate the framework
automatically. After editing anything under GoLib/:
./GoLib/build.sh
Then rebuild the app. If you skip this, the app keeps using the previous Go binary, and new exported symbols will not exist in Swift.
gomobile bind generates Objective-C/Swift bindings prefixed with Golib.
Interface names that collide with generated class names get a Protocol suffix
in Swift (for example GolibCallbackProtocol). Bridge details and type limits
are documented in docs/golang/0001-notes.md.
Before you commit
-
If you changed
GoLib/, run./GoLib/build.sh. -
Run the unit tests:
xcodebuild test \ -project AT-IRC.xcodeproj \ -scheme AT-IRC \ -destination "$DEST" \ -derivedDataPath .build \ -only-testing:AT-IRCTests -
Run the full suite if you changed UI code or UI tests.
-
Do not commit
.build/orGoLib/AtircGo.xcframework/; both are generated.
Troubleshooting
| Symptom | Fix |
|---|---|
No such module 'AtircGo' or framework not found AtircGo | Run ./GoLib/build.sh. Confirm GoLib/AtircGo.xcframework exists. |
gobind was not found | Install gomobile and gobind; build.sh adds $(go env GOPATH)/bin to PATH for you. |
Unable to find a destination matching ... | Run xcrun simctl list devices available and xcodebuild -showdestinations; use the exact device name, or install the iOS 27.2 runtime. |
| Tests cannot launch the app | Boot the simulator explicitly with xcrun simctl bootstatus "$DEVICE" -b and retry. |
| App still shows old Go behavior | Rerun ./GoLib/build.sh, then rebuild. Do a clean build (rm -rf .build) if the stale binary persists. |
| Installed app does not reflect a new build | Reinstall, then launch with --terminate-running-process; uninstall first if needed. |
| Code-signing error | Simulator builds need no team. Device builds need your own signing team in the target settings. |
For device runs, use the device destination and a configured signing team:
xcodebuild test \
-project AT-IRC.xcodeproj \
-scheme AT-IRC \
-destination 'platform=iOS,id=<device-udid>' \
-derivedDataPath .build \
-only-testing:AT-IRCTests