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
2 changes: 1 addition & 1 deletion .github/workflows/test-ios.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ jobs:
run: |
set -euo pipefail
pmd="$GITHUB_WORKSPACE/.ci/python-multiplatform"
slice="$pmd/python-multiplatform/build/python-standalone/extracted/3.14.7/ios/Python.xcframework/ios-arm64_x86_64-simulator"
slice="$pmd/python-multiplatform/build/python-standalone/extracted/3.15.0/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 usage-example/src/iosMain/app.xcodeproj -target iosApp \
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,12 @@ jobs:
- uses: gradle/actions/setup-gradle@v4
- uses: actions/setup-python@v5
with:
python-version: "3.14"
python-version: "3.15"
- uses: astral-sh/setup-uv@v6

# 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
# usage-example/gradle/libs.versions.toml (library 3.14.7-alpha01, plugin 3.13.0 until
# usage-example/gradle/libs.versions.toml (library 3.15.0-alpha01, plugin 3.13.0 until
# python-multiplatform#70).
- name: Publish packpack, the toolchain plugin and python-multiplatform to mavenLocal
run: |
Expand Down Expand Up @@ -178,7 +178,7 @@ jobs:
- uses: gradle/actions/setup-gradle@v4
- uses: actions/setup-python@v5
with:
python-version: "3.14"
python-version: "3.15"
- uses: astral-sh/setup-uv@v6

