Skip to content

Commit b2f5e20

Browse files
committed
Merge remote-tracking branch 'origin/develop' into work/compose-host
2 parents 655479b + 86012ca commit b2f5e20

21 files changed

Lines changed: 1383 additions & 241 deletions

File tree

‎.github/workflows/stubs.yml‎

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
name: Kotlin-name stubs
2+
3+
# Issue #32: pythonx-compose consumes the binder's Kotlin-name `.pyi` stubs for the real Compose jars
4+
# but cannot run this repository's Gradle build. This job runs the stub task over the Compose version the
5+
# build resolves (`compose-plugin` in gradle/libs.versions.toml) and publishes the result.
6+
#
7+
# - every push to develop and every pull request: the stubs are generated, type-checked against
8+
# tools/stubs/consumer.py with mypy (issue #31), and uploaded as the workflow artifact `kotlin-stubs`;
9+
# - every `v*` tag: the same stubs are also attached to the tag's GitHub release as `kotlin-stubs.zip`.
10+
11+
on:
12+
push:
13+
branches: [ "develop" ]
14+
tags: [ "v*" ]
15+
pull_request:
16+
branches: [ "develop" ]
17+
paths:
18+
- "python-multiplatform-gradle-plugin/**"
19+
- "ksp-fixtures/compose/**"
20+
- "tools/stubs/**"
21+
- "gradle/libs.versions.toml"
22+
- ".github/workflows/stubs.yml"
23+
workflow_dispatch:
24+
25+
permissions:
26+
contents: read
27+
28+
concurrency:
29+
group: stubs-${{ github.ref }}
30+
cancel-in-progress: true
31+
32+
jobs:
33+
stubs:
34+
name: Generate stubs
35+
# ubuntu, not macos: two Kotlin names can differ only by case (`graphics.Shadow` the class module and
36+
# `graphics.shadow` the package), which are two paths here and one on a case-insensitive filesystem.
37+
runs-on: ubuntu-latest
38+
permissions:
39+
contents: write # attaching the stubs to a tag's release
40+
steps:
41+
- name: Checkout
42+
uses: actions/checkout@v4
43+
44+
- name: Setup Java
45+
uses: actions/setup-java@v4
46+
with:
47+
java-version: '21'
48+
distribution: 'zulu'
49+
cache: 'gradle'
50+
51+
- name: Setup Python (for mypy)
52+
uses: actions/setup-python@v5
53+
with:
54+
python-version: '3.13'
55+
56+
- name: Generate the stubs over the Compose jars
57+
run: ./gradlew :ksp-fixtures:compose:generatePythonStubs --console=plain
58+
59+
- name: Type-check a consumer against them (mypy)
60+
run: tools/stubs/check-stubs.sh ksp-fixtures/compose/build/generated/pythonStubs/desktopMain
61+
62+
- name: Assemble the artifact
63+
run: |
64+
set -euo pipefail
65+
compose_version="$(sed -nE 's/^compose-plugin = "([^"]+)".*/\1/p' gradle/libs.versions.toml)"
66+
test -n "$compose_version"
67+
out=build/kotlin-stubs
68+
rm -rf "$out"
69+
mkdir -p "$out"
70+
cp -R ksp-fixtures/compose/build/generated/pythonStubs/desktopMain/. "$out/"
71+
cat > "$out/README.md" <<README
72+
# Kotlin-name stubs for Compose ${compose_version}
73+
74+
\`.pyi\` stubs for the Kotlin declarations the python-multiplatform binder serves, under the
75+
**Kotlin** module paths and Kotlin names (\`androidx.compose.material3.Checkbox\`, parameters by
76+
their Kotlin names). Nothing here is renamed or Pythonic; that is pythonx-compose's layer.
77+
78+
| | |
79+
|---|---|
80+
| Compose | ${compose_version} (desktop jars) |
81+
| python-multiplatform commit | ${GITHUB_SHA} |
82+
| ref | ${GITHUB_REF} |
83+
| workflow run | ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID} |
84+
| generator | \`:ksp-fixtures:compose:generatePythonStubs\` |
85+
86+
Packages stubbed: the \`artifactIncludePackages\` of \`ksp-fixtures/compose/build.gradle.kts\` at
87+
that commit.
88+
89+
Use: put this directory on the type checker's path (\`MYPYPATH=<this directory>\`, or the
90+
IDE's "stub path" / interpreter paths). It is checked with mypy against
91+
\`tools/stubs/consumer.py\` in the commit above.
92+
README
93+
(cd build && zip -qr kotlin-stubs.zip kotlin-stubs)
94+
95+
- name: Upload the stubs
96+
uses: actions/upload-artifact@v4
97+
with:
98+
name: kotlin-stubs
99+
path: build/kotlin-stubs
100+
if-no-files-found: error
101+
102+
- name: Attach the stubs to the tag's release
103+
if: startsWith(github.ref, 'refs/tags/')
104+
env:
105+
GH_TOKEN: ${{ github.token }}
106+
run: |
107+
set -euo pipefail
108+
gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1 \
109+
|| gh release create "$GITHUB_REF_NAME" --title "$GITHUB_REF_NAME" --notes "Kotlin-name stubs for the Compose version this tag builds against."
110+
gh release upload "$GITHUB_REF_NAME" build/kotlin-stubs.zip --clobber

‎AGENTS.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ to implement", say what you counted against.
135135
counts, lifetimes, class loaders) gets the strongest tier; work a test will catch can use a
136136
cheaper one.
137137
- Give every agent prompt the absolute paths it may write to, and repeat rule 2 in it.
138+
- **Subagents do not run heavy local builds.** Subagents write code, design, investigate, review
139+
and document. Gradle builds, cargo builds, the test gate and model runs are done by the session
140+
itself — one at a time on this machine — or by CI (GitHub Actions) on a pushed branch. Several
141+
sessions share one machine; parallel local builds slow every one of them.
138142

139143
---
140144

‎docs/SPEC.md‎

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -221,10 +221,22 @@ All in `PM/commonTest`, so they run wherever the interpreter loads.
221221
`Status: implemented` on desktop — `ksp-fixtures/compose/.../PythonContentRenderTest.kt`; the module
222222
compiles for Android, nothing runs there yet.
223223
- **B-7** The plugin generates `.pyi` stubs for the Kotlin-named modules only, under Kotlin names
224-
(keyword parameters by Kotlin name, `= ...` for a Kotlin default, receiver positional-only). It emits
225-
nothing under `pythonx` and renames nothing; a Pythonic stub product belongs to pythonx-compose.
226-
`Status: partial` — `GP/stubs/PyiRenderingTest.kt`, `GP/stubs/KotlinNamesOnlyStubTest.kt`,
227-
`ksp-fixtures/artifact/.../WalkedArtifactStubTest.kt`; handle-returning stubs are not wrapped.
224+
(keyword parameters by Kotlin name, `= ...` for a Kotlin default, receiver positional-only). Types are
225+
the declared Kotlin types: one stub class per bound type in the module of its own package, `Dp | float`
226+
for a value class bound as its primitive, `Callable[...]` for a function type, `| None` for a nullable;
227+
an extension function is also a callable attribute of its receiver's class; an overload set is
228+
`@overload`ed under its base name in table-key order; a required parameter after a defaulted one is
229+
keyword-only like `inspect.signature`. It emits nothing under `pythonx` and renames nothing; a
230+
Pythonic stub product belongs to pythonx-compose. `Status: partial` — `GP/stubs/PyiRenderingTest.kt`,
231+
`GP/stubs/TypedStubTest.kt`, `GP/stubs/KotlinNamesOnlyStubTest.kt`,
232+
`ksp-fixtures/artifact/.../WalkedArtifactStubTest.kt`, `.../StubSignatureAgreesWithRuntimeTest.kt`,
233+
`tools/stubs/check-stubs.sh` (mypy over the Compose stubs). A class whose name is also a function in
234+
its module is `Any`.
235+
- **B-8** CI generates the stubs over the Compose version the build resolves and publishes them
236+
(`.github/workflows/stubs.yml`): workflow artifact `kotlin-stubs` on every push to `develop`, with a
237+
README naming the Compose version and the commit, and `kotlin-stubs.zip` on every `v*` tag's release.
238+
`Status: partial` — the workflow could not be run where it was written; its YAML parses and its
239+
assemble step was executed locally.
228240

229241
## 7. Threading and builds
230242

‎docs/design/kotlin-extensions-in-python.md‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -598,9 +598,10 @@ Generation belongs to the Gradle plugin (`AGENTS.md` §12.5), and it reads the s
598598
the binder, so the stub and the binding cannot drift. **What it emits is the Kotlin surface, not a
599599
Pythonic one** (SPEC B-7, `docs/design/pyi-generation-design.md`): one `def` per table key under the
600600
Kotlin module path, Kotlin parameter names, `= ...` for a Kotlin default, the extension receiver as a
601-
positional-only first parameter, and boundary-type annotations (`Dp` → `float`, `Modifier` → `int`)
602-
with the Kotlin signature in the docstring. Extension-as-method, `@overload` sets over a base name and
603-
`Dp | float` are what a Pythonic stub would say; that stub belongs to pythonx-compose. The section
601+
positional-only first parameter, the declared Kotlin types as stub classes (`Modifier`, `Dp | float`,
602+
`Callable[...]`), each extension also as a callable attribute of its receiver's class, and `@overload`
603+
sets over a base name in table-key order, with the Kotlin signature in the docstring (issue #31). What
604+
stays out is snake_case, `pythonx` and the Pythonic allowlist narrowing, which belong to pythonx-compose. The section
604605
this replaces — the metaclass stub and the three measured requirements for a Pythonic stub — is in
605606
[`docs/archive/kotlin-extensions-in-python-pyi.md`](../archive/kotlin-extensions-in-python-pyi.md).
606607

0 commit comments

Comments
 (0)