Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 6 additions & 6 deletions .github/workflows/test-ios.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: iOS e2e

# toolchain#22, iOS: sample's app on an arm64 iOS simulator runs Python with the stdlib and
# toolchain#22, iOS: usage-example's app on an arm64 iOS simulator runs Python with the stdlib and
# Python.framework python-multiplatform puts into the .app, plus this plugin's python/ payload.
# macOS minutes cost ~10x, so this runs only for iOS wiring changes, on develop and by hand, and is
# informational (not a required check). Lead decision, 2026-10-03.
Expand All @@ -9,9 +9,9 @@ on:
branches: [develop]
pull_request:
paths:
- "sample/src/iosMain/**"
- "sample/build.gradle.kts"
- "sample/gradle/**"
- "usage-example/src/iosMain/**"
- "usage-example/build.gradle.kts"
- "usage-example/gradle/**"
- "toolchain/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/**"
- ".github/workflows/test-ios.yml"
workflow_dispatch:
Expand Down Expand Up @@ -48,7 +48,7 @@ jobs:
- uses: actions/cache@v4
with:
path: ~/.konan
key: konan-${{ runner.os }}-${{ hashFiles('sample/gradle/libs.versions.toml') }}
key: konan-${{ runner.os }}-${{ hashFiles('usage-example/gradle/libs.versions.toml') }}

- name: Publish packpack, the toolchain plugin and python-multiplatform (iOS simulator)
run: |
Expand All @@ -68,7 +68,7 @@ jobs:
slice="$pmd/python-multiplatform/build/python-standalone/extracted/3.14.7/ios/Python.xcframework/ios-arm64_x86_64-simulator"
# TODO(python-multiplatform#90): PYTHON_MULTIPLATFORM_DIR and the -framework Python link flags
# are interim; the published plugin will stage Python.xcframework and its link options.
xcodebuild -project sample/src/iosMain/app.xcodeproj -target iosApp \
xcodebuild -project usage-example/src/iosMain/app.xcodeproj -target iosApp \
-sdk iphonesimulator -configuration Debug \
ARCHS=arm64 ONLY_ACTIVE_ARCH=YES CODE_SIGNING_ALLOWED=NO \
SYMROOT="$GITHUB_WORKSPACE/build-ios" \
Expand Down
20 changes: 10 additions & 10 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ name: Test
#
# Both modules depend on org.thisisthepy.python.multiplatform:packpack:0.1.0 from
# mavenLocal, so each job first publishes it from pypackpack's develop branch.
# sample/ is its own build (toolchain#22, #62; python-multiplatform's Kotlin), run by the
# consumer and e2e jobs with this repository's wrapper (../gradlew from sample/ = ./gradlew -p sample).
# usage-example/ is its own build (toolchain#22, #62; python-multiplatform's Kotlin), run by the
# consumer and e2e jobs with this repository's wrapper (../gradlew from usage-example/ = ./gradlew -p usage-example).
on:
pull_request:
push:
Expand Down Expand Up @@ -94,7 +94,7 @@ jobs:

# Unit tests run on Kotlin 2.x's stdlib, but a consumer runs the plugin on the kotlin-stdlib Gradle
# embeds. pypackpack#31 broke every suspend call that way and no unit test could see it. This job is
# a real consumer: sample (its own build) with a real pip dependency installs, bundles, zips.
# a real consumer: usage-example (its own build) with a real pip dependency installs, bundles, zips.
consumer:
runs-on: ubuntu-latest
timeout-minutes: 60
Expand All @@ -120,9 +120,9 @@ jobs:
python-version: "3.14"
- uses: astral-sh/setup-uv@v6

# sample applies three things from mavenLocal: packpack (through the plugin), the
# usage-example applies three things from mavenLocal: packpack (through the plugin), the
# toolchain plugin, and python-multiplatform's library and bindings plugin. Versions pinned in
# sample/gradle/libs.versions.toml (library 3.14.7-alpha01, plugin 3.13.0 until
# usage-example/gradle/libs.versions.toml (library 3.14.7-alpha01, plugin 3.13.0 until
# python-multiplatform#70).
- name: Publish packpack, the toolchain plugin and python-multiplatform to mavenLocal
run: |
Expand All @@ -137,7 +137,7 @@ jobs:
(cd .ci/python-multiplatform && ./gradlew -p python-multiplatform-gradle-plugin publishToMavenLocal --console=plain)

