1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# 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:
```bash
xcode-select -p # should point into /Applications/Xcode.app
xcodebuild -version
xcrun simctl list runtimes
```
- Go 1.27.3+ if you will change `GoLib`:
```bash
go version
go install golang.org/x/mobile/cmd/gomobile@latest
go install golang.org/x/mobile/cmd/gobind@latest
```
Install **both** tools. `gomobile bind` shells out to `gobind`; installing only
`gomobile` fails with `gobind was not found`.
The generated Go framework is not checked in. On a fresh clone, build it once
before building the app:
```bash
./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.
```bash
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:
```bash
xcrun simctl list devices available
xcodebuild -showdestinations -project AT-IRC.xcodeproj -scheme AT-IRC
```
## Build from the command line
Debug build for a simulator:
```bash
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:
```bash
xcodebuild build \
-project AT-IRC.xcodeproj \
-scheme AT-IRC \
-configuration Debug \
-destination 'generic/platform=iOS Simulator' \
-derivedDataPath .build
```
The app is always at:
```text
.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.
```bash
xcodebuild test \
-project AT-IRC.xcodeproj \
-scheme AT-IRC \
-destination "$DEST" \
-derivedDataPath .build \
-only-testing:AT-IRCTests
```
Run only UI tests:
```bash
xcodebuild test \
-project AT-IRC.xcodeproj \
-scheme AT-IRC \
-destination "$DEST" \
-derivedDataPath .build \
-only-testing:AT-IRCUITests
```
Run everything (unit + UI):
```bash
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:
```bash
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):
```bash
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):
```bash
xcrun simctl bootstatus "$DEVICE" -b
open -a Simulator
```
Build, install, and relaunch:
```bash
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:
```bash
# 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:
```bash
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):
```bash
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/`:
```bash
./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](docs/golang/0001-notes.md).
## Before you commit
1. If you changed `GoLib/`, run `./GoLib/build.sh`.
2. Run the unit tests:
```bash
xcodebuild test \
-project AT-IRC.xcodeproj \
-scheme AT-IRC \
-destination "$DEST" \
-derivedDataPath .build \
-only-testing:AT-IRCTests
```
3. Run the full suite if you changed UI code or UI tests.
4. Do not commit `.build/` or `GoLib/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:
```bash
xcodebuild test \
-project AT-IRC.xcodeproj \
-scheme AT-IRC \
-destination 'platform=iOS,id=<device-udid>' \
-derivedDataPath .build \
-only-testing:AT-IRCTests
```