- name: Publish packpack, the toolchain plugin and python-multiplatform to mavenLocal
Expand Down Expand Up @@ -219,7 +219,7 @@ jobs:
# python-multiplatform ships and unpacks (PythonBootstrap) plus this plugin's assets/python/
# (example_py and iniconfig installed for x86_64-linux-android). The smoke result goes to logcat.
# Green since python-multiplatform#72 (PR #75): the emulator logs PYTHON_SMOKE_OK with the stdlib
# unpacked under /data/data/<app>/files/lib/python3.14/.
# unpacked under /data/data/<app>/files/lib/python3.15/.
android-e2e:
runs-on: ubuntu-latest
timeout-minutes: 75
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ Prerequisites: `org.thisisthepy.python.multiplatform:packpack:0.1.0` in `~/.m2`
`pypackpack` repository), `uv` on `PATH`, and network access: `InstallDependenciesTaskTest` and
`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).
`./gradlew publishAllToMavenLocal` (library `3.15.0-alpha01`, bindings plugin `3.13.0` until its #70).

Run each module separately (rule 8), with output to a file under `.tmp/`:

Expand Down
2 changes: 1 addition & 1 deletion PROJECT.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Kotlin Multiplatform 앱의 Python 부분을 Gradle `python { }` 블록으로
## 현재 상태 (2026-10-03, 코드와 테스트를 읽고 판정)

**구현 (테스트 있음)**
- `compileSdk` 버전 문자열 파싱 (alpha / rc / normal 채널), 그리고 이름 상수 `PY3_14_7`·`PY3_13_0`
- `compileSdk` 버전 문자열 파싱 (alpha / rc / normal 채널), 그리고 이름 상수 `PY3_15_0`·`PY3_14_7`·`PY3_13_0`
(python-multiplatform 이 런타임을 제공하는 버전만). 제공되지 않는 버전 문자열은 `buildPython…` 에서 이유와 함께 실패
- `python { }` 최상위의 `android("android") { }` / `ios { }` / `desktop()` 과 변형 호출
(`androidArm64()` 등) → `pypackpack` 타깃 트리플 매핑, Kotlin 타깃 대조 경고, 최소 SDK 전달.
Expand Down
22 changes: 18 additions & 4 deletions docs/SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,30 @@ form `pypackpack` accepts. A blank value is a silent skip; a malformed one fails
**Status: implemented**. `plugin/dsl/PythonVersion.kt`; `ptest/dsl/PythonVersionTest.kt`.

`compileSdk` takes either form the example build file writes, both with `=` (Gradle's Kotlin
assignment overloading on `PythonSdk`): a string, `compileSdk = "3.14.7"`, or a named constant,
`compileSdk = PY3_14_7`. Constants exist only for versions python-multiplatform provides a runtime
for (its pinned `pythonVersion`, 3.14.7, and the older 3.13.0 archive), so the example's
assignment overloading on `PythonSdk`): a string, `compileSdk = "3.15.0"`, or a named constant,
`compileSdk = PY3_15_0`. Constants exist only for versions python-multiplatform provides a runtime
for (its pinned `pythonVersion`, 3.15.0; 3.14.7 and the older 3.13.0 archive are kept), so the example's
`PY3_11_9_ALPHA` does not compile (INTENT §4.1). A string resolves to a provided version (`X.Y`
takes the newest `X.Y.*`). A string naming a version that is not provided would be built
automatically (INTENT §4.1), which is not available yet: the reason fails `buildPython…` (only
when there is a package to bundle), not the configuration.

A trailing `t` names the free-threaded build (`compileSdk = "3.15t"`, `"3.15.0t"`); it resolves
against the same published list (python-multiplatform ships both builds of a pinned version) and
there is no free-threaded constant. The `t` never reaches `toReleaseString()`, so the agreement
check with python-multiplatform's `pythonVersion` (§1.12) compares `X.Y.Z` only. **Not checked:**
whether python-multiplatform was built free-threaded (its `freeThreaded` flag is a separate
attribute that toolchain does not read).

The resolved version is also forwarded to every bundle as pypackpack's
`BundleRequest.pythonVersion` (`3.15.0`, or `3.15.0t`; absent without `compileSdk`), which fixes the
wheel tags (`cp315-cp315`, `cp315-cp315t`). **Status: implemented**.
`ptest/bundle/BuildPythonArtifactTaskTest.kt`, `ptest/dsl/PythonSdkTest.kt`,
`ptest/dsl/PythonVersionTest.kt`. Needs a `packpack` in `~/.m2` built from pypackpack#77 or later
(the same `0.1.0` coordinates; CI publishes it from pypackpack `develop`).

**Status: implemented**. `plugin/dsl/PythonSdk.kt`; `ptest/dsl/PythonSdkTest.kt`,
`ptest/bundle/BuildPythonSdkRejectionTest.kt`; `usage-example` uses `compileSdk = PY3_14_7`.
`ptest/bundle/BuildPythonSdkRejectionTest.kt`; `usage-example` uses `compileSdk = PY3_15_0`.

**What `compileSdk` selects (#42).** `compileSdk` selects the Python version of the wheels that
go into `python/`: it is the `--python-version` of every per-target install (§1.10, #16). It does
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/guide-variants.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ <h2><span data-lang="en">Build types</span><span data-lang="ko">빌드 타입</s
}</code></pre>
<p data-lang="en">Only <code>debug</code> and <code>release</code> exist. <code>useCodeMinifier</code>, <code>excludeMetaclass</code> and <code>enableCodePush</code> are release-only: setting them on <code>debug</code> throws.</p><p data-lang="ko"><code>debug</code> 와 <code>release</code> 만 있습니다. <code>useCodeMinifier</code>, <code>excludeMetaclass</code>, <code>enableCodePush</code> 는 release 전용이며 <code>debug</code> 에 설정하면 예외가 납니다.</p>
<div class="table-wrap"><table><thead><tr><th><span data-lang="en">compileLevel</span><span data-lang="ko">compileLevel</span></th><th><span data-lang="en">Meaning</span><span data-lang="ko">의미</span></th><th><span data-lang="en">Today</span><span data-lang="ko">지금</span></th></tr></thead><tbody><tr><td><code>instant</code> <span data-lang="en">(or blank)</span><span data-lang="ko">(또는 빈 값)</span></td><td><span data-lang="en">.py only</span><span data-lang="ko">.py 만</span></td><td><span data-lang="en" class="pill ok">implemented</span><span data-lang="ko" class="pill ok">구현</span></td></tr><tr><td><code>bytecode</code></td><td><span data-lang="en">debug: .py + .pyc; release: .pyc only</span><span data-lang="ko">debug: .py + .pyc, release: .pyc 만</span></td><td><span data-lang="en" class="pill ok">implemented</span><span data-lang="ko" class="pill ok">구현</span></td></tr><tr><td><code>native</code></td><td><span data-lang="en">native code only</span><span data-lang="ko">네이티브 코드만</span></td><td><span data-lang="en" class="pill plan">planned (pypackpack#19)</span><span data-lang="ko" class="pill plan">계획 (pypackpack#19)</span></td></tr><tr><td><code>mixed</code></td><td><span data-lang="en">your code as bytecode, libraries native</span><span data-lang="ko">내 코드는 바이트코드, 라이브러리는 네이티브</span></td><td><span data-lang="en" class="pill plan">planned (pypackpack#19)</span><span data-lang="ko" class="pill plan">계획 (pypackpack#19)</span></td></tr></tbody></table></div>
<div class="note"><p data-lang="en"><code>bytecode</code> compiles with the Python in a <code>.venv</code> at or above your package directory. <code>installPythonDependencies</code> leaves one there (<code>uv add</code> creates <code>&lt;package&gt;/.venv</code>) once you declare a dependency; otherwise run <code>uv venv --python 3.14</code> in the package directory. Its minor version must match <code>compileSdk</code>, because a <code>.pyc</code> only loads on the CPython minor version that wrote it; a mismatch fails the variant. Without a declared <code>compileSdk</code>, or a <code>pyvenv.cfg</code> to read, the versions are not compared.</p><p data-lang="ko"><code>bytecode</code> 는 패키지 디렉터리나 그 위의 <code>.venv</code> 에 있는 Python 으로 컴파일합니다. 의존성을 하나라도 선언하면 <code>installPythonDependencies</code> 가 그것을 만들어 둡니다(<code>uv add</code> 가 <code>&lt;package&gt;/.venv</code> 를 만듭니다). 그렇지 않으면 패키지 디렉터리에서 <code>uv venv --python 3.14</code> 를 실행하세요. 그 마이너 버전은 <code>compileSdk</code> 와 같아야 합니다. <code>.pyc</code> 는 그것을 쓴 CPython 마이너 버전에서만 로드되기 때문이며, 다르면 그 변형이 실패합니다. <code>compileSdk</code> 를 선언하지 않았거나 읽을 <code>pyvenv.cfg</code> 가 없으면 버전을 비교하지 않습니다.</p></div>
<div class="note"><p data-lang="en"><code>bytecode</code> compiles with the Python in a <code>.venv</code> at or above your package directory. <code>installPythonDependencies</code> leaves one there (<code>uv add</code> creates <code>&lt;package&gt;/.venv</code>) once you declare a dependency; otherwise run <code>uv venv --python 3.15</code> in the package directory. Its minor version must match <code>compileSdk</code>, because a <code>.pyc</code> only loads on the CPython minor version that wrote it; a mismatch fails the variant. Without a declared <code>compileSdk</code>, or a <code>pyvenv.cfg</code> to read, the versions are not compared.</p><p data-lang="ko"><code>bytecode</code> 는 패키지 디렉터리나 그 위의 <code>.venv</code> 에 있는 Python 으로 컴파일합니다. 의존성을 하나라도 선언하면 <code>installPythonDependencies</code> 가 그것을 만들어 둡니다(<code>uv add</code> 가 <code>&lt;package&gt;/.venv</code> 를 만듭니다). 그렇지 않으면 패키지 디렉터리에서 <code>uv venv --python 3.15</code> 를 실행하세요. 그 마이너 버전은 <code>compileSdk</code> 와 같아야 합니다. <code>.pyc</code> 는 그것을 쓴 CPython 마이너 버전에서만 로드되기 때문이며, 다르면 그 변형이 실패합니다. <code>compileSdk</code> 를 선언하지 않았거나 읽을 <code>pyvenv.cfg</code> 가 없으면 버전을 비교하지 않습니다.</p></div>
<p data-lang="en">A planned level fails only its own variant's <code>buildPython…</code> task, so <code>./gradlew packagePython --continue</code> still builds every other variant.</p><p data-lang="ko">계획 단계의 레벨은 그 변형의 <code>buildPython…</code> 태스크만 실패시키므로, <code>./gradlew packagePython --continue</code> 는 나머지 변형을 모두 빌드합니다.</p>
</section>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,11 @@ open class BuildPythonArtifactTask : DefaultTask() {
?.let { throw GradleException(it) }
}

val result = bundleWithPackpack(source, target, buildType, bundleDir, resolvedBuildLevel, minSdk, metaDirs, libDirs, versionName, versionCode)
val result =
bundleWithPackpack(
source, target, buildType, bundleDir, resolvedBuildLevel, minSdk, metaDirs, libDirs,
versionName, versionCode, compileSdkVersion?.let { wheelVersionOf(it) },
)
logger.lifecycle(
"Bundled ${result.fileCount} file(s) for Python '$pythonVersion' via packpack's " +
"'${result.bundleType.id}' bundler into ${result.outputDir} " +
Expand All @@ -202,6 +206,35 @@ open class BuildPythonArtifactTask : DefaultTask() {
}
}

/** The [BundleRequest] [bundleWithPackpack] hands pypackpack; separate so a test can read what is forwarded. */
fun bundleRequestFor(
packageDir: File,
target: String,
buildType: String,
outputDir: File,
buildLevel: String = "instant",
minSdk: Int? = null,
metaDirs: List<File> = emptyList(),
libDirs: List<File> = emptyList(),
versionName: String? = null,
versionCode: Int? = null,
pythonVersion: String? = null,
): BundleRequest =
BundleRequest(
packageDir = packageDir,
target = target,
buildType = buildType,
buildLevel = buildLevel,
outputDir = outputDir,
overwrite = true,
minSdk = minSdk,
metaDirs = metaDirs,
libDirs = libDirs,
versionName = versionName,
versionCode = versionCode,
pythonVersion = pythonVersion,
)

/**
* The actual delegation to `pypackpack`, factored out of [BuildPythonArtifactTask.buildPython] so
* it can be exercised without a Gradle [org.gradle.api.Project] -- see
Expand All @@ -217,6 +250,9 @@ open class BuildPythonArtifactTask : DefaultTask() {
* caller that declares no `python { sourceSets { commonMain { metaDirs(...) } } }`.
* @param libDirs forwarded to [BundleRequest.libDirs] verbatim. Empty by default, matching every
* caller that declares no `python { sourceSets { commonMain { libDirs(...) } } }`.
* @param pythonVersion forwarded to [BundleRequest.pythonVersion] verbatim (`3.15.0`, `3.15.0t`): the
* app's interpreter, which fixes the wheel tags of the wheel bundlers. `null` (no `compileSdk`)
* leaves pypackpack's own default.
*/
fun bundleWithPackpack(
packageDir: File,
Expand All @@ -229,20 +265,12 @@ fun bundleWithPackpack(
libDirs: List<File> = emptyList(),
versionName: String? = null,
versionCode: Int? = null,
pythonVersion: String? = null,
): BundleResult {
val request =
BundleRequest(
packageDir = packageDir,
target = target,
buildType = buildType,
buildLevel = buildLevel,
outputDir = outputDir,
overwrite = true,
minSdk = minSdk,
metaDirs = metaDirs,
libDirs = libDirs,
versionName = versionName,
versionCode = versionCode,
bundleRequestFor(
packageDir, target, buildType, outputDir, buildLevel, minSdk, metaDirs, libDirs,
versionName, versionCode, pythonVersion,
)
return BundlerInterface.create(BundleType.RESOURCE)
.bundle(request)
Expand Down Expand Up @@ -330,3 +358,6 @@ fun bytecodeInterpreterRejection(
"Recreate that .venv with Python ${compileSdk.major}.${compileSdk.minor} " +
"(`uv venv --python ${compileSdk.major}.${compileSdk.minor}`)."
}

/** [PythonVersion.toWheelVersion] of a resolved `compileSdk` label (`PythonVersion.toString()`). */
fun wheelVersionOf(compileSdkLabel: String): String = PythonVersion.parse(compileSdkLabel).toWheelVersion()
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import javax.inject.Inject


open class PythonExtension @Inject constructor(objects: ObjectFactory) {
/** `compileSdk = "3.14.7"` or `compileSdk = PY3_14_7`; see [PythonSdk]. */
/** `compileSdk = "3.15.0"` or `compileSdk = PY3_15_0` (`"3.15t"` for the free-threaded build); see [PythonSdk]. */
val compileSdk: PythonSdk = PythonSdk()
val PY3_15_0: PythonSdkVersion = PythonSdkVersion.PY3_15_0
val PY3_14_7: PythonSdkVersion = PythonSdkVersion.PY3_14_7
val PY3_13_0: PythonSdkVersion = PythonSdkVersion.PY3_13_0
var localLibraryPath: String? = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@ import org.gradle.api.SupportsKotlinAssignmentOverloading

/**
* The CPython versions python-multiplatform actually provides a runtime for: its
* `gradle.properties` `pythonVersion` (3.14.7 on 2026-10-03), acquired as described in its
* `gradle.properties` `pythonVersion` (3.15.0 since python-multiplatform#197; 3.14.7 is kept), acquired as described in its
* `docs/platforms/python-version-acquisition.md` (python-build-standalone for desktop, python.org
* for Android, BeeWare's Python-Apple-support for iOS), plus 3.13.0, the older archive on its
* `release` branch's `binary/` that pypackpack's `DefaultBackend.installPython` still downloads.
* Add a version here and a constant to [PythonSdkVersion] when python-multiplatform pins one.
*/
object PythonReleaseServer {
val PUBLISHED: List<String> = listOf("3.14.7", "3.13.0")
val PUBLISHED: List<String> = listOf("3.15.0", "3.14.7", "3.13.0")
}

/** `compileSdk = PY3_14_7`: a version python-multiplatform provides. Only those exist as constants. */
enum class PythonSdkVersion(val version: String) {
PY3_15_0("3.15.0"),
PY3_14_7("3.14.7"),
PY3_13_0("3.13.0"),
}
Expand Down Expand Up @@ -76,7 +77,7 @@ fun resolvePythonSdk(sdk: PythonSdk): ResolvedPythonSdk? {
.maxByOrNull { it.micro }

return if (published != null) {
ResolvedPythonSdk(version = published, fromConstant = sdk.constant != null, rejection = null)
ResolvedPythonSdk(version = published.copy(freeThreaded = parsed.freeThreaded), fromConstant = sdk.constant != null, rejection = null)
} else {
ResolvedPythonSdk(
version = parsed,
Expand Down
Loading
Loading