Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
191 commits
Select commit Hold shift + click to select a range
8d4ffa8
Docs: Notebook E2E entry (SPEC B-10, planned, #26)
b-re-w Oct 3, 2026
350136e
Test: notebook-e2e fixture runs UI.ipynb scenarios against the python…
b-re-w Oct 3, 2026
158a9b4
Spec: IR nodes for fixed-layout classes, None and identity (N-11)
b-re-w Oct 3, 2026
9a47c92
Bench: Record the measured commit in the result JSON
b-re-w Oct 3, 2026
d2dc9dd
Feat: Lower fixed-layout classes in the TypedPython front end (N-11)
b-re-w Oct 3, 2026
962ab17
Feat: Generate C for fixed-layout classes in TypedPython (N-11)
b-re-w Oct 3, 2026
fc15dff
Feat: Verify fixed-layout class nodes in TypedPython IR (N-11)
b-re-w Oct 3, 2026
b0e5fb0
Merge branch 'feat/tp-cls-cg' into feat/typedpython-classes
b-re-w Oct 3, 2026
46c97c2
Merge branch 'feat/tp-cls-ve' into feat/typedpython-classes
b-re-w Oct 3, 2026
c3945b6
Test: Accept Const(OBJ, None) as a reference source after N-11
b-re-w Oct 3, 2026
47c767e
Docs: A slot-writing function is never closed over its globals (N-11)
b-re-w Oct 3, 2026
4987883
Bench: binary-trees declares __slots__; record a no-slots CPython bas…
b-re-w Oct 3, 2026
710a70e
Feat: Keep compiled classes exact when the class or its global change…
b-re-w Oct 3, 2026
3dbb6f9
Merge branch 'feat/tp-cls-version' into feat/typedpython-classes
b-re-w Oct 3, 2026
11e52af
Docs: Record N-11's post-init change semantics and class-param deopt
b-re-w Oct 3, 2026
205cd4d
Merge remote-tracking branch 'origin/develop' into work/e2e-notebook
b-re-w Oct 3, 2026
aea7fca
Feat: Embed package-relative source paths and report why a module has…
b-re-w Oct 3, 2026
902a389
Merge pull request #124 from thisisthepy/feat/typedpython-classes
b-re-w Oct 3, 2026
baf65d4
WIP: Restore Pythonic names in the binder (#131) — agent stopped by u…
b-re-w Oct 3, 2026
a50be42
Merge remote-tracking branch 'origin/develop' into feat/tp-module-report
b-re-w Oct 3, 2026
13e31f0
Chore: License the project under Apache-2.0 (#134)
b-re-w Oct 3, 2026
f155ebf
Merge pull request #135 from thisisthepy/feat/apache-license
b-re-w Oct 3, 2026
6a976c1
Docs: No em-dash; examples use uv, ppp or tcl; pythonx-compose tone (…
b-re-w Oct 3, 2026
de9f1fa
Merge pull request #137 from thisisthepy/feat/doc-rules
b-re-w Oct 3, 2026
fd155a9
Fix: Resolve snake aliases in describe(module, alias) on the proxy-on…
b-re-w Oct 3, 2026
66e3fdd
Merge remote-tracking branch 'origin/develop' into feat/restore-pytho…
b-re-w Oct 3, 2026
970be90
Merge pull request #133 from thisisthepy/feat/tp-module-report
b-re-w Oct 3, 2026
2c12619
Test: Align four tests with SPEC U-12 (snake keywords, binder-served …
b-re-w Oct 3, 2026
0db0857
Merge remote-tracking branch 'origin/develop' into feat/restore-pytho…
b-re-w Oct 3, 2026
452cc80
Merge pull request #138 from thisisthepy/feat/restore-pythonic-names
b-re-w Oct 3, 2026
9b8fdb3
Docs: Remove every em-dash and the last pip example (#139)
b-re-w Oct 3, 2026
706404e
Merge pull request #140 from thisisthepy/feat/no-em-dash
b-re-w Oct 3, 2026
e7c9357
Docs: Design virtual objects, refcount elision and region allocation …
b-re-w Oct 3, 2026
c21ce24
Merge pull request #103 from thisisthepy/feat/bench-commit-hash
b-re-w Oct 3, 2026
aac43f0
Docs: Compare TypedPython with Cython, numba and LPython at design level
b-re-w Oct 3, 2026
ba12b43
Merge remote-tracking branch 'origin/develop' into work/e2e-notebook
b-re-w Oct 3, 2026
19139c3
Feat: Compiled loops and calls run CPython's eval breaker (#141 phase 1)
b-re-w Oct 3, 2026
d4ec1e4
Docs: GC collection timing is outside N-12's identity (user decision)
b-re-w Oct 3, 2026
b374da5
Perf: Poll loops through a function-local countdown (#141)
b-re-w Oct 3, 2026
542c42b
Docs: Bring the TypedPython design up to date (N-11 landed, #141 phas…
b-re-w Oct 3, 2026
5784f52
Fix: An int argument picks the overload its Kotlin literal would (#146)
b-re-w Oct 3, 2026
a8de4c4
Merge pull request #156 from thisisthepy/feat/int-literal-overload
b-re-w Oct 3, 2026
fd0f8d1
Docs: Record the 2026-10-04 priorities and pythonx family decisions
b-re-w Oct 3, 2026
22fd95c
Merge pull request #166 from thisisthepy/feat/ecosystem-pythonx-plan
b-re-w Oct 3, 2026
3ca65f4
Refactor: Restore the host API names PythonLauncher, PythonWidget and…
b-re-w Oct 3, 2026
6de021a
Merge pull request #167 from thisisthepy/feat/host-api-names
b-re-w Oct 3, 2026
ded793c
Merge remote-tracking branch 'origin/develop' into work/e2e-notebook
b-re-w Oct 3, 2026
847ff26
Test: Notebook E2E uses PythonAppView/PythonWidget, builds the wheel …
b-re-w Oct 3, 2026
e858d0b
Merge pull request #145 from thisisthepy/feat/tp-compiler-comparison
b-re-w Oct 3, 2026
7e1bb09
Merge pull request #144 from thisisthepy/feat/tp-virtual-objects
b-re-w Oct 3, 2026
18df817
Fix: A value class whose underlying property is not public crosses as…
b-re-w Oct 3, 2026
9d414e7
Merge pull request #169 from thisisthepy/feat/value-class-box
b-re-w Oct 3, 2026
605a6e5
Merge remote-tracking branch 'origin/develop' into feat/e2e-notebook
b-re-w Oct 3, 2026
1c71422
Docs: SPEC B-10 is implemented on desktop: 20 of 20 notebook scenario…
b-re-w Oct 3, 2026
844cf29
Merge pull request #170 from thisisthepy/feat/e2e-notebook
b-re-w Oct 3, 2026
4dd1860
Refactor: Sample's Python sources live under sample/src/commonMain/py…
b-re-w Oct 3, 2026
96aeac7
Merge pull request #171 from thisisthepy/feat/sample-python-src
b-re-w Oct 3, 2026
1f05d3e
Refactor: The iOS app is sample/src/iosMain/app.xcodeproj; root iosAp…
b-re-w Oct 3, 2026
a01572e
Fix: Python.xcframework and Demo.framework are under sample/build, tw…
b-re-w Oct 3, 2026
54a3a54
Merge pull request #173 from thisisthepy/feat/ios-project-in-sample
b-re-w Oct 3, 2026
17b7ed9
Feat: Fast entry without per-call depth counting for bounded compiled…
b-re-w Oct 3, 2026
f2c8e43
Feat: Exact frame count at redo and poll points inside fast trees (#147)
b-re-w Oct 3, 2026
0cfa054
Feat: rememberSaveableWrapper gives Python Compose's rememberSaveable…
b-re-w Oct 3, 2026
8c56f19
Merge pull request #176 from thisisthepy/feat/remember-saveable
b-re-w Oct 3, 2026
b41d1e9
Test: Notebook E2E uses the notebook's state spelling, main.App.messa…
b-re-w Oct 3, 2026
9ebca7a
Merge pull request #178 from thisisthepy/feat/e2e-notebook-spelling
b-re-w Oct 3, 2026
9754452
Refactor: Dissolve the root tools/ directory (#109)
b-re-w Oct 3, 2026
c06f31f
Merge pull request #179 from thisisthepy/feat/dissolve-tools
b-re-w Oct 3, 2026
98e6e5d
Feat: Free-threaded-safe TypedPython modules declare Py_MOD_GIL_NOT_U…
b-re-w Oct 3, 2026
db8b857
Refactor: Fixtures live inside the modules they test; root ksp-fixtur…
b-re-w Oct 3, 2026
339ec94
Merge pull request #180 from thisisthepy/feat/fixtures-into-modules
b-re-w Oct 3, 2026
1d257cc
Chore: Remove unreferenced binary/ and stop tracking local .claude se…
b-re-w Oct 3, 2026
81250ef
Merge pull request #182 from thisisthepy/feat/drop-binary
b-re-w Oct 3, 2026
353d266
Docs: SPEC N-7b, the type gate runs at import in development mode (#183)
b-re-w Oct 3, 2026
e182450
Merge pull request #184 from thisisthepy/feat/spec-import-gate
b-re-w Oct 3, 2026
13b0d77
Docs: commit TypedPython to GPU kernels and CPU SIMD, record the kern…
b-re-w Oct 4, 2026
e3ec95a
Merge pull request #186 from thisisthepy/feat/typedpython-kernel-commit
b-re-w Oct 4, 2026
775b7fa
Docs: TypedPython kernels get build-time autotuning, a performance ba…
b-re-w Oct 4, 2026
310266e
Merge pull request #188 from thisisthepy/feat/typedpython-kernel-perf
b-re-w Oct 4, 2026
e1468d5
Docs: TypedPython kernels against JAX, torch delegation only where it…
b-re-w Oct 4, 2026
c4b515e
Merge pull request #190 from thisisthepy/feat/typedpython-vs-jax
b-re-w Oct 4, 2026
d979964
Docs: TypedPython ownership inference, memory against Rust and minima…
b-re-w Oct 4, 2026
5d7a7cb
Merge pull request #195 from thisisthepy/feat/typedpython-ownership-m…
b-re-w Oct 4, 2026
ce87827
Build: Default to CPython 3.15.0 final with python-build-standalone 2…
b-re-w Oct 15, 2026
c8d5057
Test: Refresh the vendored CPython headers to 3.15.0
b-re-w Oct 15, 2026
ebf3375
Merge pull request #197 from thisisthepy/feat/python-3.15.0
b-re-w Oct 15, 2026
3f1a349
Spec: N-12 elides refcounts and borrows field reads only on thread-lo…
b-re-w Oct 17, 2026
0ef75a4
Gradle plugin: The artifact walker skips internal packages and honour…
b-re-w Oct 17, 2026
bc75999
Merge pull request #198 from thisisthepy/feat/n12-thread-local
b-re-w Oct 17, 2026
467c139
Test: checkIndexOverflow is now skipped with its internal package; ch…
b-re-w Oct 17, 2026
0d41092
Merge pull request #199 from thisisthepy/feat/walker-internal-packages
b-re-w Oct 17, 2026
222dd84
CI: Run the tests on pushes to develop, not on every pull request
b-re-w Oct 17, 2026
9c865e0
Merge pull request #201 from thisisthepy/feat/ci-on-develop
b-re-w Oct 17, 2026
a2da58c
TypedPython: Development-mode import gate checks a project module bef…
b-re-w Oct 17, 2026
12d79bf
Merge pull request #202 from thisisthepy/feat/import-gate
b-re-w Oct 17, 2026
85d7d5b
CI: Build free-threaded CPython 3.15t for Android arm64 and iOS
b-re-w Oct 17, 2026
a8f88b1
Merge pull request #204 from thisisthepy/feat/ft-mobile-build
b-re-w Oct 17, 2026
6be2e7e
Fix: An ArrayParam function that can run user code stays interpreted …
b-re-w Oct 17, 2026
49c2c47
Merge pull request #205 from thisisthepy/feat/tp-arrayparam-closed
b-re-w Oct 17, 2026
7c73a90
Merge remote-tracking branch 'origin/develop' into feat/tp-eval-breaker
b-re-w Oct 17, 2026
130e64e
Perf: Keep the eval-breaker slow path out of line and the loop tick i…
b-re-w Oct 17, 2026
9187516
Merge remote-tracking branch 'origin/feat/tp-eval-breaker' into feat/…
b-re-w Oct 17, 2026
56a25e2
Docs: Record the eval-breaker polling cost (#141)
b-re-w Oct 17, 2026
736c1e1
Merge pull request #207 from thisisthepy/feat/tp-eval-breaker
b-re-w Oct 17, 2026
b668dc3
Merge remote-tracking branch 'origin/develop' into feat/tp-free-threaded
b-re-w Oct 17, 2026
ee91dec
CI: Use the macOS build interpreter's real name in the iOS 3.15t check
b-re-w Oct 17, 2026
9ee0af6
Merge pull request #208 from thisisthepy/feat/ft-mobile-ios-check
b-re-w Oct 17, 2026
68f4a98
Merge pull request #209 from thisisthepy/feat/tp-free-threaded
b-re-w Oct 17, 2026
49fabab
Merge remote-tracking branch 'origin/develop' into feat/tp-fast-entry
b-re-w Oct 17, 2026
7b20277
Bench: Record the fast-entry A/B (#147)
b-re-w Oct 17, 2026
de8f94f
Merge pull request #210 from thisisthepy/feat/tp-fast-entry
b-re-w Oct 17, 2026
1794586
Fix: Make the TypedPython develop CI deterministic (race mutants, TSa…
b-re-w Oct 17, 2026
c039b7e
Merge pull request #211 from thisisthepy/feat/tp-ci-fixes
b-re-w Oct 17, 2026
83e2d1a
Build: Publish the TypedPython gate bundle beside the KSP processor (…
b-re-w Oct 17, 2026
8a8d06d
Merge pull request #212 from thisisthepy/feat/tp-gate-artifact
b-re-w Oct 17, 2026
bbc6e6f
CI: Run the notebook E2E on develop pushes
b-re-w Oct 17, 2026
9231df9
Merge pull request #214 from thisisthepy/feat/notebook-e2e-ci
b-re-w Oct 17, 2026
d69f1a9
Feat: Interval proofs from guards and locals drop provably safe integ…
b-re-w Oct 17, 2026
de73628
Test: The sanitizer run checks every proven op (TP_CHECK_PROVEN); SPE…
b-re-w Oct 17, 2026
8011a60
Bench: Record the interval-proofs A/B (#148)
b-re-w Oct 17, 2026
4026696
Merge pull request #218 from thisisthepy/feat/tp-interval-proofs
b-re-w Oct 17, 2026
01e05e9
Docs: Specify member functions of classes and interfaces (SPEC B-1, B…
b-re-w Oct 17, 2026
2aae536
Test: Pin member functions as METHOD entries, stubs and proxy methods…
b-re-w Oct 17, 2026
1f0754b
Fix: Bind member functions of classes and interfaces (#215)
b-re-w Oct 17, 2026
e745ff1
Merge pull request #219 from thisisthepy/feat/member-functions
b-re-w Oct 17, 2026
2110184
Feat: Zero-copy buffer array parameters (array.array, numpy) with ato…
b-re-w Oct 17, 2026
90a291f
Test: Show the first TSan report on failure; the plain-epoch TSan che…
b-re-w Oct 17, 2026
35647cb
Merge pull request #221 from thisisthepy/feat/tp-tsan-diagnose
b-re-w Oct 17, 2026
4b22954
Fix: Drop the numpy buffer kind; array.array only (#149, numpy is #220)
b-re-w Oct 17, 2026
4d1b1e6
Merge remote-tracking branch 'origin/develop' into feat/tp-buffer-arrays
b-re-w Oct 17, 2026
f22f8ad
Docs: Specify functional-interface parameters (SPEC B-5, #216)
b-re-w Oct 17, 2026
068694e
Test: Pin functional-interface slots, and that Kotlin handles still f…
b-re-w Oct 17, 2026
6b851a5
Fix: Fill functional-interface parameters with a Python callable (#216)
b-re-w Oct 17, 2026
8fb2787
Test: Start the class-modification race only once the readers run
b-re-w Oct 17, 2026
a6e1730
Merge pull request #222 from thisisthepy/feat/tp-flaky-class-test
b-re-w Oct 17, 2026
4bfeeeb
Merge pull request #223 from thisisthepy/feat/sam-params
b-re-w Oct 17, 2026
f120a0a
Docs: Specify the stub-private class of a taken name (SPEC B-7, #217)
b-re-w Oct 17, 2026
b4172da
Test: Pin the stub-private class of a taken name, and the runtime it …
b-re-w Oct 17, 2026
973f696
Fix: Stub a type whose name a factory takes under a private name (#217)
b-re-w Oct 17, 2026
9ddf88d
Merge pull request #224 from thisisthepy/feat/taken-name-stubs
b-re-w Oct 17, 2026
4371312
Bench: Record the atomic vs plain buffer access cost (#149)
b-re-w Oct 17, 2026
cdd1159
Merge remote-tracking branch 'origin/develop' into feat/tp-buffer-arrays
b-re-w Oct 17, 2026
8642e98
Docs: SPEC N-9 row for array.array buffer parameters (#149)
b-re-w Oct 17, 2026
ec80622
Merge pull request #225 from thisisthepy/feat/tp-buffer-arrays
b-re-w Oct 17, 2026
93de63e
Fix: Delete without following symbolic links in the plugin, the build…
b-re-w Oct 17, 2026
e650658
Merge pull request #228 from thisisthepy/feat/nofollow-delete
b-re-w Oct 17, 2026
eee9243
Feat: Strings, subscripts, iteration and f-strings compile through th…
b-re-w Oct 17, 2026
7d5c594
Fix: The buffer tests run without numpy; CI installs numpy for the nu…
b-re-w Oct 17, 2026
67dc1d2
Merge pull request #229 from thisisthepy/feat/tp-numpy-optional
b-re-w Oct 17, 2026
921a483
Test: Count a TSan report only when the racing access is in the exten…
b-re-w Oct 17, 2026
71a7a03
Merge pull request #230 from thisisthepy/feat/tp-tsan-access-frame
b-re-w Oct 17, 2026
0ad892b
Merge remote-tracking branch 'origin/develop' into feat/tp-dict-str
b-re-w Oct 17, 2026
928465d
CI: Let a develop run in progress finish; a newer push replaces only …
b-re-w Oct 17, 2026
8acb347
Merge pull request #231 from thisisthepy/feat/ci-finish-runs
b-re-w Oct 17, 2026
5e56d45
Feat: Cost rule: object-only impure functions stay interpreted (#150)
b-re-w Oct 17, 2026
73517df
Fix: The cost rule rejects only object-only loops; straight-line impu…
b-re-w Oct 17, 2026
d2418f0
Dev agent: a debug-only channel from a notebook or hot reload into th…
b-re-w Oct 17, 2026
967ad6a
Docs: Specify object handles on the klib boundary (SPEC B-2, #234)
b-re-w Oct 17, 2026
bb51b39
Test: Pin klib object handles and run the klib fixture on the iOS sim…
b-re-w Oct 17, 2026
366c57e
Fix: Carry Kotlin objects as handles across the klib boundary (#234)
b-re-w Oct 17, 2026
e25804c
Merge pull request #236 from thisisthepy/feat/klib-object-handles
b-re-w Oct 17, 2026
0335742
Dev agent: release builds open no port; 32-byte token minimum; port c…
b-re-w Oct 17, 2026
d8eafe0
Merge pull request #235 from thisisthepy/feat/dev-agent
b-re-w Oct 17, 2026
85588c7
Docs: Specify member functions from a klib (SPEC B-2, #237)
b-re-w Oct 17, 2026
a0e088a
Test: Pin klib member functions and call them on the iOS simulator (#…
b-re-w Oct 17, 2026
555edcf
Fix: Bind member functions of klib classes and interfaces (#237)
b-re-w Oct 17, 2026
9154caa
Merge pull request #239 from thisisthepy/feat/klib-member-functions
b-re-w Oct 17, 2026
4314626
CI: Run the klib fixture's suite on the iOS simulator on develop pushes
b-re-w Oct 17, 2026
af4deba
Merge pull request #241 from thisisthepy/feat/klib-ios-ci
b-re-w Oct 17, 2026
6a93b18
CI: run the dev agent protocol tests on ubuntu and windows (#238)
b-re-w Oct 17, 2026
a17913d
Docs: Investigate the fork's desktop paths on macOS (#191, #242)
b-re-w Oct 17, 2026
c20546c
Merge pull request #243 from thisisthepy/feat/fork-desktop-paths-doc
b-re-w Oct 17, 2026
14054fe
Docs: Correct the native-image note on reflection registration
b-re-w Oct 17, 2026
d08757a
Merge pull request #245 from thisisthepy/feat/graal-note-reflection
b-re-w Oct 17, 2026
709487f
Dev agent: refuse Windows-rooted push paths; stop() frees the port on…
b-re-w Oct 17, 2026
1f7aa04
Docs: Specify PYTHONHOME beside a native image (SPEC L-9, #246)
b-re-w Oct 17, 2026
8ef8f92
Test: Pin the prefix beside a native image and its staging task (#246)
b-re-w Oct 17, 2026
30807ab
Fix: Find and stage PYTHONHOME beside a native-image executable (#246)
b-re-w Oct 17, 2026
3ad1e08
Merge pull request #247 from thisisthepy/feat/native-image-python-home
b-re-w Oct 17, 2026
38a5d25
SPEC L-12: the push path rule, and where the agent's tests run (#238)
b-re-w Oct 17, 2026
c985469
Merge pull request #248 from thisisthepy/feat/devagent-ci
b-re-w Oct 17, 2026
c82a7a7
Docs: Specify the notebook E2E in a macOS native image (SPEC B-10, #249)
b-re-w Oct 17, 2026
58ee600
Test: Run the notebook E2E in a macOS GraalVM native image (#249)
b-re-w Oct 17, 2026
cac9613
Merge pull request #250 from thisisthepy/feat/notebook-e2e-native-image
b-re-w Oct 17, 2026
d1f2876
CI: Prepare the native-image notebook E2E job for the fork's GitHub P…
b-re-w Oct 17, 2026
20944cb
Merge pull request #252 from thisisthepy/feat/notebook-e2e-native-ci
b-re-w Oct 17, 2026
7f9ae1e
Bench: Record why object-only compiled code lost to the interpreter (…
b-re-w Oct 17, 2026
913ab17
Merge pull request #253 from thisisthepy/feat/tp-dict-str
b-re-w Oct 17, 2026
db6c452
Docs: TypedPython SPEC statuses match the code (N-7 to N-11)
b-re-w Oct 17, 2026
860fbc8
Merge pull request #254 from thisisthepy/feat/tp-spec-status
b-re-w Oct 17, 2026
eaa2521
Release: sync from 860fbc8207c65f3b658aed1e0cae86c051aa6ed4 860fbc8
github-actions[bot] Oct 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 0 additions & 3 deletions .claude/settings.json

This file was deleted.

4 changes: 2 additions & 2 deletions tools/check_guide.py → .github/scripts/check_guide.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""Checks the bilingual GitHub Pages guide in docs/guide/.

python3 tools/check_guide.py [guide-dir]
python3 .github/scripts/check_guide.py [guide-dir]

Fails (exit 1) when:
1. an HTML file does not parse into balanced elements;
Expand Down Expand Up @@ -153,7 +153,7 @@ def check(guide):

def main():
here = os.path.dirname(os.path.abspath(__file__))
guide = sys.argv[1] if len(sys.argv) > 1 else os.path.join(here, "..", "docs", "guide")
guide = sys.argv[1] if len(sys.argv) > 1 else os.path.join(here, "..", "..", "docs", "guide")
failures = check(guide)
for f in failures:
print("FAIL", f)
Expand Down
63 changes: 63 additions & 0 deletions .github/scripts/ft-mobile/build-android.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env bash
# Build free-threaded CPython (--disable-gil) for aarch64-linux-android with
# CPython's own tooling (Platforms/Android). Usage: build-android.sh [VERSION]
# Needs ANDROID_HOME (SDK with cmdline-tools/latest) and a JDK 17, 21 or 25.
set -euo pipefail
here="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=common.sh
source "$here/common.sh"

PY_VERSION="${1:-3.15.0}"
HOST=aarch64-linux-android
: "${ANDROID_HOME:?ANDROID_HOME must point at the Android SDK}"

src="$(fetch_source "$PY_VERSION")"
cd "$src"

# `Platforms/Android package` lists the files to ship with `git ls-files`, so
# the unpacked tarball needs an index that tracks Platforms/Android.
if [ ! -d .git ]; then
git init -q .
git add Platforms/Android
fi

# Install the NDK version the tooling pins (android-env.sh would do it on first
# use; doing it here makes a missing cmdline-tools fail early and visibly).
ndk_version="$(sed -n 's/^ndk_version=//p' Platforms/Android/android-env.sh)"
[ -n "$ndk_version" ] || die "no ndk_version in Platforms/Android/android-env.sh"
log "NDK required by CPython $PY_VERSION: $ndk_version"
if [ ! -e "$ANDROID_HOME/ndk/$ndk_version/package.xml" ]; then
yes | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" "ndk;$ndk_version" >/dev/null || true
fi
[ -e "$ANDROID_HOME/ndk/$ndk_version/package.xml" ] || die "NDK $ndk_version not installed"

# --disable-gil goes to configure after `--`, for the build Python and the host.
python3 Platforms/Android configure-build -- --disable-gil
python3 Platforms/Android make-build
python3 Platforms/Android pythoninfo-build
python3 Platforms/Android configure-host "$HOST" -- --disable-gil
python3 Platforms/Android make-host "$HOST"
python3 Platforms/Android package "$HOST"

# ---- verification -------------------------------------------------------
minor="$(py_minor "$PY_VERSION")"
archive="$(ls cross-build/$HOST/dist/python-*-$HOST.tar.gz)"
log "package: $archive"

# The build Python must itself be free-threaded.
cross-build/build/python -c 'import sys, sysconfig; assert sysconfig.get_config_var("Py_GIL_DISABLED") == 1; print("build python free-threaded:", sys.version)'

check="$FT_WORK/android-check"
rm -rf "$check"; mkdir -p "$check"
tar -xzf "$archive" -C "$check"

require_gil_disabled "$check/prefix/include/python${minor}t/pyconfig.h"
lib="$(ls "$check"/prefix/lib/libpython${minor}t*.so* 2>/dev/null | head -n 1 || true)"
[ -n "$lib" ] || die "no libpython${minor}t*.so in the package"
log "ok: $lib"
file "$lib" | tee /dev/stderr | grep -q 'ARM aarch64' || die "$lib is not an aarch64 ELF"
[ -d "$check/prefix/lib/python${minor}t" ] || die "no lib/python${minor}t in the package"

cp "$archive" "$FT_OUT/"
echo "Android archive:" >> "${GITHUB_STEP_SUMMARY:-/dev/null}"
record_sha256 "$FT_OUT/$(basename "$archive")"
82 changes: 82 additions & 0 deletions .github/scripts/ft-mobile/build-ios.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/usr/bin/env bash
# Build free-threaded CPython (--disable-gil) as an iOS XCframework with
# CPython's own tooling (Platforms/Apple). Usage: build-ios.sh [VERSION]
# Needs macOS with a full Xcode and python3 >= 3.10.
set -euo pipefail
here="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=common.sh
source "$here/common.sh"

PY_VERSION="${1:-3.15.0}"
# The XCframework needs every slice Platforms/Apple declares: one device host
# and two simulator hosts (arm64, x86_64).
HOSTS=(arm64-apple-ios arm64-apple-ios-simulator x86_64-apple-ios-simulator)

src="$(fetch_source "$PY_VERSION")"
cd "$src"
xcodebuild -version

# Platforms/Apple names the standard library directory "python<major.minor>"
# when it packages and in the Xcode build helper, but a free-threaded build
# installs "python<major.minor>t" (LIBDEST has $(ABI_THREAD)). Without these two
# edits `package` fails copying the xcprivacy files and the helper script would
# not find the standard library. Each edit is asserted so a changed upstream
# script fails loudly instead of silently skipping the patch.
python3 -I - <<'PY'
import pathlib
def patch(path, old, new):
p = pathlib.Path(path)
s = p.read_text()
assert s.count(old) == 1, f"{path}: expected exactly one {old!r}"
p.write_text(s.replace(old, new))
patch("Platforms/Apple/__main__.py",
'version_tag = ".".join(version.split(".")[:2])',
'version_tag = ".".join(version.split(".")[:2]) + "t"')
patch("Platforms/Apple/testbed/Python.xcframework/build/utils.sh",
'grep -E "^python3\\.\\d+$"',
'grep -E "^python3\\.[0-9]+t?$"')
PY

# --disable-gil goes to configure after `--`, for the build Python and each host.
python3 Platforms/Apple configure-build -- --disable-gil
python3 Platforms/Apple make-build
python3 Platforms/Apple pythoninfo-build
for host in "${HOSTS[@]}"; do
python3 Platforms/Apple configure-host iOS "$host" -- --disable-gil
python3 Platforms/Apple make-host iOS "$host"
done
python3 Platforms/Apple package iOS

# ---- verification -------------------------------------------------------
minor="$(py_minor "$PY_VERSION")"
archive="$(ls cross-build/dist/python-*-iOS-XCframework.tar.gz)"
log "package: $archive"

# On macOS the build interpreter is python.exe (cross-build/build/python is a directory).
build_py=cross-build/build/python.exe; [ -x "$build_py" ] || build_py=cross-build/build/python
"$build_py" -c 'import sys, sysconfig; assert sysconfig.get_config_var("Py_GIL_DISABLED") == 1; print("build python free-threaded:", sys.version)'

check="$FT_WORK/ios-check"
rm -rf "$check"; mkdir -p "$check"
tar -xzf "$archive" -C "$check"
xcf="$check/Python.xcframework"
[ -d "$xcf" ] || die "no Python.xcframework in the package"

[ -d "$xcf/lib/python${minor}t" ] || die "no lib/python${minor}t in the XCframework"
slices=0
for slice in "$xcf"/ios-*; do
slices=$((slices + 1))
# Headers/pyconfig.h is a wrapper that includes pyconfig-<arch>.h.
found=0
for h in "$slice"/Python.framework/Headers/pyconfig-*.h; do
require_gil_disabled "$h"; found=$((found + 1))
done
[ "$found" -gt 0 ] || die "no pyconfig-<arch>.h in $slice"
[ -e "$slice/lib/libpython${minor}t.dylib" ] || die "no libpython${minor}t.dylib in $slice"
lipo -info "$slice/Python.framework/Python"
done
[ "$slices" -eq 2 ] || die "expected 2 slices (device, simulator), found $slices"

cp "$archive" "$FT_OUT/"
echo "iOS archive:" >> "${GITHUB_STEP_SUMMARY:-/dev/null}"
record_sha256 "$FT_OUT/$(basename "$archive")"
53 changes: 53 additions & 0 deletions .github/scripts/ft-mobile/common.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/usr/bin/env bash
# Helpers shared by build-android.sh and build-ios.sh. Source this file, do not run it.

# FT_WORK: scratch directory (source tree and cross-build output).
# FT_OUT : directory that receives the archives the workflow uploads.
FT_WORK="${FT_WORK:-$PWD/.tmp/ft-mobile}"
FT_OUT="${FT_OUT:-$PWD/.tmp/ft-mobile-out}"

log() { echo "[ft-mobile] $*" >&2; }
die() { echo "[ft-mobile] ERROR: $*" >&2; exit 1; }

# "3.15.0" -> "3.15" (also "3.15.0rc1" -> "3.15").
py_minor() { echo "$1" | sed -E 's/^([0-9]+\.[0-9]+).*/\1/'; }

# Download and unpack the CPython source tarball; print the source directory.
# CPython's mobile tooling needs the directory to be a git work tree only for
# `Platforms/Android package` (it runs `git ls-files` there), so the caller
# decides whether to `git init`.
fetch_source() {
local version="$1"
local dirver="${version%%[a-z]*}" # the ftp directory has no rc/alpha suffix
local tgz="$FT_WORK/Python-$version.tgz"
mkdir -p "$FT_WORK" "$FT_OUT"
if [ ! -f "$tgz" ]; then
log "downloading CPython $version"
curl -fL --retry 5 --retry-all-errors -o "$tgz" \
"https://www.python.org/ftp/python/$dirver/Python-$version.tgz"
fi
if [ ! -d "$FT_WORK/Python-$version" ]; then
tar -xzf "$tgz" -C "$FT_WORK"
fi
echo "$FT_WORK/Python-$version"
}

# Fail unless FILE contains a line matching `#define Py_GIL_DISABLED 1`.
require_gil_disabled() {
local file="$1"
grep -Eq '^#define Py_GIL_DISABLED 1$' "$file" \
|| die "$file does not define Py_GIL_DISABLED 1"
log "ok: Py_GIL_DISABLED 1 in $file"
}

# Print "sha256 name" lines for the given files, to stdout and the job summary.
record_sha256() {
local f line
for f in "$@"; do
line="$(cd "$(dirname "$f")" && shasum -a 256 "$(basename "$f")")"
echo "$line"
if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
echo "$line" >> "$GITHUB_STEP_SUMMARY"
fi
done
}
10 changes: 5 additions & 5 deletions tools/release/README.md → .github/scripts/release/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ between runs, and any existing `release` (e.g. a stale one from an older workflo
ignored and overwritten.

1. Every push to `develop` triggers `.github/workflows/release-sync.yml`, which runs
`tools/release/sync-release.sh --push`. It rebuilds `release` from scratch as exactly
`.github/scripts/release/sync-release.sh --push`. It rebuilds `release` from scratch as exactly
one commit (`Release: sync from develop <sha>`) whose parent is that develop commit and
whose tree is the develop tree minus the files below. This happens even when nothing
is dropped. It is a no-op only if `release` already has that tree and that parent.
Expand All @@ -31,10 +31,10 @@ Only Markdown is ever dropped.

## Local use

tools/release/sync-release.sh --dry-run # list paths that would be dropped
tools/release/sync-release.sh # regenerate local release branch
tools/release/sync-release.sh --push # also push to origin
tools/release/test-sync-release.sh # tests (throwaway repo in .tmp/)
.github/scripts/release/sync-release.sh --dry-run # list paths that would be dropped
.github/scripts/release/sync-release.sh # regenerate local release branch
.github/scripts/release/sync-release.sh --push # also push to origin
.github/scripts/release/test-sync-release.sh # tests (throwaway repo in .tmp/)

The script uses plumbing and a private index, so your working tree, index and HEAD are untouched.
It refuses to run while `release` is checked out.
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Tests for sync-release.sh. Builds a throwaway repo under <repo>/.tmp/.
set -u
HERE="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(cd "$HERE/../.." && pwd)"
ROOT="$(cd "$HERE/../../.." && pwd)"
SYNC="$HERE/sync-release.sh"
SCRATCH="$ROOT/.tmp/release-sync-test.$$"
FAILS=0; PASSES=0
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
#!/usr/bin/env bash
# Type-checks tools/stubs/consumer.py against a directory of generated Kotlin-name stubs (issue #31).
# Type-checks .github/scripts/stubs/consumer.py against a directory of generated Kotlin-name stubs (issue #31).
#
# tools/stubs/check-stubs.sh <stub-dir> [venv-dir]
# .github/scripts/stubs/check-stubs.sh <stub-dir> [venv-dir]
#
# <stub-dir> is what `generatePythonStubs` wrote, e.g.
# ksp-fixtures/compose/build/generated/pythonStubs/desktopMain
# python-multiplatform-compose/fixtures/compose/build/generated/pythonStubs/desktopMain
# The venv defaults to <repo>/.tmp/stubs-venv (git-ignored) and is created on first use.
set -euo pipefail

root="$(cd "$(dirname "$0")/../.." && pwd)"
root="$(cd "$(dirname "$0")/../../.." && pwd)"
stubs="$(cd "${1:?usage: check-stubs.sh <stub-dir> [venv-dir]}" && pwd)"
venv="${2:-$root/.tmp/stubs-venv}"

Expand All @@ -17,10 +17,10 @@ if [ ! -x "$venv/bin/mypy" ]; then
"$venv/bin/pip" install --quiet mypy
fi

# tools/stubs/mypy.ini: `strict`, and `warn_unused_ignores` for the consumer so that the lines it
# .github/scripts/stubs/mypy.ini: `strict`, and `warn_unused_ignores` for the consumer so that the lines it
# expects to be rejected fail the check when they stop being. Errors inside the stubs themselves are
# reported too: MYPYPATH modules are checked, not silenced.
MYPYPATH="$stubs" "$venv/bin/mypy" \
--config-file "$root/tools/stubs/mypy.ini" \
--config-file "$root/.github/scripts/stubs/mypy.ini" \
--cache-dir "$root/.tmp/mypy-cache" \
"$root/tools/stubs/consumer.py"
"$root/.github/scripts/stubs/consumer.py"
45 changes: 32 additions & 13 deletions tools/stubs/consumer.py → .github/scripts/stubs/consumer.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,48 @@
"""A small consumer of the generated Kotlin-name stubs, type-checked by `check-stubs.sh` (issue #31).
"""A small consumer of the generated Kotlin-named stubs, type-checked by `check-stubs.sh` (issue #31).

It is the code a user writes against the binder -- Kotlin names, Kotlin parameter names -- and it
must type-check against the stubs generated for the real Compose jars: a Modifier chain started from
the class and continued from an instance, `Checkbox`, `Text`, and a container with its trailing
`content` lambda. The last four lines are **meant** to be rejected; they carry `# type: ignore[...]`
and mypy runs with `warn_unused_ignores`, so a stub that stopped rejecting one fails the check too.
It is the code a user writes against the binder, and it must type-check against the stubs generated
for the real Compose jars: a Modifier chain started from the class and continued from an instance,
`Checkbox`, `Text`, and a container with its trailing `content` lambda.

Names: a declaration is reachable by its Kotlin name and by its Pythonic (snake_case) alias, and the
stubs carry both (issue #131), so both spellings are written below. Keywords: a stub can name a
parameter only once, and it names it the way `inspect.signature` does -- by the Pythonic keyword
(`on_checked_change`). The Kotlin keyword (`onCheckedChange=`) still works at run time; a checker
reports it, so it is not written here.

The last four lines are **meant** to be rejected; they carry `# type: ignore[...]` and mypy runs
with `warn_unused_ignores`, so a stub that stopped rejecting one fails the check too.
"""

from typing import assert_type

from androidx.compose.foundation.layout import Arrangement, Column, Row, fillMaxWidth, padding__Dp
from androidx.compose.foundation.layout import (
Arrangement,
Column,
Row,
fill_max_width,
fillMaxWidth,
padding__Dp,
)
from androidx.compose.material.icons import Icons
from androidx.compose.material3 import Checkbox, Icon, Text
from androidx.compose.ui import Modifier
from androidx.compose.ui.graphics.vector import ImageVector

# A chain started from the class, the way Kotlin writes `Modifier.padding(16.dp)`.
chain: Modifier = Modifier.fillMaxWidth().padding(16.0).size(24.0)
# ... and continued from an instance, and with Kotlin parameter names.
# ... and continued from an instance, with keyword arguments.
more: Modifier = chain.padding(horizontal=8.0, vertical=4.0).padding(start=1.0, bottom=2.0)
# The explicit table-key spelling, as an attribute and as a module function.
explicit: Modifier = chain.padding__Dp(4.0)
by_function: Modifier = padding__Dp(chain, 4.0)
by_extension: Modifier = fillMaxWidth(chain, fraction=0.5)

# Issue #131: the Pythonic alias of a module function and of a method is the same declaration.
by_alias: Modifier = fill_max_width(chain, fraction=0.5)
assert_type(fill_max_width(chain), Modifier)
pythonic_chain: Modifier = Modifier.fill_max_width().padding(16.0).fill_max_width(0.5)

checked = True


Expand All @@ -33,7 +52,7 @@ def on_change(value: bool) -> None:


Checkbox(checked, on_change, modifier=more, enabled=True)
Checkbox(checked=False, onCheckedChange=None)
Checkbox(checked=False, on_checked_change=None)
Text("hello", modifier=Modifier.padding(8.0))


Expand All @@ -44,18 +63,18 @@ def content() -> None:
Column(modifier=chain, content=lambda scope: None)
Row(content=lambda scope: None)
# `SpaceBetween` is a `HorizontalOrVertical`: a `Vertical` slot and a `Horizontal` slot both take it (#71).
Column(verticalArrangement=Arrangement.SpaceBetween, content=lambda scope: None)
Row(horizontalArrangement=Arrangement.SpaceBetween, content=lambda scope: None)
Column(vertical_arrangement=Arrangement.SpaceBetween, content=lambda scope: None)
Row(horizontal_arrangement=Arrangement.SpaceBetween, content=lambda scope: None)

# An extension property on a type nested in an object (issue #68): `Icons.Default` is `Icons.Filled`.
# `assert_type`, not an annotation: an `Any` (the stub before #53/#67) would satisfy an annotation.
assert_type(Icons.Default.Add, ImageVector)
Icon(Icons.Default.Add, contentDescription=None)
Icon(Icons.Default.Add, content_description=None)

# Rejected on purpose. (`padding("sixteen")` is not among them: one `padding` overload takes a
# `PaddingValues`, a type the stubs cannot name -- `PaddingValues(...)` the factory owns the name -- so
# it is `Any` and accepts anything.)
Modifier.fillMaxWidth("wide") # type: ignore[arg-type]
Modifier.fill_max_width("wide") # type: ignore[arg-type]
Checkbox("not a bool", on_change) # type: ignore[call-overload]
Checkbox(checked, on_change, enabled="yes") # type: ignore[call-overload]
Column(chain) # type: ignore[call-arg]
File renamed without changes.
11 changes: 6 additions & 5 deletions .github/workflows/android-native.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
name: Android Native CI

on:
# Checked on develop after merging, not on every pull request (user decision,
# 2026-10-17). A red develop is fixed forward by its own pull request.
push:
branches: [ "develop", "main" ]
pull_request:
branches: [ "develop", "main" ]

workflow_dispatch:
concurrency:
# A newer push to the same ref supersedes this run; frees the shared macOS runners.
# A run in progress finishes; a newer push replaces only the queued run (frequent merges
# to develop otherwise cancelled every run before it reported).
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
cancel-in-progress: false

jobs:
compile:
Expand Down
Loading