-
Notifications
You must be signed in to change notification settings - Fork 33
Expand file tree
/
Copy pathMakefile.toml
More file actions
731 lines (643 loc) · 28.8 KB
/
Copy pathMakefile.toml
File metadata and controls
731 lines (643 loc) · 28.8 KB
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
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
# Single source of truth for build / test / bindgen / publish across every
# binding language. Run `cargo make <task>` locally; CI calls the exact same
# tasks so local and CI never drift.
#
# Common tasks:
# cargo make test-all # rust + python + js + kotlin + swift
# cargo make test-rust
# cargo make test-python
# cargo make test-js
# cargo make test-kotlin
# cargo make test-swift # macOS-arm64 slice (fast, for local/CI test)
# cargo make format # rustfmt (workspace)
# cargo make clippy
#
# Bindgen / packaging:
# cargo make bindgen-kotlin
# cargo make swift-xcframework # full 4-target Apple xcframework (release)
# cargo make python-wheel
#
# Most tasks are workspace = false so cargo-make does not fan out per member.
[config]
default_to_workspace = false
skip_core_tasks = true
[env]
LIB_NAME = "libiroh_ffi"
UNIFFI_CONFIG = "uniffi.toml"
# ---------------------------------------------------------------------------
# Formatting / lint / docs (Rust)
# ---------------------------------------------------------------------------
[tasks.format]
command = "cargo"
args = [
"fmt", "--all", "--",
"--config", "unstable_features=true",
"--config", "imports_granularity=Crate,group_imports=StdExternalCrate,reorder_imports=true,format_code_in_doc_comments=true",
]
[tasks.format-check]
command = "cargo"
args = [
"fmt", "--all", "--check", "--",
"--config", "unstable_features=true",
"--config", "imports_granularity=Crate,group_imports=StdExternalCrate,reorder_imports=true,format_code_in_doc_comments=true",
]
[tasks.clippy]
command = "cargo"
args = ["clippy", "--all-targets", "--bins", "--tests", "--benches"]
[tasks.docs]
command = "cargo"
args = ["doc", "--no-deps", "--document-private-items"]
# ---------------------------------------------------------------------------
# Rust core
# ---------------------------------------------------------------------------
[tasks.build]
description = "Build the iroh-ffi cdylib/staticlib."
command = "cargo"
args = ["build", "--lib", "-p", "iroh-ffi"]
[tasks.test-rust]
description = "Run the Rust unit tests."
command = "cargo"
args = ["test", "--lib", "-p", "iroh-ffi", "--", "--test-threads=1"]
# ---------------------------------------------------------------------------
# uniffi bindgen helpers (target-dir aware: honours CARGO_TARGET_DIR / config)
# ---------------------------------------------------------------------------
[tasks.bindgen-kotlin]
description = "Build the lib + generate the Kotlin binding + stage the cdylib."
dependencies = ["build"]
# `sh` resolves to Git Bash's sh.exe on Windows (Git for Windows ships
# sh.exe; System32 only has bash.exe = WSL launcher, which has no distro
# installed). On Linux/macOS, sh runs the script fine (POSIX-compatible).
command = "sh"
args = ["scripts/bindgen_kotlin.sh"]
# ---------------------------------------------------------------------------
# Python
# ---------------------------------------------------------------------------
[tasks.python-develop]
description = "maturin develop into the active virtualenv."
command = "maturin"
args = ["develop"]
[tasks.test-python]
description = "Build + run the Python test suite."
dependencies = ["python-develop"]
command = "python"
args = ["-m", "pytest", "-q"]
cwd = "python"
[tasks.python-wheel]
description = "Build a release wheel into target/wheels."
command = "maturin"
args = ["build", "--release"]
[tasks.python-wheel-manylinux]
description = "Build a manylinux_2_28 release wheel (run inside the manylinux container)."
command = "maturin"
args = ["build", "--release", "--manylinux", "2_28"]
# ---------------------------------------------------------------------------
# JavaScript (napi-rs)
# ---------------------------------------------------------------------------
[tasks.js-install]
description = "yarn install for iroh-js."
command = "yarn"
args = ["install"]
cwd = "iroh-js"
[tasks.js-build]
description = "Build the napi addon (platform-tagged debug)."
dependencies = ["js-install"]
script_runner = "@shell"
script = '''
set -eu
cd iroh-js
node_modules/.bin/napi build --platform
'''
[tasks.test-js]
description = "Build + run the JS test suite."
dependencies = ["js-build"]
command = "yarn"
args = ["test"]
cwd = "iroh-js"
[tasks.js-build-release]
description = "Build the napi addon (release, stripped)."
dependencies = ["js-install"]
command = "yarn"
args = ["build"]
cwd = "iroh-js"
# ---------------------------------------------------------------------------
# Kotlin
# ---------------------------------------------------------------------------
[tasks.test-kotlin]
description = "Generate the Kotlin binding + run the Gradle test suite (JVM)."
dependencies = ["bindgen-kotlin"]
script_runner = "@shell"
script = '''
set -eu
# Gradle 8.13 + AGP 8.13 + Kotlin 2.2.20 + Dokka 2 require Java 17-21. If
# the user's default JAVA_HOME is e.g. Homebrew's openjdk@26, shim to an
# installed JDK 17 (macOS only — `java_home -v 17`); CI explicitly sets
# Java 21 via actions/setup-java.
if [ "$(uname -s)" = "Darwin" ] && [ -x /usr/libexec/java_home ]; then
JDK17=$(/usr/libexec/java_home -v 17 2>/dev/null || true)
[ -n "$JDK17" ] && [ -d "$JDK17" ] && export JAVA_HOME="$JDK17"
fi
cd kotlin
./gradlew :lib:test --no-daemon --console=plain
'''
[tasks.kotlin-android]
description = "Cross-compile the Android .so's for all ABIs + generate the binding (build verification). Needs Android NDK + cargo-ndk."
script_runner = "@shell"
script = '''
set -eu
command -v cargo-ndk >/dev/null 2>&1 || cargo install --version 3.5.4 cargo-ndk --locked
# Host lib only needed so uniffi-bindgen can read metadata.
cargo build --lib
TARGET_DIR=$(cargo metadata --format-version 1 --no-deps | python3 -c 'import json,sys;print(json.load(sys.stdin)["target_directory"])')
cargo ndk -o ./kotlin/android/src/main/jniLibs \
--manifest-path ./Cargo.toml \
-t armeabi-v7a -t arm64-v8a -t x86 -t x86_64 \
build --release
cargo run --bin uniffi-bindgen generate --language kotlin \
--out-dir kotlin/lib/src/main/kotlin/ --config "$UNIFFI_CONFIG" \
--library "$TARGET_DIR/debug/libiroh_ffi.so"
'''
# ---------------------------------------------------------------------------
# Swift
# ---------------------------------------------------------------------------
[tasks.test-swift]
description = "Build the full Apple xcframework + run the Swift test suite (xcodebuild)."
dependencies = ["swift-xcframework"]
script_runner = "@shell"
script = '''
set -eu
# `swift test` (SwiftPM CLI) fails to form the `Iroh` clang module from the
# framework-bundle xcframework on the CI runner (every uniffi type "not in
# scope"), while full Xcode consumes it fine. Drive tests through xcodebuild
# so local and CI both use the working toolchain.
#
# Use a clean, repo-local derived-data dir: the self-hosted CI runner reuses
# the shared DerivedData across runs and otherwise relinks a stale extracted
# Iroh.framework (missing the freshly built uniffi symbols). A fresh path per
# run forces re-extraction of the just-built xcframework.
#
# Run xcodebuild as the logged-in console user and force arm64, so it uses
# the right session and the arm64-only xcframework macOS slice.
CONSOLE_USER=$(stat -f%Su /dev/console)
CONSOLE_UID=$(id -u "$CONSOLE_USER")
echo "console user: $CONSOLE_USER ($CONSOLE_UID)"
if [ "$CONSOLE_USER" = "root" ]; then
echo "ERROR: no interactive GUI session; cannot run xcodebuild test" >&2
exit 1
fi
rm -rf .xcode-ddata
launchctl asuser "$CONSOLE_UID" arch -arm64 xcodebuild test \
-scheme IrohLib -destination 'platform=macOS' -derivedDataPath .xcode-ddata
'''
[tasks.swift-xcframework]
description = "Full release xcframework for all Apple targets (CI / release)."
script_runner = "@shell"
script = '''
set -eu
./make_swift.sh
'''
[tasks.swift-package]
description = "Zip + checksum the xcframework for SwiftPM distribution."
dependencies = ["swift-xcframework"]
script_runner = "@shell"
script = '''
set -eu
./package_swift.sh
'''
# ---------------------------------------------------------------------------
# Pre-release artifact verification — assert each cross-built artifact has the
# layout consumers expect, BEFORE any publish. CI gates the publish job on
# these; local pre-release-check runs them too. Catches the class of "build
# succeeds, artifact is broken, runtime crash on consumer device" bugs.
# ---------------------------------------------------------------------------
[tasks.verify-swift-xcframework]
description = "Verify Iroh.xcframework slices have the flat -library layout xcodebuild produces (libiroh_ffi.a + Headers/, no .framework directory). Catches stale-skeleton remnants from before the build pivoted off the hand-laid Iroh.framework approach."
script_runner = "@shell"
script = '''
set -eu
XCF="${IROH_XCFRAMEWORK:-Iroh.xcframework}"
LIB="libiroh_ffi.a"
[ -d "$XCF" ] || { echo "ERROR: $XCF not found (build it first: cargo make swift-xcframework)" >&2; exit 1; }
[ -f "$XCF/Info.plist" ] || { echo "ERROR: missing $XCF/Info.plist" >&2; exit 1; }
fail=0
err() { printf 'FAIL: %s\n' "$*" >&2; fail=1; }
for slice_dir in "$XCF"/*/; do
slice=$(basename "$slice_dir")
printf ' slice %s\n' "$slice"
# Flat library layout: each slice has lib.a + Headers/ at the slice root.
# No .framework directory anywhere — that was the deprecated hand-laid
# pattern that needed per-Xcode-major maintenance.
[ -f "$slice_dir/$LIB" ] || err "$slice: missing $LIB"
[ -d "$slice_dir/Headers" ] || err "$slice: missing Headers/"
[ -f "$slice_dir/Headers/iroh_ffiFFI.h" ] || err "$slice: missing Headers/iroh_ffiFFI.h"
[ -f "$slice_dir/Headers/Export.h" ] || err "$slice: missing Headers/Export.h (umbrella)"
[ -f "$slice_dir/Headers/module.modulemap" ] || err "$slice: missing Headers/module.modulemap"
[ -d "$slice_dir/Iroh.framework" ] && err "$slice: stale Iroh.framework/ present — xcodebuild -create-xcframework -library produces a flat layout with no .framework dir"
done
[ $fail -eq 0 ] || { echo "verify-swift-xcframework: FAILED" >&2; exit 1; }
echo "verify-swift-xcframework: OK"
'''
[tasks.verify-kotlin-artifact]
description = "Verify the two Kotlin artifact source trees have every required native lib with the correct architecture, before Maven publish. JVM JAR: 4 desktop JNA paths under kotlin/lib/src/main/resources/. AAR: 4 Android ABIs under kotlin/android/src/main/jniLibs/."
script_runner = "@shell"
script = '''
set -eu
fail=0
err() { printf 'FAIL: %s\n' "$*" >&2; fail=1; }
# Two source trees, one per published artifact:
# - kotlin/lib/src/main/resources/<jna-platform>/<lib> → JVM JAR; JNA
# discovers libs at <jna-platform>/<libname>.<ext> from classpath.
# - kotlin/android/src/main/jniLibs/<abi>/libiroh_ffi.so → AAR; AGP packages
# these into consumer APKs at lib/<abi>/.
# Patterns are file(1) substring regexes — distinguish 32/64-bit, ARM/x86,
# Mach-O/PE/ELF.
JVM_RES="${IROH_KOTLIN_RES:-kotlin/lib/src/main/resources}"
AND_JNI="${IROH_KOTLIN_JNI:-kotlin/android/src/main/jniLibs}"
[ -d "$JVM_RES" ] || { echo "ERROR: $JVM_RES not found" >&2; exit 1; }
[ -d "$AND_JNI" ] || { echo "ERROR: $AND_JNI not found" >&2; exit 1; }
SPECS="
$JVM_RES/linux-x86-64/libiroh_ffi.so|ELF 64-bit.*x86-64
$JVM_RES/linux-aarch64/libiroh_ffi.so|ELF 64-bit.*aarch64
$JVM_RES/darwin-aarch64/libiroh_ffi.dylib|Mach-O 64-bit.*arm64
$JVM_RES/win32-x86-64/iroh_ffi.dll|PE32\\+.*x86-64
$AND_JNI/armeabi-v7a/libiroh_ffi.so|ELF 32-bit.*ARM, EABI
$AND_JNI/arm64-v8a/libiroh_ffi.so|ELF 64-bit.*aarch64
$AND_JNI/x86/libiroh_ffi.so|ELF 32-bit.*Intel 80386
$AND_JNI/x86_64/libiroh_ffi.so|ELF 64-bit.*x86-64
"
while IFS='|' read -r path pat; do
[ -n "$path" ] || continue
if [ ! -f "$path" ]; then
err "missing $path"
continue
fi
desc=$(file -b "$path")
if ! printf '%s' "$desc" | grep -Eq "$pat"; then
err "$path: arch mismatch (want /$pat/, got: $desc)"
else
printf ' ok %-60s %s\n' "$path" "$desc"
fi
done <<EOF
$SPECS
EOF
[ $fail -eq 0 ] || { echo "verify-kotlin-artifact: FAILED" >&2; exit 1; }
echo "verify-kotlin-artifact: OK"
'''
[tasks.verify-python-wheel]
description = "Install the just-built Python wheel into a clean venv and exercise a native API. Catches 'wheel built but doesn't import / SecretKey.generate crashes'."
script_runner = "@shell"
script = '''
set -eu
WHEEL_DIR="${IROH_WHEEL_DIR:-target/wheels}"
WHEEL=$(ls "$WHEEL_DIR"/iroh-*.whl 2>/dev/null | head -1)
[ -n "$WHEEL" ] || { echo "ERROR: no wheel in $WHEEL_DIR/ (build it first: maturin build --release --out $WHEEL_DIR)" >&2; exit 1; }
echo " wheel: $WHEEL"
VENV=$(mktemp -d)/v
python3 -m venv "$VENV"
"$VENV/bin/pip" install --quiet --upgrade pip
"$VENV/bin/pip" install --quiet "$WHEEL"
# Force-resolve the loaded module so `import` errors surface, then exercise a
# native call that round-trips through the FFI boundary (SecretKey.generate
# allocates a Rust object; .public() and .to_bytes() cross the boundary back).
"$VENV/bin/python3" - <<'PY'
import iroh, sys
print(f" loaded iroh from {iroh.__file__}")
secret = iroh.SecretKey.generate()
pub = secret.public()
assert len(pub.to_bytes()) == 32, f"unexpected pubkey size: {len(pub.to_bytes())}"
print(f" SecretKey roundtrip: pub={pub.fmt_short()}")
PY
echo "verify-python-wheel: OK"
'''
[tasks.verify-js-package]
description = "Build the napi binding and exercise a native API from a fresh consumer process. Equivalent to test-js for the host platform — the released shape (per-target sub-packages + optionalDependencies) is rewritten by `napi pre-publish` at publish time, so it can't be reproduced locally without publishing."
dependencies = ["test-js"]
script_runner = "@shell"
script = '''
set -eu
# test-js (this task's dependency) already builds index.node + runs the test
# suite, which loads the binding the same way `require('@number0/iroh')` does
# in a consumer project. The per-target sub-package layout that napi-rs ships
# is only built up at publish time inside CI (`napi pre-publish` rewrites
# optionalDependencies to per-target packages that don't exist on npm until
# the publish itself succeeds), so a from-tarball local smoke would install
# broken optionalDependencies.
echo "verify-js-package: OK (covered by test-js)"
'''
[tasks.verify-swift-consumer]
description = "Build IrohLib for iOS device + boot an iOS Simulator and run the IrohLib test suite on it. test-swift covers macOS; this catches link/symbol issues + runtime-on-iOS regressions (loader errors, missing exports, dyld surprises) that a pure macOS test misses."
dependencies = ["swift-xcframework"]
script_runner = "@shell"
script = '''
set -eu
# Fail loudly if the iOS platform SDK isn't installed (e.g., fresh CI runner
# with just the macOS SDK). Listing SDKs is much faster than waiting for
# xcodebuild to discover destinations and emit the same error.
if ! xcodebuild -showsdks 2>/dev/null | grep -q "iphoneos"; then
echo "ERROR: iOS SDK not installed on this host." >&2
echo " Install via: sudo xcodebuild -downloadPlatform iOS" >&2
echo " (the xcode16 self-hosted runner needs this run once)" >&2
exit 1
fi
# xcodebuild test on a Simulator destination needs an interactive GUI session
# (Simulator.app spawns under the console user). Mirror test-swift's pattern.
CONSOLE_USER=$(stat -f%Su /dev/console)
CONSOLE_UID=$(id -u "$CONSOLE_USER")
if [ "$CONSOLE_USER" = "root" ]; then
echo "ERROR: no interactive GUI session; cannot run xcodebuild test on iOS sim" >&2
exit 1
fi
rm -rf .xcode-verify-ddata
# 1. iOS device — build only. Real-device deploy needs signing; build is
# enough to catch link/symbol errors specific to the device slice.
echo "==> xcodebuild build for iOS device (build-only, no run)"
arch -arm64 xcodebuild build \
-scheme IrohLib \
-destination "generic/platform=iOS" \
-derivedDataPath .xcode-verify-ddata \
CODE_SIGNING_REQUIRED=NO CODE_SIGN_IDENTITY="" \
-quiet
# 2. iOS Simulator — full xcodebuild test boots a sim, installs the test
# bundle, and runs IrohLibTests on the iOS runtime. Catches "links but
# crashes on iOS startup" + verifies the simulator slice actually loads.
# Pick any available iOS Simulator via simctl: local dev (iPhone 17 Pro,
# iPhone Air, …) and CI (iPhone 16 default) both work, no hardcoded name
# to keep up with Apple's annual lineup.
SIM_ID=$(xcrun simctl list devices available -j | python3 -c '
import json, sys
data = json.load(sys.stdin)
for runtime, devices in data["devices"].items():
if "SimRuntime.iOS" in runtime:
for d in devices:
if d.get("isAvailable"):
print(d["udid"]); sys.exit(0)
sys.exit(1)
' || true)
if [ -z "$SIM_ID" ]; then
echo "ERROR: no available iOS Simulator. Install one via Xcode > Settings > Components." >&2
exit 1
fi
DEST="${IROH_IOS_SIM_DEST:-platform=iOS Simulator,id=$SIM_ID}"
echo "==> xcodebuild test on iOS Simulator ($DEST)"
launchctl asuser "$CONSOLE_UID" arch -arm64 xcodebuild test \
-scheme IrohLib \
-destination "$DEST" \
-derivedDataPath .xcode-verify-ddata
echo "verify-swift-consumer: OK"
'''
[tasks.verify-kotlin-android-consumer]
description = "Build a tiny Android app that depends on computer.iroh:iroh and run an instrumented test on a connected device/emulator. Catches 'AGP merge from JAR to APK is broken (issue #246)' and 'native lib in APK but won't load / no JNI exports'. Locally: needs an emulator running (Android Studio / `emulator -avd <name>`); CI runs inside reactivecircus/android-emulator-runner."
dependencies = ["bindgen-kotlin"]
script_runner = "@shell"
script = '''
set -eu
# 1. Cross-compile the 4 Android ABIs. Hard prerequisites: cargo-ndk + an NDK.
# Verification tasks must not mutate global state — install upfront via
# `cargo install cargo-ndk` (or CI's setup-android job) and the appropriate
# `sdkmanager --install ndk;<version>`.
command -v cargo-ndk >/dev/null 2>&1 || { echo "ERROR: cargo-ndk not installed (cargo install --version 3.5.4 cargo-ndk --locked)" >&2; exit 1; }
[ -n "${ANDROID_NDK_HOME:-}" ] || { echo "ERROR: ANDROID_NDK_HOME must point at an installed NDK (r23+)" >&2; exit 1; }
NDK_STAGE=$(mktemp -d)
echo "==> cross-build android ABIs → $NDK_STAGE"
cargo ndk -o "$NDK_STAGE" \
-t armeabi-v7a -t arm64-v8a -t x86 -t x86_64 \
build --release --lib -p iroh-ffi
# 2. Stage at the AAR source-set path — AGP packages files here into the AAR's
# jni/<abi>/, which consumers' APKs then merge.
JNI=kotlin/android/src/main/jniLibs
for abi in armeabi-v7a arm64-v8a x86 x86_64; do
install -D -m644 "$NDK_STAGE/$abi/libiroh_ffi.so" "$JNI/$abi/libiroh_ffi.so"
done
# 3. Run the consumer smoke from the kotlin/ root. With a device connected,
# connectedDebugAndroidTest installs both APKs and runs the test. Without
# one, assembleDebugAndroidTest just builds them — enough to catch
# "AAR-merge broken" but not "lib loads but JNI symbol missing".
cd kotlin
if adb devices 2>/dev/null | awk 'NR>1 && $2=="device"{found=1} END{exit !found}'; then
./gradlew :android-smoke:connectedDebugAndroidTest --no-daemon --console=plain
else
echo "no connected device — building APKs only; start an emulator + re-run for the full smoke"
./gradlew :android-smoke:assembleDebugAndroidTest --no-daemon --console=plain
fi
echo "verify-kotlin-android-consumer: OK"
'''
[tasks.verify-kotlin-consumer]
description = "Run the JVM test suite against a release-built, per-JNA-platform-staged cdylib (not the dev-mode flat .so). Catches 'staging path wrong / JNA-platform name typo / .so doesn't load from the per-platform subdir'. Cross-platform — runs on linux/macOS/windows (uses bash via Git for Windows on win32)."
dependencies = ["bindgen-kotlin"]
# `sh` resolves to Git Bash's sh.exe on Windows (Git for Windows ships
# sh.exe; System32 only has bash.exe = WSL launcher, which has no distro
# installed). On Linux/macOS, sh runs the script fine (POSIX-compatible).
command = "sh"
args = ["scripts/verify_kotlin_consumer.sh"]
# ---------------------------------------------------------------------------
# Docs (self-hosted on GitHub Pages; Swift is additionally on the SPI)
# ---------------------------------------------------------------------------
[tasks.docs-swift]
description = "Build the Swift DocC docs, laid out for Pages static hosting."
# Always builds the full 4-target xcframework. The previous `swift-macos-
# framework` macOS-only path used a hand-laid framework skeleton; with the
# `xcodebuild -create-xcframework -library` build pipeline, all four slices
# come from the same one-shot xcodebuild invocation and there's no slim
# macOS-only fast-path worth maintaining separately.
dependencies = ["swift-xcframework"]
script_runner = "@shell"
script = '''
set -eu
./make_swift_docs.sh
'''
[tasks.docs-python]
description = "Build the Python extension + render API docs with pdoc."
dependencies = ["python-develop"]
script_runner = "@shell"
script = '''
set -eu
python3 -m pip install --quiet --upgrade pdoc
rm -rf site/python
# `iroh/__init__.py` is just `from .iroh_ffi import *`; documenting the
# `iroh` package makes pdoc choke on the trivial `__init__.pyi` re-export
# ("attempted relative import"). Target the real module directly — same
# public symbols, fully populated.
python3 -m pdoc iroh.iroh_ffi -o site/python
echo "python docs -> site/python"
'''
[tasks.docs-js]
description = "Build the napi addon (for index.d.ts) + render API docs with TypeDoc."
dependencies = ["js-build-release"]
script_runner = "@shell"
script = '''
set -eu
cd iroh-js
yarn docs
echo "js docs -> site/js"
'''
[tasks.docs-kotlin]
description = "Generate the Kotlin binding + render API docs with Dokka."
dependencies = ["bindgen-kotlin"]
script_runner = "@shell"
script = '''
set -eu
cd kotlin
./gradlew :lib:dokkaGeneratePublicationHtml --no-daemon --console=plain
cd ..
rm -rf site/kotlin
mkdir -p site
cp -R kotlin/lib/build/dokka/html site/kotlin
echo "kotlin docs -> site/kotlin"
'''
[tasks.docs-landing]
description = "Render the docs landing page from support-matrix.yaml."
script_runner = "@shell"
script = '''
set -eu
python3 -m pip install --quiet --upgrade pyyaml
python3 scripts/docs/gen_landing.py
'''
[tasks.docs-site]
description = "Build the complete Pages site (all languages + landing) into ./site."
dependencies = [
"docs-swift",
"docs-python",
"docs-js",
"docs-kotlin",
"docs-landing",
]
# ---------------------------------------------------------------------------
# Aggregates
# ---------------------------------------------------------------------------
[tasks.test-all]
description = "Run every binding's test suite."
dependencies = [
"test-rust",
"test-python",
"test-js",
"test-kotlin",
"test-swift",
]
[tasks.ci-rust]
description = "What the Rust CI job runs: fmt check, clippy, docs, tests."
dependencies = ["format-check", "clippy", "docs", "test-rust"]
# ---------------------------------------------------------------------------
# Release prep (local; produces a self-consistent release PR — see RELEASING.md)
# ---------------------------------------------------------------------------
[tasks.pre-release-check]
description = "Local dry-run of every release artifact build (no uploads, no network publishes). Run before opening the release PR. Swift xcframework + zip is exercised here as a sanity check but the published release uses the zip that release_swift.yml builds on PR CI — local SHAs do NOT need to match CI."
script_runner = "@shell"
script = '''
set -eu
if [ "$(uname -s)" != "Darwin" ]; then
echo "ERROR: pre-release-check must run on macOS (xcframework needs Xcode)" >&2
exit 1
fi
echo "==> swift: build + verify layout + iOS sim test + same-host zip determinism"
cargo make swift-xcframework >/dev/null
cargo make verify-swift-xcframework >/dev/null
cargo make verify-swift-consumer >/dev/null
bash scripts/release/zip_xcframework.sh
H1=$(shasum -a 256 IrohLib.xcframework.zip | awk '{print $1}')
bash scripts/release/zip_xcframework.sh
H2=$(shasum -a 256 IrohLib.xcframework.zip | awk '{print $1}')
# Same-host determinism: the build must produce identical bytes when run twice
# on the same machine. Catches stale caches / non-deterministic flags. Doesn't
# attempt cross-host match — release_swift.yml is the only build whose zip
# actually ships.
[ "$H1" = "$H2" ] || { echo "swift zip non-deterministic on this host: $H1 vs $H2" >&2; exit 1; }
echo " ok (sha256=$H1, ships from CI not from here)"
echo "==> python: maturin build + wheel consumer smoke"
PYVENV=$(mktemp -d)/venv
python3 -m venv "$PYVENV"
"$PYVENV/bin/pip" install --quiet --upgrade maturin uniffi-bindgen
WHEELS=$(mktemp -d)
"$PYVENV/bin/maturin" build --release --quiet --out "$WHEELS"
ls "$WHEELS"/*.whl
IROH_WHEEL_DIR="$WHEELS" cargo make verify-python-wheel >/dev/null
echo "==> js: napi build + consumer smoke (loads .node, exercises SecretKey)"
( cd iroh-js && yarn install --silent && yarn build >/dev/null && npm publish --dry-run 2>&1 | tail -8 )
cargo make verify-js-package >/dev/null
echo "==> kotlin: gradle publishToMavenLocal (signed)"
# Dokka 2.0 (javadoc jar) can't parse JDK 25; CI uses Temurin 17. Pin 17 here.
JH17=$(/usr/libexec/java_home -v 17 2>/dev/null || true)
if [ -z "$JH17" ]; then
echo "ERROR: JDK 17 required (Dokka 2.0 crashes on JDK 25). Install: brew install --cask temurin@17" >&2
exit 1
fi
export JAVA_HOME="$JH17"
# signAllPublications() makes the .asc signatures part of the publication, so
# publishToMavenLocal needs the key — same armored private key as the
# SIGNING_KEY CI secret. Export its file contents:
# export ORG_GRADLE_PROJECT_signingInMemoryKey="$(cat ~/.keys/maven_key.sec.asc)"
if [ -z "${ORG_GRADLE_PROJECT_signingInMemoryKey:-}" ]; then
echo "ERROR: export ORG_GRADLE_PROJECT_signingInMemoryKey (armored private key) to validate signing" >&2
exit 1
fi
( cd kotlin && ./gradlew :lib:publishToMavenLocal --no-daemon --console=plain --quiet )
V=$(sed -nE 's/.*coordinates\("computer\.iroh", "iroh", "([^"]+)"\).*/\1/p' kotlin/lib/build.gradle.kts)
ls "$HOME/.m2/repository/computer/iroh/iroh/$V/" >/dev/null 2>&1 && echo " ok (staged in ~/.m2)"
cargo make verify-kotlin-consumer >/dev/null
echo " consumer smoke: JNA loaded from per-platform staged path"
# Android consumer smoke is intentionally NOT run here: it needs an Android
# NDK + cargo-ndk + an active emulator. CI gates it via the
# verify-kotlin-android-consumer job (release.yml). Locally, run:
# ANDROID_NDK_HOME=... cargo make verify-kotlin-android-consumer
echo ""
echo "pre-release-check passed — safe to run cargo make prepare-release <VERSION>"
'''
[tasks.prepare-release]
description = "Bump version literals across every binding manifest. The Swift xcframework checksum is filled in by PR CI (release_swift.yml) once the release PR is open, so this no longer needs Xcode locally. Usage: cargo make prepare-release <VERSION>"
script_runner = "@shell"
script = '''
set -eu
VERSION="${1:?usage: cargo make prepare-release <VERSION> (no leading v)}"
echo "==> rewrite version literals"
python3 scripts/release/bump_version.py "$VERSION"
echo "==> refresh Cargo.lock"
cargo update -p iroh-ffi --precise "$VERSION" >/dev/null
echo "==> refresh iroh-js/yarn.lock"
( cd iroh-js && yarn install --mode update-lockfile >/dev/null )
# `napi build` regenerates iroh-js/index.js with the new version baked into
# every `bindingPackageVersion !== '<V>'` check + error message. Without this
# step, publish CI regenerates index.js on the runner and ships a fresh copy
# to npm — but the committed file on main stays stuck on the old literal,
# drifting further each release.
echo "==> regenerate iroh-js/index.js via napi build"
cargo make js-build >/dev/null
cat <<EOF
Prepared release v$VERSION (versions only; Swift xcframework SHA is CI's job).
Next:
git checkout -b release/v$VERSION
git commit -am "chore(release): v$VERSION"
git push -u origin release/v$VERSION
# Open PR. release_swift.yml builds the xcframework on a CI runner, bakes
# the resulting SHA into Package.swift on this branch, and uploads the zip
# to a draft GitHub release at v$VERSION. Review the SHA-bake commit + the
# draft release asset, then merge and tag:
git tag v$VERSION <merge-commit-sha>
git push origin v$VERSION
# release.yml promotes the draft release to published.
EOF
'''
# ---------------------------------------------------------------------------
# Publish (wired in Phase 6 — placeholders so the task graph is stable)
# ---------------------------------------------------------------------------
[tasks.publish-python]
description = "Publish the Python wheel to PyPI (maturin)."
command = "maturin"
args = ["publish", "--skip-existing"]
[tasks.publish-js]
description = "Publish the npm package (napi prepublish + npm publish)."
cwd = "iroh-js"
script_runner = "@shell"
script = '''
set -eu
node_modules/.bin/napi pre-publish -t npm --no-gh-release
npm publish --access public
'''
[tasks.publish-swift]
description = "Package + checksum the xcframework for a Swift release."
dependencies = ["swift-package"]
[tasks.publish-kotlin]
description = "Publish the Kotlin artifact (Maven Central — wired in Phase 6)."
script_runner = "@shell"
script = '''
set -eu
echo "publish-kotlin: wired in Phase 6 (Maven Central / Sonatype)" >&2
exit 1
'''