- name: Install, bundle and zip as a consumer
working-directory: sample
working-directory: usage-example
run: |
../gradlew installPythonDependencies buildPython packagePython \
--console=plain > consumer.log 2>&1 || { cat consumer.log; exit 1; }
Expand Down Expand Up @@ -194,11 +194,11 @@ jobs:
(cd .ci/python-multiplatform && ./gradlew -p python-multiplatform-gradle-plugin publishToMavenLocal --console=plain)

- name: Package the desktop app
working-directory: sample
working-directory: usage-example
run: ../gradlew createDistributable --console=plain

- name: Run the packaged app's Python, headless and without PYTHONHOME
working-directory: sample
working-directory: usage-example
run: |
set -euo pipefail
app=$(find build/compose/binaries/main/app -maxdepth 3 -type f -path '*/bin/*' | head -n 1)
Expand Down Expand Up @@ -253,7 +253,7 @@ jobs:
(cd .ci/python-multiplatform && ./gradlew -p python-multiplatform-gradle-plugin publishToMavenLocal --console=plain)

- name: Build the debug APK
working-directory: sample
working-directory: usage-example
run: ../gradlew assembleDebug --console=plain

- name: Enable KVM for the emulator
Expand All @@ -270,7 +270,7 @@ jobs:
target: google_apis
disable-animations: true
script: |
set -eu; apk=$(find sample/build/outputs/apk/debug -name '*.apk' | head -n 1); [ -n "$apk" ]; adb install -r "$apk"; adb logcat -c; adb shell am start -W -n org.thisisthepy.python.multiplatform.toolchain.example/.MainActivity --ez python-smoke true; sleep 20; adb logcat -d -s PythonSmoke:V > smoke.log; cat smoke.log; grep -q 'PYTHON_SMOKE_OK ' smoke.log; grep 'PYTHON_SMOKE_OK ' smoke.log | grep -q '/data/'
set -eu; apk=$(find usage-example/build/outputs/apk/debug -name '*.apk' | head -n 1); [ -n "$apk" ]; adb install -r "$apk"; adb logcat -c; adb shell am start -W -n org.thisisthepy.python.multiplatform.toolchain.example/.MainActivity --ez python-smoke true; sleep 20; adb logcat -d -s PythonSmoke:V > smoke.log; cat smoke.log; grep -q 'PYTHON_SMOKE_OK ' smoke.log; grep 'PYTHON_SMOKE_OK ' smoke.log | grep -q '/data/'

guide:
runs-on: ubuntu-latest
Expand Down
20 changes: 10 additions & 10 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ there. Work belongs inside an existing module or directory: sources under `src/<
CI-only scripts under `.github/scripts/`, temporary files under the git-ignored `.tmp/`. If you think
a new top-level entry is needed, propose it (what, why, which alternatives inside existing
directories you ruled out) and wait for approval. This was added after unapproved root folders
(`ksp-fixtures/`, `tools/`, `kotlin-js-store/`, `iosApp/`, `sample/python`) had to be dismantled.
(`ksp-fixtures/`, `tools/`, `kotlin-js-store/`, `iosApp/`, `usage-example/python`) had to be dismantled.

## 3. Worktrees link large artefacts instead of copying them

Expand Down Expand Up @@ -170,16 +170,16 @@ GitHub issues `thisisthepy/toolchain#2` (plugin checklist) and `#1` (`tcl`), it
- Never edit, move, rename or `git add` it.
- When the code's DSL disagrees with it, the file wins, and the disagreement is recorded in `docs/SPEC.md` and
`docs/INTENT.md` §4 until the maintainer decides. Do not "fix" either side on your own.
- `sample/` is the **executable subset** of that file: the part the plugin actually reads.
When a DSL property becomes live, add it to `sample/build.gradle.kts`, so there is a build
- `usage-example/` is the **executable subset** of that file: the part the plugin actually reads.
When a DSL property becomes live, add it to `usage-example/build.gradle.kts`, so there is a build
that fails if the wiring breaks.

## 12. Layout

| Module | What it is |
|---|---|
| `toolchain/` (`:toolchain`) | The Gradle plugin (id `org.thisisthepy.python.multiplatform`, class `PythonPlugin`; source set `main`). DSL in `dsl/`, tasks in `bundle/`, `dependency/`, `hotreload/`. The same module holds toolchain-lite, the `tcl install <package>` CLI, in its own source sets (`src/cliMain`, `src/cliTest`; #78): they depend on packpack and coroutines only, never on `main`, and the plugin's publication carries none of them. |
| `sample/` | **Its own Gradle build** (own settings and catalog; toolchain#22): a Compose Multiplatform app applying this plugin and python-multiplatform, on python-multiplatform's Kotlin/Compose/AGP. Run it with this repository's wrapper: `./gradlew -p sample …`. Its Python package is `sample/src/commonMain/python/`. |
| `usage-example/` | **Its own Gradle build** (own settings and catalog; toolchain#22): a Compose Multiplatform app applying this plugin and python-multiplatform, on python-multiplatform's Kotlin/Compose/AGP. Run it with this repository's wrapper: `./gradlew -p usage-example …`. Its Python package is `usage-example/src/commonMain/python/`. |
| `.github/scripts/release/` | The develop → release → main sync (rule 4). |

`pyproject.toml` at the root is toolchain-lite, the PyPI distribution of `tcl` (#69): one platform
Expand All @@ -188,8 +188,8 @@ wheel per OS carrying the native `tcl` binary, plus the launcher package `toolch
wheels; its version must equal `toolchain/build.gradle.kts`'s `tclVersion`, and `docs/pypi/README.md` is its PyPI page.
Keep the launcher a launcher: the work stays in Kotlin.

Nothing else belongs at the root (#62): no IDE directories, no lock-file directories (the sample's wasm
yarn lock is `sample/gradle/wasm-yarn.lock`), no copied wrappers. The pre-#62 layout is the tag
Nothing else belongs at the root (#62): no IDE directories, no lock-file directories (usage-example's wasm
yarn lock is `usage-example/gradle/wasm-yarn.lock`), no copied wrappers. The pre-#62 layout is the tag
`archive/pre-restructure`.

## 13. pypackpack owns the work; toolchain owns the vocabulary
Expand Down Expand Up @@ -233,18 +233,18 @@ history removing.
classpath broke every consumer's wasm target once.
- `-Xskip-metadata-version-check` exists because `packpack` is built with a newer Kotlin than this
build. Bumping Kotlin build-wide is a decision for the maintainer, not a fix.
- `sample` resolves the plugin **by Maven coordinate from `mavenLocal()`**, not through
- `usage-example` resolves the plugin **by Maven coordinate from `mavenLocal()`**, not through
`includeBuild`. After changing `:toolchain`, run `:toolchain:publishToMavenLocal` before building
`sample`, or you are testing the previous plugin.
- `sample` is a separate build on python-multiplatform's toolchain (Kotlin 2.4.20-Beta2, Compose
`usage-example`, or you are testing the previous plugin.
- `usage-example` is a separate build on python-multiplatform's toolchain (Kotlin 2.4.20-Beta2, Compose
1.11.1, AGP 8.10.1, Gradle 8.11.1), because a Kotlin 2.1 compiler cannot read python-multiplatform's
klibs. The plugin itself stays on this build's Kotlin for its consumers; do not bump it to match.

## 16. Building and testing

Prerequisites: `org.thisisthepy.python.multiplatform:packpack:0.1.0` in `~/.m2` (published from the
`pypackpack` repository), `uv` on `PATH`, and network access: `InstallDependenciesTaskTest` and
`InstallerTest` run a real `uv add` against PyPI. `sample` additionally needs an Android SDK
`InstallerTest` run a real `uv add` against PyPI. `usage-example` additionally needs an Android SDK
(`ANDROID_HOME`) and python-multiplatform in `~/.m2`: from a python-multiplatform checkout,
`./gradlew publishAllToMavenLocal` (library `3.14.7-alpha01`, bindings plugin `3.13.0` until its #70).

Expand Down
18 changes: 9 additions & 9 deletions PROJECT.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Kotlin Multiplatform 앱의 Python 부분을 Gradle `python { }` 블록으로
| 목표 DSL (사용자 작성) | 메인 체크아웃 루트의 `(플러그인예시)build.gradle.kts`: 추적되지 않는 파일, worktree 에는 없다 |
| 플러그인 체크리스트 | GitHub 이슈 `thisisthepy/toolchain#2` |
| toolchain-lite | GitHub 이슈 `thisisthepy/toolchain#1` (`tcl install pythonx-compose`) |
| 실행 가능한 부분집합 | `sample/build.gradle.kts` |
| 실행 가능한 부분집합 | `usage-example/build.gradle.kts` |
| 의도 / 계약 | `docs/INTENT.md` / `docs/SPEC.md` |

## 현재 상태 (2026-10-03, 코드와 테스트를 읽고 판정)
Expand Down Expand Up @@ -86,7 +86,7 @@ Python 코드와 의존성이 실려 빌드·실행되는 상태. 기한을 맞

| 마일스톤 | 기한 | 범위 | 완료 기준 | 근거 |
|---|---|---|---|---|
| M1 DSL = 예시 파일 | 10-17 | 플랫폼(#7 완료), pip(#9), `compileSdk` 상수(#10), `defaultConfig` 버전(#11), `buildFeatures`(#12), `projectFlavors`(#13), 플러그인 적용·zip·jar/APK 테스트(#14) | 예시 파일의 모든 속성이 컴파일되고, 읽히거나 좁게 거부됨. `sample` 이 그 모양으로 빌드됨 | 이슈 7개, 외부 의존 없음 |
| M1 DSL = 예시 파일 | 10-17 | 플랫폼(#7 완료), pip(#9), `compileSdk` 상수(#10), `defaultConfig` 버전(#11), `buildFeatures`(#12), `projectFlavors`(#13), 플러그인 적용·zip·jar/APK 테스트(#14) | 예시 파일의 모든 속성이 컴파일되고, 읽히거나 좁게 거부됨. `usage-example` 이 그 모양으로 빌드됨 | 이슈 7개, 외부 의존 없음 |
| M2 실제 페이로드 | 11-07 | `bytecode`(#15), 소스셋·타깃별 의존성(#16), `embedLevel`(#17), ~~`compileSdk` 로 인터프리터 선택(#18)~~ → python-multiplatform `pythonVersion` 대조(#42), `KLIBDEPENS`(#19) | 각 변형의 번들에 그 타깃의 wheel 이 들어가고, bytecode 변형은 `.pyc` 를 실음 | pypackpack M2(타깃별 실제 설치)에 의존 |
| M3 쓸 수 있는 수준 | 11-30 | iOS 페이로드 연결(#20), TypedPython 검사 태스크(#21), 세 플랫폼 실행(#22) | 예시 앱이 Android 에뮬레이터·iOS 시뮬레이터·데스크톱에서 Python 과 의존성을 실행 | python-multiplatform 런타임 로딩에 의존 |

Expand All @@ -99,7 +99,7 @@ Python 코드와 의존성이 실려 빌드·실행되는 상태. 기한을 맞
```
toolchain/ Gradle 플러그인 (dsl/, bundle/, dependency/, hotreload/)
toolchain/src/cliMain, src/cliTest toolchain-lite CLI `tcl` (`:toolchain` 의 별도 소스셋, #78)
sample/ 별도 Gradle 빌드: 플러그인과 python-multiplatform 을 적용한 Compose Multiplatform 앱 + python/
usage-example/ 별도 Gradle 빌드: 플러그인과 python-multiplatform 을 적용한 Compose Multiplatform 앱 + python/
(python-multiplatform 의 Kotlin 2.4.20-Beta2 / Compose 1.11.1 / AGP 8.10.1, 자체 wrapper)
docs/ INTENT.md, SPEC.md, locale/, guide/ (GitHub Pages)
```
Expand All @@ -111,19 +111,19 @@ docs/ INTENT.md, SPEC.md, locale/, guide/ (GitHub Pages)
```bash
./gradlew :toolchain:test --rerun --console=plain > .tmp/toolchain-test.log 2>&1; echo "EXIT=$?"
./gradlew :toolchain:cliTest --rerun --console=plain > .tmp/tcl-test.log 2>&1; echo "EXIT=$?"
./gradlew :toolchain:publishToMavenLocal # sample 빌드 전에 반드시
./gradlew :toolchain:publishToMavenLocal # usage-example 빌드 전에 반드시
python3 docs/guide/check_guide.py # 가이드 검사
# sample 은 별도 빌드: python-multiplatform 체크아웃에서 ./gradlew publishAllToMavenLocal 후
./gradlew -p sample packagePython
./gradlew -p sample createDistributable # 데스크톱 앱; 실행 시 --python-smoke 로 헤드리스 확인
# usage-example 은 별도 빌드: python-multiplatform 체크아웃에서 ./gradlew publishAllToMavenLocal 후
./gradlew -p usage-example packagePython
./gradlew -p usage-example createDistributable # 데스크톱 앱; 실행 시 --python-smoke 로 헤드리스 확인
```

Python 테스트는 없다. 루트 `pyproject.toml` 이 가리키는 Python 패키지도 존재하지 않는다.

## 결정 사항

- **sample 은 별도 빌드** (#22, 리드 결정 2026-10-03): python-multiplatform 은 Kotlin 2.4.20-Beta2 로 빌드되고 Kotlin 2.1
컴파일러는 그 klib 을 읽지 못한다. 소비자인 sample 이 의존성의 툴체인에 맞추고, 플러그인 자체는 소비자를 위해
- **usage-example 은 별도 빌드** (#22, 리드 결정 2026-10-03): python-multiplatform 은 Kotlin 2.4.20-Beta2 로 빌드되고 Kotlin 2.1
컴파일러는 그 klib 을 읽지 못한다. 소비자인 usage-example 이 의존성의 툴체인에 맞추고, 플러그인 자체는 소비자를 위해
지금의 Kotlin 에 둔다.

- **위임**: 의존성은 `BackendInterface.create(BackendType.UV)`, 번들은
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ owns the Gradle vocabulary in between.
| PyPI `toolchain-lite` | `tcl`, the command line for Python users (`uv tool install toolchain-lite`) |

Apply the plugin next to Kotlin Multiplatform and point it at a `pypackpack` package. This is
`sample/build.gradle.kts`, trimmed:
`usage-example/build.gradle.kts`, trimmed:

```kotlin
plugins {
Expand All @@ -76,17 +76,17 @@ python {
compileSdk = "3.13"
localLibraryPath = "src/commonMain/python" // a pypackpack package: pyproject.toml + src/main/<pkg>
packaging {
fileName = "sample"
fileName = "usage-example"
}
}
```

```shell
./gradlew -p sample packagePython # install → bundle → zip (sample/ is its own build)
./gradlew -p sample stagePythonBundle # stage python/ for android, ios and desktop
./gradlew -p usage-example packagePython # install → bundle → zip (usage-example/ is its own build)
./gradlew -p usage-example stagePythonBundle # stage python/ for android, ios and desktop
```

> **iOS, interim:** sample's Xcode phase (`sample/src/iosMain/install-python-phase.sh`) needs
> **iOS, interim:** usage-example's Xcode phase (`usage-example/src/iosMain/install-python-phase.sh`) needs
> `PYTHON_MULTIPLATFORM_DIR` pointing at a python-multiplatform checkout for `Python.xcframework` and the
> stdlib, until python-multiplatform#90 ships that wiring in its published plugin.

Expand Down
2 changes: 1 addition & 1 deletion docs/INTENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ The intent is derived from what the maintainer wrote by hand, in this order of a
| GitHub issue [`toolchain#2`](https://github.com/thisisthepy/toolchain/issues/2), "Kotlin Gradle Plugin and Build Tools" | The plugin checklist: Python version setup, target platforms, hot reload / code push, source sets, compile levels, build automation. |
| GitHub issue [`toolchain#1`](https://github.com/thisisthepy/toolchain/issues/1), "Toolchain-lite for python-only users" | A simplified CLI for Python users: `tcl install pythonx-compose`. |
| `pyproject.toml` / the original `README.md` | "Python Multiplatform Build Plugin/Tool with Kotlin Multiplatform Mobile". |
| `sample/` | The executable subset of the target DSL that the plugin reads today. |
| `usage-example/` | The executable subset of the target DSL that the plugin reads today. |

Anything below that is an inference rather than a statement from those sources is marked
`> Inferred: confirm with the maintainer.`
Expand Down
Loading
Loading