From a519485c4f7bf74164fbbb41b62c4661cadcaa53 Mon Sep 17 00:00:00 2001 From: BREW <81044362+b-re-w@users.noreply.github.com> Date: Sat, 3 Oct 2026 19:43:20 +0900 Subject: [PATCH 01/12] Refactor: Restructure the repository root User directive, via the lead (#62). The root now holds only the modules, docs/, gradle/, .github/ and the files tools need there: - toolchain/ -> toolchain-gradle-plugin/ (:toolchain-gradle-plugin; plugin id unchanged) - tcl/ -> toolchain-cli/ (:toolchain-cli) - usage-example/ -> sample/, its Python package under sample/src/commonMain/python/; still its own build (python-multiplatform's Kotlin), now run with the root wrapper (./gradlew -p sample), so its copied wrapper is gone and the root wrapper moves to Gradle 8.11.1 (AGP 8.10 needs it) - tools/release/ -> .github/scripts/release/ - tools/xcode/stage-python-payload.sh deleted: an iOS app without python-multiplatform has no runtime to feed; the guide keeps the safe hand-wiring snippet - kotlin-js-store/ deleted (the root build has no JS/wasm target since #52); the sample's wasm yarn lock is sample/gradle/wasm-yarn.lock via WasmYarnRootExtension's lockFile*Property - .fleet/ and the never-built pyproject.toml deleted Workflows, Xcode phases, README (en/ko, with the new artifactIds), guide, SPEC, PROJECT.md and AGENTS.md follow; AGENTS.md also gains "Do not add top-level folders" (python-multiplatform #116). The old layout is the tag archive/pre-restructure. Closes #62 --- .fleet/receipt.json | 29 - {tools => .github/scripts}/release/README.md | 14 +- .../scripts}/release/protect-main.sh | 0 .../scripts}/release/sync-release.sh | 0 .../scripts}/release/test-sync-release.sh | 0 .github/workflows/ios-e2e.yml | 17 +- .github/workflows/release-sync.yml | 4 +- .github/workflows/test.yml | 36 +- .gitignore | 3 + AGENTS.md | 46 +- PROJECT.md | 26 +- README.md | 21 +- docs/INTENT.md | 2 +- docs/SPEC.md | 43 +- docs/guide/getting-started.html | 15 +- docs/guide/guide-staging.html | 4 +- docs/guide/guide-tcl.html | 2 +- docs/guide/status.html | 4 +- docs/locale/README_ko.md | 21 +- gradle/wrapper/gradle-wrapper.properties | 2 +- kotlin-js-store/yarn.lock | 2886 ----------------- pyproject.toml | 65 - {usage-example => sample}/build.gradle.kts | 19 +- {usage-example => sample}/gradle.properties | 0 .../gradle/libs.versions.toml | 6 +- {usage-example => sample}/settings.gradle.kts | 9 +- .../src/androidMain/AndroidManifest.xml | 0 .../toolchain/example/MainActivity.kt | 0 .../toolchain/example/Platform.android.kt | 0 .../toolchain/example/PythonSmoke.android.kt | 0 .../drawable-v24/ic_launcher_foreground.xml | 0 .../res/drawable/ic_launcher_background.xml | 0 .../res/mipmap-anydpi-v26/ic_launcher.xml | 0 .../mipmap-anydpi-v26/ic_launcher_round.xml | 0 .../res/mipmap-hdpi/ic_launcher.png | Bin .../res/mipmap-hdpi/ic_launcher_round.png | Bin .../res/mipmap-mdpi/ic_launcher.png | Bin .../res/mipmap-mdpi/ic_launcher_round.png | Bin .../res/mipmap-xhdpi/ic_launcher.png | Bin .../res/mipmap-xhdpi/ic_launcher_round.png | Bin .../res/mipmap-xxhdpi/ic_launcher.png | Bin .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin .../res/mipmap-xxxhdpi/ic_launcher.png | Bin .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin .../src/androidMain/res/values/strings.xml | 0 .../multiplatform/toolchain/example/App.kt | 0 .../toolchain/example/Greeting.kt | 0 .../toolchain/example/Platform.kt | 0 sample/src/commonMain/python/pyproject.toml | 5 + .../python/src/main/example_py/__init__.py | 2 +- .../src/main/example_py/data/table.json | 0 .../toolchain/example/Platform.jvm.kt | 0 .../toolchain/example/PythonSmoke.kt | 0 .../multiplatform/toolchain/example/main.kt | 0 .../src/iosMain/Configuration/Config.xcconfig | 0 .../src/iosMain/app.xcodeproj/project.pbxproj | 2 +- .../src/iosMain/install-python-phase.sh | 10 +- .../toolchain/example/MainViewController.kt | 0 .../toolchain/example/Platform.ios.kt | 0 .../toolchain/example/PythonSmoke.ios.kt | 0 .../AccentColor.colorset/Contents.json | 0 .../AppIcon.appiconset/Contents.json | 0 .../AppIcon.appiconset/app-icon-1024.png | Bin .../swift/Assets.xcassets/Contents.json | 0 .../src/iosMain/swift/ContentView.swift | 0 .../src/iosMain/swift/Info.plist | 0 .../Preview Assets.xcassets/Contents.json | 0 .../src/iosMain/swift/app.swift | 0 .../toolchain/example/Platform.wasmJs.kt | 0 .../multiplatform/toolchain/example/main.kt | 0 .../src/wasmJsMain/resources/index.html | 0 .../src/wasmJsMain/resources/styles.css | 0 settings.gradle.kts | 8 +- {tcl => toolchain-cli}/build.gradle.kts | 4 +- .../python/multiplatform/tcl/Cli.kt | 0 .../python/multiplatform/tcl/Installer.kt | 2 +- .../python/multiplatform/tcl/CliArgsTest.kt | 0 .../python/multiplatform/tcl/InstallerTest.kt | 4 +- .../build.gradle.kts | 6 +- .../multiplatform/toolchain/BuildFeatures.kt | 0 .../multiplatform/toolchain/EmbedLevel.kt | 0 .../toolchain/PublishedPythonVersion.kt | 0 .../toolchain/PythonMultiplatformPlugin.kt | 0 .../multiplatform/toolchain/PythonPlugin.kt | 18 +- .../toolchain/PythonVersionAgreement.kt | 0 .../toolchain/TargetDependencies.kt | 0 .../bundle/AssemblePythonPackageTask.kt | 2 +- .../bundle/BuildPythonArtifactTask.kt | 2 +- .../toolchain/bundle/PythonLocalLoader.kt | 0 .../StagePythonBundleIosForXcodeTask.kt | 0 .../toolchain/bundle/StagePythonBundleTask.kt | 0 .../toolchain/dependency/DependencyType.kt | 0 .../lang/kotlin/decompileKotlinMeta.kt | 0 .../lang/python/InstallDependenciesTask.kt | 4 +- .../python/InstallTargetDependenciesTask.kt | 0 .../dependency/lang/python/KlibDepens.kt | 0 .../dependency/lang/python/PipRepositories.kt | 0 .../toolchain/dependency/reslover.kt | 0 .../multiplatform/toolchain/dsl/DSLBuild.kt | 0 .../multiplatform/toolchain/dsl/DSLCore.kt | 0 .../multiplatform/toolchain/dsl/DSLFlavors.kt | 0 .../toolchain/dsl/DSLPackaging.kt | 0 .../toolchain/dsl/DSLPlatforms.kt | 0 .../multiplatform/toolchain/dsl/PythonSdk.kt | 0 .../toolchain/dsl/PythonVersion.kt | 0 .../hotreload/CodePushPendingTask.kt | 0 .../toolchain/hotreload/HotReloadConfig.kt | 0 .../toolchain/hotreload/HotReloadPushTask.kt | 0 .../toolchain/hotreload/WatchAndPush.kt | 0 .../typedpython/TypedpythonCheckTask.kt | 0 .../toolchain/BuildFeaturesTest.kt | 0 .../multiplatform/toolchain/EmbedLevelTest.kt | 0 .../toolchain/PublishedPythonVersionTest.kt | 0 .../toolchain/PythonPluginApplyTest.kt | 2 +- .../toolchain/PythonPluginAttachmentTest.kt | 2 +- .../PythonPluginBuildFeaturesTest.kt | 0 .../toolchain/PythonPluginBuildLevelTest.kt | 2 +- .../toolchain/PythonPluginBuildTypeTest.kt | 0 .../toolchain/PythonPluginCodePushTest.kt | 0 .../toolchain/PythonPluginDependencyTest.kt | 0 .../toolchain/PythonPluginEmbedLevelTest.kt | 0 .../toolchain/PythonPluginFlavorsTest.kt | 0 .../toolchain/PythonPluginHotReloadTest.kt | 0 .../PythonPluginIntegrationListTest.kt | 0 .../PythonPluginPayloadVersionTest.kt | 0 .../toolchain/PythonPluginPlatformsTest.kt | 0 .../toolchain/PythonPluginPythonOnlyTest.kt | 0 .../toolchain/PythonPluginSourceSetTest.kt | 2 +- .../toolchain/PythonPluginStagingTest.kt | 0 .../PythonPluginTargetDependenciesTest.kt | 0 .../toolchain/PythonPluginVariantGraphTest.kt | 2 +- .../toolchain/PythonVersionAgreementTest.kt | 0 .../toolchain/SourceSetGettingDelegateTest.kt | 0 .../toolchain/TargetDependenciesTest.kt | 0 .../bundle/AssemblePythonPackageTaskTest.kt | 0 .../bundle/BuildPythonArtifactTaskTest.kt | 0 .../bundle/BuildPythonSdkRejectionTest.kt | 0 .../toolchain/bundle/BytecodeLevelTest.kt | 0 .../StagePythonBundleIosForXcodeTaskTest.kt | 0 .../bundle/StagePythonBundleTaskTest.kt | 2 +- .../python/InstallDependenciesTaskTest.kt | 0 .../InstallTargetDependenciesTaskTest.kt | 0 .../dependency/lang/python/KlibDepensTest.kt | 0 .../lang/python/PipRepositoriesTest.kt | 0 .../dsl/PlatformTargetMappingTest.kt | 0 .../dsl/PythonExtensionPlatformDslTest.kt | 0 .../toolchain/dsl/PythonSdkTest.kt | 0 .../toolchain/dsl/PythonVersionTest.kt | 0 .../toolchain/hotreload/WatchAndPushTest.kt | 0 .../typedpython/TypedpythonCheckTaskTest.kt | 8 +- .../typedpython/TypedpythonDecisionsTest.kt | 0 .../typedpython/TypedpythonNoWheelTest.kt | 0 .../bundle/StagePythonPayloadScriptTest.kt | 82 - tools/xcode/stage-python-payload.sh | 14 - .../gradle/wrapper/gradle-wrapper.jar | Bin 43504 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 - usage-example/gradlew | 252 -- usage-example/gradlew.bat | 94 - usage-example/python/pyproject.toml | 5 - 159 files changed, 210 insertions(+), 3607 deletions(-) delete mode 100644 .fleet/receipt.json rename {tools => .github/scripts}/release/README.md (77%) rename {tools => .github/scripts}/release/protect-main.sh (100%) rename {tools => .github/scripts}/release/sync-release.sh (100%) rename {tools => .github/scripts}/release/test-sync-release.sh (100%) delete mode 100644 kotlin-js-store/yarn.lock delete mode 100644 pyproject.toml rename {usage-example => sample}/build.gradle.kts (88%) rename {usage-example => sample}/gradle.properties (100%) rename {usage-example => sample}/gradle/libs.versions.toml (85%) rename {usage-example => sample}/settings.gradle.kts (67%) rename {usage-example => sample}/src/androidMain/AndroidManifest.xml (100%) rename {usage-example => sample}/src/androidMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/MainActivity.kt (100%) rename {usage-example => sample}/src/androidMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/Platform.android.kt (100%) rename {usage-example => sample}/src/androidMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/PythonSmoke.android.kt (100%) rename {usage-example => sample}/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml (100%) rename {usage-example => sample}/src/androidMain/res/drawable/ic_launcher_background.xml (100%) rename {usage-example => sample}/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml (100%) rename {usage-example => sample}/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml (100%) rename {usage-example => sample}/src/androidMain/res/mipmap-hdpi/ic_launcher.png (100%) rename {usage-example => sample}/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png (100%) rename {usage-example => sample}/src/androidMain/res/mipmap-mdpi/ic_launcher.png (100%) rename {usage-example => sample}/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png (100%) rename {usage-example => sample}/src/androidMain/res/mipmap-xhdpi/ic_launcher.png (100%) rename {usage-example => sample}/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png (100%) rename {usage-example => sample}/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png (100%) rename {usage-example => sample}/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png (100%) rename {usage-example => sample}/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename {usage-example => sample}/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png (100%) rename {usage-example => sample}/src/androidMain/res/values/strings.xml (100%) rename {usage-example => sample}/src/commonMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/App.kt (100%) rename {usage-example => sample}/src/commonMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/Greeting.kt (100%) rename {usage-example => sample}/src/commonMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/Platform.kt (100%) create mode 100644 sample/src/commonMain/python/pyproject.toml rename {usage-example => sample/src/commonMain}/python/src/main/example_py/__init__.py (80%) rename {usage-example => sample/src/commonMain}/python/src/main/example_py/data/table.json (100%) rename {usage-example => sample}/src/desktopMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/Platform.jvm.kt (100%) rename {usage-example => sample}/src/desktopMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/PythonSmoke.kt (100%) rename {usage-example => sample}/src/desktopMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/main.kt (100%) rename {usage-example => sample}/src/iosMain/Configuration/Config.xcconfig (100%) rename {usage-example => sample}/src/iosMain/app.xcodeproj/project.pbxproj (99%) rename {usage-example => sample}/src/iosMain/install-python-phase.sh (89%) rename {usage-example => sample}/src/iosMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/MainViewController.kt (100%) rename {usage-example => sample}/src/iosMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/Platform.ios.kt (100%) rename {usage-example => sample}/src/iosMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/PythonSmoke.ios.kt (100%) rename {usage-example => sample}/src/iosMain/swift/Assets.xcassets/AccentColor.colorset/Contents.json (100%) rename {usage-example => sample}/src/iosMain/swift/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename {usage-example => sample}/src/iosMain/swift/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png (100%) rename {usage-example => sample}/src/iosMain/swift/Assets.xcassets/Contents.json (100%) rename {usage-example => sample}/src/iosMain/swift/ContentView.swift (100%) rename {usage-example => sample}/src/iosMain/swift/Info.plist (100%) rename {usage-example => sample}/src/iosMain/swift/Preview Content/Preview Assets.xcassets/Contents.json (100%) rename {usage-example => sample}/src/iosMain/swift/app.swift (100%) rename {usage-example => sample}/src/wasmJsMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/Platform.wasmJs.kt (100%) rename {usage-example => sample}/src/wasmJsMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/main.kt (100%) rename {usage-example => sample}/src/wasmJsMain/resources/index.html (100%) rename {usage-example => sample}/src/wasmJsMain/resources/styles.css (100%) rename {tcl => toolchain-cli}/build.gradle.kts (91%) rename {tcl => toolchain-cli}/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Cli.kt (100%) rename {tcl => toolchain-cli}/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Installer.kt (97%) rename {tcl => toolchain-cli}/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/CliArgsTest.kt (100%) rename {tcl => toolchain-cli}/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/InstallerTest.kt (93%) rename {toolchain => toolchain-gradle-plugin}/build.gradle.kts (95%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/BuildFeatures.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/EmbedLevel.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PublishedPythonVersion.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonMultiplatformPlugin.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPlugin.kt (98%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonVersionAgreement.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/TargetDependencies.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/AssemblePythonPackageTask.kt (97%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/BuildPythonArtifactTask.kt (99%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/PythonLocalLoader.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/StagePythonBundleIosForXcodeTask.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/StagePythonBundleTask.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/DependencyType.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/kotlin/decompileKotlinMeta.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/InstallDependenciesTask.kt (97%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/InstallTargetDependenciesTask.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/KlibDepens.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/PipRepositories.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/reslover.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/DSLBuild.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/DSLCore.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/DSLFlavors.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/DSLPackaging.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/DSLPlatforms.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/PythonSdk.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/PythonVersion.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/hotreload/CodePushPendingTask.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/hotreload/HotReloadConfig.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/hotreload/HotReloadPushTask.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/hotreload/WatchAndPush.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/typedpython/TypedpythonCheckTask.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/BuildFeaturesTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/EmbedLevelTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PublishedPythonVersionTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginApplyTest.kt (99%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginAttachmentTest.kt (96%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginBuildFeaturesTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginBuildLevelTest.kt (97%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginBuildTypeTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginCodePushTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginDependencyTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginEmbedLevelTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginFlavorsTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginHotReloadTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginIntegrationListTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginPayloadVersionTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginPlatformsTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginPythonOnlyTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginSourceSetTest.kt (98%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginStagingTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginTargetDependenciesTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginVariantGraphTest.kt (99%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonVersionAgreementTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/SourceSetGettingDelegateTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/TargetDependenciesTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/AssemblePythonPackageTaskTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/BuildPythonArtifactTaskTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/BuildPythonSdkRejectionTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/BytecodeLevelTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/StagePythonBundleIosForXcodeTaskTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/StagePythonBundleTaskTest.kt (99%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/InstallDependenciesTaskTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/InstallTargetDependenciesTaskTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/KlibDepensTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/PipRepositoriesTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/PlatformTargetMappingTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/PythonExtensionPlatformDslTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/PythonSdkTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/PythonVersionTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/hotreload/WatchAndPushTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/typedpython/TypedpythonCheckTaskTest.kt (96%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/typedpython/TypedpythonDecisionsTest.kt (100%) rename {toolchain => toolchain-gradle-plugin}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/typedpython/TypedpythonNoWheelTest.kt (100%) delete mode 100644 toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/StagePythonPayloadScriptTest.kt delete mode 100755 tools/xcode/stage-python-payload.sh delete mode 100644 usage-example/gradle/wrapper/gradle-wrapper.jar delete mode 100644 usage-example/gradle/wrapper/gradle-wrapper.properties delete mode 100755 usage-example/gradlew delete mode 100644 usage-example/gradlew.bat delete mode 100644 usage-example/python/pyproject.toml diff --git a/.fleet/receipt.json b/.fleet/receipt.json deleted file mode 100644 index 68e96b8..0000000 --- a/.fleet/receipt.json +++ /dev/null @@ -1,29 +0,0 @@ -// Project generated by Kotlin Multiplatform Wizard -{ - "spec": { - "template_id": "kmt", - "targets": { - "android": { - "ui": [ - "compose" - ] - }, - "ios": { - "ui": [ - "compose" - ] - }, - "desktop": { - "ui": [ - "compose" - ] - }, - "web": { - "ui": [ - "compose" - ] - } - } - }, - "timestamp": "2025-02-08T16:29:50.359091746Z" -} \ No newline at end of file diff --git a/tools/release/README.md b/.github/scripts/release/README.md similarity index 77% rename from tools/release/README.md rename to .github/scripts/release/README.md index ed135b6..9b72a96 100644 --- a/tools/release/README.md +++ b/.github/scripts/release/README.md @@ -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 `) 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. @@ -33,18 +33,18 @@ 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. ## Protecting main - tools/release/protect-main.sh # print what would be applied - tools/release/protect-main.sh --apply # apply via gh api (needs admin) + .github/scripts/release/protect-main.sh # print what would be applied + .github/scripts/release/protect-main.sh --apply # apply via gh api (needs admin) This requires a pull request, blocks force-push and deletion. Classic protection cannot restrict a PR's source branch, so add the check `only-release-into-main` as required diff --git a/tools/release/protect-main.sh b/.github/scripts/release/protect-main.sh similarity index 100% rename from tools/release/protect-main.sh rename to .github/scripts/release/protect-main.sh diff --git a/tools/release/sync-release.sh b/.github/scripts/release/sync-release.sh similarity index 100% rename from tools/release/sync-release.sh rename to .github/scripts/release/sync-release.sh diff --git a/tools/release/test-sync-release.sh b/.github/scripts/release/test-sync-release.sh similarity index 100% rename from tools/release/test-sync-release.sh rename to .github/scripts/release/test-sync-release.sh diff --git a/.github/workflows/ios-e2e.yml b/.github/workflows/ios-e2e.yml index 4414f24..379876f 100644 --- a/.github/workflows/ios-e2e.yml +++ b/.github/workflows/ios-e2e.yml @@ -1,6 +1,6 @@ name: iOS e2e -# toolchain#22, iOS: usage-example's app on an arm64 iOS simulator runs Python with the stdlib and +# toolchain#22, iOS: sample'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. @@ -9,11 +9,10 @@ on: branches: [develop] pull_request: paths: - - "usage-example/src/iosMain/**" - - "usage-example/build.gradle.kts" - - "usage-example/gradle/**" - - "tools/xcode/**" - - "toolchain/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/**" + - "sample/src/iosMain/**" + - "sample/build.gradle.kts" + - "sample/gradle/**" + - "toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/**" - ".github/workflows/ios-e2e.yml" workflow_dispatch: @@ -49,12 +48,12 @@ jobs: - uses: actions/cache@v4 with: path: ~/.konan - key: konan-${{ runner.os }}-${{ hashFiles('usage-example/gradle/libs.versions.toml') }} + key: konan-${{ runner.os }}-${{ hashFiles('sample/gradle/libs.versions.toml') }} - name: Publish packpack, the toolchain plugin and python-multiplatform (iOS simulator) run: | (cd .ci/pypackpack && ./gradlew :packpack:publishToMavenLocal --console=plain) - ./gradlew :toolchain:publishToMavenLocal --console=plain + ./gradlew :toolchain-gradle-plugin:publishToMavenLocal --console=plain (cd .ci/python-multiplatform && ./gradlew \ :python-multiplatform:publishIosSimulatorArm64PublicationToMavenLocal \ :python-multiplatform:publishKotlinMultiplatformPublicationToMavenLocal \ @@ -69,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 usage-example/src/iosMain/app.xcodeproj -target iosApp \ + xcodebuild -project sample/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" \ diff --git a/.github/workflows/release-sync.yml b/.github/workflows/release-sync.yml index d411bb2..cf35f65 100644 --- a/.github/workflows/release-sync.yml +++ b/.github/workflows/release-sync.yml @@ -30,7 +30,7 @@ jobs: git config user.email "41898282+github-actions[bot]@users.noreply.github.com" # Not used as a base (release is regenerated); the script reads the # remote sha itself for --force-with-lease. - bash tools/release/sync-release.sh --source "${GITHUB_SHA}" --push + bash .github/scripts/release/sync-release.sh --source "${GITHUB_SHA}" --push - name: Open or update PR release -> main env: @@ -44,7 +44,7 @@ jobs: echo "release already equals main; nothing to PR"; exit 0 fi title="Release: merge release into main" - body="Automated sync of \`develop\` (${GITHUB_SHA::7}) into \`release\`. Markdown outside README.md and docs/ subfolders is removed by tools/release/sync-release.sh." + body="Automated sync of \`develop\` (${GITHUB_SHA::7}) into \`release\`. Markdown outside README.md and docs/ subfolders is removed by .github/scripts/release/sync-release.sh." existing="$(gh pr list --head release --base main --state open --json number --jq '.[0].number // empty')" if [ -n "$existing" ]; then gh pr edit "$existing" --title "$title" --body "$body" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e339413..24eb91d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,13 +1,13 @@ name: Test -# Every pull request and every push to develop runs the plugin's and tcl's tests, +# Every pull request and every push to develop runs the plugin's and the CLI's tests, # each in its own job (AGENTS.md section 8). Results are counted from the JUnit # XML, not from the log. # # 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. -# usage-example is its own build (toolchain#22, python-multiplatform's Kotlin), run -# by the consumer and desktop-e2e jobs with its own wrapper. +# 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). on: pull_request: push: @@ -28,7 +28,7 @@ jobs: strategy: fail-fast: false matrix: - module: [toolchain, tcl] + module: [toolchain-gradle-plugin, toolchain-cli] steps: - uses: actions/checkout@v4 @@ -79,7 +79,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: usage-example (its own build) with a real pip dependency installs, bundles, zips. + # a real consumer: sample (its own build) with a real pip dependency installs, bundles, zips. consumer: runs-on: ubuntu-latest timeout-minutes: 60 @@ -105,14 +105,14 @@ jobs: python-version: "3.14" - uses: astral-sh/setup-uv@v6 - # usage-example applies three things from mavenLocal: packpack (through the plugin), the + # sample 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 + # sample/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: | (cd .ci/pypackpack && ./gradlew :packpack:publishToMavenLocal --console=plain) - ./gradlew :toolchain:publishToMavenLocal --console=plain + ./gradlew :toolchain-gradle-plugin:publishToMavenLocal --console=plain # Targeted, not publishAllToMavenLocal: that currently fails on the Android release Java # resources (generatePythonProperties has no declared consumer; reported to the lead). (cd .ci/python-multiplatform && ./gradlew \ @@ -122,9 +122,9 @@ jobs: (cd .ci/python-multiplatform && ./gradlew -p python-multiplatform-gradle-plugin publishToMavenLocal --console=plain) - name: Install, bundle and zip as a consumer - working-directory: usage-example + working-directory: sample run: | - ./gradlew installPythonDependencies buildPython packagePython \ + ../gradlew installPythonDependencies buildPython packagePython \ --console=plain > consumer.log 2>&1 || { cat consumer.log; exit 1; } cat consumer.log grep -q "iniconfig" consumer.log || { echo "the dependency was not installed"; exit 1; } @@ -169,7 +169,7 @@ jobs: - name: Publish packpack, the toolchain plugin and python-multiplatform to mavenLocal run: | (cd .ci/pypackpack && ./gradlew :packpack:publishToMavenLocal --console=plain) - ./gradlew :toolchain:publishToMavenLocal --console=plain + ./gradlew :toolchain-gradle-plugin:publishToMavenLocal --console=plain # Targeted, not publishAllToMavenLocal: that currently fails on the Android release Java # resources (generatePythonProperties has no declared consumer; reported to the lead). (cd .ci/python-multiplatform && ./gradlew \ @@ -179,11 +179,11 @@ jobs: (cd .ci/python-multiplatform && ./gradlew -p python-multiplatform-gradle-plugin publishToMavenLocal --console=plain) - name: Package the desktop app - working-directory: usage-example - run: ./gradlew createDistributable --console=plain + working-directory: sample + run: ../gradlew createDistributable --console=plain - name: Run the packaged app's Python, headless and without PYTHONHOME - working-directory: usage-example + working-directory: sample run: | set -euo pipefail app=$(find build/compose/binaries/main/app -maxdepth 3 -type f -path '*/bin/*' | head -n 1) @@ -230,7 +230,7 @@ jobs: - name: Publish packpack, the toolchain plugin and python-multiplatform (Android too) run: | (cd .ci/pypackpack && ./gradlew :packpack:publishToMavenLocal --console=plain) - ./gradlew :toolchain:publishToMavenLocal --console=plain + ./gradlew :toolchain-gradle-plugin:publishToMavenLocal --console=plain (cd .ci/python-multiplatform && ./gradlew \ :python-multiplatform:publishAndroidReleasePublicationToMavenLocal \ :python-multiplatform:publishKotlinMultiplatformPublicationToMavenLocal \ @@ -238,8 +238,8 @@ jobs: (cd .ci/python-multiplatform && ./gradlew -p python-multiplatform-gradle-plugin publishToMavenLocal --console=plain) - name: Build the debug APK - working-directory: usage-example - run: ./gradlew assembleDebug --console=plain + working-directory: sample + run: ../gradlew assembleDebug --console=plain - name: Enable KVM for the emulator run: | @@ -255,7 +255,7 @@ jobs: target: google_apis disable-animations: true script: | - 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/' + 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/' guide: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index e26908b..d6829ba 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,6 @@ captures # Agent working areas (AGENTS.md §2) .worktrees/ .tmp/ + +# IDE (JetBrains Fleet) +.fleet/ diff --git a/AGENTS.md b/AGENTS.md index 287db20..60b22ee 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,6 +31,16 @@ ask first.** Writing to *another* repository is not an exception either. Do it only when told to work there. +### Do not add top-level folders + +**Never add a new directory (or a new file) at the repository root on your own.** The root layout is +the maintainer's: source modules, `docs/`, `gradle/`, `.github/` and the files that tools require +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. + ## 3. Worktrees link large artefacts instead of copying them A worktree is a full checkout. Copying large untracked artefacts (prebuilt runtimes, vendored trees, @@ -60,7 +70,7 @@ landed or reported, not left. Branches named `release-*` are preserved snapshots `main` carries a reduced layout: of the Markdown files, only `README.md` stays at the repository root, and `docs/` keeps only its subdirectories (no Markdown files directly under `docs/`). -CI runs `tools/release/sync-release.sh` (`.github/workflows/release-sync.yml`) to produce that layout; do not hand-edit `release` or `main`. +CI runs `.github/scripts/release/sync-release.sh` (`.github/workflows/release-sync.yml`) to produce that layout; do not hand-edit `release` or `main`. ### Issues and pull requests @@ -161,20 +171,22 @@ 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. -- `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 +- `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 that fails if the wiring breaks. ## 12. Layout | Module | What it is | |---|---| -| `:toolchain` | The Gradle plugin (`org.thisisthepy.python.multiplatform`, class `PythonPlugin`). DSL in `dsl/`, tasks in `bundle/`, `dependency/`, `hotreload/`. | -| `:tcl` | toolchain-lite, a CLI application (`tcl install `). | -| `usage-example/` | **Its own Gradle build** (own settings, catalog and wrapper; toolchain#22): a Compose Multiplatform app applying this plugin and python-multiplatform, on python-multiplatform's Kotlin/Compose/AGP. Run it with its own wrapper: `(cd usage-example && ./gradlew …)`. | +| `toolchain-gradle-plugin/` (`:toolchain-gradle-plugin`) | The Gradle plugin (id `org.thisisthepy.python.multiplatform`, class `PythonPlugin`). DSL in `dsl/`, tasks in `bundle/`, `dependency/`, `hotreload/`. | +| `toolchain-cli/` (`:toolchain-cli`) | toolchain-lite, a CLI application (`tcl install `). | +| `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/`. | +| `.github/scripts/release/` | The develop → release → main sync (rule 4). | -`pyproject.toml` at the root describes a Python package `toolchain` that does not exist; nothing -builds it. Do not add Python code to satisfy it without asking (`docs/SPEC.md`, "Outside intent"). +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 +`archive/pre-restructure`. ## 13. pypackpack owns the work; toolchain owns the vocabulary @@ -217,10 +229,10 @@ 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. -- `usage-example` resolves the plugin **by Maven coordinate from `mavenLocal()`**, not through - `includeBuild`. After changing `:toolchain`, run `:toolchain:publishToMavenLocal` before building - `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 +- `sample` resolves the plugin **by Maven coordinate from `mavenLocal()`**, not through + `includeBuild`. After changing `:toolchain-gradle-plugin`, run `:toolchain-gradle-plugin: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 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. @@ -228,17 +240,17 @@ history removing. 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. `usage-example` additionally needs an Android SDK +`InstallerTest` run a real `uv add` against PyPI. `sample` 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). Run each module separately (rule 8), with output to a file under `.tmp/`: ```bash -rm -rf toolchain/build/test-results -./gradlew :toolchain:test --rerun --console=plain > .tmp/toolchain-test.log 2>&1; echo "EXIT=$?" -rm -rf tcl/build/test-results -./gradlew :tcl:test --rerun --console=plain > .tmp/tcl-test.log 2>&1; echo "EXIT=$?" +rm -rf toolchain-gradle-plugin/build/test-results +./gradlew :toolchain-gradle-plugin:test --rerun --console=plain > .tmp/toolchain-test.log 2>&1; echo "EXIT=$?" +rm -rf toolchain-cli/build/test-results +./gradlew :toolchain-cli:test --rerun --console=plain > .tmp/tcl-test.log 2>&1; echo "EXIT=$?" ``` Count results from `/build/test-results/test/*.xml`, not from the log. diff --git a/PROJECT.md b/PROJECT.md index 0d76646..4d2bec3 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -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`) | -| 실행 가능한 부분집합 | `usage-example/build.gradle.kts` | +| 실행 가능한 부분집합 | `sample/build.gradle.kts` | | 의도 / 계약 | `docs/INTENT.md` / `docs/SPEC.md` | ## 현재 상태 (2026-10-03, 코드와 테스트를 읽고 판정) @@ -61,7 +61,7 @@ Kotlin Multiplatform 앱의 Python 부분을 Gradle `python { }` 블록으로 **부분** - APK 로의 스테이징 연결: `android.sourceSets.main.assets` 등록은 실제 AGP 로 테스트됨 (`PythonPluginAttachmentTest`), `preBuild`/`merge*Assets` 의존성은 Android SDK 가 있어야 생기므로 테스트 없음 -- iOS: `stagePythonBundleIosForXcode` 가 스테이징 후 `PYTHON_PAYLOAD_DIR=<경로>` 한 줄을 출력(`tools/xcode/stage-python-payload.sh`, python-multiplatform 없이 쓸 때만; python-multiplatform 을 쓰면 그쪽 `install-python.sh` 가 `PYTHON_PAYLOAD_TASK` 로 이 태스크를 불러 복사하며, `python/` 을 복사하는 Xcode 단계는 하나뿐이어야 함). `.app` 으로 복사하는 Xcode 단계는 python-multiplatform(#59) 소유 +- iOS: `stagePythonBundleIosForXcode` 가 스테이징 후 `PYTHON_PAYLOAD_DIR=<경로>` 한 줄을 출력(python-multiplatform 의 `install-python.sh` 가 `PYTHON_PAYLOAD_DIR` 로 받아 복사하며, `python/` 을 복사하는 Xcode 단계는 하나뿐이어야 함. 손으로 배선할 때의 안전한 스니펫은 가이드에 있음; 스크립트 파일은 #62 에서 삭제). `.app` 으로 복사하는 Xcode 단계는 python-multiplatform(#59) 소유 - 핫 리로드: Android 전용 `adb push` + 브로드캐스트. `serverHost`, `cert` 는 검증만 - 코드 푸시: 검증과 안내 태스크만, 업로드 없음 - `installPythonDependencies`(`uv add`)는 여전히 모든 소스셋을 호스트용으로 설치하므로, 호스트 wheel 이 없는 @@ -86,7 +86,7 @@ Python 코드와 의존성이 실려 빌드·실행되는 상태. 기한을 맞 | 마일스톤 | 기한 | 범위 | 완료 기준 | 근거 | |---|---|---|---|---| -| M1 DSL = 예시 파일 | 10-17 | 플랫폼(#7 완료), pip(#9), `compileSdk` 상수(#10), `defaultConfig` 버전(#11), `buildFeatures`(#12), `projectFlavors`(#13), 플러그인 적용·zip·jar/APK 테스트(#14) | 예시 파일의 모든 속성이 컴파일되고, 읽히거나 좁게 거부됨. `usage-example` 이 그 모양으로 빌드됨 | 이슈 7개, 외부 의존 없음 | +| M1 DSL = 예시 파일 | 10-17 | 플랫폼(#7 완료), pip(#9), `compileSdk` 상수(#10), `defaultConfig` 버전(#11), `buildFeatures`(#12), `projectFlavors`(#13), 플러그인 적용·zip·jar/APK 테스트(#14) | 예시 파일의 모든 속성이 컴파일되고, 읽히거나 좁게 거부됨. `sample` 이 그 모양으로 빌드됨 | 이슈 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 런타임 로딩에 의존 | @@ -98,8 +98,8 @@ Python 코드와 의존성이 실려 빌드·실행되는 상태. 기한을 맞 ``` toolchain/ Gradle 플러그인 (dsl/, bundle/, dependency/, hotreload/) -tcl/ toolchain-lite CLI -usage-example/ 별도 Gradle 빌드: 플러그인과 python-multiplatform 을 적용한 Compose Multiplatform 앱 + python/ +toolchain-cli/ toolchain-lite CLI (`:toolchain-cli`) +sample/ 별도 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) ``` @@ -109,21 +109,21 @@ docs/ INTENT.md, SPEC.md, locale/, guide/ (GitHub Pages) 전제: `~/.m2` 에 `packpack:0.1.0` (pypackpack 에서 `publishToMavenLocal`), `PATH` 에 `uv`, 네트워크. ```bash -./gradlew :toolchain:test --rerun --console=plain > .tmp/toolchain-test.log 2>&1; echo "EXIT=$?" -./gradlew :tcl:test --rerun --console=plain > .tmp/tcl-test.log 2>&1; echo "EXIT=$?" -./gradlew :toolchain:publishToMavenLocal # usage-example 빌드 전에 반드시 +./gradlew :toolchain-gradle-plugin:test --rerun --console=plain > .tmp/toolchain-test.log 2>&1; echo "EXIT=$?" +./gradlew :toolchain-cli:test --rerun --console=plain > .tmp/tcl-test.log 2>&1; echo "EXIT=$?" +./gradlew :toolchain-gradle-plugin:publishToMavenLocal # sample 빌드 전에 반드시 python3 docs/guide/check_guide.py # 가이드 검사 -# usage-example 은 별도 빌드: python-multiplatform 체크아웃에서 ./gradlew publishAllToMavenLocal 후 -(cd usage-example && ./gradlew packagePython) -(cd usage-example && ./gradlew createDistributable) # 데스크톱 앱; 실행 시 --python-smoke 로 헤드리스 확인 +# sample 은 별도 빌드: python-multiplatform 체크아웃에서 ./gradlew publishAllToMavenLocal 후 +./gradlew -p sample packagePython +./gradlew -p sample createDistributable # 데스크톱 앱; 실행 시 --python-smoke 로 헤드리스 확인 ``` Python 테스트는 없다. 루트 `pyproject.toml` 이 가리키는 Python 패키지도 존재하지 않는다. ## 결정 사항 -- **usage-example 은 별도 빌드** (#22, 리드 결정 2026-10-03): python-multiplatform 은 Kotlin 2.4.20-Beta2 로 빌드되고 Kotlin 2.1 - 컴파일러는 그 klib 을 읽지 못한다. 소비자인 usage-example 이 의존성의 툴체인에 맞추고, 플러그인 자체는 소비자를 위해 +- **sample 은 별도 빌드** (#22, 리드 결정 2026-10-03): python-multiplatform 은 Kotlin 2.4.20-Beta2 로 빌드되고 Kotlin 2.1 + 컴파일러는 그 klib 을 읽지 못한다. 소비자인 sample 이 의존성의 툴체인에 맞추고, 플러그인 자체는 소비자를 위해 지금의 Kotlin 에 둔다. - **위임**: 의존성은 `BackendInterface.create(BackendType.UV)`, 번들은 diff --git a/README.md b/README.md index dafec36..b66a85a 100644 --- a/README.md +++ b/README.md @@ -53,11 +53,17 @@ owns the Gradle vocabulary in between. ```shell # in pypackpack: ./gradlew :packpack:publishToMavenLocal -./gradlew :toolchain:publishToMavenLocal +./gradlew :toolchain-gradle-plugin:publishToMavenLocal ``` +| Coordinates | What | +|---|---| +| plugin id `org.thisisthepy.python.multiplatform` | the Gradle plugin; apply it by id (unchanged) | +| `org.thisisthepy.python.multiplatform:toolchain-gradle-plugin` | the plugin's implementation artifact (was `…:toolchain` before #62) | +| `org.thisisthepy.python.multiplatform:toolchain-cli` | the CLI, toolchain-lite (was `…:tcl` before #62) | + Apply the plugin next to Kotlin Multiplatform and point it at a `pypackpack` package. This is -`usage-example/build.gradle.kts`, trimmed: +`sample/build.gradle.kts`, trimmed: ```kotlin plugins { @@ -70,18 +76,17 @@ python { compileSdk = "3.13" localLibraryPath = "python" // a pypackpack package: pyproject.toml + src/main/ packaging { - fileName = "usage-example" + fileName = "sample" } } ``` ```shell -cd usage-example # its own build, on python-multiplatform's Kotlin -./gradlew packagePython # install → bundle → zip -./gradlew stagePythonBundle # stage python/ for android, ios and desktop +./gradlew -p sample packagePython # install → bundle → zip (sample/ is its own build) +./gradlew -p sample stagePythonBundle # stage python/ for android, ios and desktop ``` -> **iOS, interim:** usage-example's Xcode phase (`usage-example/src/iosMain/install-python-phase.sh`) needs +> **iOS, interim:** sample's Xcode phase (`sample/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. @@ -103,7 +108,7 @@ python { Python only? Skip Gradle builds entirely: ```shell -./gradlew :tcl:run --args="install pythonx-compose" +./gradlew :toolchain-cli:run --args="install pythonx-compose" ``` ## 🧭 Architecture at a glance diff --git a/docs/INTENT.md b/docs/INTENT.md index f9f2115..b5f36cd 100644 --- a/docs/INTENT.md +++ b/docs/INTENT.md @@ -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". | -| `usage-example/` | The executable subset of the target DSL that the plugin reads today. | +| `sample/` | 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.` diff --git a/docs/SPEC.md b/docs/SPEC.md index f3600b0..a78ff09 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -12,11 +12,11 @@ Each item carries a status: - **planned** — the example build file or an issue asks for it; the code does not do it (or only declares a DSL property that nothing reads). -Status was assigned on 2026-10-02 by reading `toolchain/src`, `tcl/src` and their tests. No status +Status was assigned on 2026-10-02 by reading the plugin's and the CLI's sources and their tests (now `toolchain-gradle-plugin/src`, `toolchain-cli/src`). No status here comes from a roadmap, a commit message or an issue checkbox. -Paths below are abbreviated: `plugin/` = `toolchain/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/`, -`ptest/` = the matching `toolchain/src/test/kotlin/...` directory, `tcl/` = `tcl/src/{main,test}/kotlin/org/thisisthepy/python/multiplatform/tcl/`. +Paths below are abbreviated: `plugin/` = `toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/`, +`ptest/` = the matching `toolchain-gradle-plugin/src/test/kotlin/...` directory, `cli/` = `toolchain-cli/src/{main,test}/kotlin/org/thisisthepy/python/multiplatform/tcl/`. --- @@ -24,9 +24,9 @@ Paths below are abbreviated: `plugin/` = `toolchain/src/main/kotlin/org/thisisth ### 1.1 Applying the plugin Plugin id `org.thisisthepy.python.multiplatform`, implementation class `PythonPlugin` -(`toolchain/build.gradle.kts` `gradlePlugin { }`). Applying it creates the `python` extension +(`toolchain-gradle-plugin/build.gradle.kts` `gradlePlugin { }`). Applying it creates the `python` extension (`PythonExtension`) and registers the tasks of §1.10–§1.15 and §1.18, all in group `python`. -Consumers resolve it from `mavenLocal()` after `./gradlew :toolchain:publishToMavenLocal`; the plugin +Consumers resolve it from `mavenLocal()` after `./gradlew :toolchain-gradle-plugin:publishToMavenLocal`; the plugin itself depends on `org.thisisthepy.python.multiplatform:packpack:0.1.0` from `mavenLocal()`. **Status: implemented** — `plugin/PythonPlugin.kt`; `ptest/PythonPluginApplyTest.kt` applies the @@ -34,7 +34,7 @@ plugin id to a ProjectBuilder project and checks the extension, every task name (`installPythonDependencies`, `buildPython`, `packagePython`, `stagePythonBundle` and its `Android`/`Ios`/`Desktop` tasks; `hotReloadPython` and `codePushPython` after evaluation), the `python` group, and the `packagePython → buildPython → installPythonDependencies` chain. Resolving -the plugin from `mavenLocal()` is still exercised only by building `usage-example`. +the plugin from `mavenLocal()` is still exercised only by building `sample`. ### 1.2 `compileSdk` — the Python version `compileSdk` accepts `X.Y`, `X.Y.Z`, `X.Y.Z-alpha[N]` or `X.Y.Z-rc[N]` and classifies it into @@ -53,7 +53,7 @@ automatically (INTENT §4.1), which is not available yet: the reason fails `buil when there is a package to bundle), not the configuration. **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`; `sample` uses `compileSdk = PY3_14_7`. **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 @@ -284,7 +284,7 @@ family and flavor, options, rejection), `ptest/PythonPluginTargetDependenciesTes install directories, `libDirs` and dependencies on an applied plugin), `ptest/dependency/lang/python/InstallDependenciesTaskTest.kt` and `ptest/dependency/lang/python/InstallTargetDependenciesTaskTest.kt` (a real install of `six` for -`aarch64-linux-android`; both need `uv` and network). CI's consumer job checks that usage-example's +`aarch64-linux-android`; both need `uv` and network). CI's consumer job checks that sample's `iniconfig` is in the bundle and the zip. → *Known limits: without `compileSdk` no `python-version` is passed and uv uses the interpreter it finds. `installPythonDependencies` still `uv add`s every source set for the host, so a package with @@ -378,18 +378,18 @@ Hand-off to the platform's packaging step: real `com.android.application` (AGP 8.5.2, test classpath only) and checks the staged root is a `main` asset source directory. Not tested: the `preBuild` / `merge*Assets` → `stagePythonBundleAndroid` dependency (AGP creates those tasks only when the project is evaluated against an Android SDK, - which `:toolchain:test` does not require), and the built APK. + which `:toolchain-gradle-plugin:test` does not require), and the built APK. - iOS: toolchain stages and exposes the path; python-multiplatform (#59) owns the Xcode build phase that attaches it. `stagePythonBundleIosForXcode` (group `python`) depends on `stagePythonBundleIos` and prints exactly one line `PYTHON_PAYLOAD_DIR=` (stdout, visible under `--quiet`). It fails with a reason when no iOS variant of the active build type exists, or the staged `build/pythonStaging/ios/python/` is missing or empty (no package configured). It never copies into - the `.app`. The phase script is `tools/xcode/stage-python-payload.sh` (`set -euo pipefail`, checks - the directory exists before `rsync -a --delete "$dir/" "$TARGET_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH/python/"`), - for apps **without** python-multiplatform. With python-multiplatform, its `tools/xcode/install-python.sh` - is the single phase that copies `python/` (and the stdlib): it takes `PYTHON_PAYLOAD_TASK` (a task - printing `PYTHON_PAYLOAD_DIR=`, i.e. this one). Exactly one Xcode phase may copy `python/`; both - scripts `rsync --delete` into the same `/python/`. + the `.app`. With python-multiplatform, its `tools/xcode/install-python.sh` is the single phase that + copies `python/` (and the stdlib): it takes `PYTHON_PAYLOAD_DIR` (or `PYTHON_PAYLOAD_TASK` within its + own Gradle root); the sample's `sample/src/iosMain/install-python-phase.sh` computes the directory + with this task and passes it. Exactly one Xcode phase may copy `python/`. Wiring by hand without + python-multiplatform: the guide's staging page has the safe phase snippet (capture, parse, check + the directory, then `rsync --delete`); the repository no longer ships it as a script (#62). **partial** — `xcodePayloadLine`, `ptest/bundle/StagePythonBundleIosForXcodeTaskTest.kt`, `ptest/bundle/StagePythonPayloadScriptTest.kt`. Not tested: an actual Xcode build. - Putting the staged `python/` on `sys.path` at run time is `python-multiplatform`'s side. @@ -520,7 +520,7 @@ commands, skip warning, mode, exit-code interpretation). The TestKit tests in `pyrefly` wheels) and `uv`; without it they are **skipped** with that reason, as on CI. Not covered by a test here: exit 2 end to end (only `interpretTypedpythonResult`), and the install in a *consumer's* classpath — `withPluginClasspath()` bypasses the Kotlin DSL's `kotlin-stdlib` pin; -that was verified by building usage-example's `buildPython`. +that was verified by building sample's `buildPython`. → *Until `typedpython` is on PyPI, a build without `-Ptypedpython.wheelDir` skips the check with a warning.* → *Platform overlays (`src/`) are checked as files but only `src/main` is an import root: **partial**.* @@ -534,8 +534,8 @@ Finds the nearest `pyproject.toml` at or above the working directory. If there i message on stderr for a missing argument, an unknown command or a failed install. No arguments prints `Usage: tcl install `. -**Status: implemented** — `tcl/Cli.kt`, `tcl/Installer.kt`; `tcl/CliArgsTest.kt`, -`tcl/InstallerTest.kt` (needs `uv` and network). Run with `./gradlew :tcl:run --args="install "`. +**Status: implemented** — `cli/Cli.kt`, `cli/Installer.kt`; `cli/CliArgsTest.kt`, +`cli/InstallerTest.kt` (needs `uv` and network). Run with `./gradlew :toolchain-cli:run --args="install "`. --- @@ -545,7 +545,7 @@ These exist in the code but are not asked for by the example build file or the i 1. **`python { localLibraryPath = "…" }`** — not in the example build file, which uses `sourceSets { commonMain { srcDirs(…) } }`. It overrides `srcDirs` and is the only source root - hot reload reads. `usage-example` depends on it. + hot reload reads. `sample` depends on it. 2. **`-Ppython.buildType=`** — a project property choosing the active build type when no platform variant is declared. The example build file says nothing about selecting a build type. 3. **Hot reload over `adb push` + broadcast.** The example describes an HTTPS `serverHost` with a @@ -556,6 +556,5 @@ These exist in the code but are not asked for by the example build file or the i wired), `bundle/PythonLocalLoader.kt`, `dependency/DependencyType.kt`, and `BinariesExtension` / `FrozenPackConfig` / `BuildTypeEnum` in `dsl/DSLPlatforms.kt`. Nothing calls any of them. -5. **`pyproject.toml`** declares a flit-built Python package named `toolchain` (Python 3.9–3.13) - that does not exist in the repository — the root `toolchain/` directory is the Gradle module. - Nothing builds or tests it. +5. ~~**`pyproject.toml`**~~ — deleted in #62 (a flit stub for a Python package that never existed; + nothing built it). It survives in the tag `archive/pre-restructure`. diff --git a/docs/guide/getting-started.html b/docs/guide/getting-started.html index ef8d704..4c49b1d 100644 --- a/docs/guide/getting-started.html +++ b/docs/guide/getting-started.html @@ -33,14 +33,14 @@

1. Prerequisites1. 준비 ./gradlew :packpack:publishToMavenLocal # in this repository -./gradlew :toolchain:publishToMavenLocal +./gradlew :toolchain-gradle-plugin:publishToMavenLocal

The plugin is pre-release and resolved from mavenLocal(). Add mavenLocal() to pluginManagement.repositories in your settings.gradle.kts.

플러그인은 출시 전이며 mavenLocal() 에서 받습니다. settings.gradle.kts 의 pluginManagement.repositories 에 mavenLocal() 을 추가하세요.

2. A Python package2. Python 패키지

The plugin bundles a pypackpack package: a directory with a pyproject.toml and its code under src/main/. Platform-specific code can sit in src/android, src/ios and so on; the bundler overlays it.

플러그인이 번들링하는 것은 pypackpack 패키지입니다. pyproject.toml 이 있고 코드가 src/main/ 아래에 있는 디렉터리입니다. 플랫폼 전용 코드는 src/android, src/ios 등에 두면 번들러가 덮어씌웁니다.

-
usage-example/python/
+
sample/src/commonMain/python/
 ├── pyproject.toml
 └── src/main/example_py/
     ├── __init__.py
@@ -59,18 +59,17 @@ 

3. Apply the plugin3. 플 compileSdk = "3.13" localLibraryPath = "python" packaging { - fileName = "usage-example" + fileName = "sample" } }

-

This is the repository's own usage-example/build.gradle.kts. Instead of localLibraryPath you can point sourceSets { commonMain { srcDirs(...) } } at the package.

이 저장소의 usage-example/build.gradle.kts 그대로입니다. localLibraryPath 대신 sourceSets { commonMain { srcDirs(...) } } 로 패키지를 가리켜도 됩니다.

+

This is the repository's own sample/build.gradle.kts. Instead of localLibraryPath you can point sourceSets { commonMain { srcDirs(...) } } at the package.

이 저장소의 sample/build.gradle.kts 그대로입니다. localLibraryPath 대신 sourceSets { commonMain { srcDirs(...) } } 로 패키지를 가리켜도 됩니다.

4. Run it4. 실행

-
cd usage-example                             # its own build / 별도 빌드
-./gradlew packagePython                      # installPythonDependencies → buildPython → zip
-./gradlew stagePythonBundle                  # copy python/ where each platform packages from
-./gradlew tasks --group python               # everything the plugin registered
+
./gradlew -p sample packagePython            # installPythonDependencies → buildPython → zip
+./gradlew -p sample stagePythonBundle        # copy python/ where each platform packages from
+./gradlew -p sample tasks --group python     # everything the plugin registered
diff --git a/docs/guide/guide-staging.html b/docs/guide/guide-staging.html index fb11e73..189d8da 100644 --- a/docs/guide/guide-staging.html +++ b/docs/guide/guide-staging.html @@ -46,13 +46,13 @@

Which bundle goes where어

iOS: the Xcode build phaseiOS: Xcode 빌드 단계

-

stagePythonBundleIosForXcode runs stagePythonBundleIos and prints exactly one line, PYTHON_PAYLOAD_DIR=<absolute path>, even under --quiet. It fails, naming why, when no iOS variant of the active build type exists or the staged directory is empty (no package configured). It never copies into the .app. The phase that does is owned by python-multiplatform (its #59); this plugin only stages and exposes the path. The script it uses is tools/xcode/stage-python-payload.sh:

stagePythonBundleIosForXcode 는 stagePythonBundleIos 를 실행한 뒤 --quiet 에서도 정확히 한 줄, PYTHON_PAYLOAD_DIR=<절대 경로> 를 출력합니다. 활성 빌드 타입의 iOS 변형이 없거나 스테이징 디렉터리가 비어 있으면(패키지 미설정) 이유를 밝히며 실패합니다. .app 안으로는 절대 복사하지 않습니다. 복사하는 단계는 python-multiplatform(#59)이 소유하며, 이 플러그인은 스테이징과 경로 노출만 합니다. 사용하는 스크립트는 tools/xcode/stage-python-payload.sh 입니다:

+

stagePythonBundleIosForXcode runs stagePythonBundleIos and prints exactly one line, PYTHON_PAYLOAD_DIR=<absolute path>, even under --quiet. It fails, naming why, when no iOS variant of the active build type exists or the staged directory is empty (no package configured). It never copies into the .app. The phase that does is owned by python-multiplatform (its #59); this plugin only stages and exposes the path. Wiring the phase by hand, without python-multiplatform, it is:

stagePythonBundleIosForXcode 는 stagePythonBundleIos 를 실행한 뒤 --quiet 에서도 정확히 한 줄, PYTHON_PAYLOAD_DIR=<절대 경로> 를 출력합니다. 활성 빌드 타입의 iOS 변형이 없거나 스테이징 디렉터리가 비어 있으면(패키지 미설정) 이유를 밝히며 실패합니다. .app 안으로는 절대 복사하지 않습니다. 복사하는 단계는 python-multiplatform(#59)이 소유하며, 이 플러그인은 스테이징과 경로 노출만 합니다. 사용하는 python-multiplatform 없이 손으로 배선한다면 단계는 다음과 같습니다:

set -euo pipefail
 out=$(./gradlew -q :app:stagePythonBundleIosForXcode)
 dir=$(printf '%s\n' "$out" | sed -n 's/^PYTHON_PAYLOAD_DIR=//p')
 [ -n "$dir" ] && [ -d "$dir" ] || { echo "error: no Python payload dir from Gradle" >&2; exit 1; }
 rsync -a --delete "$dir/" "$TARGET_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH/python/"
-

This script is for apps without python-multiplatform. With python-multiplatform, its tools/xcode/install-python.sh is the one phase that copies python/ (and the stdlib): set PYTHON_PAYLOAD_TASK=:app:stagePythonBundleIosForXcode there and do not add this script as well. Only one Xcode phase may copy python/, because each rsync --deletes the same directory.

이 스크립트는 python-multiplatform 을 쓰지 않는 앱용입니다. python-multiplatform 을 쓰면 그쪽 tools/xcode/install-python.sh 가 python/(와 stdlib)을 복사하는 유일한 단계입니다. 거기에 PYTHON_PAYLOAD_TASK=:app:stagePythonBundleIosForXcode 를 주고, 이 스크립트를 함께 넣지 마세요. 둘 다 같은 디렉터리를 rsync --delete 하므로 python/ 을 복사하는 Xcode 단계는 하나여야 합니다.

+

With python-multiplatform, its tools/xcode/install-python.sh is the one phase that copies python/ (and the stdlib): give it the directory this task prints as PYTHON_PAYLOAD_DIR, as the sample's sample/src/iosMain/install-python-phase.sh does, and do not add the snippet above as well. Only one Xcode phase may copy python/, because each rsync --deletes the same directory. The snippet above is for wiring by hand without python-multiplatform.

python-multiplatform 을 쓰면 그쪽 tools/xcode/install-python.sh 가 python/(와 stdlib)을 복사하는 유일한 단계입니다. 이 태스크가 출력한 디렉터리를 PYTHON_PAYLOAD_DIR 로 넘기세요(샘플의 sample/src/iosMain/install-python-phase.sh 처럼). 위 스니펫을 함께 넣지 마세요. 둘 다 같은 디렉터리를 rsync --delete 하므로 python/ 을 복사하는 Xcode 단계는 하나여야 합니다. 위 스니펫은 python-multiplatform 없이 손으로 배선할 때를 위한 것입니다.

Do not shorten it to dir=$(./gradlew … | sed …) && rsync …: the pipeline's status is sed's, so a failed Gradle leaves dir empty and rsync --delete "/" runs.

dir=$(./gradlew … | sed …) && rsync … 처럼 줄이면 안 됩니다. 파이프라인의 종료 상태는 sed 의 것이라, Gradle 이 실패하면 dir 이 비어 rsync --delete "/" 가 실행됩니다.

diff --git a/docs/guide/guide-tcl.html b/docs/guide/guide-tcl.html index 0962b9a..6d01f31 100644 --- a/docs/guide/guide-tcl.html +++ b/docs/guide/guide-tcl.html @@ -28,7 +28,7 @@

tcl for Python usersPython

Install a package패키지 설치

-
./gradlew :tcl:run --args="install pythonx-compose"
+
./gradlew :toolchain-cli:run --args="install pythonx-compose"

tcl install <package> looks for a pyproject.toml in the current directory and its parents. If there is none it runs uv init --bare first; then it adds the package with uv add, through the same pypackpack backend the Gradle plugin uses.

tcl install <package> 는 현재 디렉터리와 그 상위에서 pyproject.toml 을 찾습니다. 없으면 먼저 uv init --bare 를 실행하고, Gradle 플러그인과 같은 pypackpack 백엔드로 uv add 를 해서 패키지를 추가합니다.

Invocation호출Result결과
tcl install pkgexit 0, uv output on stdout종료 코드 0, stdout 에 uv 출력
tclexit 1, Usage: tcl install <package>종료 코드 1, Usage: tcl install <package>
tcl installexit 1, missing argument종료 코드 1, 인자 누락
tcl otherexit 1, unknown command종료 코드 1, 알 수 없는 명령

install is the only command, matching issue #1. tcl needs uv on PATH and network access to PyPI.

명령은 이슈 #1 그대로 install 하나뿐입니다. tcl 은 PATH 의 uv 와 PyPI 네트워크 접근이 필요합니다.

diff --git a/docs/guide/status.html b/docs/guide/status.html index 46c44c1..53c0689 100644 --- a/docs/guide/status.html +++ b/docs/guide/status.html @@ -47,8 +47,8 @@

Is the DSL final?DSL 은

Contributing기여하기

Changes go intent → spec → test → code. A behaviour change begins as a change to the specification; its test is written first and seen failing; then the code makes it pass. A DSL value is never accepted silently — it is wired to a task, or rejected with a reason.

변경은 의도 → 스펙 → 테스트 → 코드 순서로 갑니다. 동작 변경은 스펙 변경에서 시작하고, 테스트를 먼저 써서 실패를 확인한 뒤, 코드로 통과시킵니다. DSL 값은 조용히 받아들이지 않습니다 — 태스크에 연결하거나, 이유와 함께 거부합니다.

-
./gradlew :toolchain:test --rerun     # plugin; needs packpack in Maven Local, uv and network
-./gradlew :tcl:test --rerun           # tcl; needs uv and network
+
./gradlew :toolchain-gradle-plugin:test --rerun     # plugin; needs packpack in Maven Local, uv and network
+./gradlew :toolchain-cli:test --rerun           # tcl; needs uv and network
 python3 docs/guide/check_guide.py     # this site

Run each module on its own, and count results from build/test-results/ rather than from the console.

모듈은 따로따로 실행하고, 결과는 콘솔이 아니라 build/test-results/ 에서 세세요.

diff --git a/docs/locale/README_ko.md b/docs/locale/README_ko.md index 54a3970..265c6e0 100644 --- a/docs/locale/README_ko.md +++ b/docs/locale/README_ko.md @@ -52,11 +52,17 @@ Python 을 내장한 Kotlin Multiplatform 앱에는 맞물려 돌아가야 하 ```shell # pypackpack 에서: ./gradlew :packpack:publishToMavenLocal -./gradlew :toolchain:publishToMavenLocal +./gradlew :toolchain-gradle-plugin:publishToMavenLocal ``` +| 좌표 | 무엇 | +|---|---| +| 플러그인 id `org.thisisthepy.python.multiplatform` | Gradle 플러그인; id 로 적용 (변경 없음) | +| `org.thisisthepy.python.multiplatform:toolchain-gradle-plugin` | 플러그인 구현 아티팩트 (#62 이전에는 `…:toolchain`) | +| `org.thisisthepy.python.multiplatform:toolchain-cli` | CLI, toolchain-lite (#62 이전에는 `…:tcl`) | + Kotlin Multiplatform 옆에 플러그인을 적용하고 `pypackpack` 패키지를 가리키게 합니다. 아래는 -`usage-example/build.gradle.kts` 를 줄인 것입니다. +`sample/build.gradle.kts` 를 줄인 것입니다. ```kotlin plugins { @@ -69,18 +75,17 @@ python { compileSdk = "3.13" localLibraryPath = "python" // pypackpack 패키지: pyproject.toml + src/main/ packaging { - fileName = "usage-example" + fileName = "sample" } } ``` ```shell -cd usage-example # 별도 빌드 (python-multiplatform 의 Kotlin 사용) -./gradlew packagePython # 설치 → 번들 → zip -./gradlew stagePythonBundle # android, ios, desktop 용 python/ 스테이징 +./gradlew -p sample packagePython # 설치 → 번들 → zip (sample/ 은 별도 빌드) +./gradlew -p sample stagePythonBundle # android, ios, desktop 용 python/ 스테이징 ``` -> **iOS, 임시:** usage-example 의 Xcode 단계(`usage-example/src/iosMain/install-python-phase.sh`)는 +> **iOS, 임시:** sample 의 Xcode 단계(`sample/src/iosMain/install-python-phase.sh`)는 > python-multiplatform#90 이 published 플러그인으로 그 배선을 제공할 때까지 `Python.xcframework` 와 stdlib 을 위해 > python-multiplatform 체크아웃을 가리키는 `PYTHON_MULTIPLATFORM_DIR` 가 필요합니다. @@ -102,7 +107,7 @@ python { Python 만 쓰나요? Gradle 빌드 없이: ```shell -./gradlew :tcl:run --args="install pythonx-compose" +./gradlew :toolchain-cli:run --args="install pythonx-compose" ``` ## 🧭 한눈에 보는 구조 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 09523c0..e2847c8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock deleted file mode 100644 index 8001f2e..0000000 --- a/kotlin-js-store/yarn.lock +++ /dev/null @@ -1,2886 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@colors/colors@1.5.0": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" - integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== - -"@discoveryjs/json-ext@^0.5.0": - version "0.5.7" - resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" - integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== - -"@jridgewell/gen-mapping@^0.3.5": - version "0.3.13" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f" - integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA== - dependencies: - "@jridgewell/sourcemap-codec" "^1.5.0" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/resolve-uri@^3.1.0": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" - integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== - -"@jridgewell/source-map@^0.3.3": - version "0.3.11" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.11.tgz#b21835cbd36db656b857c2ad02ebd413cc13a9ba" - integrity sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA== - dependencies: - "@jridgewell/gen-mapping" "^0.3.5" - "@jridgewell/trace-mapping" "^0.3.25" - -"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0": - version "1.5.5" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" - integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== - -"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": - version "0.3.31" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz#db15d6781c931f3a251a3dac39501c98a6082fd0" - integrity sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - -"@js-joda/core@3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-3.2.0.tgz#3e61e21b7b2b8a6be746df1335cf91d70db2a273" - integrity sha512-PMqgJ0sw5B7FKb2d5bWYIoxjri+QlW/Pys7+Rw82jSH0QN3rB05jZ/VrrsUdh1w4+i2kw9JOejXGq/KhDOX7Kg== - -"@leichtgewicht/ip-codec@^2.0.1": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz#4fc56c15c580b9adb7dc3c333a134e540b44bfb1" - integrity sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw== - -"@socket.io/component-emitter@~3.1.0": - version "3.1.2" - resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2" - integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA== - -"@types/body-parser@*": - version "1.19.6" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.6.tgz#1859bebb8fd7dac9918a45d54c1971ab8b5af474" - integrity sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/bonjour@^3.5.9": - version "3.5.13" - resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.13.tgz#adf90ce1a105e81dd1f9c61fdc5afda1bfb92956" - integrity sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ== - dependencies: - "@types/node" "*" - -"@types/connect-history-api-fallback@^1.3.5": - version "1.5.4" - resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz#7de71645a103056b48ac3ce07b3520b819c1d5b3" - integrity sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw== - dependencies: - "@types/express-serve-static-core" "*" - "@types/node" "*" - -"@types/connect@*": - version "3.4.38" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" - integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== - dependencies: - "@types/node" "*" - -"@types/cors@^2.8.12": - version "2.8.19" - resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.19.tgz#d93ea2673fd8c9f697367f5eeefc2bbfa94f0342" - integrity sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg== - dependencies: - "@types/node" "*" - -"@types/estree@^1.0.5": - version "1.0.9" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.9.tgz#cf3f0e876d7bee15a93ab925b82bf570a3904a24" - integrity sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg== - -"@types/express-serve-static-core@*", "@types/express-serve-static-core@^5.0.0": - version "5.1.3" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.1.3.tgz#9d34c88c0c9ee62b9a6e4d9f8ab8d7e29688e6b4" - integrity sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - "@types/send" "*" - -"@types/express-serve-static-core@^4.17.33": - version "4.19.9" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.9.tgz#b746a8bb6c389af7a31141397bb539f775b0ae84" - integrity sha512-QP2ESEe/ImWY0HDwNAnK9PvEffUyhLTnWkk7KXzHfyeWAnlrDe1fN77bXl6ia8KT3wPlmA7t9/VPRpnf4Ex9sg== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - "@types/send" "*" - -"@types/express@*": - version "5.0.6" - resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.6.tgz#2d724b2c990dcb8c8444063f3580a903f6d500cc" - integrity sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^5.0.0" - "@types/serve-static" "^2" - -"@types/express@^4.17.13": - version "4.17.25" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.25.tgz#070c8c73a6fee6936d65c195dbbfb7da5026649b" - integrity sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.33" - "@types/qs" "*" - "@types/serve-static" "^1" - -"@types/http-errors@*": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472" - integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== - -"@types/http-proxy@^1.17.8": - version "1.17.17" - resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.17.tgz#d9e2c4571fe3507343cb210cd41790375e59a533" - integrity sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw== - dependencies: - "@types/node" "*" - -"@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": - version "7.0.15" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" - integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== - -"@types/mime@^1": - version "1.3.5" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" - integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== - -"@types/node-forge@^1.3.0": - version "1.3.14" - resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.14.tgz#006c2616ccd65550560c2757d8472eb6d3ecea0b" - integrity sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw== - dependencies: - "@types/node" "*" - -"@types/node@*", "@types/node@>=10.0.0": - version "26.2.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-26.2.0.tgz#5a4875a862fda8fdc57de8faa579bb81ecba1685" - integrity sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg== - dependencies: - undici-types "~8.3.0" - -"@types/qs@*": - version "6.15.1" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.15.1.tgz#8606884272c63f0db96986bd3548650d8a9388bf" - integrity sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw== - -"@types/range-parser@*": - version "1.2.7" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" - integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== - -"@types/retry@0.12.0": - version "0.12.0" - resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" - integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== - -"@types/send@*": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@types/send/-/send-1.2.1.tgz#6a784e45543c18c774c049bff6d3dbaf045c9c74" - integrity sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ== - dependencies: - "@types/node" "*" - -"@types/send@<1": - version "0.17.6" - resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.6.tgz#aeb5385be62ff58a52cd5459daa509ae91651d25" - integrity sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og== - dependencies: - "@types/mime" "^1" - "@types/node" "*" - -"@types/serve-index@^1.9.1": - version "1.9.4" - resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.4.tgz#e6ae13d5053cb06ed36392110b4f9a49ac4ec898" - integrity sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug== - dependencies: - "@types/express" "*" - -"@types/serve-static@^1", "@types/serve-static@^1.13.10": - version "1.15.10" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.10.tgz#768169145a778f8f5dfcb6360aead414a3994fee" - integrity sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw== - dependencies: - "@types/http-errors" "*" - "@types/node" "*" - "@types/send" "<1" - -"@types/serve-static@^2": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-2.2.0.tgz#d4a447503ead0d1671132d1ab6bd58b805d8de6a" - integrity sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ== - dependencies: - "@types/http-errors" "*" - "@types/node" "*" - -"@types/sockjs@^0.3.33": - version "0.3.36" - resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.36.tgz#ce322cf07bcc119d4cbf7f88954f3a3bd0f67535" - integrity sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q== - dependencies: - "@types/node" "*" - -"@types/ws@^8.5.12", "@types/ws@^8.5.5": - version "8.18.1" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.18.1.tgz#48464e4bf2ddfd17db13d845467f6070ffea4aa9" - integrity sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg== - dependencies: - "@types/node" "*" - -"@webassemblyjs/ast@1.14.1", "@webassemblyjs/ast@^1.12.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.14.1.tgz#a9f6a07f2b03c95c8d38c4536a1fdfb521ff55b6" - integrity sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ== - dependencies: - "@webassemblyjs/helper-numbers" "1.13.2" - "@webassemblyjs/helper-wasm-bytecode" "1.13.2" - -"@webassemblyjs/floating-point-hex-parser@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz#fcca1eeddb1cc4e7b6eed4fc7956d6813b21b9fb" - integrity sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA== - -"@webassemblyjs/helper-api-error@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz#e0a16152248bc38daee76dd7e21f15c5ef3ab1e7" - integrity sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ== - -"@webassemblyjs/helper-buffer@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz#822a9bc603166531f7d5df84e67b5bf99b72b96b" - integrity sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA== - -"@webassemblyjs/helper-numbers@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz#dbd932548e7119f4b8a7877fd5a8d20e63490b2d" - integrity sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA== - dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.13.2" - "@webassemblyjs/helper-api-error" "1.13.2" - "@xtuc/long" "4.2.2" - -"@webassemblyjs/helper-wasm-bytecode@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz#e556108758f448aae84c850e593ce18a0eb31e0b" - integrity sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA== - -"@webassemblyjs/helper-wasm-section@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz#9629dda9c4430eab54b591053d6dc6f3ba050348" - integrity sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@webassemblyjs/helper-buffer" "1.14.1" - "@webassemblyjs/helper-wasm-bytecode" "1.13.2" - "@webassemblyjs/wasm-gen" "1.14.1" - -"@webassemblyjs/ieee754@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz#1c5eaace1d606ada2c7fd7045ea9356c59ee0dba" - integrity sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw== - dependencies: - "@xtuc/ieee754" "^1.2.0" - -"@webassemblyjs/leb128@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.13.2.tgz#57c5c3deb0105d02ce25fa3fd74f4ebc9fd0bbb0" - integrity sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw== - dependencies: - "@xtuc/long" "4.2.2" - -"@webassemblyjs/utf8@1.13.2": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.13.2.tgz#917a20e93f71ad5602966c2d685ae0c6c21f60f1" - integrity sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ== - -"@webassemblyjs/wasm-edit@^1.12.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz#ac6689f502219b59198ddec42dcd496b1004d597" - integrity sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@webassemblyjs/helper-buffer" "1.14.1" - "@webassemblyjs/helper-wasm-bytecode" "1.13.2" - "@webassemblyjs/helper-wasm-section" "1.14.1" - "@webassemblyjs/wasm-gen" "1.14.1" - "@webassemblyjs/wasm-opt" "1.14.1" - "@webassemblyjs/wasm-parser" "1.14.1" - "@webassemblyjs/wast-printer" "1.14.1" - -"@webassemblyjs/wasm-gen@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz#991e7f0c090cb0bb62bbac882076e3d219da9570" - integrity sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@webassemblyjs/helper-wasm-bytecode" "1.13.2" - "@webassemblyjs/ieee754" "1.13.2" - "@webassemblyjs/leb128" "1.13.2" - "@webassemblyjs/utf8" "1.13.2" - -"@webassemblyjs/wasm-opt@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz#e6f71ed7ccae46781c206017d3c14c50efa8106b" - integrity sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@webassemblyjs/helper-buffer" "1.14.1" - "@webassemblyjs/wasm-gen" "1.14.1" - "@webassemblyjs/wasm-parser" "1.14.1" - -"@webassemblyjs/wasm-parser@1.14.1", "@webassemblyjs/wasm-parser@^1.12.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz#b3e13f1893605ca78b52c68e54cf6a865f90b9fb" - integrity sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@webassemblyjs/helper-api-error" "1.13.2" - "@webassemblyjs/helper-wasm-bytecode" "1.13.2" - "@webassemblyjs/ieee754" "1.13.2" - "@webassemblyjs/leb128" "1.13.2" - "@webassemblyjs/utf8" "1.13.2" - -"@webassemblyjs/wast-printer@1.14.1": - version "1.14.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz#3bb3e9638a8ae5fdaf9610e7a06b4d9f9aa6fe07" - integrity sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw== - dependencies: - "@webassemblyjs/ast" "1.14.1" - "@xtuc/long" "4.2.2" - -"@webpack-cli/configtest@^2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-2.1.1.tgz#3b2f852e91dac6e3b85fb2a314fb8bef46d94646" - integrity sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw== - -"@webpack-cli/info@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-2.0.2.tgz#cc3fbf22efeb88ff62310cf885c5b09f44ae0fdd" - integrity sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A== - -"@webpack-cli/serve@^2.0.5": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-2.0.5.tgz#325db42395cd49fe6c14057f9a900e427df8810e" - integrity sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ== - -"@xtuc/ieee754@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" - integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== - -"@xtuc/long@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" - integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== - -accepts@~1.3.4, accepts@~1.3.8: - version "1.3.8" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - -acorn-import-attributes@^1.9.5: - version "1.9.5" - resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" - integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== - -acorn@^8.15.0, acorn@^8.7.1: - version "8.18.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.18.0.tgz#4faf01b2d6d326bfeed97aea1f52220b5f4c1940" - integrity sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ== - -ajv-formats@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" - integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== - dependencies: - ajv "^8.0.0" - -ajv-keywords@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" - integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== - -ajv-keywords@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" - integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== - dependencies: - fast-deep-equal "^3.1.3" - -ajv@^6.12.5: - version "6.15.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.15.0.tgz#07e982c74626167aa7a2495c53817892d7139492" - integrity sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ajv@^8.0.0, ajv@^8.9.0: - version "8.20.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.20.0.tgz#304b3636add88ba7d936760dd50ece006dea95f9" - integrity sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA== - dependencies: - fast-deep-equal "^3.1.3" - fast-uri "^3.0.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - -ansi-colors@^4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" - integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== - -ansi-html-community@^0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" - integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -anymatch@~3.1.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -base64id@2.0.0, base64id@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/base64id/-/base64id-2.0.0.tgz#2770ac6bc47d312af97a8bf9a634342e0cd25cb6" - integrity sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog== - -baseline-browser-mapping@^2.11.12: - version "2.11.14" - resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.11.14.tgz#d51bb92285608b0e6356841a2eea76842a771d52" - integrity sha512-JyJ954WzuIR8/FFzX0o5krdSTrBAkcCSRfWSleRsIHSWV+cZe2FI1PKggVkFke1hBldRs+LRxUczzE9iPmgZww== - -batch@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" - integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== - -binary-extensions@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" - integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== - -body-parser@^1.19.0, body-parser@~1.20.5: - version "1.20.6" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.6.tgz#60c789c78e0992d906da0a29d71ae01d15c1ed76" - integrity sha512-p5tAzS57i5MV9fZFDj9LeIiTZEufbSe2eDozP+ElheSUq1m74CRq1jI4mYNDdVs9vQztXFLuk/Gd6BWTdwRJ5g== - dependencies: - bytes "~3.1.2" - content-type "~1.0.5" - debug "2.6.9" - depd "2.0.0" - destroy "~1.2.0" - http-errors "~2.0.1" - iconv-lite "~0.4.24" - on-finished "~2.4.1" - qs "~6.15.1" - raw-body "~2.5.3" - type-is "~1.6.18" - unpipe "~1.0.0" - -bonjour-service@^1.0.11: - version "1.4.4" - resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.4.4.tgz#8ff5b85fa0641e0996bf40bcf61fc0c6727b66d3" - integrity sha512-jCZcVv7eoc4QesRscwEZtSROBen+6LpKAmBIsQYQrsAeVHLyMXWX/t6eIV5KiRZYNUBl8eVqImEEMQ8L5+c/Kw== - dependencies: - fast-deep-equal "^3.1.3" - multicast-dns "^7.2.5" - -brace-expansion@^1.1.7: - version "1.1.18" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.18.tgz#3ce74d89885136be1535341f8c3d4425c29a5cab" - integrity sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^2.0.1, brace-expansion@^2.0.2: - version "2.1.4" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.1.4.tgz#589dab11c0018d0366be64cd8bf12c8dbecc8326" - integrity sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg== - dependencies: - balanced-match "^1.0.0" - -braces@^3.0.2, braces@^3.0.3, braces@~3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" - integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== - dependencies: - fill-range "^7.1.1" - -browser-stdout@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" - integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== - -browserslist@^4.21.10: - version "4.28.8" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.8.tgz#a3c79ceb70028527e5da7dafc887f3200b5168c0" - integrity sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA== - dependencies: - baseline-browser-mapping "^2.11.12" - caniuse-lite "^1.0.30001809" - electron-to-chromium "^1.5.402" - node-releases "^2.0.53" - update-browserslist-db "^1.3.0" - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -bytes@3.1.2, bytes@~3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - -call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" - integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - -call-bound@^1.0.2, call-bound@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" - integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== - dependencies: - call-bind-apply-helpers "^1.0.2" - get-intrinsic "^1.3.0" - -camelcase@^6.0.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" - integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== - -caniuse-lite@^1.0.30001809: - version "1.0.30001809" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz#e6cf71f14ddfe008f114dd2a846923be3c03a07b" - integrity sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ== - -chalk@^4.1.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chokidar@^3.5.1, chokidar@^3.5.3: - version "3.6.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" - integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -chrome-trace-event@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b" - integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ== - -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" - -clone-deep@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" - integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== - dependencies: - is-plain-object "^2.0.4" - kind-of "^6.0.2" - shallow-clone "^3.0.0" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -colorette@^2.0.10, colorette@^2.0.14: - version "2.0.20" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" - integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== - -commander@^10.0.1: - version "10.0.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" - integrity sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug== - -commander@^2.20.0: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -compressible@~2.0.18: - version "2.0.18" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" - integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== - dependencies: - mime-db ">= 1.43.0 < 2" - -compression@^1.7.4: - version "1.8.1" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.8.1.tgz#4a45d909ac16509195a9a28bd91094889c180d79" - integrity sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w== - dependencies: - bytes "3.1.2" - compressible "~2.0.18" - debug "2.6.9" - negotiator "~0.6.4" - on-headers "~1.1.0" - safe-buffer "5.2.1" - vary "~1.1.2" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -connect-history-api-fallback@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8" - integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA== - -connect@^3.7.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/connect/-/connect-3.7.0.tgz#5d49348910caa5e07a01800b030d0c35f20484f8" - integrity sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ== - dependencies: - debug "2.6.9" - finalhandler "1.1.2" - parseurl "~1.3.3" - utils-merge "1.0.1" - -content-disposition@~0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" - -content-type@~1.0.4, content-type@~1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" - integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== - -cookie-signature@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.7.tgz#ab5dd7ab757c54e60f37ef6550f481c426d10454" - integrity sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA== - -cookie@~0.7.1, cookie@~0.7.2: - version "0.7.2" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" - integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== - -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - -cors@~2.8.5: - version "2.8.6" - resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.6.tgz#ff5dd69bd95e547503820d29aba4f8faf8dfec96" - integrity sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw== - dependencies: - object-assign "^4" - vary "^1" - -cross-spawn@^7.0.3: - version "7.0.6" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" - integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -custom-event@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425" - integrity sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg== - -date-format@^4.0.14: - version "4.0.14" - resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.14.tgz#7a8e584434fb169a521c8b7aa481f355810d9400" - integrity sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg== - -debug@2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@^4.1.0, debug@^4.3.4, debug@^4.3.5, debug@~4.4.1: - version "4.4.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" - integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== - dependencies: - ms "^2.1.3" - -decamelize@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" - integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== - -default-gateway@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71" - integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg== - dependencies: - execa "^5.0.0" - -define-lazy-prop@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" - integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== - -depd@2.0.0, depd@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== - -destroy@1.2.0, destroy@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - -detect-node@^2.0.4: - version "2.1.0" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" - integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== - -di@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c" - integrity sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA== - -diff@^5.2.0: - version "5.2.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.2.tgz#0a4742797281d09cfa699b79ea32d27723623bad" - integrity sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A== - -dns-packet@^5.2.2: - version "5.6.1" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.6.1.tgz#ae888ad425a9d1478a0674256ab866de1012cf2f" - integrity sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw== - dependencies: - "@leichtgewicht/ip-codec" "^2.0.1" - -dom-serialize@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/dom-serialize/-/dom-serialize-2.2.1.tgz#562ae8999f44be5ea3076f5419dcd59eb43ac95b" - integrity sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ== - dependencies: - custom-event "~1.0.0" - ent "~2.2.0" - extend "^3.0.0" - void-elements "^2.0.0" - -dunder-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" - integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== - dependencies: - call-bind-apply-helpers "^1.0.1" - es-errors "^1.3.0" - gopd "^1.2.0" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== - -electron-to-chromium@^1.5.402: - version "1.5.407" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.407.tgz#c34e509f8644a7375fdaa5ea5cb7ca73eeaf18df" - integrity sha512-4R8XgQOdfxexCd/u63lRm6wCHjECwI45MV9wxAs2ggtfWe2hwlo1ql97jKsju2IcJ+jFSTwBssyYoiWhh7mauQ== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== - -encodeurl@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" - integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== - -engine.io-parser@~5.2.1: - version "5.2.3" - resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.3.tgz#00dc5b97b1f233a23c9398d0209504cf5f94d92f" - integrity sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q== - -engine.io@~6.6.0: - version "6.6.9" - resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.6.9.tgz#fd17e9f4e3a256423592574b60ac262e91af4b82" - integrity sha512-clKkw4C7nJ22mGgoVcCg6V/W/TxdNyIOTr89k2ONZu81qqkddPFDF0LXcbAwhzPD8DjkiRCjzuiO6Y+fkpD4vg== - dependencies: - "@types/cors" "^2.8.12" - "@types/node" ">=10.0.0" - "@types/ws" "^8.5.12" - accepts "~1.3.4" - base64id "2.0.0" - cookie "~0.7.2" - cors "~2.8.5" - debug "~4.4.1" - engine.io-parser "~5.2.1" - ws "~8.21.0" - -enhanced-resolve@^5.17.1: - version "5.24.5" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz#b4dad3255b7545f07ba5535189868e9f85f47573" - integrity sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.3.3" - -ent@~2.2.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.2.tgz#22a5ed2fd7ce0cbcff1d1474cf4909a44bdb6e85" - integrity sha512-kKvD1tO6BM+oK9HzCPpUdRb4vKFQY/FPTFmurMvh6LlN68VMrdj77w8yp51/kDbpkFOS9J8w5W6zIzgM2H8/hw== - dependencies: - call-bound "^1.0.3" - es-errors "^1.3.0" - punycode "^1.4.1" - safe-regex-test "^1.1.0" - -envinfo@^7.7.3: - version "7.21.0" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.21.0.tgz#04a251be79f92548541f37d13c8b6f22940c3bae" - integrity sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow== - -es-define-property@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" - integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== - -es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -es-module-lexer@^1.2.1: - version "1.7.0" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz#9159601561880a85f2734560a9099b2c31e5372a" - integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA== - -es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.2.tgz#a2d0b373205724dfa525d23b0c3e1b1ca582c99b" - integrity sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw== - dependencies: - es-errors "^1.3.0" - -escalade@^3.1.1, escalade@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" - integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== - -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== - -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -eslint-scope@5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== - -eventemitter3@^4.0.0: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - -events@^3.2.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== - -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - -express@^4.17.3: - version "4.22.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.22.2.tgz#c17ae0981e5efc24b22272f0e041c4662503b700" - integrity sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q== - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "~1.20.5" - content-disposition "~0.5.4" - content-type "~1.0.4" - cookie "~0.7.1" - cookie-signature "~1.0.6" - debug "2.6.9" - depd "2.0.0" - encodeurl "~2.0.0" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "~1.3.1" - fresh "~0.5.2" - http-errors "~2.0.0" - merge-descriptors "1.0.3" - methods "~1.1.2" - on-finished "~2.4.1" - parseurl "~1.3.3" - path-to-regexp "~0.1.12" - proxy-addr "~2.0.7" - qs "~6.15.1" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "~0.19.0" - serve-static "~1.16.2" - setprototypeof "1.2.0" - statuses "~2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -extend@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-uri@^3.0.1: - version "3.1.5" - resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.5.tgz#610f37419a030270430cecd68d74e3d4d96725d0" - integrity sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw== - -fastest-levenshtein@^1.0.12: - version "1.0.16" - resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5" - integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== - -faye-websocket@^0.11.3: - version "0.11.4" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" - integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== - dependencies: - websocket-driver ">=0.5.1" - -fill-range@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" - integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== - dependencies: - to-regex-range "^5.0.1" - -finalhandler@1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" - integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "~2.3.0" - parseurl "~1.3.3" - statuses "~1.5.0" - unpipe "~1.0.0" - -finalhandler@~1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.2.tgz#1ebc2228fc7673aac4a472c310cc05b77d852b88" - integrity sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg== - dependencies: - debug "2.6.9" - encodeurl "~2.0.0" - escape-html "~1.0.3" - on-finished "~2.4.1" - parseurl "~1.3.3" - statuses "~2.0.2" - unpipe "~1.0.0" - -find-up@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -flat@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" - integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== - -flatted@^3.2.7: - version "3.4.4" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.4.tgz#aeeca2a506303f0cee61c59e6c9f2a88d2f29fc6" - integrity sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q== - -follow-redirects@^1.0.0: - version "1.16.0" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.16.0.tgz#28474a159d3b9d11ef62050a14ed60e4df6d61bc" - integrity sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw== - -format-util@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/format-util/-/format-util-1.0.5.tgz#1ffb450c8a03e7bccffe40643180918cc297d271" - integrity sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg== - -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - -fresh@~0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== - -fs-extra@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" - integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^4.0.0" - universalify "^0.1.0" - -fs-monkey@^1.0.4: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.1.0.tgz#632aa15a20e71828ed56b24303363fb1414e5997" - integrity sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw== - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-intrinsic@^1.2.5, get-intrinsic@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" - integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== - dependencies: - call-bind-apply-helpers "^1.0.2" - es-define-property "^1.0.1" - es-errors "^1.3.0" - es-object-atoms "^1.1.1" - function-bind "^1.1.2" - get-proto "^1.0.1" - gopd "^1.2.0" - has-symbols "^1.1.0" - hasown "^2.0.2" - math-intrinsics "^1.1.0" - -get-proto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" - integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== - dependencies: - dunder-proto "^1.0.1" - es-object-atoms "^1.0.0" - -get-stream@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - -glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-to-regexp@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" - integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== - -glob@^7.1.3, glob@^7.1.7: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" - integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^5.0.1" - once "^1.3.0" - -gopd@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" - integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== - -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6: - version "4.2.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -handle-thing@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" - integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-symbols@^1.0.3, has-symbols@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" - integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== - -has-tostringtag@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" - integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== - dependencies: - has-symbols "^1.0.3" - -hasown@^2.0.2, hasown@^2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.4.tgz#8c62d8cb90beb2aad5d0a5b67581ad9854c3f003" - integrity sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A== - dependencies: - function-bind "^1.1.2" - -he@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - -hpack.js@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" - integrity sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ== - dependencies: - inherits "^2.0.1" - obuf "^1.0.0" - readable-stream "^2.0.1" - wbuf "^1.1.0" - -html-entities@^2.3.2: - version "2.6.0" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.6.0.tgz#7c64f1ea3b36818ccae3d3fb48b6974208e984f8" - integrity sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ== - -http-deceiver@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" - integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== - -http-errors@~1.8.0: - version "1.8.1" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c" - integrity sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g== - dependencies: - depd "~1.1.2" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.1" - -http-errors@~2.0.0, http-errors@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.1.tgz#36d2f65bc909c8790018dd36fb4d93da6caae06b" - integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ== - dependencies: - depd "~2.0.0" - inherits "~2.0.4" - setprototypeof "~1.2.0" - statuses "~2.0.2" - toidentifier "~1.0.1" - -http-parser-js@>=0.5.1: - version "0.5.10" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.10.tgz#b3277bd6d7ed5588e20ea73bf724fcbe44609075" - integrity sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA== - -http-proxy-middleware@^2.0.3: - version "2.0.10" - resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.10.tgz#b2df7b705203d7a8c269ac8450cf96b00c532f94" - integrity sha512-RKzRWNPxUZqbuk3BC5mGVJbBnWgr+diEnjJexIOytFbBzDy88Fbh/YvBr3DsNrl1jYAfjWfpATEv0NO35FDuPQ== - dependencies: - "@types/http-proxy" "^1.17.8" - http-proxy "^1.18.1" - is-glob "^4.0.1" - is-plain-obj "^3.0.0" - micromatch "^4.0.2" - -http-proxy@^1.18.1: - version "1.18.1" - resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" - integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== - dependencies: - eventemitter3 "^4.0.0" - follow-redirects "^1.0.0" - requires-port "^1.0.0" - -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - -iconv-lite@^0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - -iconv-lite@~0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -import-local@^3.0.2: - version "3.2.0" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" - integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== - dependencies: - pkg-dir "^4.2.0" - resolve-cwd "^3.0.0" - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3, inherits@~2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -interpret@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4" - integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== - -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -ipaddr.js@^2.0.1: - version "2.5.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.5.0.tgz#7d4b6c39f9392fb61cf807de6e425e22c10e061f" - integrity sha512-aq+t5NAc+cS6rZQQVWC2x98CPqGtKKTMDd4Gaodv0wShnItdKg/51djkGJ1hqH+Oy0ivDftCbSLCQob8zso01w== - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-core-module@^2.16.1: - version "2.16.2" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.2.tgz#3e07450a8080ebce3fbf0cac494f4d2ab324e082" - integrity sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA== - dependencies: - hasown "^2.0.3" - -is-docker@^2.0.0, is-docker@^2.1.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" - integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-plain-obj@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" - integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== - -is-plain-obj@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" - integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== - -is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-regex@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22" - integrity sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g== - dependencies: - call-bound "^1.0.2" - gopd "^1.2.0" - has-tostringtag "^1.0.2" - hasown "^2.0.2" - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -is-unicode-supported@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" - integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== - -is-wsl@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== - dependencies: - is-docker "^2.0.0" - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - -isbinaryfile@^4.0.8: - version "4.0.10" - resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.10.tgz#0c5b5e30c2557a2f06febd37b7322946aaee42b3" - integrity sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== - -jest-worker@^27.4.5: - version "27.5.1" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" - integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -js-yaml@^4.1.0: - version "4.3.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.3.1.tgz#01216c001d67f48e2cd560d708c7af21090a3848" - integrity sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ== - dependencies: - argparse "^2.0.1" - -json-parse-even-better-errors@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-schema-traverse@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" - integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== - -jsonfile@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== - optionalDependencies: - graceful-fs "^4.1.6" - -karma-chrome-launcher@3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-3.2.0.tgz#eb9c95024f2d6dfbb3748d3415ac9b381906b9a9" - integrity sha512-rE9RkUPI7I9mAxByQWkGJFXfFD6lE4gC5nPuZdobf/QdTEJI6EU4yIay/cfU/xV4ZxlM5JiTv7zWYgA64NpS5Q== - dependencies: - which "^1.2.1" - -karma-mocha@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/karma-mocha/-/karma-mocha-2.0.1.tgz#4b0254a18dfee71bdbe6188d9a6861bf86b0cd7d" - integrity sha512-Tzd5HBjm8his2OA4bouAsATYEpZrp9vC7z5E5j4C5Of5Rrs1jY67RAwXNcVmd/Bnk1wgvQRou0zGVLey44G4tQ== - dependencies: - minimist "^1.2.3" - -karma-sourcemap-loader@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/karma-sourcemap-loader/-/karma-sourcemap-loader-0.4.0.tgz#b01d73f8f688f533bcc8f5d273d43458e13b5488" - integrity sha512-xCRL3/pmhAYF3I6qOrcn0uhbQevitc2DERMPH82FMnG+4WReoGcGFZb1pURf2a5apyrOHRdvD+O6K7NljqKHyA== - dependencies: - graceful-fs "^4.2.10" - -karma-webpack@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-5.0.1.tgz#4eafd31bbe684a747a6e8f3e4ad373e53979ced4" - integrity sha512-oo38O+P3W2mSPCSUrQdySSPv1LvPpXP+f+bBimNomS5sW+1V4SuhCuW8TfJzV+rDv921w2fDSDw0xJbPe6U+kQ== - dependencies: - glob "^7.1.3" - minimatch "^9.0.3" - webpack-merge "^4.1.5" - -karma@6.4.4: - version "6.4.4" - resolved "https://registry.yarnpkg.com/karma/-/karma-6.4.4.tgz#dfa5a426cf5a8b53b43cd54ef0d0d09742351492" - integrity sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w== - dependencies: - "@colors/colors" "1.5.0" - body-parser "^1.19.0" - braces "^3.0.2" - chokidar "^3.5.1" - connect "^3.7.0" - di "^0.0.1" - dom-serialize "^2.2.1" - glob "^7.1.7" - graceful-fs "^4.2.6" - http-proxy "^1.18.1" - isbinaryfile "^4.0.8" - lodash "^4.17.21" - log4js "^6.4.1" - mime "^2.5.2" - minimatch "^3.0.4" - mkdirp "^0.5.5" - qjobs "^1.2.0" - range-parser "^1.2.1" - rimraf "^3.0.2" - socket.io "^4.7.2" - source-map "^0.6.1" - tmp "^0.2.1" - ua-parser-js "^0.7.30" - yargs "^16.1.1" - -kind-of@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -kotlin-web-helpers@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/kotlin-web-helpers/-/kotlin-web-helpers-2.0.0.tgz#b112096b273c1e733e0b86560998235c09a19286" - integrity sha512-xkVGl60Ygn/zuLkDPx+oHj7jeLR7hCvoNF99nhwXMn8a3ApB4lLiC9pk4ol4NHPjyoCbvQctBqvzUcp8pkqyWw== - dependencies: - format-util "^1.0.5" - -launch-editor@^2.6.0: - version "2.14.1" - resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.14.1.tgz#f7e0da3f58aaea03fea01074d840b5f739ed7ddc" - integrity sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA== - dependencies: - picocolors "^1.1.1" - shell-quote "^1.8.4" - -loader-runner@^4.2.0: - version "4.3.2" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.2.tgz#9913d3a15971f8f635915e601fb5c9d495d918e9" - integrity sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w== - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -lodash@^4.17.15, lodash@^4.17.21: - version "4.18.1" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.18.1.tgz#ff2b66c1f6326d59513de2407bf881439812771c" - integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q== - -log-symbols@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" - integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== - dependencies: - chalk "^4.1.0" - is-unicode-supported "^0.1.0" - -log4js@^6.4.1: - version "6.9.1" - resolved "https://registry.yarnpkg.com/log4js/-/log4js-6.9.1.tgz#aba5a3ff4e7872ae34f8b4c533706753709e38b6" - integrity sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g== - dependencies: - date-format "^4.0.14" - debug "^4.3.4" - flatted "^3.2.7" - rfdc "^1.3.0" - streamroller "^3.1.5" - -math-intrinsics@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" - integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== - -memfs@^3.4.3: - version "3.6.0" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6" - integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ== - dependencies: - fs-monkey "^1.0.4" - -merge-descriptors@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" - integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== - -micromatch@^4.0.2: - version "4.0.8" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" - integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== - dependencies: - braces "^3.0.3" - picomatch "^2.3.1" - -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -"mime-db@>= 1.43.0 < 2": - version "1.54.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5" - integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== - -mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.24, mime-types@~2.1.34, mime-types@~2.1.35: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -mime@^2.5.2: - version "2.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" - integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -minimalistic-assert@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== - -minimatch@^3.0.4, minimatch@^3.1.1: - version "3.1.5" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.5.tgz#580c88f8d5445f2bd6aa8f3cadefa0de79fbd69e" - integrity sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^5.0.1, minimatch@^5.1.6: - version "5.1.9" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.9.tgz#1293ef15db0098b394540e8f9f744f9fda8dee4b" - integrity sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw== - dependencies: - brace-expansion "^2.0.1" - -minimatch@^9.0.3: - version "9.0.9" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.9.tgz#9b0cb9fcb78087f6fd7eababe2511c4d3d60574e" - integrity sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg== - dependencies: - brace-expansion "^2.0.2" - -minimist@^1.2.3, minimist@^1.2.6: - version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - -mkdirp@^0.5.5: - version "0.5.6" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" - integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== - dependencies: - minimist "^1.2.6" - -mocha@10.7.3: - version "10.7.3" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.7.3.tgz#ae32003cabbd52b59aece17846056a68eb4b0752" - integrity sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A== - dependencies: - ansi-colors "^4.1.3" - browser-stdout "^1.3.1" - chokidar "^3.5.3" - debug "^4.3.5" - diff "^5.2.0" - escape-string-regexp "^4.0.0" - find-up "^5.0.0" - glob "^8.1.0" - he "^1.2.0" - js-yaml "^4.1.0" - log-symbols "^4.1.0" - minimatch "^5.1.6" - ms "^2.1.3" - serialize-javascript "^6.0.2" - strip-json-comments "^3.1.1" - supports-color "^8.1.1" - workerpool "^6.5.1" - yargs "^16.2.0" - yargs-parser "^20.2.9" - yargs-unparser "^2.0.0" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - -ms@2.1.3, ms@^2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -multicast-dns@^7.2.5: - version "7.2.5" - resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced" - integrity sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg== - dependencies: - dns-packet "^5.2.2" - thunky "^1.0.2" - -negotiator@0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - -negotiator@~0.6.4: - version "0.6.4" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7" - integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w== - -neo-async@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - -node-forge@^1: - version "1.4.0" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.4.0.tgz#1c7b7d8bdc2d078739f58287d589d903a11b2fc2" - integrity sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ== - -node-releases@^2.0.53: - version "2.0.53" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.53.tgz#0fe5ad8a7935e075172f574e56f0c20f07fa41af" - integrity sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ== - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - -object-assign@^4: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - -object-inspect@^1.13.3, object-inspect@^1.13.4: - version "1.13.4" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" - integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== - -obuf@^1.0.0, obuf@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" - integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== - -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - integrity sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww== - dependencies: - ee-first "1.1.1" - -on-finished@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - -on-headers@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.1.0.tgz#59da4f91c45f5f989c6e4bcedc5a3b0aed70ff65" - integrity sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A== - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -open@^8.0.9: - version "8.4.2" - resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" - integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== - dependencies: - define-lazy-prop "^2.0.0" - is-docker "^2.1.1" - is-wsl "^2.2.0" - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-limit@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -p-retry@^4.5.0: - version "4.6.2" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16" - integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== - dependencies: - "@types/retry" "0.12.0" - retry "^0.13.1" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-to-regexp@~0.1.12: - version "0.1.13" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.13.tgz#9b22ec16bc3ab88d05a0c7e369869421401ab17d" - integrity sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA== - -picocolors@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" - integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== - -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.2.tgz#5a942915e26b372dc0f0e6753149a16e6b1c5601" - integrity sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA== - -pkg-dir@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -proxy-addr@~2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - -punycode@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== - -punycode@^2.1.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" - integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== - -qjobs@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/qjobs/-/qjobs-1.2.0.tgz#c45e9c61800bd087ef88d7e256423bdd49e5d071" - integrity sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg== - -qs@~6.15.1: - version "6.15.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.15.3.tgz#76852132a58ed5c7c0ef67e4441b9bb5d6061b3b" - integrity sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A== - dependencies: - es-define-property "^1.0.1" - side-channel "^1.1.1" - -randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -range-parser@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.3.0.tgz#d7f19be812bb62721472b45d3be219ef09572b47" - integrity sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw== - -range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@~2.5.3: - version "2.5.3" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.3.tgz#11c6650ee770a7de1b494f197927de0c923822e2" - integrity sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA== - dependencies: - bytes "~3.1.2" - http-errors "~2.0.1" - iconv-lite "~0.4.24" - unpipe "~1.0.0" - -readable-stream@^2.0.1: - version "2.3.8" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" - integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@^3.0.6: - version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -rechoir@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22" - integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ== - dependencies: - resolve "^1.20.0" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -require-from-string@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" - integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== - -requires-port@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" - integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== - -resolve-cwd@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" - integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== - dependencies: - resolve-from "^5.0.0" - -resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - -resolve@^1.20.0: - version "1.22.12" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.12.tgz#f5b2a680897c69c238a13cd16b15671f8b73549f" - integrity sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA== - dependencies: - es-errors "^1.3.0" - is-core-module "^2.16.1" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -retry@^0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" - integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== - -rfdc@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca" - integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== - -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.1.0, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-regex-test@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz#7f87dfb67a3150782eaaf18583ff5d1711ac10c1" - integrity sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - is-regex "^1.2.1" - -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -schema-utils@^3.2.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" - integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== - dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - -schema-utils@^4.0.0, schema-utils@^4.3.0: - version "4.3.3" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.3.3.tgz#5b1850912fa31df90716963d45d9121fdfc09f46" - integrity sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA== - dependencies: - "@types/json-schema" "^7.0.9" - ajv "^8.9.0" - ajv-formats "^2.1.1" - ajv-keywords "^5.1.0" - -select-hose@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" - integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== - -selfsigned@^2.1.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.4.1.tgz#560d90565442a3ed35b674034cec4e95dceb4ae0" - integrity sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q== - dependencies: - "@types/node-forge" "^1.3.0" - node-forge "^1" - -send@~0.19.0, send@~0.19.1: - version "0.19.2" - resolved "https://registry.yarnpkg.com/send/-/send-0.19.2.tgz#59bc0da1b4ea7ad42736fd642b1c4294e114ff29" - integrity sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~2.0.0" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "~0.5.2" - http-errors "~2.0.1" - mime "1.6.0" - ms "2.1.3" - on-finished "~2.4.1" - range-parser "~1.2.1" - statuses "~2.0.2" - -serialize-javascript@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" - integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== - dependencies: - randombytes "^2.1.0" - -serve-index@^1.9.1: - version "1.9.2" - resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.2.tgz#2988e3612106d78a5e4849ddff552ce7bd3d9bcb" - integrity sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ== - dependencies: - accepts "~1.3.8" - batch "0.6.1" - debug "2.6.9" - escape-html "~1.0.3" - http-errors "~1.8.0" - mime-types "~2.1.35" - parseurl "~1.3.3" - -serve-static@~1.16.2: - version "1.16.3" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.3.tgz#a97b74d955778583f3862a4f0b841eb4d5d78cf9" - integrity sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA== - dependencies: - encodeurl "~2.0.0" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "~0.19.1" - -setprototypeof@1.2.0, setprototypeof@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - -shallow-clone@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" - integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== - dependencies: - kind-of "^6.0.2" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -shell-quote@^1.8.4: - version "1.10.0" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.10.0.tgz#482033e192e4f5c07151521ffa03400ec71b1b0f" - integrity sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA== - -side-channel-list@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.1.tgz#c2e0b5a14a540aebee3bbc6c3f8666cc9b509127" - integrity sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w== - dependencies: - es-errors "^1.3.0" - object-inspect "^1.13.4" - -side-channel-map@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" - integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - get-intrinsic "^1.2.5" - object-inspect "^1.13.3" - -side-channel-weakmap@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" - integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== - dependencies: - call-bound "^1.0.2" - es-errors "^1.3.0" - get-intrinsic "^1.2.5" - object-inspect "^1.13.3" - side-channel-map "^1.0.1" - -side-channel@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.1.tgz#ea02c62e05dc4bea67d4442f0fb71ee192f8e0ab" - integrity sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ== - dependencies: - es-errors "^1.3.0" - object-inspect "^1.13.4" - side-channel-list "^1.0.1" - side-channel-map "^1.0.1" - side-channel-weakmap "^1.0.2" - -signal-exit@^3.0.3: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -socket.io-adapter@~2.5.2: - version "2.5.8" - resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.8.tgz#c9fcabf602dbd5b09258ca98e5ec6a7ae4360698" - integrity sha512-6Oy52pbg+kvdCVvjcN+FnY7BvxZ7cIHNScbvztT/It5d0vbwoJoVZmF2gjJmnV0/4WlXRfG15zc45ySk9Ah8bw== - dependencies: - debug "~4.4.1" - ws "~8.21.0" - -socket.io-parser@~4.2.4: - version "4.2.7" - resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.7.tgz#679e51fe24d1c81df90fc5f7efe4a5f432fe99c0" - integrity sha512-IH/iSeO9T6gz1KkFleGDWkG9N3dl4jXVYUtMhIqH10Md0ttMer8nUNWiP1DKuNrybD2xBrixLJdCC9J6ECoYkg== - dependencies: - "@socket.io/component-emitter" "~3.1.0" - debug "~4.4.1" - -socket.io@^4.7.2: - version "4.8.3" - resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.8.3.tgz#ca6ba1431c69532e1e0a6f496deebeb601dbc4df" - integrity sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A== - dependencies: - accepts "~1.3.4" - base64id "~2.0.0" - cors "~2.8.5" - debug "~4.4.1" - engine.io "~6.6.0" - socket.io-adapter "~2.5.2" - socket.io-parser "~4.2.4" - -sockjs@^0.3.24: - version "0.3.24" - resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" - integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== - dependencies: - faye-websocket "^0.11.3" - uuid "^8.3.2" - websocket-driver "^0.7.4" - -source-map-js@^1.0.2: - version "1.2.1" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" - integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== - -source-map-loader@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/source-map-loader/-/source-map-loader-5.0.0.tgz#f593a916e1cc54471cfc8851b905c8a845fc7e38" - integrity sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA== - dependencies: - iconv-lite "^0.6.3" - source-map-js "^1.0.2" - -source-map-support@~0.5.20: - version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0, source-map@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -spdy-transport@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" - integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== - dependencies: - debug "^4.1.0" - detect-node "^2.0.4" - hpack.js "^2.1.6" - obuf "^1.1.2" - readable-stream "^3.0.6" - wbuf "^1.7.3" - -spdy@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" - integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== - dependencies: - debug "^4.1.0" - handle-thing "^2.0.0" - http-deceiver "^1.2.7" - select-hose "^2.0.0" - spdy-transport "^3.0.0" - -"statuses@>= 1.5.0 < 2", statuses@~1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== - -statuses@~2.0.1, statuses@~2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382" - integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== - -streamroller@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-3.1.5.tgz#1263182329a45def1ffaef58d31b15d13d2ee7ff" - integrity sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw== - dependencies: - date-format "^4.0.14" - debug "^4.3.4" - fs-extra "^8.1.0" - -string-width@^4.1.0, string-width@^4.2.0: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - -strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-color@^8.0.0, supports-color@^8.1.1: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -tapable@^2.1.1, tapable@^2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.3.3.tgz#5da7c9992c46038221267985ab28421a8879f160" - integrity sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A== - -terser-webpack-plugin@^5.3.10: - version "5.6.1" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.6.1.tgz#47bc41bd8b8fab8383b62ec763b7394829097e7b" - integrity sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ== - dependencies: - "@jridgewell/trace-mapping" "^0.3.25" - jest-worker "^27.4.5" - schema-utils "^4.3.0" - terser "^5.31.1" - -terser@^5.31.1: - version "5.50.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.50.0.tgz#4e560a46704f9172f96ba31c3bd6dc108ad2cead" - integrity sha512-CN9BVxWhgS/hRxtUMjtC2uRWSTcSfQFHMDWma6sKKfIivCD91sM+FOPfvwoaRMqCSrUpe1nv3jDamd9eEQ4y+w== - dependencies: - "@jridgewell/source-map" "^0.3.3" - acorn "^8.15.0" - commander "^2.20.0" - source-map-support "~0.5.20" - -thunky@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" - integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== - -tmp@^0.2.1: - version "0.2.7" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.7.tgz#26f4db11d1601ce8012dcb8a798ece1c06a99059" - integrity sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw== - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -toidentifier@1.0.1, toidentifier@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -typescript@5.5.4: - version "5.5.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" - integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== - -ua-parser-js@^0.7.30: - version "0.7.41" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.41.tgz#9f6dee58c389e8afababa62a4a2dc22edb69a452" - integrity sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg== - -undici-types@~8.3.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-8.3.0.tgz#44e9fc9f3244648cdea35e4f9bb2d681e9410809" - integrity sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ== - -universalify@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" - integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== - -unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== - -update-browserslist-db@^1.3.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz#a71c28dd22f505481dbc4689087b18d933e90afd" - integrity sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ== - dependencies: - escalade "^3.2.0" - picocolors "^1.1.1" - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -util-deprecate@^1.0.1, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== - -uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - -vary@^1, vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== - -void-elements@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" - integrity sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung== - -watchpack@^2.4.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.5.2.tgz#e12e82d84674266fc1c6dbfe38891b92ff0522ec" - integrity sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg== - dependencies: - graceful-fs "^4.1.2" - -wbuf@^1.1.0, wbuf@^1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" - integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== - dependencies: - minimalistic-assert "^1.0.0" - -webpack-cli@5.1.4: - version "5.1.4" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-5.1.4.tgz#c8e046ba7eaae4911d7e71e2b25b776fcc35759b" - integrity sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg== - dependencies: - "@discoveryjs/json-ext" "^0.5.0" - "@webpack-cli/configtest" "^2.1.1" - "@webpack-cli/info" "^2.0.2" - "@webpack-cli/serve" "^2.0.5" - colorette "^2.0.14" - commander "^10.0.1" - cross-spawn "^7.0.3" - envinfo "^7.7.3" - fastest-levenshtein "^1.0.12" - import-local "^3.0.2" - interpret "^3.1.1" - rechoir "^0.8.0" - webpack-merge "^5.7.3" - -webpack-dev-middleware@^5.3.4: - version "5.3.4" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz#eb7b39281cbce10e104eb2b8bf2b63fce49a3517" - integrity sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q== - dependencies: - colorette "^2.0.10" - memfs "^3.4.3" - mime-types "^2.1.31" - range-parser "^1.2.1" - schema-utils "^4.0.0" - -webpack-dev-server@4.15.2: - version "4.15.2" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz#9e0c70a42a012560860adb186986da1248333173" - integrity sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g== - dependencies: - "@types/bonjour" "^3.5.9" - "@types/connect-history-api-fallback" "^1.3.5" - "@types/express" "^4.17.13" - "@types/serve-index" "^1.9.1" - "@types/serve-static" "^1.13.10" - "@types/sockjs" "^0.3.33" - "@types/ws" "^8.5.5" - ansi-html-community "^0.0.8" - bonjour-service "^1.0.11" - chokidar "^3.5.3" - colorette "^2.0.10" - compression "^1.7.4" - connect-history-api-fallback "^2.0.0" - default-gateway "^6.0.3" - express "^4.17.3" - graceful-fs "^4.2.6" - html-entities "^2.3.2" - http-proxy-middleware "^2.0.3" - ipaddr.js "^2.0.1" - launch-editor "^2.6.0" - open "^8.0.9" - p-retry "^4.5.0" - rimraf "^3.0.2" - schema-utils "^4.0.0" - selfsigned "^2.1.1" - serve-index "^1.9.1" - sockjs "^0.3.24" - spdy "^4.0.2" - webpack-dev-middleware "^5.3.4" - ws "^8.13.0" - -webpack-merge@^4.1.5: - version "4.2.2" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.2.2.tgz#a27c52ea783d1398afd2087f547d7b9d2f43634d" - integrity sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g== - dependencies: - lodash "^4.17.15" - -webpack-merge@^5.7.3: - version "5.10.0" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.10.0.tgz#a3ad5d773241e9c682803abf628d4cd62b8a4177" - integrity sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA== - dependencies: - clone-deep "^4.0.1" - flat "^5.0.2" - wildcard "^2.0.0" - -webpack-sources@^3.2.3: - version "3.5.1" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.5.1.tgz#76c2418486dcc02b2aa0694c104176c2858fe84a" - integrity sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw== - -webpack@5.94.0: - version "5.94.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.94.0.tgz#77a6089c716e7ab90c1c67574a28da518a20970f" - integrity sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg== - dependencies: - "@types/estree" "^1.0.5" - "@webassemblyjs/ast" "^1.12.1" - "@webassemblyjs/wasm-edit" "^1.12.1" - "@webassemblyjs/wasm-parser" "^1.12.1" - acorn "^8.7.1" - acorn-import-attributes "^1.9.5" - browserslist "^4.21.10" - chrome-trace-event "^1.0.2" - enhanced-resolve "^5.17.1" - es-module-lexer "^1.2.1" - eslint-scope "5.1.1" - events "^3.2.0" - glob-to-regexp "^0.4.1" - graceful-fs "^4.2.11" - json-parse-even-better-errors "^2.3.1" - loader-runner "^4.2.0" - mime-types "^2.1.27" - neo-async "^2.6.2" - schema-utils "^3.2.0" - tapable "^2.1.1" - terser-webpack-plugin "^5.3.10" - watchpack "^2.4.1" - webpack-sources "^3.2.3" - -websocket-driver@>=0.5.1, websocket-driver@^0.7.4: - version "0.7.5" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.5.tgz#569d22764ab21f2de20af0e74b411e8ae5a0fa46" - integrity sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA== - dependencies: - http-parser-js ">=0.5.1" - safe-buffer ">=5.1.0" - websocket-extensions ">=0.1.1" - -websocket-extensions@>=0.1.1: - version "0.1.4" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" - integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== - -which@^1.2.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -wildcard@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67" - integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== - -workerpool@^6.5.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" - integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -ws@^8.13.0, ws@~8.21.0: - version "8.21.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.21.3.tgz#660b4faddb6a3e575c86e078126919961f4de4fc" - integrity sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw== - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yargs-parser@^20.2.2, yargs-parser@^20.2.9: - version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== - -yargs-unparser@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" - integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== - dependencies: - camelcase "^6.0.0" - decamelize "^4.0.0" - flat "^5.0.2" - is-plain-obj "^2.1.0" - -yargs@^16.1.1, yargs@^16.2.0: - version "16.2.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.2.tgz#c56731dca0d2788ae0866dd3c83907d6bab85f7d" - integrity sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index c0a92ca..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,65 +0,0 @@ -[build-system] -requires = ["flit_core>=3.4,<4"] -build-backend = "flit_core.buildapi" - -[project] -name = "toolchain" -version = "0.0.1" -description = "Python Multiplatform Build Plugin/Tool with Kotlin Multiplatform Mobile" -readme = "README.md" -requires-python = ">=3.9, <3.14" -license = {file = "LICENSE"} -authors = [ - {name = "THISISTHEPY", email = "thisisthepy@gmail.com"} -] -classifiers = [ - "Development Status :: 3 - Alpha", - "Environment :: Console", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", - "Topic :: Software Development :: Build Tools", -] -dependencies = [ -] - -[project.optional-dependencies] -dev = [ - "pytest>=7.0.0", - "black>=23.0.0", - "isort>=5.12.0", - "mypy>=1.0.0", -] -docs = [ - "sphinx>=6.0.0", - "sphinx-rtd-theme>=1.2.0", -] - -[project.urls] -"Homepage" = "https://github.com/thisisthepy/toolchain" -"Bug Tracker" = "https://github.com/thisisthepy/toolchain/issues" -"Documentation" = "https://thisisthepy.github.io/" - -[tool.flit.module] -name = "toolchain" - -[tool.black] -line-length = 88 -target-version = ["py39", "py310", "py311", "py312"] - -[tool.isort] -profile = "black" -line_length = 88 - -[tool.mypy] -python_version = "3.9" -warn_return_any = true -warn_unused_configs = true -disallow_untyped_defs = true -disallow_incomplete_defs = true diff --git a/usage-example/build.gradle.kts b/sample/build.gradle.kts similarity index 88% rename from usage-example/build.gradle.kts rename to sample/build.gradle.kts index b3481d6..d78d0de 100644 --- a/usage-example/build.gradle.kts +++ b/sample/build.gradle.kts @@ -13,14 +13,14 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.compose.multiplatform) alias(libs.plugins.compose.compiler) - // usage-example is its own build (settings.gradle.kts here). The toolchain plugin is resolved by - // coordinate from mavenLocal (`./gradlew :toolchain:publishToMavenLocal` in ../), as is + // sample is its own build (settings.gradle.kts here). The toolchain plugin is resolved by + // coordinate from mavenLocal (`./gradlew :toolchain-gradle-plugin:publishToMavenLocal` in ../), as is // python-multiplatform's bindings plugin, which stages and packages the CPython stdlib (#22). alias(libs.plugins.toolchain) alias(libs.plugins.python.multiplatform.bindings) } -// Exercises the target DSL surface end to end -- until now `usage-example` did not apply the +// Exercises the target DSL surface end to end -- until now `sample` did not apply the // plugin at all, so the DSL in `dsl/` had no executable definition (`docs/ecosystem.md` §2, §4 // item 7). This is deliberately the subset of `(플러그인예시)build.gradle.kts` that today's tasks // (`PythonPlugin.kt`) actually read: `compileSdk`, `packaging`, `buildTypes`, `platforms`, and @@ -33,7 +33,7 @@ python { // -- the zip, and now the staging tasks -- correctly produces nothing, which makes "the artifact // contains Python" unfalsifiable. `example_py` deliberately includes a non-`.py` file, because // `ResourceBundler` carries data files next to modules and staging has to preserve that. - localLibraryPath = "python" + localLibraryPath = "src/commonMain/python" // One real dependency, so a bundle and the packaged desktop app prove that installed packages // arrive (CI's consumer and desktop-e2e jobs; the --python-smoke mode imports it). sourceSets { @@ -56,7 +56,7 @@ python { } } packaging { - fileName = "usage-example" + fileName = "sample" } // `compose = true` is left out: it needs `python.compose.pythonxCompose` and // `python.compose.kotlinModule`, and neither artifact is published yet (docs/SPEC.md §1.16). @@ -180,3 +180,12 @@ compose.desktop { } } } + +// The wasm target's yarn lock lives in this build's gradle/ rather than a kotlin-js-store/ at the root +// (toolchain#62; the same decision as python-multiplatform #110). +plugins.withType { + extensions.getByType().apply { + lockFileDirectoryProperty.set(layout.projectDirectory.dir("gradle")) + lockFileNameProperty.set("wasm-yarn.lock") + } +} diff --git a/usage-example/gradle.properties b/sample/gradle.properties similarity index 100% rename from usage-example/gradle.properties rename to sample/gradle.properties diff --git a/usage-example/gradle/libs.versions.toml b/sample/gradle/libs.versions.toml similarity index 85% rename from usage-example/gradle/libs.versions.toml rename to sample/gradle/libs.versions.toml index 5e4f42c..c11606c 100644 --- a/usage-example/gradle/libs.versions.toml +++ b/sample/gradle/libs.versions.toml @@ -1,6 +1,6 @@ -# usage-example is its own build (toolchain#22): a consumer of python-multiplatform, so it uses +# sample/ is its own build (toolchain#22): a consumer of python-multiplatform, so it uses # python-multiplatform's toolchain (its gradle/libs.versions.toml), not the toolchain plugin's. -# The plugin itself (../toolchain) stays on its own Kotlin for its consumers. +# The plugin itself (../toolchain-gradle-plugin) stays on its own Kotlin for its consumers. [versions] agp = "8.10.1" android-compileSdk = "36" @@ -14,7 +14,7 @@ kotlinx-coroutines = "1.10.1" # python-multiplatform's coordinates today (its #70 will unify the plugin's version with the library's). python-multiplatform = "3.14.7-alpha01" python-multiplatform-plugin = "3.13.0" -# The toolchain plugin, from ../toolchain via mavenLocal (`./gradlew :toolchain:publishToMavenLocal`). +# The toolchain plugin, from ../toolchain-gradle-plugin via mavenLocal (`./gradlew :toolchain-gradle-plugin:publishToMavenLocal`). toolchain = "1.0.0-alpha" [libraries] diff --git a/usage-example/settings.gradle.kts b/sample/settings.gradle.kts similarity index 67% rename from usage-example/settings.gradle.kts rename to sample/settings.gradle.kts index 9d87156..20191c3 100644 --- a/usage-example/settings.gradle.kts +++ b/sample/settings.gradle.kts @@ -1,6 +1,7 @@ -// usage-example is its own Gradle build (toolchain#22). It consumes python-multiplatform, which is -// built with Kotlin 2.4, so it uses that toolchain (gradle/libs.versions.toml here); the toolchain -// plugin in ../toolchain stays on its own Kotlin for its consumers. Both plugins and the +// sample/ is its own Gradle build (toolchain#22, #62), run with the repository's wrapper: +// ./gradlew -p sample …. It consumes python-multiplatform, which is built with Kotlin 2.4, so it uses +// that toolchain (gradle/libs.versions.toml here); the plugin in ../toolchain-gradle-plugin stays on +// its own Kotlin for its consumers. Both plugins and the // python-multiplatform library come from mavenLocal: publish them first (see AGENTS.md §16). pluginManagement { repositories { @@ -31,4 +32,4 @@ dependencyResolutionManagement { } } -rootProject.name = "usage-example" +rootProject.name = "sample" diff --git a/usage-example/src/androidMain/AndroidManifest.xml b/sample/src/androidMain/AndroidManifest.xml similarity index 100% rename from usage-example/src/androidMain/AndroidManifest.xml rename to sample/src/androidMain/AndroidManifest.xml diff --git a/usage-example/src/androidMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/MainActivity.kt b/sample/src/androidMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/MainActivity.kt similarity index 100% rename from usage-example/src/androidMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/MainActivity.kt rename to sample/src/androidMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/MainActivity.kt diff --git a/usage-example/src/androidMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/Platform.android.kt b/sample/src/androidMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/Platform.android.kt similarity index 100% rename from usage-example/src/androidMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/Platform.android.kt rename to sample/src/androidMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/Platform.android.kt diff --git a/usage-example/src/androidMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/PythonSmoke.android.kt b/sample/src/androidMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/PythonSmoke.android.kt similarity index 100% rename from usage-example/src/androidMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/PythonSmoke.android.kt rename to sample/src/androidMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/PythonSmoke.android.kt diff --git a/usage-example/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml b/sample/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml similarity index 100% rename from usage-example/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml rename to sample/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml diff --git a/usage-example/src/androidMain/res/drawable/ic_launcher_background.xml b/sample/src/androidMain/res/drawable/ic_launcher_background.xml similarity index 100% rename from usage-example/src/androidMain/res/drawable/ic_launcher_background.xml rename to sample/src/androidMain/res/drawable/ic_launcher_background.xml diff --git a/usage-example/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml b/sample/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from usage-example/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml rename to sample/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml diff --git a/usage-example/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml b/sample/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml similarity index 100% rename from usage-example/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml rename to sample/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml diff --git a/usage-example/src/androidMain/res/mipmap-hdpi/ic_launcher.png b/sample/src/androidMain/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from usage-example/src/androidMain/res/mipmap-hdpi/ic_launcher.png rename to sample/src/androidMain/res/mipmap-hdpi/ic_launcher.png diff --git a/usage-example/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png b/sample/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png similarity index 100% rename from usage-example/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png rename to sample/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png diff --git a/usage-example/src/androidMain/res/mipmap-mdpi/ic_launcher.png b/sample/src/androidMain/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from usage-example/src/androidMain/res/mipmap-mdpi/ic_launcher.png rename to sample/src/androidMain/res/mipmap-mdpi/ic_launcher.png diff --git a/usage-example/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png b/sample/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png similarity index 100% rename from usage-example/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png rename to sample/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png diff --git a/usage-example/src/androidMain/res/mipmap-xhdpi/ic_launcher.png b/sample/src/androidMain/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from usage-example/src/androidMain/res/mipmap-xhdpi/ic_launcher.png rename to sample/src/androidMain/res/mipmap-xhdpi/ic_launcher.png diff --git a/usage-example/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png b/sample/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png similarity index 100% rename from usage-example/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png rename to sample/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png diff --git a/usage-example/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png b/sample/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from usage-example/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png rename to sample/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png diff --git a/usage-example/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png b/sample/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png similarity index 100% rename from usage-example/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png rename to sample/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png diff --git a/usage-example/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png b/sample/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from usage-example/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png rename to sample/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/usage-example/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png b/sample/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png similarity index 100% rename from usage-example/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png rename to sample/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png diff --git a/usage-example/src/androidMain/res/values/strings.xml b/sample/src/androidMain/res/values/strings.xml similarity index 100% rename from usage-example/src/androidMain/res/values/strings.xml rename to sample/src/androidMain/res/values/strings.xml diff --git a/usage-example/src/commonMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/App.kt b/sample/src/commonMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/App.kt similarity index 100% rename from usage-example/src/commonMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/App.kt rename to sample/src/commonMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/App.kt diff --git a/usage-example/src/commonMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/Greeting.kt b/sample/src/commonMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/Greeting.kt similarity index 100% rename from usage-example/src/commonMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/Greeting.kt rename to sample/src/commonMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/Greeting.kt diff --git a/usage-example/src/commonMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/Platform.kt b/sample/src/commonMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/Platform.kt similarity index 100% rename from usage-example/src/commonMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/Platform.kt rename to sample/src/commonMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/Platform.kt diff --git a/sample/src/commonMain/python/pyproject.toml b/sample/src/commonMain/python/pyproject.toml new file mode 100644 index 0000000..16ef0c6 --- /dev/null +++ b/sample/src/commonMain/python/pyproject.toml @@ -0,0 +1,5 @@ +[project] +name = "sample-python" +version = "0.1.0" +description = "The Python half of the sample app, bundled by pypackpack and staged by the toolchain plugin." +requires-python = ">=3.13" diff --git a/usage-example/python/src/main/example_py/__init__.py b/sample/src/commonMain/python/src/main/example_py/__init__.py similarity index 80% rename from usage-example/python/src/main/example_py/__init__.py rename to sample/src/commonMain/python/src/main/example_py/__init__.py index 330c213..2b57ef6 100644 --- a/usage-example/python/src/main/example_py/__init__.py +++ b/sample/src/commonMain/python/src/main/example_py/__init__.py @@ -8,4 +8,4 @@ def greeting() -> str: - return f"Hello from usage-example's Python payload {VERSION}" + return f"Hello from the sample's Python payload {VERSION}" diff --git a/usage-example/python/src/main/example_py/data/table.json b/sample/src/commonMain/python/src/main/example_py/data/table.json similarity index 100% rename from usage-example/python/src/main/example_py/data/table.json rename to sample/src/commonMain/python/src/main/example_py/data/table.json diff --git a/usage-example/src/desktopMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/Platform.jvm.kt b/sample/src/desktopMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/Platform.jvm.kt similarity index 100% rename from usage-example/src/desktopMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/Platform.jvm.kt rename to sample/src/desktopMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/Platform.jvm.kt diff --git a/usage-example/src/desktopMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/PythonSmoke.kt b/sample/src/desktopMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/PythonSmoke.kt similarity index 100% rename from usage-example/src/desktopMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/PythonSmoke.kt rename to sample/src/desktopMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/PythonSmoke.kt diff --git a/usage-example/src/desktopMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/main.kt b/sample/src/desktopMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/main.kt similarity index 100% rename from usage-example/src/desktopMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/main.kt rename to sample/src/desktopMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/main.kt diff --git a/usage-example/src/iosMain/Configuration/Config.xcconfig b/sample/src/iosMain/Configuration/Config.xcconfig similarity index 100% rename from usage-example/src/iosMain/Configuration/Config.xcconfig rename to sample/src/iosMain/Configuration/Config.xcconfig diff --git a/usage-example/src/iosMain/app.xcodeproj/project.pbxproj b/sample/src/iosMain/app.xcodeproj/project.pbxproj similarity index 99% rename from usage-example/src/iosMain/app.xcodeproj/project.pbxproj rename to sample/src/iosMain/app.xcodeproj/project.pbxproj index cd5702a..7217dbf 100644 --- a/usage-example/src/iosMain/app.xcodeproj/project.pbxproj +++ b/sample/src/iosMain/app.xcodeproj/project.pbxproj @@ -176,7 +176,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \\\"YES\\\"\"\n exit 0\nfi\ncd \"$SRCROOT/../..\"\n./gradlew embedAndSignAppleFrameworkForXcode\n"; + shellScript = "if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \\\"YES\\\"\"\n exit 0\nfi\ncd \"$SRCROOT/../../..\"\n./gradlew -p sample embedAndSignAppleFrameworkForXcode\n"; }; A7C3F1E20B5D4C6E8F901234 /* Install Python */ = { isa = PBXShellScriptBuildPhase; diff --git a/usage-example/src/iosMain/install-python-phase.sh b/sample/src/iosMain/install-python-phase.sh similarity index 89% rename from usage-example/src/iosMain/install-python-phase.sh rename to sample/src/iosMain/install-python-phase.sh index 38e762a..7772614 100755 --- a/usage-example/src/iosMain/install-python-phase.sh +++ b/sample/src/iosMain/install-python-phase.sh @@ -1,11 +1,11 @@ #!/usr/bin/env bash -# Xcode Run Script phase for usage-example's iOS app (toolchain#22): put this app's python/ payload, +# Xcode Run Script phase for the sample's iOS app (toolchain#22): put this app's python/ payload, # CPython's standard library and Python.framework into the .app. # # The payload comes from this build: stagePythonBundleIosForXcode prints PYTHON_PAYLOAD_DIR=, which is # passed to python-multiplatform's install-python.sh as PYTHON_PAYLOAD_DIR (that stays valid after # python-multiplatform#90). install-python.sh is the ONLY phase that copies /python/; do not also -# wire toolchain's tools/xcode/stage-python-payload.sh. +# hand-wire a second phase that copies python/. # # INTERIM, until python-multiplatform#90 gives consumers the iOS wiring through its published plugin: # a python-multiplatform checkout (PYTHON_MULTIPLATFORM_DIR, an Xcode build setting or environment @@ -15,10 +15,10 @@ set -euo pipefail # TODO(python-multiplatform#90): no checkout once the plugin provides these to consumers. : "${PYTHON_MULTIPLATFORM_DIR:?set PYTHON_MULTIPLATFORM_DIR to a python-multiplatform checkout (interim, python-multiplatform#90)}" -# 1. This app's payload, from usage-example's own build. Captured, parsed, then checked, so a failing +# 1. This app's payload, from the sample's own build. Captured, parsed, then checked, so a failing # Gradle can never hand rsync an empty source. -cd "$SRCROOT/../.." -out=$(./gradlew -q stagePythonBundleIosForXcode) +cd "$SRCROOT/../../.." +out=$(./gradlew -q -p sample stagePythonBundleIosForXcode) payload=$(printf '%s\n' "$out" | sed -n 's/^PYTHON_PAYLOAD_DIR=//p') [ -n "$payload" ] && [ -d "$payload" ] || { echo "error: no Python payload dir from stagePythonBundleIosForXcode" >&2; exit 1; } export PYTHON_PAYLOAD_DIR="$payload" diff --git a/usage-example/src/iosMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/MainViewController.kt b/sample/src/iosMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/MainViewController.kt similarity index 100% rename from usage-example/src/iosMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/MainViewController.kt rename to sample/src/iosMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/MainViewController.kt diff --git a/usage-example/src/iosMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/Platform.ios.kt b/sample/src/iosMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/Platform.ios.kt similarity index 100% rename from usage-example/src/iosMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/Platform.ios.kt rename to sample/src/iosMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/Platform.ios.kt diff --git a/usage-example/src/iosMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/PythonSmoke.ios.kt b/sample/src/iosMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/PythonSmoke.ios.kt similarity index 100% rename from usage-example/src/iosMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/PythonSmoke.ios.kt rename to sample/src/iosMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/PythonSmoke.ios.kt diff --git a/usage-example/src/iosMain/swift/Assets.xcassets/AccentColor.colorset/Contents.json b/sample/src/iosMain/swift/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from usage-example/src/iosMain/swift/Assets.xcassets/AccentColor.colorset/Contents.json rename to sample/src/iosMain/swift/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/usage-example/src/iosMain/swift/Assets.xcassets/AppIcon.appiconset/Contents.json b/sample/src/iosMain/swift/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from usage-example/src/iosMain/swift/Assets.xcassets/AppIcon.appiconset/Contents.json rename to sample/src/iosMain/swift/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/usage-example/src/iosMain/swift/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png b/sample/src/iosMain/swift/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png similarity index 100% rename from usage-example/src/iosMain/swift/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png rename to sample/src/iosMain/swift/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png diff --git a/usage-example/src/iosMain/swift/Assets.xcassets/Contents.json b/sample/src/iosMain/swift/Assets.xcassets/Contents.json similarity index 100% rename from usage-example/src/iosMain/swift/Assets.xcassets/Contents.json rename to sample/src/iosMain/swift/Assets.xcassets/Contents.json diff --git a/usage-example/src/iosMain/swift/ContentView.swift b/sample/src/iosMain/swift/ContentView.swift similarity index 100% rename from usage-example/src/iosMain/swift/ContentView.swift rename to sample/src/iosMain/swift/ContentView.swift diff --git a/usage-example/src/iosMain/swift/Info.plist b/sample/src/iosMain/swift/Info.plist similarity index 100% rename from usage-example/src/iosMain/swift/Info.plist rename to sample/src/iosMain/swift/Info.plist diff --git a/usage-example/src/iosMain/swift/Preview Content/Preview Assets.xcassets/Contents.json b/sample/src/iosMain/swift/Preview Content/Preview Assets.xcassets/Contents.json similarity index 100% rename from usage-example/src/iosMain/swift/Preview Content/Preview Assets.xcassets/Contents.json rename to sample/src/iosMain/swift/Preview Content/Preview Assets.xcassets/Contents.json diff --git a/usage-example/src/iosMain/swift/app.swift b/sample/src/iosMain/swift/app.swift similarity index 100% rename from usage-example/src/iosMain/swift/app.swift rename to sample/src/iosMain/swift/app.swift diff --git a/usage-example/src/wasmJsMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/Platform.wasmJs.kt b/sample/src/wasmJsMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/Platform.wasmJs.kt similarity index 100% rename from usage-example/src/wasmJsMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/Platform.wasmJs.kt rename to sample/src/wasmJsMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/Platform.wasmJs.kt diff --git a/usage-example/src/wasmJsMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/main.kt b/sample/src/wasmJsMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/main.kt similarity index 100% rename from usage-example/src/wasmJsMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/main.kt rename to sample/src/wasmJsMain/kotlin/org/thisisthepy/python/multiplatform/toolchain/example/main.kt diff --git a/usage-example/src/wasmJsMain/resources/index.html b/sample/src/wasmJsMain/resources/index.html similarity index 100% rename from usage-example/src/wasmJsMain/resources/index.html rename to sample/src/wasmJsMain/resources/index.html diff --git a/usage-example/src/wasmJsMain/resources/styles.css b/sample/src/wasmJsMain/resources/styles.css similarity index 100% rename from usage-example/src/wasmJsMain/resources/styles.css rename to sample/src/wasmJsMain/resources/styles.css diff --git a/settings.gradle.kts b/settings.gradle.kts index de25ed4..d2b0246 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -31,7 +31,7 @@ dependencyResolutionManagement { rootProject.name = "ToolchainProject" -include(":toolchain") -// usage-example is its own build, with its own wrapper: (cd usage-example && ./gradlew …). It uses -// python-multiplatform's Kotlin; the plugin here stays on its own for its consumers (toolchain#22). -include(":tcl") +include(":toolchain-gradle-plugin") +// sample/ is its own build, run with this wrapper: ./gradlew -p sample …. It uses python-multiplatform's +// Kotlin; the plugin here stays on its own for its consumers (toolchain#22, #62). +include(":toolchain-cli") diff --git a/tcl/build.gradle.kts b/toolchain-cli/build.gradle.kts similarity index 91% rename from tcl/build.gradle.kts rename to toolchain-cli/build.gradle.kts index 198bbee..8f37309 100644 --- a/tcl/build.gradle.kts +++ b/toolchain-cli/build.gradle.kts @@ -14,7 +14,7 @@ dependencies { implementation(libs.kotlinx.coroutines.core) // `tcl` ("toolchain-lite", GitHub issue thisisthepy/toolchain#1) is a thin CLI layer over - // `pypackpack`'s dependency backend -- the same artifact `:toolchain` already resolves through + // `pypackpack`'s dependency backend -- the same artifact `:toolchain-gradle-plugin` already resolves through // `mavenLocal()` (see that module's `build.gradle.kts` for the `publishToMavenLocal` // prerequisite this also depends on). implementation("org.thisisthepy.python.multiplatform:packpack:0.1.0") @@ -30,7 +30,7 @@ application { // `packpack` is built with Kotlin 2.3.0 while this module's own `compileKotlin` resolves to // Kotlin 2.1.0 (whichever Kotlin Gradle Plugin version wins the shared plugin classpath across -// this settings). Same escape hatch `:toolchain/build.gradle.kts` already uses for the same +// this settings). Same escape hatch `:toolchain-gradle-plugin/build.gradle.kts` already uses for the same // mismatch, for the same reason: this only relaxes the metadata-format version gate, not // language/API level compatibility. tasks.withType().configureEach { diff --git a/tcl/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Cli.kt b/toolchain-cli/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Cli.kt similarity index 100% rename from tcl/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Cli.kt rename to toolchain-cli/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Cli.kt diff --git a/tcl/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Installer.kt b/toolchain-cli/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Installer.kt similarity index 97% rename from tcl/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Installer.kt rename to toolchain-cli/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Installer.kt index b483bfc..38340b4 100644 --- a/tcl/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Installer.kt +++ b/toolchain-cli/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Installer.kt @@ -33,7 +33,7 @@ fun findProjectRoot(startDir: File): File? { * call the issue's usage example shows. * * Deliberately calls `dependency.backend.BackendInterface` directly -- the same layer - * `:toolchain`'s `InstallDependenciesTask.installWithPackpack` uses -- rather than the + * `:toolchain-gradle-plugin`'s `InstallDependenciesTask.installWithPackpack` uses -- rather than the * `dependency.frontend`/`middleware` layers `pypackpack`'s own CLI is built on. Those resolve the * project root from the JVM-global `user.dir` system property (`pypackpack`'s internal * `findProjectRoot()`); the backend layer takes `workingDir: File?` as an explicit parameter diff --git a/tcl/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/CliArgsTest.kt b/toolchain-cli/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/CliArgsTest.kt similarity index 100% rename from tcl/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/CliArgsTest.kt rename to toolchain-cli/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/CliArgsTest.kt diff --git a/tcl/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/InstallerTest.kt b/toolchain-cli/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/InstallerTest.kt similarity index 93% rename from tcl/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/InstallerTest.kt rename to toolchain-cli/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/InstallerTest.kt index a6f42cd..88019b1 100644 --- a/tcl/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/InstallerTest.kt +++ b/toolchain-cli/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/InstallerTest.kt @@ -11,12 +11,12 @@ import kotlin.test.assertTrue * (toolchain-lite, `tcl install `): a python-only user may or may not already have a * `pypackpack` project at the target directory, and `install` must handle both. Uses real `uv` * and real network access (PyPI), the same tradeoff `InstallDependenciesTaskTest` - * (`:toolchain`) documents and accepts for the same reason: a fake backend would only prove this + * (`:toolchain-gradle-plugin`) documents and accepts for the same reason: a fake backend would only prove this * module calls the right function, not that the resulting `pyproject.toml` is real. * * Pre-implementation state: this file fails to compile (no `Installer`, no `installBlocking`, no * `findProjectRoot` on the classpath) until `Installer.kt` exists -- that is the "red" this - * repository's TDD rule asks for, confirmed by running `:tcl:test` against a `tcl` module that + * repository's TDD rule asks for, confirmed by running `:toolchain-cli:test` against a `tcl` module that * has this test file and a `build.gradle.kts` but no `src/main` yet. */ class InstallerTest { diff --git a/toolchain/build.gradle.kts b/toolchain-gradle-plugin/build.gradle.kts similarity index 95% rename from toolchain/build.gradle.kts rename to toolchain-gradle-plugin/build.gradle.kts index 300aeb1..ee20c67 100644 --- a/toolchain/build.gradle.kts +++ b/toolchain-gradle-plugin/build.gradle.kts @@ -18,8 +18,8 @@ dependencies { // not left as an independent literal. A skewed pin here (previously `2.0.0` against a `2.1.0` // root) put two different `kotlin-gradle-plugin` versions on the plugin classpath any consumer // sees once it applies both this plugin and `org.jetbrains.kotlin.multiplatform` in the same - // `plugins {}` block (as `usage-example/build.gradle.kts` does). Gradle does not merge/conflict - // -resolve those into one: `usage-example/build.gradle.kts` compiled its `KotlinWebpackConfig + // `plugins {}` block (as `sample/build.gradle.kts` does). Gradle does not merge/conflict + // -resolve those into one: `sample/build.gradle.kts` compiled its `KotlinWebpackConfig // .DevServer()` call (browser { commonWebpackConfig { ... } }) against 2.0.0's shape (`proxy: // Map`), while the actually-applied multiplatform plugin (2.1.0, `proxy: // List` as of that release) drove `wasmJsBrowserTest` task creation at runtime -- @@ -49,7 +49,7 @@ dependencies { // Test-only: `PythonPluginAttachmentTest` applies `com.android.application` to a ProjectBuilder // project to check that the staged Android root really lands in `android.sourceSets.main.assets` // (the plugin reaches that object reflectively, so only a real AGP proves the method chain). - // Same version as `usage-example` (`gradle/libs.versions.toml` `agp`); never on the plugin's + // Same version as `sample` (`gradle/libs.versions.toml` `agp`); never on the plugin's // own runtime classpath. testImplementation("com.android.tools.build:gradle:${libs.versions.agp.get()}") } diff --git a/toolchain/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/BuildFeatures.kt b/toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/BuildFeatures.kt similarity index 100% rename from toolchain/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/BuildFeatures.kt rename to toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/BuildFeatures.kt diff --git a/toolchain/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/EmbedLevel.kt b/toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/EmbedLevel.kt similarity index 100% rename from toolchain/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/EmbedLevel.kt rename to toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/EmbedLevel.kt diff --git a/toolchain/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PublishedPythonVersion.kt b/toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PublishedPythonVersion.kt similarity index 100% rename from toolchain/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PublishedPythonVersion.kt rename to toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PublishedPythonVersion.kt diff --git a/toolchain/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonMultiplatformPlugin.kt b/toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonMultiplatformPlugin.kt similarity index 100% rename from toolchain/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonMultiplatformPlugin.kt rename to toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonMultiplatformPlugin.kt diff --git a/toolchain/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPlugin.kt b/toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPlugin.kt similarity index 98% rename from toolchain/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPlugin.kt rename to toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPlugin.kt index 21365fa..835879d 100644 --- a/toolchain/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPlugin.kt +++ b/toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPlugin.kt @@ -107,7 +107,7 @@ class PythonPlugin : Plugin { // destination roots are derived from the build directory alone (`PythonStagingPlatform. // rootIn`): an AGP asset source root has to be declared while the consumer's build script is // still being evaluated, because AGP reads its source sets in its own `afterEvaluate` and - // this plugin -- applied last in a `plugins { }` block, as `usage-example` applies it -- + // this plugin -- applied last in a `plugins { }` block, as `sample` applies it -- // registers its `afterEvaluate` callback after AGP's and therefore runs after it. A // `srcDir` added at that point is added to a source set nothing will look at again. // @@ -266,8 +266,8 @@ class PythonPlugin : Plugin { // second code path doing the same job -- `buildTask` (`BuildPythonArtifactTask`) already // delegates the real work to `pypackpack`'s `ResourceBundler` into `build/pythonBundle` // (this task's own report; `docs/ecosystem.md` §1, §5). Confirmed nothing depended on - // it before deleting: no source under `usage-example/` reads `src/main/assets/python` or - // `build/pythonLibraries`, `usage-example` itself never sets `python.localLibraryPath` + // it before deleting: no source under `sample/` reads `src/main/assets/python` or + // `build/pythonLibraries`, `sample` itself never sets `python.localLibraryPath` // today, and `copyPythonLibrariesToAssets`/`preBuild` wiring existed only inside this // now-removed block, so nothing outside it could have depended on that task by name // either. Wiring the real bundle output into Android assets (or any other platform's @@ -620,7 +620,7 @@ private fun registerStagingTasks(project: Project): Map): List.zip`. `usage-example` declares no platforms, so the only + * host-target bundle, one `.zip`. `sample` declares no platforms, so the only * chain that currently runs end to end is unaffected by any of this. The platform block is what * creates more than one target; without it there is exactly one, and one build type chosen by * `-Ppython.buildType` as before. diff --git a/toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonVersionAgreementTest.kt b/toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonVersionAgreementTest.kt similarity index 100% rename from toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonVersionAgreementTest.kt rename to toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonVersionAgreementTest.kt diff --git a/toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/SourceSetGettingDelegateTest.kt b/toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/SourceSetGettingDelegateTest.kt similarity index 100% rename from toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/SourceSetGettingDelegateTest.kt rename to toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/SourceSetGettingDelegateTest.kt diff --git a/toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/TargetDependenciesTest.kt b/toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/TargetDependenciesTest.kt similarity index 100% rename from toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/TargetDependenciesTest.kt rename to toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/TargetDependenciesTest.kt diff --git a/toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/AssemblePythonPackageTaskTest.kt b/toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/AssemblePythonPackageTaskTest.kt similarity index 100% rename from toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/AssemblePythonPackageTaskTest.kt rename to toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/AssemblePythonPackageTaskTest.kt diff --git a/toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/BuildPythonArtifactTaskTest.kt b/toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/BuildPythonArtifactTaskTest.kt similarity index 100% rename from toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/BuildPythonArtifactTaskTest.kt rename to toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/BuildPythonArtifactTaskTest.kt diff --git a/toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/BuildPythonSdkRejectionTest.kt b/toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/BuildPythonSdkRejectionTest.kt similarity index 100% rename from toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/BuildPythonSdkRejectionTest.kt rename to toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/BuildPythonSdkRejectionTest.kt diff --git a/toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/BytecodeLevelTest.kt b/toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/BytecodeLevelTest.kt similarity index 100% rename from toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/BytecodeLevelTest.kt rename to toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/BytecodeLevelTest.kt diff --git a/toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/StagePythonBundleIosForXcodeTaskTest.kt b/toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/StagePythonBundleIosForXcodeTaskTest.kt similarity index 100% rename from toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/StagePythonBundleIosForXcodeTaskTest.kt rename to toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/StagePythonBundleIosForXcodeTaskTest.kt diff --git a/toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/StagePythonBundleTaskTest.kt b/toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/StagePythonBundleTaskTest.kt similarity index 99% rename from toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/StagePythonBundleTaskTest.kt rename to toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/StagePythonBundleTaskTest.kt index 56c2ba9..384cdc1 100644 --- a/toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/StagePythonBundleTaskTest.kt +++ b/toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/StagePythonBundleTaskTest.kt @@ -121,7 +121,7 @@ class StagePythonBundleTaskTest { // A regression guard for a defect this task actually had. With the bundle declared nowhere, // Gradle had nothing to compare and reported `stagePythonBundleDesktop UP-TO-DATE` on every // run after the first, so the staged tree -- and the jar built from it -- kept a payload - // from an earlier revision of the Python package. Found by building `:usage-example: + // from an earlier revision of the Python package. Found by building `:sample: // desktopJar`, deleting a module, and building again: the module was still in the archive. // // Asserted on the annotation rather than through a Gradle run because up-to-date checking diff --git a/toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/InstallDependenciesTaskTest.kt b/toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/InstallDependenciesTaskTest.kt similarity index 100% rename from toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/InstallDependenciesTaskTest.kt rename to toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/InstallDependenciesTaskTest.kt diff --git a/toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/InstallTargetDependenciesTaskTest.kt b/toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/InstallTargetDependenciesTaskTest.kt similarity index 100% rename from toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/InstallTargetDependenciesTaskTest.kt rename to toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/InstallTargetDependenciesTaskTest.kt diff --git a/toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/KlibDepensTest.kt b/toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/KlibDepensTest.kt similarity index 100% rename from toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/KlibDepensTest.kt rename to toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/KlibDepensTest.kt diff --git a/toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/PipRepositoriesTest.kt b/toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/PipRepositoriesTest.kt similarity index 100% rename from toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/PipRepositoriesTest.kt rename to toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/PipRepositoriesTest.kt diff --git a/toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/PlatformTargetMappingTest.kt b/toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/PlatformTargetMappingTest.kt similarity index 100% rename from toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/PlatformTargetMappingTest.kt rename to toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/PlatformTargetMappingTest.kt diff --git a/toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/PythonExtensionPlatformDslTest.kt b/toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/PythonExtensionPlatformDslTest.kt similarity index 100% rename from toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/PythonExtensionPlatformDslTest.kt rename to toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/PythonExtensionPlatformDslTest.kt diff --git a/toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/PythonSdkTest.kt b/toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/PythonSdkTest.kt similarity index 100% rename from toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/PythonSdkTest.kt rename to toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/PythonSdkTest.kt diff --git a/toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/PythonVersionTest.kt b/toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/PythonVersionTest.kt similarity index 100% rename from toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/PythonVersionTest.kt rename to toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/PythonVersionTest.kt diff --git a/toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/hotreload/WatchAndPushTest.kt b/toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/hotreload/WatchAndPushTest.kt similarity index 100% rename from toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/hotreload/WatchAndPushTest.kt rename to toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/hotreload/WatchAndPushTest.kt diff --git a/toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/typedpython/TypedpythonCheckTaskTest.kt b/toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/typedpython/TypedpythonCheckTaskTest.kt similarity index 96% rename from toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/typedpython/TypedpythonCheckTaskTest.kt rename to toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/typedpython/TypedpythonCheckTaskTest.kt index 7e40e2b..bf6f994 100644 --- a/toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/typedpython/TypedpythonCheckTaskTest.kt +++ b/toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/typedpython/TypedpythonCheckTaskTest.kt @@ -17,7 +17,7 @@ import kotlin.test.BeforeTest * * Needs `uv` on `PATH` and a wheel directory holding the `typedpython` wheel **and** the `pyrefly` * wheel it pins (the gate is installed with `--no-index`, so nothing comes from PyPI): run with - * `-Ptypedpython.wheelDir=` (`toolchain/build.gradle.kts` forwards it as a system property). + * `-Ptypedpython.wheelDir=` (`toolchain-gradle-plugin/build.gradle.kts` forwards it as a system property). * Without it these tests are skipped with that reason -- CI has no wheel directory. * * Every test project lives under this module's `build/` (AGENTS.md §2), and so does the TestKit @@ -26,10 +26,10 @@ import kotlin.test.BeforeTest * The build scripts are Kotlin DSL (`build.gradle.kts`), as a consumer's are. What these tests * **cannot** see: `withPluginClasspath()` injects the plugin's classes instead of resolving them, so * the Kotlin DSL's pin of the buildscript `kotlin-stdlib` to Gradle's embedded version - * (`{strictly 1.9.23}` on Gradle 8.9, see `:usage-example:buildEnvironment`) does not apply here. A + * (`{strictly 1.9.23}` on Gradle 8.9, see `:sample:buildEnvironment`) does not apply here. A * gate install through `pypackpack`'s suspend backend passed every test below and still died in - * `usage-example` with `NoClassDefFoundError: kotlin/coroutines/jvm/internal/SpillingKt` -- see - * [TypedpythonCheckTask]. Classpath problems show up in `usage-example`, not here. + * `sample` with `NoClassDefFoundError: kotlin/coroutines/jvm/internal/SpillingKt` -- see + * [TypedpythonCheckTask]. Classpath problems show up in `sample`, not here. * * Before `typedpythonCheck` exists, these fail with "Task 'typedpythonCheck' not found" or with * `buildPython` succeeding over an `Any` leak -- the pre-implementation failure, not a regression. diff --git a/toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/typedpython/TypedpythonDecisionsTest.kt b/toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/typedpython/TypedpythonDecisionsTest.kt similarity index 100% rename from toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/typedpython/TypedpythonDecisionsTest.kt rename to toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/typedpython/TypedpythonDecisionsTest.kt diff --git a/toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/typedpython/TypedpythonNoWheelTest.kt b/toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/typedpython/TypedpythonNoWheelTest.kt similarity index 100% rename from toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/typedpython/TypedpythonNoWheelTest.kt rename to toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/typedpython/TypedpythonNoWheelTest.kt diff --git a/toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/StagePythonPayloadScriptTest.kt b/toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/StagePythonPayloadScriptTest.kt deleted file mode 100644 index 654d9aa..0000000 --- a/toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/StagePythonPayloadScriptTest.kt +++ /dev/null @@ -1,82 +0,0 @@ -package org.thisisthepy.python.multiplatform.toolchain.bundle - -import java.io.File -import java.util.concurrent.TimeUnit -import kotlin.io.path.createTempDirectory -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertNotEquals -import kotlin.test.assertTrue - -/** Runs `tools/xcode/stage-python-payload.sh` with a fake gradlew and a fake rsync that records calls. */ -class StagePythonPayloadScriptTest { - private val bash: String? = listOf("/bin/bash", "/usr/bin/bash", "/usr/local/bin/bash", "/opt/homebrew/bin/bash") - .firstOrNull { File(it).canExecute() } - - private fun script(): File { - var dir: File? = File("").absoluteFile - while (dir != null) { - val candidate = File(dir, "tools/xcode/stage-python-payload.sh") - if (candidate.isFile) return candidate - dir = dir.parentFile - } - error("tools/xcode/stage-python-payload.sh not found above ${File("").absolutePath}") - } - - private class Run(val exit: Int, val rsyncCalls: List) - - private fun run(gradlewBody: String): Pair { - val work = createTempDirectory("xcode-script").toFile() - val gradlew = File(work, "fake-gradlew").apply { writeText("#!/bin/sh\n$gradlewBody\n"); setExecutable(true) } - val bin = File(work, "bin").apply { mkdirs() } - val log = File(work, "rsync.log") - File(bin, "rsync").apply { - writeText("#!/bin/sh\necho \"\$*\" >> '${log.path}'\n") - setExecutable(true) - } - val pb = ProcessBuilder(bash, script().path).redirectErrorStream(true) - pb.environment().apply { - put("PATH", "${bin.path}:${get("PATH")}") - put("GRADLEW", gradlew.path) - put("TARGET_BUILD_DIR", "/tb") - put("UNLOCALIZED_RESOURCES_FOLDER_PATH", "App.app") - } - val p = pb.start() - p.inputStream.readBytes() - assertTrue(p.waitFor(30, TimeUnit.SECONDS)) - return Run(p.exitValue(), if (log.exists()) log.readLines() else emptyList()) to work - } - - @Test - fun `a failing gradle exits non-zero and never calls rsync`() { - if (bash == null) return - val (r, _) = run("exit 1") - assertNotEquals(0, r.exit) - assertTrue(r.rsyncCalls.isEmpty()) - } - - @Test - fun `no PYTHON_PAYLOAD_DIR line exits non-zero and never calls rsync`() { - if (bash == null) return - val (r, _) = run("echo hello") - assertNotEquals(0, r.exit) - assertTrue(r.rsyncCalls.isEmpty()) - } - - @Test - fun `a payload dir that does not exist exits non-zero and never calls rsync`() { - if (bash == null) return - val (r, _) = run("echo PYTHON_PAYLOAD_DIR=/nonexistent/xyz/python") - assertNotEquals(0, r.exit) - assertTrue(r.rsyncCalls.isEmpty()) - } - - @Test - fun `a valid payload dir calls rsync once with the dir and the app destination`() { - if (bash == null) return - val payload = createTempDirectory("payload").toFile() - val (r, _) = run("echo noise; echo PYTHON_PAYLOAD_DIR=${payload.path}") - assertEquals(0, r.exit) - assertEquals(listOf("-a --delete ${payload.path}/ /tb/App.app/python/"), r.rsyncCalls) - } -} diff --git a/tools/xcode/stage-python-payload.sh b/tools/xcode/stage-python-payload.sh deleted file mode 100755 index b25306e..0000000 --- a/tools/xcode/stage-python-payload.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -# Xcode build-phase helper: stage the iOS Python payload with Gradle and copy it into the .app. -# Usage: stage-python-payload.sh [:app:stagePythonBundleIosForXcode] -# Env: GRADLEW (default ./gradlew); TARGET_BUILD_DIR and UNLOCALIZED_RESOURCES_FOLDER_PATH (Xcode sets both). -# Use it only WITHOUT python-multiplatform. With python-multiplatform, its tools/xcode/install-python.sh is -# the one phase that copies python/ (and the stdlib): give it PYTHON_PAYLOAD_TASK=:app:stagePythonBundleIosForXcode -# and do not add this script too -- both rsync --delete into the same /python/. -set -euo pipefail -: "${TARGET_BUILD_DIR:?Xcode must set TARGET_BUILD_DIR}" "${UNLOCALIZED_RESOURCES_FOLDER_PATH:?Xcode must set UNLOCALIZED_RESOURCES_FOLDER_PATH}" -task="${1:-:app:stagePythonBundleIosForXcode}" -out=$("${GRADLEW:-./gradlew}" -q "$task") -dir=$(printf '%s\n' "$out" | sed -n 's/^PYTHON_PAYLOAD_DIR=//p') -[ -n "$dir" ] && [ -d "$dir" ] || { echo "error: no Python payload dir from Gradle" >&2; exit 1; } -rsync -a --delete "$dir/" "$TARGET_BUILD_DIR/$UNLOCALIZED_RESOURCES_FOLDER_PATH/python/" diff --git a/usage-example/gradle/wrapper/gradle-wrapper.jar b/usage-example/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 2c3521197d7c4586c843d1d3e9090525f1898cde..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43504 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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 TZiJi*F`$tkWLn) diff --git a/usage-example/gradle/wrapper/gradle-wrapper.properties b/usage-example/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index e2847c8..0000000 --- a/usage-example/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,7 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/usage-example/gradlew b/usage-example/gradlew deleted file mode 100755 index f5feea6..0000000 --- a/usage-example/gradlew +++ /dev/null @@ -1,252 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s -' "$PWD" ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/usage-example/gradlew.bat b/usage-example/gradlew.bat deleted file mode 100644 index 9d21a21..0000000 --- a/usage-example/gradlew.bat +++ /dev/null @@ -1,94 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/usage-example/python/pyproject.toml b/usage-example/python/pyproject.toml deleted file mode 100644 index 7650695..0000000 --- a/usage-example/python/pyproject.toml +++ /dev/null @@ -1,5 +0,0 @@ -[project] -name = "usage-example-python" -version = "0.1.0" -description = "The Python half of usage-example, bundled by pypackpack and staged by the toolchain plugin." -requires-python = ">=3.13" From c1254b156e7ef74a9a446031886a53e01b1bcb12 Mon Sep 17 00:00:00 2001 From: BREW <81044362+b-re-w@users.noreply.github.com> Date: Sat, 3 Oct 2026 19:44:47 +0900 Subject: [PATCH 02/12] Docs: Point the README quick start at the sample's new Python path --- README.md | 2 +- docs/locale/README_ko.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b66a85a..b6f40f3 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ plugins { python { compileSdk = "3.13" - localLibraryPath = "python" // a pypackpack package: pyproject.toml + src/main/ + localLibraryPath = "src/commonMain/python" // a pypackpack package: pyproject.toml + src/main/ packaging { fileName = "sample" } diff --git a/docs/locale/README_ko.md b/docs/locale/README_ko.md index 265c6e0..862b5c6 100644 --- a/docs/locale/README_ko.md +++ b/docs/locale/README_ko.md @@ -73,7 +73,7 @@ plugins { python { compileSdk = "3.13" - localLibraryPath = "python" // pypackpack 패키지: pyproject.toml + src/main/ + localLibraryPath = "src/commonMain/python" // pypackpack 패키지: pyproject.toml + src/main/ packaging { fileName = "sample" } From 718dc1fcd115adf244c3c0becef7b000c23221a3 Mon Sep 17 00:00:00 2001 From: BREW <81044362+b-re-w@users.noreply.github.com> Date: Sat, 3 Oct 2026 19:47:49 +0900 Subject: [PATCH 03/12] Docs: Point the guide's getting-started at the sample's new Python path --- docs/guide/getting-started.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guide/getting-started.html b/docs/guide/getting-started.html index 4c49b1d..bbb1e64 100644 --- a/docs/guide/getting-started.html +++ b/docs/guide/getting-started.html @@ -57,7 +57,7 @@

3. Apply the plugin3. 플 python { compileSdk = "3.13" - localLibraryPath = "python" + localLibraryPath = "src/commonMain/python" packaging { fileName = "sample" } From 06a9c2993bfdfa2d201e43cca87014ac3c60ce46 Mon Sep 17 00:00:00 2001 From: BREW <81044362+b-re-w@users.noreply.github.com> Date: Sat, 3 Oct 2026 20:48:16 +0900 Subject: [PATCH 04/12] Fix: Keep tcl and toolchain-lite's pyproject.toml in the restructure Correction from the lead: tcl is toolchain-lite, and the root pyproject.toml is its PyPI build (toolchain-lite 0.0.1 on PyPI carries its description and URLs). The restructure wrongly deleted pyproject.toml and renamed tcl/ to toolchain-cli/. - pyproject.toml is restored unchanged; AGENTS.md section 12 and SPEC's outside-intent item 5 now say what it is. Its name (toolchain, not toolchain-lite) and missing flit module belong to the toolchain-lite publishing work. - toolchain-cli/ is tcl/ again (:tcl, artifactId ...:tcl unchanged); every path, command, CI matrix entry and the README coordinates table follow. SPEC keeps its original tcl/ path abbreviation. --- .github/workflows/test.yml | 2 +- AGENTS.md | 10 ++- PROJECT.md | 4 +- README.md | 4 +- docs/SPEC.md | 13 ++-- docs/guide/guide-tcl.html | 2 +- docs/guide/status.html | 2 +- docs/locale/README_ko.md | 4 +- pyproject.toml | 65 +++++++++++++++++++ settings.gradle.kts | 2 +- {toolchain-cli => tcl}/build.gradle.kts | 0 .../python/multiplatform/tcl/Cli.kt | 0 .../python/multiplatform/tcl/Installer.kt | 0 .../python/multiplatform/tcl/CliArgsTest.kt | 0 .../python/multiplatform/tcl/InstallerTest.kt | 2 +- 15 files changed, 90 insertions(+), 20 deletions(-) create mode 100644 pyproject.toml rename {toolchain-cli => tcl}/build.gradle.kts (100%) rename {toolchain-cli => tcl}/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Cli.kt (100%) rename {toolchain-cli => tcl}/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Installer.kt (100%) rename {toolchain-cli => tcl}/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/CliArgsTest.kt (100%) rename {toolchain-cli => tcl}/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/InstallerTest.kt (97%) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 24eb91d..354874e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,7 +28,7 @@ jobs: strategy: fail-fast: false matrix: - module: [toolchain-gradle-plugin, toolchain-cli] + module: [toolchain-gradle-plugin, tcl] steps: - uses: actions/checkout@v4 diff --git a/AGENTS.md b/AGENTS.md index 60b22ee..3e3a38e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -180,10 +180,14 @@ GitHub issues `thisisthepy/toolchain#2` (plugin checklist) and `#1` (`tcl`), it | Module | What it is | |---|---| | `toolchain-gradle-plugin/` (`:toolchain-gradle-plugin`) | The Gradle plugin (id `org.thisisthepy.python.multiplatform`, class `PythonPlugin`). DSL in `dsl/`, tasks in `bundle/`, `dependency/`, `hotreload/`. | -| `toolchain-cli/` (`:toolchain-cli`) | toolchain-lite, a CLI application (`tcl install `). | +| `tcl/` (`:tcl`) | toolchain-lite, a CLI application (`tcl install `). | | `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/`. | | `.github/scripts/release/` | The develop → release → main sync (rule 4). | +`pyproject.toml` at the root is toolchain-lite's PyPI build: `toolchain-lite` 0.0.1 on PyPI came +from it. Keep it. (It still declares the name `toolchain` and a flit module that is not in the +repository; fixing that belongs to the toolchain-lite publishing work, not to a cleanup.) + 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 `archive/pre-restructure`. @@ -249,8 +253,8 @@ Run each module separately (rule 8), with output to a file under `.tmp/`: ```bash rm -rf toolchain-gradle-plugin/build/test-results ./gradlew :toolchain-gradle-plugin:test --rerun --console=plain > .tmp/toolchain-test.log 2>&1; echo "EXIT=$?" -rm -rf toolchain-cli/build/test-results -./gradlew :toolchain-cli:test --rerun --console=plain > .tmp/tcl-test.log 2>&1; echo "EXIT=$?" +rm -rf tcl/build/test-results +./gradlew :tcl:test --rerun --console=plain > .tmp/tcl-test.log 2>&1; echo "EXIT=$?" ``` Count results from `/build/test-results/test/*.xml`, not from the log. diff --git a/PROJECT.md b/PROJECT.md index 4d2bec3..6a8579e 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -98,7 +98,7 @@ Python 코드와 의존성이 실려 빌드·실행되는 상태. 기한을 맞 ``` toolchain/ Gradle 플러그인 (dsl/, bundle/, dependency/, hotreload/) -toolchain-cli/ toolchain-lite CLI (`:toolchain-cli`) +tcl/ toolchain-lite CLI (`:tcl`) sample/ 별도 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) @@ -110,7 +110,7 @@ docs/ INTENT.md, SPEC.md, locale/, guide/ (GitHub Pages) ```bash ./gradlew :toolchain-gradle-plugin:test --rerun --console=plain > .tmp/toolchain-test.log 2>&1; echo "EXIT=$?" -./gradlew :toolchain-cli:test --rerun --console=plain > .tmp/tcl-test.log 2>&1; echo "EXIT=$?" +./gradlew :tcl:test --rerun --console=plain > .tmp/tcl-test.log 2>&1; echo "EXIT=$?" ./gradlew :toolchain-gradle-plugin:publishToMavenLocal # sample 빌드 전에 반드시 python3 docs/guide/check_guide.py # 가이드 검사 # sample 은 별도 빌드: python-multiplatform 체크아웃에서 ./gradlew publishAllToMavenLocal 후 diff --git a/README.md b/README.md index b6f40f3..f5bfadb 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ owns the Gradle vocabulary in between. |---|---| | plugin id `org.thisisthepy.python.multiplatform` | the Gradle plugin; apply it by id (unchanged) | | `org.thisisthepy.python.multiplatform:toolchain-gradle-plugin` | the plugin's implementation artifact (was `…:toolchain` before #62) | -| `org.thisisthepy.python.multiplatform:toolchain-cli` | the CLI, toolchain-lite (was `…:tcl` before #62) | +| `org.thisisthepy.python.multiplatform:tcl` | the CLI, toolchain-lite (tcl) | Apply the plugin next to Kotlin Multiplatform and point it at a `pypackpack` package. This is `sample/build.gradle.kts`, trimmed: @@ -108,7 +108,7 @@ python { Python only? Skip Gradle builds entirely: ```shell -./gradlew :toolchain-cli:run --args="install pythonx-compose" +./gradlew :tcl:run --args="install pythonx-compose" ``` ## 🧭 Architecture at a glance diff --git a/docs/SPEC.md b/docs/SPEC.md index a78ff09..ce556af 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -12,11 +12,11 @@ Each item carries a status: - **planned** — the example build file or an issue asks for it; the code does not do it (or only declares a DSL property that nothing reads). -Status was assigned on 2026-10-02 by reading the plugin's and the CLI's sources and their tests (now `toolchain-gradle-plugin/src`, `toolchain-cli/src`). No status +Status was assigned on 2026-10-02 by reading `toolchain-gradle-plugin/src` (then `toolchain/src`), `tcl/src` and their tests. No status here comes from a roadmap, a commit message or an issue checkbox. Paths below are abbreviated: `plugin/` = `toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/`, -`ptest/` = the matching `toolchain-gradle-plugin/src/test/kotlin/...` directory, `cli/` = `toolchain-cli/src/{main,test}/kotlin/org/thisisthepy/python/multiplatform/tcl/`. +`ptest/` = the matching `toolchain-gradle-plugin/src/test/kotlin/...` directory, `tcl/` = `tcl/src/{main,test}/kotlin/org/thisisthepy/python/multiplatform/tcl/`. --- @@ -534,8 +534,8 @@ Finds the nearest `pyproject.toml` at or above the working directory. If there i message on stderr for a missing argument, an unknown command or a failed install. No arguments prints `Usage: tcl install `. -**Status: implemented** — `cli/Cli.kt`, `cli/Installer.kt`; `cli/CliArgsTest.kt`, -`cli/InstallerTest.kt` (needs `uv` and network). Run with `./gradlew :toolchain-cli:run --args="install "`. +**Status: implemented** — `tcl/Cli.kt`, `tcl/Installer.kt`; `tcl/CliArgsTest.kt`, +`tcl/InstallerTest.kt` (needs `uv` and network). Run with `./gradlew :tcl:run --args="install "`. --- @@ -556,5 +556,6 @@ These exist in the code but are not asked for by the example build file or the i wired), `bundle/PythonLocalLoader.kt`, `dependency/DependencyType.kt`, and `BinariesExtension` / `FrozenPackConfig` / `BuildTypeEnum` in `dsl/DSLPlatforms.kt`. Nothing calls any of them. -5. ~~**`pyproject.toml`**~~ — deleted in #62 (a flit stub for a Python package that never existed; - nothing built it). It survives in the tag `archive/pre-restructure`. +5. **`pyproject.toml`** is toolchain-lite's PyPI build (`toolchain-lite` 0.0.1, 2025-06-12). It + still declares a flit-built package named `toolchain` (Python 3.9–3.13) whose module is not in + the repository, and no CI builds or publishes it yet. diff --git a/docs/guide/guide-tcl.html b/docs/guide/guide-tcl.html index 6d01f31..0962b9a 100644 --- a/docs/guide/guide-tcl.html +++ b/docs/guide/guide-tcl.html @@ -28,7 +28,7 @@

tcl for Python usersPython

Install a package패키지 설치

-
./gradlew :toolchain-cli:run --args="install pythonx-compose"
+
./gradlew :tcl:run --args="install pythonx-compose"

tcl install <package> looks for a pyproject.toml in the current directory and its parents. If there is none it runs uv init --bare first; then it adds the package with uv add, through the same pypackpack backend the Gradle plugin uses.

tcl install <package> 는 현재 디렉터리와 그 상위에서 pyproject.toml 을 찾습니다. 없으면 먼저 uv init --bare 를 실행하고, Gradle 플러그인과 같은 pypackpack 백엔드로 uv add 를 해서 패키지를 추가합니다.

Invocation호출Result결과
tcl install pkgexit 0, uv output on stdout종료 코드 0, stdout 에 uv 출력
tclexit 1, Usage: tcl install <package>종료 코드 1, Usage: tcl install <package>
tcl installexit 1, missing argument종료 코드 1, 인자 누락
tcl otherexit 1, unknown command종료 코드 1, 알 수 없는 명령

install is the only command, matching issue #1. tcl needs uv on PATH and network access to PyPI.

명령은 이슈 #1 그대로 install 하나뿐입니다. tcl 은 PATH 의 uv 와 PyPI 네트워크 접근이 필요합니다.

diff --git a/docs/guide/status.html b/docs/guide/status.html index 53c0689..cd7fef1 100644 --- a/docs/guide/status.html +++ b/docs/guide/status.html @@ -48,7 +48,7 @@

Is the DSL final?DSL 은

Contributing기여하기

Changes go intent → spec → test → code. A behaviour change begins as a change to the specification; its test is written first and seen failing; then the code makes it pass. A DSL value is never accepted silently — it is wired to a task, or rejected with a reason.

변경은 의도 → 스펙 → 테스트 → 코드 순서로 갑니다. 동작 변경은 스펙 변경에서 시작하고, 테스트를 먼저 써서 실패를 확인한 뒤, 코드로 통과시킵니다. DSL 값은 조용히 받아들이지 않습니다 — 태스크에 연결하거나, 이유와 함께 거부합니다.

./gradlew :toolchain-gradle-plugin:test --rerun     # plugin; needs packpack in Maven Local, uv and network
-./gradlew :toolchain-cli:test --rerun           # tcl; needs uv and network
+./gradlew :tcl:test --rerun           # tcl; needs uv and network
 python3 docs/guide/check_guide.py     # this site

Run each module on its own, and count results from build/test-results/ rather than from the console.

모듈은 따로따로 실행하고, 결과는 콘솔이 아니라 build/test-results/ 에서 세세요.

diff --git a/docs/locale/README_ko.md b/docs/locale/README_ko.md index 862b5c6..5ac74f3 100644 --- a/docs/locale/README_ko.md +++ b/docs/locale/README_ko.md @@ -59,7 +59,7 @@ Python 을 내장한 Kotlin Multiplatform 앱에는 맞물려 돌아가야 하 |---|---| | 플러그인 id `org.thisisthepy.python.multiplatform` | Gradle 플러그인; id 로 적용 (변경 없음) | | `org.thisisthepy.python.multiplatform:toolchain-gradle-plugin` | 플러그인 구현 아티팩트 (#62 이전에는 `…:toolchain`) | -| `org.thisisthepy.python.multiplatform:toolchain-cli` | CLI, toolchain-lite (#62 이전에는 `…:tcl`) | +| `org.thisisthepy.python.multiplatform:tcl` | CLI, toolchain-lite (tcl) | Kotlin Multiplatform 옆에 플러그인을 적용하고 `pypackpack` 패키지를 가리키게 합니다. 아래는 `sample/build.gradle.kts` 를 줄인 것입니다. @@ -107,7 +107,7 @@ python { Python 만 쓰나요? Gradle 빌드 없이: ```shell -./gradlew :toolchain-cli:run --args="install pythonx-compose" +./gradlew :tcl:run --args="install pythonx-compose" ``` ## 🧭 한눈에 보는 구조 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..c0a92ca --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,65 @@ +[build-system] +requires = ["flit_core>=3.4,<4"] +build-backend = "flit_core.buildapi" + +[project] +name = "toolchain" +version = "0.0.1" +description = "Python Multiplatform Build Plugin/Tool with Kotlin Multiplatform Mobile" +readme = "README.md" +requires-python = ">=3.9, <3.14" +license = {file = "LICENSE"} +authors = [ + {name = "THISISTHEPY", email = "thisisthepy@gmail.com"} +] +classifiers = [ + "Development Status :: 3 - Alpha", + "Environment :: Console", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Topic :: Software Development :: Build Tools", +] +dependencies = [ +] + +[project.optional-dependencies] +dev = [ + "pytest>=7.0.0", + "black>=23.0.0", + "isort>=5.12.0", + "mypy>=1.0.0", +] +docs = [ + "sphinx>=6.0.0", + "sphinx-rtd-theme>=1.2.0", +] + +[project.urls] +"Homepage" = "https://github.com/thisisthepy/toolchain" +"Bug Tracker" = "https://github.com/thisisthepy/toolchain/issues" +"Documentation" = "https://thisisthepy.github.io/" + +[tool.flit.module] +name = "toolchain" + +[tool.black] +line-length = 88 +target-version = ["py39", "py310", "py311", "py312"] + +[tool.isort] +profile = "black" +line_length = 88 + +[tool.mypy] +python_version = "3.9" +warn_return_any = true +warn_unused_configs = true +disallow_untyped_defs = true +disallow_incomplete_defs = true diff --git a/settings.gradle.kts b/settings.gradle.kts index d2b0246..730cc1c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -34,4 +34,4 @@ rootProject.name = "ToolchainProject" include(":toolchain-gradle-plugin") // sample/ is its own build, run with this wrapper: ./gradlew -p sample …. It uses python-multiplatform's // Kotlin; the plugin here stays on its own for its consumers (toolchain#22, #62). -include(":toolchain-cli") +include(":tcl") diff --git a/toolchain-cli/build.gradle.kts b/tcl/build.gradle.kts similarity index 100% rename from toolchain-cli/build.gradle.kts rename to tcl/build.gradle.kts diff --git a/toolchain-cli/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Cli.kt b/tcl/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Cli.kt similarity index 100% rename from toolchain-cli/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Cli.kt rename to tcl/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Cli.kt diff --git a/toolchain-cli/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Installer.kt b/tcl/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Installer.kt similarity index 100% rename from toolchain-cli/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Installer.kt rename to tcl/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Installer.kt diff --git a/toolchain-cli/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/CliArgsTest.kt b/tcl/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/CliArgsTest.kt similarity index 100% rename from toolchain-cli/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/CliArgsTest.kt rename to tcl/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/CliArgsTest.kt diff --git a/toolchain-cli/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/InstallerTest.kt b/tcl/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/InstallerTest.kt similarity index 97% rename from toolchain-cli/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/InstallerTest.kt rename to tcl/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/InstallerTest.kt index 88019b1..9c344c6 100644 --- a/toolchain-cli/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/InstallerTest.kt +++ b/tcl/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/InstallerTest.kt @@ -16,7 +16,7 @@ import kotlin.test.assertTrue * * Pre-implementation state: this file fails to compile (no `Installer`, no `installBlocking`, no * `findProjectRoot` on the classpath) until `Installer.kt` exists -- that is the "red" this - * repository's TDD rule asks for, confirmed by running `:toolchain-cli:test` against a `tcl` module that + * repository's TDD rule asks for, confirmed by running `:tcl:test` against a `tcl` module that * has this test file and a `build.gradle.kts` but no `src/main` yet. */ class InstallerTest { From 7332b24fe60a874ed7e7187166b5f80a3bde91f5 Mon Sep 17 00:00:00 2001 From: BREW <81044362+b-re-w@users.noreply.github.com> Date: Sat, 3 Oct 2026 23:28:51 +0900 Subject: [PATCH 05/12] Chore: Relicense to Apache-2.0; no em-dashes; writing rules in AGENTS.md User directives (2026-10-03, via the lead): - Apache-2.0 everywhere: LICENSE (the Apache 2.0 text, as in pypackpack), the README badges and footers (en/ko), the guide footers, pyproject (license = "Apache-2.0" with license-files; the MIT classifier removed; flit_core >= 3.12 for that form). - Every em-dash in the docs, the guide and the code is replaced by a colon, a semicolon, parentheses or a full stop; **Status: X** lines read "**Status: X.** ..."; empty table cells are -. Dash pairs that wrapped across lines were rewritten by hand. - AGENTS.md section 18: no em-dash; examples use uv, ppp or tcl, never pip install; the licence statement. Closes #65 --- AGENTS.md | 46 ++-- LICENSE | 222 ++++++++++++++++-- PROJECT.md | 18 +- README.md | 36 +-- docs/INTENT.md | 20 +- docs/SPEC.md | 134 +++++------ docs/guide/assets/site.js | 2 +- docs/guide/assets/style.css | 2 +- docs/guide/concepts.html | 4 +- docs/guide/ecosystem.html | 6 +- docs/guide/getting-started.html | 2 +- docs/guide/guide-dependencies.html | 2 +- docs/guide/guide-staging.html | 8 +- docs/guide/guide-tcl.html | 2 +- docs/guide/guide-variants.html | 4 +- docs/guide/index.html | 10 +- docs/guide/status.html | 6 +- docs/locale/README_ko.md | 32 +-- pyproject.toml | 6 +- .../multiplatform/toolchain/dsl/DSLFlavors.kt | 2 +- 20 files changed, 378 insertions(+), 186 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 3e3a38e..a8b089d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,8 +14,8 @@ overrides any default your tooling has. ## 2. Nothing is created outside this repository -Everything your work produces — worktrees, agent prompts, logs, measurements, experiments, scratch -files — lives **inside this repository's root directory.** +Everything your work produces (worktrees, agent prompts, logs, measurements, experiments, scratch +files) lives **inside this repository's root directory.** | What | Where | |---|---| @@ -26,7 +26,7 @@ files — lives **inside this repository's root directory.** Before writing a file, check that its absolute path starts with this repository's root. If it does not, stop. The only exceptions are a path the user names explicitly, and caches that build tools -manage themselves. **Re-pointing a shared cache or a home-directory symlink reaches other projects — +manage themselves. **Re-pointing a shared cache or a home-directory symlink reaches other projects; ask first.** Writing to *another* repository is not an exception either. Do it only when told to work there. @@ -35,7 +35,7 @@ Writing to *another* repository is not an exception either. Do it only when told **Never add a new directory (or a new file) at the repository root on your own.** The root layout is the maintainer's: source modules, `docs/`, `gradle/`, `.github/` and the files that tools require -there. Work belongs inside an existing module or directory — sources under `src//`, +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 @@ -49,7 +49,7 @@ build caches, model weights, `node_modules`) into every worktree is how 86 workt - Create worktrees under `.worktrees/`. - **Symlink** large untracked directories from the main checkout instead of copying or rebuilding - them. If `tools/worktree-add.sh` exists, use it — it does the linking. + them. If `tools/worktree-add.sh` exists, use it: it does the linking. - Delete a worktree once its branch is merged: `git worktree remove .worktrees/`. - Periodically delete `build/` directories inside worktrees; they only grow. @@ -78,7 +78,7 @@ Every new feature goes through an issue and a pull request: 1. Before starting, search the repository's issues (`gh issue list --state all --search ""`). 2. If no issue covers the work, open one (`gh issue create`) stating what and why, and the - completion criterion — which tests must pass. + completion criterion: which tests must pass. 3. Work on a `feat/` branch, push every commit, and open a pull request into `develop` whose body contains `Closes #`. 4. Merge into `develop` through that pull request (`gh pr merge`), not by a local merge, so the @@ -102,18 +102,18 @@ If a request conflicts with `docs/INTENT.md`, say so instead of implementing it. ## 6. User-authored files are specification -Files the user wrote by hand — notebooks, example build files, sample apps — are the specification. +Files the user wrote by hand (notebooks, example build files, sample apps) are the specification. Read them **first**. Never delete, rewrite, or `git add` them without being told to. Generated documentation (roadmaps, design notes) is a record of work, not a requirement; when the two disagree, the user's file wins. ## 7. Show a conclusion before acting on it -Anything beyond the immediate request — another repository, a public API signature, deleting -files, killing processes, force-pushing, changing branch protection — state what you would do and +Anything beyond the immediate request (another repository, a public API signature, deleting +files, killing processes, force-pushing, changing branch protection): state what you would do and why, and wait. Investigating, measuring, and reporting are always fine. -**Push every commit right away.** After you commit — on a work branch or on `develop` — push it to +**Push every commit right away.** After you commit (on a work branch or on `develop`) push it to the remote immediately; no confirmation is needed. Never push to `main` or `release` by hand, and never force-push without the user's explicit approval. @@ -153,18 +153,18 @@ to implement", say what you counted against. - Give every agent prompt the absolute paths it may write to, and repeat rule 2 in it. - **Subagents do not run heavy local builds.** Subagents write code, design, investigate, review and document. Gradle builds, cargo builds, the test gate and model runs are done by the session - itself — one at a time on this machine — or by CI (GitHub Actions) on a pushed branch. Several + itself (one at a time on this machine) or by CI (GitHub Actions) on a pushed branch. Several sessions share one machine; parallel local builds slow every one of them. --- -# Repository-specific rules — `toolchain` +# Repository-specific rules: `toolchain` ## 11. The example build file is the specification `(플러그인예시)build.gradle.kts` at the root of the **main checkout** (`/Volumes/macMini/thisisthepy/toolchain/`) is the maintainer's hand-written target DSL. It is -untracked, so **a new worktree does not contain it** — read it from the main checkout. Together with +untracked, so **a new worktree does not contain it**: read it from the main checkout. Together with GitHub issues `thisisthepy/toolchain#2` (plugin checklist) and `#1` (`tcl`), it is the source of `docs/INTENT.md`. @@ -202,7 +202,7 @@ yarn lock is `sample/gradle/wasm-yarn.lock`), no copied wrappers. The pre-#62 la frontend/middleware layers: they locate the project through the JVM-global `user.dir`, which a Gradle daemon shares between unrelated builds. - When a feature needs something `pypackpack` does not have, **reject the DSL value loudly** here - and propose the `pypackpack` change — that is another repository, so rule 7 applies. + and propose the `pypackpack` change: that is another repository, so rule 7 applies. - Loading the payload at run time, `sys.path`, and reacting to the hot-reload broadcast belong to `python-multiplatform`. This plugin's job ends at the staged directory and the broadcast. @@ -228,7 +228,7 @@ history removing. artifact stale. - Anything AGP reads (asset source roots) is registered in `apply`, not in `afterEvaluate`: AGP reads its source sets in its own `afterEvaluate`, which runs first. -- Generated files go under `build/`, registered as extra source roots — never into `src/`. +- Generated files go under `build/`, registered as extra source roots: never into `src/`. - `kotlin-gradle-plugin` is pinned to the catalog's `kotlin` version. Two versions on the plugin classpath broke every consumer's wasm target once. - `-Xskip-metadata-version-check` exists because `packpack` is built with a newer Kotlin than this @@ -243,7 +243,7 @@ history removing. ## 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 +`pypackpack` repository), `uv` on `PATH`, and network access: `InstallDependenciesTaskTest` and `InstallerTest` run a real `uv add` against PyPI. `sample` 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). @@ -275,7 +275,7 @@ python3 docs/guide/check_guide.py | `docs/INTENT.md` | English | Why the project exists and what it is not. | | `docs/SPEC.md` | English | The behavioural contract, one `Status:` per item. | | `docs/guide/` | en + ko | The GitHub Pages site. Static HTML; every visible string in both languages. | -| `docs//` | — | Anything else. No other `.md` directly in `docs/`. | +| `docs//` | - | Anything else. No other `.md` directly in `docs/`. | - `Status: implemented` requires a test in this repository that exercises the behaviour; cite it. Wiring with no test is `partial`. @@ -285,3 +285,15 @@ python3 docs/guide/check_guide.py - Many KDoc comments cite `docs/ecosystem.md` and `docs/SPEC.md`. Those are **other repositories'** files (`python-multiplatform`'s and `pypackpack`'s respectively), written before this repository had its own `docs/`. When you touch such a comment, name the repository. + +## 18. Writing + +- **No em-dash (U+2014)**, anywhere: documentation, the guide, code comments, KDoc and strings. Use + a comma, a colon, parentheses, or two sentences. An empty table cell is `-`. +- **Examples install and run with `uv`, `ppp` (pypackpack) or `tcl` (toolchain-lite), never + `pip install`.** `uv pip …` is fine where a pip-style command is the point. Text that describes + what pip itself does is not an example. +- The licence is Apache-2.0 everywhere it is stated: `LICENSE`, `pyproject.toml` (`license`), the + READMEs and the guide footer. + +(User directives, 2026-10-03.) diff --git a/LICENSE b/LICENSE index bfa9e81..261eeb9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,201 @@ -MIT License - -Copyright (c) 2024 thisisthepy - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/PROJECT.md b/PROJECT.md index 6a8579e..a5ac3b1 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -1,4 +1,4 @@ -# PROJECT — toolchain +# PROJECT: toolchain Kotlin Multiplatform 앱의 Python 부분을 Gradle `python { }` 블록으로 선언하게 해 주는 Gradle 플러그인, 그리고 Gradle 없이 쓰는 CLI `tcl`. 실제 작업(의존성 해석, 번들링)은 `pypackpack` 에 위임한다. @@ -7,7 +7,7 @@ Kotlin Multiplatform 앱의 Python 부분을 Gradle `python { }` 블록으로 | 무엇 | 어디 | |---|---| -| 목표 DSL (사용자 작성) | 메인 체크아웃 루트의 `(플러그인예시)build.gradle.kts` — 추적되지 않는 파일, worktree 에는 없다 | +| 목표 DSL (사용자 작성) | 메인 체크아웃 루트의 `(플러그인예시)build.gradle.kts`: 추적되지 않는 파일, worktree 에는 없다 | | 플러그인 체크리스트 | GitHub 이슈 `thisisthepy/toolchain#2` | | toolchain-lite | GitHub 이슈 `thisisthepy/toolchain#1` (`tcl install pythonx-compose`) | | 실행 가능한 부분집합 | `sample/build.gradle.kts` | @@ -20,7 +20,7 @@ Kotlin Multiplatform 앱의 Python 부분을 Gradle `python { }` 블록으로 (python-multiplatform 이 런타임을 제공하는 버전만). 제공되지 않는 버전 문자열은 `buildPython…` 에서 이유와 함께 실패 - `python { }` 최상위의 `android("android") { }` / `ios { }` / `desktop()` 과 변형 호출 (`androidArm64()` 등) → `pypackpack` 타깃 트리플 매핑, Kotlin 타깃 대조 경고, 최소 SDK 전달. - 안드로이드는 CPython 공식 지원(PEP 738)대로 arm64·x86_64 만 — `androidArm32()`/`androidX86()` 은 + 안드로이드는 CPython 공식 지원(PEP 738)대로 arm64·x86_64 만: `androidArm32()`/`androidX86()` 은 이유를 밝히는 컴파일 오류 - `debug` / `release` 빌드 타입, `-Ppython.buildType`, 변형 × 빌드 타입 태스크 그래프 - `projectFlavors { create("free") }` → 변형 × 플레이버 × 빌드 타입(`buildPythonAndroidArm64FreeDebug`), @@ -37,7 +37,7 @@ Kotlin Multiplatform 앱의 Python 부분을 Gradle `python { }` 블록으로 `installPythonDependenciesHost`(호스트 트리플, `build/pythonDeps/host/`). 어떤 변형도 읽지 않는 소스셋의 의존성은 설치 태스크에서 거부 (`TargetDependenciesTest`, `PythonPluginTargetDependenciesTest`, `InstallTargetDependenciesTaskTest`) -- `integration` 패키지는 설치 후 `.venv` site-packages 의 `*.dist-info/KLIBDEPENS` 를 확인하고 없으면 `implementation` 을 쓰라고 경고 (실패 아님; KLIBDEPENS 위치는 가정 — 어디에도 정의 없음) +- `integration` 패키지는 설치 후 `.venv` site-packages 의 `*.dist-info/KLIBDEPENS` 를 확인하고 없으면 `implementation` 을 쓰라고 경고 (실패 아님; KLIBDEPENS 위치는 가정: 어디에도 정의 없음) - `defaultConfig { pip { autoUpdate; repositories { central / local } } }` → `uv add` 옵션 (`--default-index`·`--index` / `--find-links` / `--upgrade`). `jit` 는 설치 태스크에서만 이유와 함께 거부 - `defaultConfig { versionCode, versionName }` → 페이로드 버전으로 번들 manifest 에 기록(`"versionName"`/`"versionCode"`) @@ -154,17 +154,17 @@ Python 테스트는 없다. 루트 `pyproject.toml` 이 가리키는 Python 패 ## 열린 질문 -1. ~~서버에 없는 `compileSdk` 버전 자동 빌드~~ — **결정(2026-10-03)**: 문자열로 주면 서버에 없을 때 +1. ~~서버에 없는 `compileSdk` 버전 자동 빌드~~: **결정(2026-10-03)**: 문자열로 주면 서버에 없을 때 자동 빌드, `PY3_11_9_ALPHA` 같은 Enum 으로 주면 서버에 있는 버전만. (구현은 계획) -2. ~~플랫폼 DSL 모양~~ — **결정(2026-10-03)**: 예시 파일 모양(최상위 `android("android") { }` + +2. ~~플랫폼 DSL 모양~~: **결정(2026-10-03)**: 예시 파일 모양(최상위 `android("android") { }` + `listOf(...)`). 안드로이드 변형은 CPython 공식 지원(arm64·x86_64)만. #7 -3. ~~`pip { repositories { central / local / jit } }` 이름~~ — **결정(2026-10-03)**: 예시 파일 철자. +3. ~~`pip { repositories { central / local / jit } }` 이름~~: **결정(2026-10-03)**: 예시 파일 철자. 4. 예시 파일에 없는 `localLibraryPath`, `-Ppython.buildType`, `adb` 기반 핫 리로드를 유지할 것인가. 5. 참조되지 않는 코드(`PythonMultiplatformPlugin.kt`, `reslover.kt`, `decompileKotlinMeta.kt`, `PythonLocalLoader.kt`, `DependencyType.kt`, `FrozenPackConfig` 등)와 빈 `pyproject.toml` 을 지울 것인가. -6. ~~레벨 2 인터프리터 확보에 필요한 pypackpack API~~ — **해결 후 철회(2026-10-03)**: pypackpack#37 의 +6. ~~레벨 2 인터프리터 확보에 필요한 pypackpack API~~: **해결 후 철회(2026-10-03)**: pypackpack#37 의 `installPython(version, target, installDir)` 를 썼으나, #42 결정으로 toolchain 은 인터프리터를 받지 않는다. -7. python-multiplatform `pythonVersion` 을 어디서 읽을 것인가 — 같은 빌드는 `:python-multiplatform` 의 +7. python-multiplatform `pythonVersion` 을 어디서 읽을 것인가: 같은 빌드는 `:python-multiplatform` 의 `pythonMultiplatform` 확장(구현), 배포된 의존은 모듈 메타데이터나 jar 리소스(미구현). 리드 결정 대기 (python-multiplatform#61). diff --git a/README.md b/README.md index f5bfadb..ab88eeb 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ English | [한국어](docs/locale/README_ko.md) # toolchain -**Declare the Python half of your Kotlin Multiplatform app in Gradle — and ship it.** +**Declare the Python half of your Kotlin Multiplatform app in Gradle, and ship it.** -[![License: MIT](https://img.shields.io/badge/license-MIT-0f9d76.svg)](LICENSE) +[![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-0f9d76.svg)](LICENSE) ![Kotlin](https://img.shields.io/badge/Kotlin-2.1-7f52ff.svg?logo=kotlin&logoColor=white) ![Gradle](https://img.shields.io/badge/Gradle-8.9-02303a.svg?logo=gradle&logoColor=white) ![Targets](https://img.shields.io/badge/targets-Android%20%7C%20iOS%20%7C%20Desktop-0f9d76.svg) @@ -21,8 +21,8 @@ English | [한국어](docs/locale/README_ko.md) ## 💡 Why A Kotlin Multiplatform app that embeds Python has two builds to keep in step: the Kotlin one Gradle -already understands, and a Python one — interpreter version, packages, per-platform bundles, -assets — that it does not. `toolchain` is the Gradle plugin that closes the gap. You write one +already understands, and a Python one (interpreter version, packages, per-platform bundles, +assets) that it does not. `toolchain` is the Gradle plugin that closes the gap. You write one `python { }` block next to your `kotlin { }` block; the build installs your Python dependencies, bundles your code per target, and puts the payload where each platform's packaging step picks it up. @@ -33,18 +33,18 @@ owns the Gradle vocabulary in between. ## ✨ Features -- 🧩 **One DSL block** — `compileSdk`, platforms, `buildTypes`, `sourceSets` and `packaging`, all +- 🧩 **One DSL block**: `compileSdk`, platforms, `buildTypes`, `sourceSets` and `packaging`, all inside `python { }`. -- 📦 **Real dependency installs** — `implementation("pkg")` and `integration("pkg")` become a real +- 📦 **Real dependency installs**: `implementation("pkg")` and `integration("pkg")` become a real `uv add` through `pypackpack`. -- 🚀 **A task per variant** — every platform variant × build type gets its own +- 🚀 **A task per variant**: every platform variant × build type gets its own `buildPython` / `packagePython`, and an unsupported one fails alone. -- 🔌 **Lands in the artifact** — the bundle is staged into the desktop jar's resources and the +- 🔌 **Lands in the artifact**: the bundle is staged into the desktop jar's resources and the APK's `assets/`. -- 🧪 **Loud, not silent** — an unknown version string, an unmapped platform or an unsupported +- 🧪 **Loud, not silent**: an unknown version string, an unmapped platform or an unsupported compile level is rejected with a message saying why, instead of compiling and doing nothing. -- 🐍 **`tcl` for Python users** — `tcl install `, no Gradle project needed. +- 🐍 **`tcl` for Python users**: `tcl install `, no Gradle project needed. ## 🚀 Quick start @@ -128,7 +128,7 @@ flowchart LR ## 📊 Status -An honest summary — the full contract, item by item, is on the guide's +An honest summary: the full contract, item by item, is on the guide's [Status page](docs/guide/status.html). | Area | State | @@ -139,17 +139,17 @@ An honest summary — the full contract, item by item, is on the guide's | `implementation` / `integration` dependencies via `uv`, per source set, installed for each variant's own triple into its bundle | ✅ implemented | | Bundling via `pypackpack` at the `instant` level | ✅ implemented | | `bytecode` compile level (`.pyc`; needs a `.venv` matching `compileSdk`) | ✅ implemented | -| `native` / `mixed` compile levels | ⏳ planned — rejected loudly today | -| Staging into the desktop jar and the APK | 🟡 partial — iOS is staged but not attached | -| Hot reload | 🟡 partial — Android only, over `adb` | -| Code push | 🟡 partial — validation only, no upload | -| `buildFeatures { metaclass, compose }` | ✅ implemented — compose locations come from two Gradle properties | +| `native` / `mixed` compile levels | ⏳ planned: rejected loudly today | +| Staging into the desktop jar and the APK | 🟡 partial: iOS is staged but not attached | +| Hot reload | 🟡 partial: Android only, over `adb` | +| Code push | 🟡 partial: validation only, no upload | +| `buildFeatures { metaclass, compose }` | ✅ implemented: compose locations come from two Gradle properties | | `embedLevel`, `projectFlavors`, `pip { }` | ⏳ planned | | `tcl install` | ✅ implemented | ## 📖 Documentation -- **[Guide](docs/guide/index.html)** — concepts, task guides, status, in English and 한국어. +- **[Guide](docs/guide/index.html)**: concepts, task guides, status, in English and 한국어. - **[한국어 README](docs/locale/README_ko.md)** ## 🤝 Contributing @@ -160,4 +160,4 @@ is written and seen failing before the implementation. The guide's ## 📄 License -[MIT](LICENSE) © 2024 thisisthepy +[Apache-2.0](LICENSE) © 2024 thisisthepy diff --git a/docs/INTENT.md b/docs/INTENT.md index b5f36cd..bee2a86 100644 --- a/docs/INTENT.md +++ b/docs/INTENT.md @@ -17,7 +17,7 @@ The intent is derived from what the maintainer wrote by hand, in this order of a | `sample/` | 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.` +`> Inferred: confirm with the maintainer.` ## 1. What toolchain is for @@ -27,14 +27,14 @@ Anything below that is an inference rather than a statement from those sources i That block, as `(플러그인예시)build.gradle.kts` writes it, covers: 1. **The Python version** the app is built against (`compileSdk = "3.11.9-alpha"`), with release - channels (alpha, rc, normal — issue #2: "Version Enum (alpha, rc, normal)"). + channels (alpha, rc, normal; issue #2: "Version Enum (alpha, rc, normal)"). 2. **The target platforms**: Android, iOS and desktop, each with its architectures and a minimum SDK (`androidSdk = 24`, `iosSdk = 14`), checked against the targets the Kotlin side has enabled (issue #2: "Check Kotlin-side enabled build target"). 3. **How the interpreter and code are packaged** (`packaging { embedLevel, fileName }`): no interpreter, an external one, or one embedded in the app. -4. **Build types** with a compile level each — debug as `bytecode` (`.py` + `.pyc`) or `instant` - (`.py` only), release as `native` or `mixed` — plus release-only minification, metaclass +4. **Build types** with a compile level each: debug as `bytecode` (`.py` + `.pyc`) or `instant` + (`.py` only), release as `native` or `mixed`, plus release-only minification, metaclass exclusion and code push. 5. **Developer loop features**: hot reload for debug builds, code push for release builds (issue #2: "Expose direct run button for hot reload server"). @@ -43,7 +43,7 @@ That block, as `(플러그인예시)build.gradle.kts` writes it, covers: that depends on Kotlin, recognised by a `KLIBDEPENS` file in its wheel). 7. **Build features**: `metaclass` and `compose` (the Compose wrapper). 8. **Automation**: "Automate the build process for Python and integrate it with the Kotlin - Multiplatform project" (issue #2) — a Gradle build produces an app that carries its Python. + Multiplatform project" (issue #2): a Gradle build produces an app that carries its Python. And, for people with no Gradle project at all: @@ -61,7 +61,7 @@ And, for people with no Gradle project at all: | `python-multiplatform` | The runtime: CPython embedded in Kotlin Multiplatform, and everything at the language boundary. | | `pythonx-compose` | Compose bound into Python. | -> Inferred — confirm with the maintainer. The split "ppp owns the work, toolchain owns the Gradle +> Inferred: confirm with the maintainer. The split "ppp owns the work, toolchain owns the Gradle > vocabulary" is recorded in `python-multiplatform`'s `docs/ecosystem.md` §1 and is what the code > does (`BundlerInterface.create(BundleType.RESOURCE)`, `BackendInterface.create(BackendType.UV)`), > but no maintainer-authored file in this repository states it in those words. The example build @@ -72,11 +72,11 @@ And, for people with no Gradle project at all: - **Not a package manager or bundler of its own.** A feature that acquires a Python distribution, resolves a dependency or builds a bundle belongs in `pypackpack`, even when a Gradle task is what - triggers it. *(Inferred from §2 — confirm.)* + triggers it. *(Inferred from §2: confirm.)* - **Not the Python runtime.** Loading the bundle, putting it on `sys.path`, and reacting to a hot - reload signal happen in `python-multiplatform`. *(Inferred from §2 — confirm.)* + reload signal happen in `python-multiplatform`. *(Inferred from §2: confirm.)* - **Not a code-push server.** The example build file points `codePush.serverHost` at a host the - user runs; nothing in it asks toolchain to provide that server. *(Inferred — confirm.)* + user runs; nothing in it asks toolchain to provide that server. *(Inferred: confirm.)* - **Not an automatic builder of brand-new CPython releases.** Issue #2 says verbatim: "do not support automatic build for new python release". @@ -85,7 +85,7 @@ And, for people with no Gradle project at all: 1. ~~**Automatic build of an unknown `compileSdk` version.**~~ *Decided 2026-10-03:* a version given as a string (`"3.11.9-alpha"`) is built automatically when the server does not have it; a version given as a named constant (`PY3_11_9_ALPHA`) is restricted to versions the server has. -2. ~~**The DSL shape for platforms.**~~ *Decided 2026-10-03:* the example's shape — platform blocks +2. ~~**The DSL shape for platforms.**~~ *Decided 2026-10-03:* the example's shape; platform blocks and variant calls directly inside `python { }`. Android variants follow CPython's official Android support (PEP 738: arm64 and x86_64); the example's `androidArm32()` / `androidX86()` predate that decision and are compile errors. diff --git a/docs/SPEC.md b/docs/SPEC.md index ce556af..285b0d5 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -1,15 +1,15 @@ # Specification -What `toolchain` does — the behavioural contract. Every item stays inside `docs/INTENT.md`; an item -that does not is listed at the end under **Outside intent — needs a decision**. +What `toolchain` does: the behavioural contract. Every item stays inside `docs/INTENT.md`; an item +that does not is listed at the end under **Outside intent: needs a decision**. Each item carries a status: -- **implemented** — the behaviour exists in code *and* a test in this repository exercises it +- **implemented**: the behaviour exists in code *and* a test in this repository exercises it (the test file is cited). -- **partial** — some of the behaviour exists, or it exists but no test here exercises it. What is +- **partial**: some of the behaviour exists, or it exists but no test here exercises it. What is missing is said. -- **planned** — the example build file or an issue asks for it; the code does not do it (or only +- **planned**: the example build file or an issue asks for it; the code does not do it (or only declares a DSL property that nothing reads). Status was assigned on 2026-10-02 by reading `toolchain-gradle-plugin/src` (then `toolchain/src`), `tcl/src` and their tests. No status @@ -29,30 +29,30 @@ Plugin id `org.thisisthepy.python.multiplatform`, implementation class `PythonPl Consumers resolve it from `mavenLocal()` after `./gradlew :toolchain-gradle-plugin:publishToMavenLocal`; the plugin itself depends on `org.thisisthepy.python.multiplatform:packpack:0.1.0` from `mavenLocal()`. -**Status: implemented** — `plugin/PythonPlugin.kt`; `ptest/PythonPluginApplyTest.kt` applies the +**Status: implemented**. `plugin/PythonPlugin.kt`; `ptest/PythonPluginApplyTest.kt` applies the plugin id to a ProjectBuilder project and checks the extension, every task name and type (`installPythonDependencies`, `buildPython`, `packagePython`, `stagePythonBundle` and its `Android`/`Ios`/`Desktop` tasks; `hotReloadPython` and `codePushPython` after evaluation), the `python` group, and the `packagePython → buildPython → installPythonDependencies` chain. Resolving the plugin from `mavenLocal()` is still exercised only by building `sample`. -### 1.2 `compileSdk` — the Python version +### 1.2 `compileSdk`: the Python version `compileSdk` accepts `X.Y`, `X.Y.Z`, `X.Y.Z-alpha[N]` or `X.Y.Z-rc[N]` and classifies it into `PythonReleaseChannel { ALPHA, RC, NORMAL }`. `toReleaseString()` strips the channel to the `X.Y.Z` form `pypackpack` accepts. A blank value is a silent skip; a malformed one fails configuration. -**Status: implemented** — `plugin/dsl/PythonVersion.kt`; `ptest/dsl/PythonVersionTest.kt`. +**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 +for (its pinned `pythonVersion`, 3.14.7, and the older 3.13.0 archive), 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. -**Status: implemented** — `plugin/dsl/PythonSdk.kt`; `ptest/dsl/PythonSdkTest.kt`, +**Status: implemented**. `plugin/dsl/PythonSdk.kt`; `ptest/dsl/PythonSdkTest.kt`, `ptest/bundle/BuildPythonSdkRejectionTest.kt`; `sample` uses `compileSdk = PY3_14_7`. **What `compileSdk` selects (#42).** `compileSdk` selects the Python version of the wheels that @@ -65,7 +65,7 @@ the linked one even at the same version (iOS 3.14.6 against 3.14.7, for example) carry two copies. The interpreter acquisition and `runtime/` bundling added for #18 (#40) were removed for this reason. -**Status: implemented** — `ptest/TargetDependenciesTest.kt`, `ptest/PythonPluginTargetDependenciesTest.kt`, +**Status: implemented**. `ptest/TargetDependenciesTest.kt`, `ptest/PythonPluginTargetDependenciesTest.kt`, `ptest/PythonPluginPythonOnlyTest.kt` (no `acquirePythonInterpreter…` task and no `runtime/` at embedLevel 2; `--python-version` still follows `compileSdk`). @@ -93,7 +93,7 @@ Where `pythonMultiplatformVersion` comes from, in order (`selectPythonMultiplatf and only for a level-2 bundle (#49); 4. otherwise unknown: no check, logged once. -**Status: implemented** — `plugin/PythonVersionAgreement.kt`, `plugin/PublishedPythonVersion.kt`; +**Status: implemented**. `plugin/PythonVersionAgreement.kt`, `plugin/PublishedPythonVersion.kt`; `ptest/PythonVersionAgreementTest.kt` (the check and the precedence), `ptest/PythonPluginPythonOnlyTest.kt` (the property and the extension reaching the bundling task, the property winning), `ptest/PublishedPythonVersionTest.kt` (a local Maven repository with a fake @@ -115,7 +115,7 @@ spelled as the example build file spells it (INTENT §4.3), becomes options of t | `autoUpdate = true` | `--upgrade` | | `jit { … }` | **rejected**: pypackpack has no recipe build. The rejection fails `installPythonDependencies` and the per-target install tasks, each only when it has something to install. | -**Status: implemented** — `resolvePipArguments` / `resolvePipSettings` in +**Status: implemented**. `resolvePipArguments` / `resolvePipSettings` in `plugin/dependency/lang/python/PipRepositories.kt`; `ptest/dependency/lang/python/PipRepositoriesTest.kt` (including a real `uv add` sent to the declared index). → *`jit` / `localRecipes`: **planned**, needs a recipe build in pypackpack.* @@ -123,7 +123,7 @@ spelled as the example build file spells it (INTENT §4.3), becomes options of t `buildPython…` task and forwarded to pypackpack's `BundleRequest`, which records them in `resource-manifest.json` as `"versionName"` / `"versionCode"` beside the package's own `"version"`. Undeclared stays `null`: nothing is written, and no default is invented. Code push reads them later. -**Status: implemented** — `ptest/PythonPluginPayloadVersionTest.kt` (host and variant tasks), +**Status: implemented**. `ptest/PythonPluginPayloadVersionTest.kt` (host and variant tasks), `ptest/bundle/BuildPythonArtifactTaskTest.kt` (the real manifest). ### 1.4 Platforms @@ -153,7 +153,7 @@ declared variant whose Kotlin target is not enabled in the consumer's `kotlin { **warning**, not a failure. `androidSdk`/`iosSdk` of `0` mean "undeclared"; negative values are rejected; a declared value is forwarded to `pypackpack` as `BundleRequest.minSdk`. -**Status: implemented** — `plugin/dsl/DSLPlatforms.kt`, `plugin/dsl/DSLCore.kt`, `plugin/PythonPlugin.kt`; +**Status: implemented**. `plugin/dsl/DSLPlatforms.kt`, `plugin/dsl/DSLCore.kt`, `plugin/PythonPlugin.kt`; `ptest/dsl/PythonExtensionPlatformDslTest.kt` (the example's declarations through `PythonExtension`), `ptest/dsl/PlatformTargetMappingTest.kt`, `ptest/PythonPluginPlatformsTest.kt`, `ptest/PythonPluginVariantGraphTest.kt`, `ptest/bundle/BuildPythonArtifactTaskTest.kt` (minSdk). @@ -167,7 +167,7 @@ exist; any other name throws. `debug` forbids setting `useCodeMinifier`, `exclud `enableCodePush` (setting them throws). Without a declared platform variant the active build type is chosen by `-Ppython.buildType=` (default `debug`); an undeclared name fails loudly. -**Status: implemented** — `plugin/dsl/DSLBuild.kt`, `resolveActiveBuildType` in `plugin/PythonPlugin.kt`; +**Status: implemented**. `plugin/dsl/DSLBuild.kt`, `resolveActiveBuildType` in `plugin/PythonPlugin.kt`; `ptest/PythonPluginBuildTypeTest.kt`. (The debug-only setter rejections have no test: partial.) ### 1.6 `compileLevel` @@ -182,15 +182,15 @@ at `/.venv/{bin/python3,bin/python,Scripts/python.exe}`, the first found wa package directory. Before calling it, the task checks that interpreter (`bytecodeInterpreterRejection`) and fails that variant when: -- **no `.venv` is found** — the message names the directory searched and two ways to make one: +- **no `.venv` is found**: the message names the directory searched and two ways to make one: declare a dependency, so `installPythonDependencies` (`uv add`) creates `/.venv`, or run `uv venv --python ` there. toolchain does not create the venv itself: choosing and acquiring an interpreter is `pypackpack`'s work, and `uv venv` would replace a `.venv` that `uv add` made. -- **its minor version differs from `compileSdk`'s** — a `.pyc`'s magic number changes with every +- **its minor version differs from `compileSdk`'s**: a `.pyc`'s magic number changes with every CPython minor release, so a 3.13 `.pyc` does not load on a 3.14 runtime. The venv's version is read from its `pyvenv.cfg` (`version =` from the stdlib `venv`, `version_info =` from `uv`/`virtualenv`). -**Status: implemented** — `resolveBuildLevel` in `plugin/PythonPlugin.kt`, `bytecodeInterpreterRejection` +**Status: implemented**. `resolveBuildLevel` in `plugin/PythonPlugin.kt`, `bytecodeInterpreterRejection` in `plugin/bundle/BuildPythonArtifactTask.kt`; `ptest/PythonPluginBuildLevelTest.kt`, `ptest/PythonPluginVariantGraphTest.kt`, `ptest/bundle/BytecodeLevelTest.kt` (real `compileall`: debug has `.py` + `.pyc`, release has `.pyc` only; needs `python3` on `PATH`), @@ -208,7 +208,7 @@ Both are declared on `ReleaseBuildType` (`debug`'s setters throw, §1.5). only: `buildPython` for each variant of it, and the host `buildPython` when it is the active build type (`-Ppython.buildType`). Other build types keep them (§1.16 `metaclass` aside). -**Status: implemented** (`excludeMetaclass`) — `resolveBundledMetaDirs` in `plugin/BuildFeatures.kt`; +**Status: implemented** (`excludeMetaclass`). `resolveBundledMetaDirs` in `plugin/BuildFeatures.kt`; `ptest/BuildFeaturesTest.kt`, `ptest/PythonPluginBuildFeaturesTest.kt`. → *`useCodeMinifier`: declared, read by nothing. **planned**, blocked on `pypackpack` (no minifier).* @@ -221,7 +221,7 @@ type (or `debug` alone if none is declared). Each pair gets lifecycle tasks (their own action is skipped). Without a declared variant there is exactly one host-target chain, as before. -**Status: implemented** (resolution and naming) — `resolveVariants`, `PythonVariant`; +**Status: implemented** (resolution and naming). `resolveVariants`, `PythonVariant`; `ptest/PythonPluginVariantGraphTest.kt`. The task registration itself is untested here. ### 1.9 Source sets @@ -229,39 +229,39 @@ host-target chain, as before. directory functions are accepted only on `commonMain` and throw elsewhere. The package directory is `localLibraryPath` if set, else the **first** `commonMain.srcDirs` entry, else none (bundling and installation are skipped). Every `metaDirs` and `libDirs` entry is forwarded to `pypackpack` as -`BundleRequest.metaDirs` / `libDirs` — `metaDirs` subject to `buildFeatures { metaclass }` and +`BundleRequest.metaDirs` / `libDirs`: `metaDirs` subject to `buildFeatures { metaclass }` and `excludeMetaclass` (§1.16, §1.7). The `by getting { … }` block is applied at declaration (`provideDelegate`), so it takes effect even if the property is never read (issue #35); see `SourceSetGettingDelegateTest`. -**Status: implemented** — `resolvePackageDir`, `resolveMetaDirs`, `resolveLibDirs`; +**Status: implemented**. `resolvePackageDir`, `resolveMetaDirs`, `resolveLibDirs`; `ptest/PythonPluginSourceSetTest.kt`, `ptest/bundle/BuildPythonArtifactTaskTest.kt`. ### 1.10 Dependencies and `installPythonDependencies` `dependencies { implementation("pkg"); integration("pkg") }` in a source set. Two installations read these declarations. -**Into the package's venv — `installPythonDependencies`.** All source sets' `implementation` **and** +**Into the package's venv: `installPythonDependencies`.** All source sets' `implementation` **and** `integration` entries are flattened into one list and installed into the package directory through `pypackpack`: `BackendInterface.create(BackendType.UV)` → `addDependencies(…, workingDir = packageDir)` (a real `uv add`). This records the dependencies in the package's `pyproject.toml` and leaves the `/.venv` that `compileLevel = "bytecode"` compiles with (§1.6). It does not feed a bundle. An empty list or no package directory is a skip, not a failure. -**Per target — `installPythonDependencies`.** What reaches a bundle is installed separately, +**Per target: `installPythonDependencies`.** What reaches a bundle is installed separately, once per *dependency set*: a platform variant crossed with a flavor (§1.17). The build type is not part of it, because it changes neither the requirement list nor the triple, so `debug` and `release` share one install. -- A set reads `commonMain`, then its family's source set — `androidMain`, `iosMain` or `desktopMain` - (macOS, Linux and Windows are one `desktopMain`, as they are one staging destination, §1.13) — +- A set reads `commonMain`, then its family's source set: `androidMain`, `iosMain` or `desktopMain` + (macOS, Linux and Windows are one `desktopMain`, as they are one staging destination, §1.13), then `Main`. Entries keep that order; repeats are dropped. `buildFeatures { compose }` adds `pythonx-compose` to every set (§1.16). - The task (`installPythonDependenciesAndroidArm64`, `…AndroidArm64Free`) clears `build/pythonDeps//` (`androidArm64`, `androidArm64-free`) and calls `pypackpack`'s `UVBackend.installDependenciesToTarget(targetDir, pythonPlatform = , extraArgs, - workingDir, requirements = )` — `uv pip install --target + workingDir, requirements = )`: `uv pip install --target --python-platform ` (pypackpack#36). The list goes to `uv` as arguments; nothing is written. `uv` runs in the task's temporary directory (`build/tmp//`), outside the install directory. @@ -277,7 +277,7 @@ share one install. - Task inputs are the requirement list, triple, options and rejections; the output is the directory. `pip { autoUpdate = true }` makes the task never up to date. No package directory skips the task. -**Status: implemented** — `plugin/dependency/lang/python/InstallDependenciesTask.kt`, +**Status: implemented**. `plugin/dependency/lang/python/InstallDependenciesTask.kt`, `plugin/dependency/lang/python/InstallTargetDependenciesTask.kt`, `plugin/TargetDependencies.kt`; `ptest/PythonPluginDependencyTest.kt`, `ptest/TargetDependenciesTest.kt` (source-set selection per family and flavor, options, rejection), `ptest/PythonPluginTargetDependenciesTest.kt` (tasks, @@ -302,12 +302,12 @@ is not found) gets a warning naming it and suggesting `implementation(...)`; it *Assumption:* no repository defines `KLIBDEPENS`; it is taken to be a file of that name inside the wheel's `*.dist-info/` directory, and only its presence is checked, not its content. -KLIBDEPENS check — **Status: implemented** — `plugin/dependency/lang/python/InstallDependenciesTask.kt`; +KLIBDEPENS check (**Status: implemented**) `plugin/dependency/lang/python/InstallDependenciesTask.kt`; `plugin/dependency/lang/python/KlibDepens.kt`; `ptest/PythonPluginDependencyTest.kt`, `ptest/PythonPluginIntegrationListTest.kt`, `ptest/dependency/lang/python/KlibDepensTest.kt` (fake site-packages trees), `ptest/dependency/lang/python/InstallDependenciesTaskTest.kt` (needs `uv` and network). -### 1.11 Bundling — `buildPython` +### 1.11 Bundling: `buildPython` Builds a `pypackpack` `BundleRequest` (package dir, target triple, build type, build level, output dir, `overwrite = true`, minSdk, metaDirs, libDirs) and calls `BundlerInterface.create(BundleType.RESOURCE).bundle(request)`. `libDirs` is the task's @@ -316,18 +316,18 @@ the installed packages land in `python/` beside the app's modules. The result is `resource-manifest.json`. A failure becomes a `GradleException`. No package directory: the output directory is created empty and `pypackpack` is not called. -**Status: implemented** — `plugin/bundle/BuildPythonArtifactTask.kt`; +**Status: implemented**. `plugin/bundle/BuildPythonArtifactTask.kt`; `ptest/bundle/BuildPythonArtifactTaskTest.kt`. -### 1.12 Packaging — `packagePython` +### 1.12 Packaging: `packagePython` Zips the bundle directory to `build/distributions/.zip` (or `-.zip`). Fails if the bundle directory does not exist. -**Status: implemented** — `plugin/bundle/AssemblePythonPackageTask.kt`; +**Status: implemented**. `plugin/bundle/AssemblePythonPackageTask.kt`; `ptest/bundle/AssemblePythonPackageTaskTest.kt` runs the real task's action: the archive name and location for the aggregate and a variant task, the zip entries, and the failure when the bundle directory is missing. -`packaging { embedLevel }` — 0 no interpreter, 1 the app uses an external interpreter, 2 +`packaging { embedLevel }`: 0 no interpreter, 1 the app uses an external interpreter, 2 python-multiplatform embeds the interpreter. **toolchain ships no interpreter at any level**: the bundle and the zip hold `python/` (and `resource-manifest.json`) only (§1.2). The level is resolved per packaging task by `resolveEmbedLevel(declared, override, platformFamily)` (the family is @@ -342,16 +342,16 @@ The `python.embedLevel` property (`gradle.properties` or `-P`) overrides the DSL raised the same way. A value outside 0..2, or not a number, fails configuration naming the property. Each packaging task carries the resolved level as an `@Input`, logs it, and writes `.embed.json` beside the zip: `embedLevel`, `platformFamily`, `warning` and -`interpreterVersion` — the version the app expects (`expectedInterpreterVersion`): the `compileSdk` +`interpreterVersion`: the version the app expects (`expectedInterpreterVersion`): the `compileSdk` release at levels 1 and 2 (at 2, the one python-multiplatform is expected to embed), otherwise, or with no or a rejected `compileSdk`, `null`. The level never changes the payload. At level 2, `compileSdk` is checked against python-multiplatform's `pythonVersion` (§1.2). -**Status: implemented** — `ptest/EmbedLevelTest.kt`, `ptest/PythonPluginEmbedLevelTest.kt`, +**Status: implemented**. `ptest/EmbedLevelTest.kt`, `ptest/PythonPluginEmbedLevelTest.kt`, `ptest/PythonPluginPythonOnlyTest.kt` (zips a level 2 variant bundle, finds `python/` and no `runtime/`, and reads its record). -### 1.13 Staging into the app — `stagePythonBundle{Android,Ios,Desktop}` +### 1.13 Staging into the app: `stagePythonBundle{Android,Ios,Desktop}` Copies the bundle's `python/` subtree (never the manifest) into `build/pythonStaging//python/`, deleting what a previous run staged first. The bundle payload is a declared input, so a changed package re-stages. One variant is chosen per @@ -362,19 +362,19 @@ stages nothing. `python/` is all toolchain stages, at every `embedLevel`: the interpreter and the stdlib reach the app through python-multiplatform, which links libpython and ships the matching stdlib itself (§1.2, #42). -**Status: implemented** (copy rule and selection) — `plugin/bundle/StagePythonBundleTask.kt`, +**Status: implemented** (copy rule and selection). `plugin/bundle/StagePythonBundleTask.kt`, `selectStagingVariants`; `ptest/bundle/StagePythonBundleTaskTest.kt`, `ptest/PythonPluginStagingTest.kt`. Hand-off to the platform's packaging step: - Desktop: the staged root is added to the JVM target's `ProcessResources`, so the payload - is in the desktop jar. **implemented** — `ptest/PythonPluginAttachmentTest.kt` (Kotlin + is in the desktop jar. **implemented**: `ptest/PythonPluginAttachmentTest.kt` (Kotlin Multiplatform with `jvm("desktop")`: `stagePythonBundleDesktop` is a dependency of `desktopProcessResources` and the staged file is among its sources). Not tested: the fallback for a `ProcessResources` that is not a `Copy` task (dependency plus warning), and the built jar itself. - Android: the staged root is added to `android.sourceSets.main.assets` (reflectively), so the - payload is in the APK's `assets/`. **partial** — `ptest/PythonPluginAttachmentTest.kt` applies the + payload is in the APK's `assets/`. **partial**: `ptest/PythonPluginAttachmentTest.kt` applies the real `com.android.application` (AGP 8.5.2, test classpath only) and checks the staged root is a `main` asset source directory. Not tested: the `preBuild` / `merge*Assets` → `stagePythonBundleAndroid` dependency (AGP creates those tasks only when the project is evaluated against an Android SDK, @@ -390,30 +390,30 @@ Hand-off to the platform's packaging step: with this task and passes it. Exactly one Xcode phase may copy `python/`. Wiring by hand without python-multiplatform: the guide's staging page has the safe phase snippet (capture, parse, check the directory, then `rsync --delete`); the repository no longer ships it as a script (#62). - **partial** — `xcodePayloadLine`, `ptest/bundle/StagePythonBundleIosForXcodeTaskTest.kt`, + **partial**: `xcodePayloadLine`, `ptest/bundle/StagePythonBundleIosForXcodeTaskTest.kt`, `ptest/bundle/StagePythonPayloadScriptTest.kt`. Not tested: an actual Xcode build. - Putting the staged `python/` on `sys.path` at run time is `python-multiplatform`'s side. -### 1.14 Hot reload — `hotReloadPython` +### 1.14 Hot reload: `hotReloadPython` `packaging { hotReload { serverHost; redirectErrorStream; cert { keyStore | autoGenerate } } }` is validated when any build type sets `enableHotReload = true`: `serverHost` must be non-blank, and `keyStore` and `autoGenerate` are mutually exclusive. The task collects every `.py` under `localLibraryPath`, runs `adb push` for each to `/data/local/tmp//python/…`, then `adb shell am broadcast -a org.thisisthepy.python.RELOAD_PYTHON`. -**Status: partial** — `plugin/hotreload/`; `ptest/PythonPluginHotReloadTest.kt`, +**Status: partial**. `plugin/hotreload/`; `ptest/PythonPluginHotReloadTest.kt`, `ptest/hotreload/WatchAndPushTest.kt` test the validation and the command list; the task action itself is untested. `serverHost` and `cert` are validated but not used for transport (adb only); the source root ignores `commonMain.srcDirs`; Android only (a desktop copy helper, `executeLocalCopy`, exists and is tested but no task calls it). → *A hot-reload server reachable at `serverHost`, and a "run" entry point for it: **planned**.* -### 1.15 Code push — `codePushPython` +### 1.15 Code push: `codePushPython` `packaging { codePush { serverHost; cert; uploadConfig { forceUpload; login { id; password } } } }` is validated the same way when any build type sets `enableCodePush = true`. The task prints that no upload client exists. -**Status: partial** (validation) — `plugin/hotreload/HotReloadConfig.kt`, `CodePushPendingTask.kt`; +**Status: partial** (validation). `plugin/hotreload/HotReloadConfig.kt`, `CodePushPendingTask.kt`; `ptest/PythonPluginCodePushTest.kt`. → *Upload: **planned**.* ### 1.16 `buildFeatures { metaclass, compose }` @@ -427,7 +427,7 @@ or `-P`) with no default: | Property | Value | Effect | |---|---|---| -| `python.compose.pythonxCompose` | a pip requirement naming `pythonx-compose` (`pythonx-compose==0.1.0`), or an existing directory of wheels (absolute, relative to the project directory, or a `file:` URI) | The requirement — or `pythonx-compose` for a directory — is appended to `installPythonDependencies`' list and to every per-target list (§1.10). A directory is appended to the `find-links` option `pip { repositories { local } }` produces, comma-separated after it (uv splits `--find-links` on commas). | +| `python.compose.pythonxCompose` | a pip requirement naming `pythonx-compose` (`pythonx-compose==0.1.0`), or an existing directory of wheels (absolute, relative to the project directory, or a `file:` URI) | The requirement (or `pythonx-compose` for a directory) is appended to `installPythonDependencies`' list and to every per-target list (§1.10). A directory is appended to the `find-links` option `pip { repositories { local } }` produces, comma-separated after it (uv splits `--find-links` on commas). | | `python.compose.kotlinModule` | a Maven coordinate `group:artifact:version` of `python-multiplatform-compose` | Added to Kotlin Multiplatform `commonMain`'s `implementation` when `org.jetbrains.kotlin.multiplatform` is applied. | Failures, each naming the property and what it is for: @@ -440,7 +440,7 @@ Failures, each naming the property and what it is for: - Kotlin Multiplatform not applied: the Kotlin half is skipped with a warning and `kotlinModule` is not required; `pythonx-compose` is still installed. -**Status: implemented** — `resolveBundledMetaDirs`, `resolveComposePythonInstall`, +**Status: implemented**. `resolveBundledMetaDirs`, `resolveComposePythonInstall`, `mergeComposeFindLinks`, `resolveComposeKotlinDependency` in `plugin/BuildFeatures.kt`; `ptest/BuildFeaturesTest.kt`, `ptest/PythonPluginBuildFeaturesTest.kt` (task wiring on a ProjectBuilder project, the Kotlin dependency with `org.jetbrains.kotlin.multiplatform` applied). Installing a real @@ -457,57 +457,57 @@ no platform variant could change nothing, so the host `buildPython` fails with t flavor's own dependencies go in a `Main` source set and are installed only for that flavor's variants (§1.10). -**Status: implemented** — `plugin/dsl/DSLFlavors.kt`, `resolveVariants`, `resolveActiveFlavor`, +**Status: implemented**. `plugin/dsl/DSLFlavors.kt`, `resolveVariants`, `resolveActiveFlavor`, `flavorsWithoutVariantsRejection` in `plugin/PythonPlugin.kt`; `ptest/PythonPluginFlavorsTest.kt` (including task registration on an applied plugin). → *Per-flavor properties: **planned**.* -### 1.18 TypedPython check — `typedpythonCheck` +### 1.18 TypedPython check: `typedpythonCheck` Source: issue [`toolchain#23`](https://github.com/thisisthepy/toolchain/issues/23) (TypedPython step 1b; checking is on by default, maintainer decision 2026-10-02). One `typedpythonCheck` task per project statically checks the project's own Python before it is bundled. Every `buildPython` and the single host `buildPython` depend on it. -- **Sources.** Every `.py` file under the directory `resolvePackageDir` resolves to (§1.9) — the - directory that gets bundled — excluding hidden directories (`.venv`, …), `__pycache__` and +- **Sources.** Every `.py` file under the directory `resolvePackageDir` resolves to (§1.9): the + directory that gets bundled: excluding hidden directories (`.venv`, …), `__pycache__` and `build`. `metaDirs` and `libDirs` are bundled but **not checked**: `libDirs` holds third-party site-packages and `metaDirs` generated metadata, neither of which the user wrote or could fix (asserted by `typedpythonCheckedDirs`). `.py` and `.pyi` files there are `@InputFiles` with `@PathSensitive(RELATIVE)`: a changed `.py` re-runs the check, a change to any other file leaves it `UP-TO-DATE`. No package directory, or no `.py` file in it: the check is skipped with a log line and the gate is not installed. - **Gate.** The `typedpython` wheel and the `pyrefly` it pins, both at pinned versions (`@Input`, - defaults `0.1.0` and `1.3.2`), installed into `build/typedpython/venv` — never the project's `.venv`. + defaults `0.1.0` and `1.3.2`), installed into `build/typedpython/venv`: never the project's `.venv`. The venv is created through `pypackpack`'s UV backend (`createVirtualEnvironment`, `--no-project --clear`, Python `>=3.13`; only when the venv does not exist). The install is `uv pip install --python --no-index --find-links typedpython== pyrefly==` (skipped when the same request is already installed): **both packages come from `` and - nothing is ever fetched from an index** — `typedpython` is unclaimed on PyPI, so a bare-name install + nothing is ever fetched from an index**: `typedpython` is unclaimed on PyPI, so a bare-name install would run whatever someone publishes under that name on every consumer's build. `` is the project property `typedpython.wheelDir` and must hold the `typedpython` wheel and the `pyrefly` wheel for the platform. An install failure fails the task and names that requirement. The install calls `uv` directly: `pypackpack`'s backend has no operation that installs named requirements into an existing venv from a local wheel directory (`installDependenciesToTarget` reads `-r pyproject.toml` - into `--target`; `addDependencies` edits `pyproject.toml`) — a `pypackpack` change, not made here. + into `--target`; `addDependencies` edits `pyproject.toml`): a `pypackpack` change, not made here. - **No wheel directory.** The check is **skipped with a loud warning** (what was skipped, that the gate is not on PyPI yet, and `-Ptypedpython.wheelDir=` to turn it on); nothing is created or installed. With a wheel directory a failed check fails the build. - **Run.** `/bin/typedpython check --mode [--search-path ]... ...`, with explicit files (the 0.1.0 gate crashes on a directory argument). `mode` is an `@Input`, default `checked`; any value other than `checked`/`compiled` fails the task. -- **Import root.** The package's own import root — `/src/main` when it exists (the - `pypackpack` layout), else the package directory — is passed as the first `--search-path`, so the +- **Import root.** The package's own import root (`/src/main` when it exists, which is the + `pypackpack` layout, else the package directory) is passed as the first `--search-path`, so the package's modules can import one another (the 0.1.0 gate otherwise infers its temporary config directory as the import root). - **Stubs.** The plugin creates a resolvable configuration `typedpythonStubs`; its files are `@InputFiles` and each is passed as `--search-path`, in order. With nothing in it, the check runs without stubs and logs that once. - **Result.** Exit 0 passes and prints any `WARNING` lines. Exit 1 with diagnostics fails the build - with them. Exit 2 — or exit 1 with no diagnostic line, which is how an uncaught exception in the - gate exits — fails the build with the tool's own output. A passing run writes + with them. Exit 2 (or exit 1 with no diagnostic line, which is how an uncaught exception in the + gate exits) fails the build with the tool's own output. A passing run writes `build/typedpython/report.txt` (the task's output, so an unchanged rerun is `UP-TO-DATE`). -**Status: implemented** — `plugin/typedpython/TypedpythonCheckTask.kt`, wiring in `plugin/PythonPlugin.kt`; +**Status: implemented**. `plugin/typedpython/TypedpythonCheckTask.kt`, wiring in `plugin/PythonPlugin.kt`; `ptest/typedpython/TypedpythonCheckTaskTest.kt` (TestKit: an `Any` leak fails `buildPython` and the fix passes; per-variant `buildPython*` depend on the check; only a `.py` change re-runs it; a `typedpythonStubs` directory resolves an import that fails without it; the no-stubs line appears @@ -519,13 +519,13 @@ commands, skip warning, mode, exit-code interpretation). The TestKit tests in `TypedpythonCheckTaskTest` need `-Ptypedpython.wheelDir=` (holding the `typedpython` and `pyrefly` wheels) and `uv`; without it they are **skipped** with that reason, as on CI. Not covered by a test here: exit 2 end to end (only `interpretTypedpythonResult`), and the install -in a *consumer's* classpath — `withPluginClasspath()` bypasses the Kotlin DSL's `kotlin-stdlib` pin; +in a *consumer's* classpath: `withPluginClasspath()` bypasses the Kotlin DSL's `kotlin-stdlib` pin; that was verified by building sample's `buildPython`. → *Until `typedpython` is on PyPI, a build without `-Ptypedpython.wheelDir` skips the check with a warning.* → *Platform overlays (`src/`) are checked as files but only `src/main` is an import root: **partial**.* -## 2. `tcl` — toolchain-lite +## 2. `tcl`: toolchain-lite ### 2.1 `tcl install ` Finds the nearest `pyproject.toml` at or above the working directory. If there is none, runs @@ -534,25 +534,25 @@ Finds the nearest `pyproject.toml` at or above the working directory. If there i message on stderr for a missing argument, an unknown command or a failed install. No arguments prints `Usage: tcl install `. -**Status: implemented** — `tcl/Cli.kt`, `tcl/Installer.kt`; `tcl/CliArgsTest.kt`, +**Status: implemented**. `tcl/Cli.kt`, `tcl/Installer.kt`; `tcl/CliArgsTest.kt`, `tcl/InstallerTest.kt` (needs `uv` and network). Run with `./gradlew :tcl:run --args="install "`. --- -## Outside intent — needs a decision +## Outside intent: needs a decision These exist in the code but are not asked for by the example build file or the issues. -1. **`python { localLibraryPath = "…" }`** — not in the example build file, which uses +1. **`python { localLibraryPath = "…" }`**: not in the example build file, which uses `sourceSets { commonMain { srcDirs(…) } }`. It overrides `srcDirs` and is the only source root hot reload reads. `sample` depends on it. -2. **`-Ppython.buildType=`** — a project property choosing the active build type when no +2. **`-Ppython.buildType=`**: a project property choosing the active build type when no platform variant is declared. The example build file says nothing about selecting a build type. 3. **Hot reload over `adb push` + broadcast.** The example describes an HTTPS `serverHost` with a certificate; the implemented transport is Android-only `adb`. 4. **Unreferenced code**: `PythonMultiplatformPlugin.kt` (a second `Plugin` that is not registered in `gradlePlugin { }`), `dependency/reslover.kt`, `dependency/lang/kotlin/decompileKotlinMeta.kt` - (a `main()` generating Python from Kotlin metadata — related to `buildFeatures.metaclass` but not + (a `main()` generating Python from Kotlin metadata, related to `buildFeatures.metaclass` but not wired), `bundle/PythonLocalLoader.kt`, `dependency/DependencyType.kt`, and `BinariesExtension` / `FrozenPackConfig` / `BuildTypeEnum` in `dsl/DSLPlatforms.kt`. Nothing calls any of them. diff --git a/docs/guide/assets/site.js b/docs/guide/assets/site.js index 286f19b..8c19455 100644 --- a/docs/guide/assets/site.js +++ b/docs/guide/assets/site.js @@ -1,4 +1,4 @@ -// toolchain guide — language and theme. Loaded synchronously in so the first paint is +// toolchain guide: language and theme. Loaded synchronously in so the first paint is // already in the right language and theme. (function () { var LANG_KEY = "thisisthepy.guide.lang"; diff --git a/docs/guide/assets/style.css b/docs/guide/assets/style.css index 66f1286..60c6610 100644 --- a/docs/guide/assets/style.css +++ b/docs/guide/assets/style.css @@ -1,4 +1,4 @@ -/* toolchain guide — shared thisisthepy family tokens, toolchain accent. */ +/* toolchain guide: shared thisisthepy family tokens, toolchain accent. */ :root { --bg: #fbfbfa; --fg: #16181d; --muted: #5b6270; --line: #e6e6e3; --code-bg: #f3f3f1; --radius: 10px; --maxw: 72rem; diff --git a/docs/guide/concepts.html b/docs/guide/concepts.html index 1eb7c47..05a55e0 100644 --- a/docs/guide/concepts.html +++ b/docs/guide/concepts.html @@ -62,13 +62,13 @@

Variants변형

How it fails실패하는 방식

-
Mistake실수When언제Scope범위
Malformed compileSdk형식이 틀린 compileSdkconfiguration구성 시점whole build빌드 전체
Platform with no pypackpack triple (androidArm32, androidX86)pypackpack 트리플이 없는 플랫폼 (androidArm32, androidX86)configuration구성 시점whole build — no valid task exists빌드 전체 — 유효한 태스크가 없음
Kotlin target not enabled for a declared platform선언된 플랫폼의 Kotlin 타깃이 꺼져 있음configuration구성 시점warning only경고만
compileLevel = native / mixed, or bytecode without a matching .venvcompileLevel = native / mixed, 또는 맞는 .venv 가 없는 bytecodetask execution태스크 실행 시점that variant only — --continue builds the rest그 변형만 — --continue 로 나머지는 빌드
compileSdk differs from python-multiplatform's pythonVersion at embedLevel = 2embedLevel = 2 에서 compileSdk 가 python-multiplatform 의 pythonVersion 과 다름task execution태스크 실행 시점that variant only — --continue builds the rest그 변형만 — --continue 로 나머지는 빌드
Unknown build type in -Ppython.buildType-Ppython.buildType 의 알 수 없는 빌드 타입configuration구성 시점whole build빌드 전체
+
Mistake실수When언제Scope범위
Malformed compileSdk형식이 틀린 compileSdkconfiguration구성 시점whole build빌드 전체
Platform with no pypackpack triple (androidArm32, androidX86)pypackpack 트리플이 없는 플랫폼 (androidArm32, androidX86)configuration구성 시점whole build: no valid task exists빌드 전체: 유효한 태스크가 없음
Kotlin target not enabled for a declared platform선언된 플랫폼의 Kotlin 타깃이 꺼져 있음configuration구성 시점warning only경고만
compileLevel = native / mixed, or bytecode without a matching .venvcompileLevel = native / mixed, 또는 맞는 .venv 가 없는 bytecodetask execution태스크 실행 시점that variant only: --continue builds the rest그 변형만: --continue 로 나머지는 빌드
compileSdk differs from python-multiplatform's pythonVersion at embedLevel = 2embedLevel = 2 에서 compileSdk 가 python-multiplatform 의 pythonVersion 과 다름task execution태스크 실행 시점that variant only: --continue builds the rest그 변형만: --continue 로 나머지는 빌드
Unknown build type in -Ppython.buildType-Ppython.buildType 의 알 수 없는 빌드 타입configuration구성 시점whole build빌드 전체
diff --git a/docs/guide/ecosystem.html b/docs/guide/ecosystem.html index b6f67f9..5e9bbd7 100644 --- a/docs/guide/ecosystem.html +++ b/docs/guide/ecosystem.html @@ -27,7 +27,7 @@

Ecosystem생태계toolchain is one part of thisisthepy: Python and Kotlin Multiplatform, in one app.

toolchain 은 thisisthepy 의 한 부분입니다. 하나의 앱 안의 Python 과 Kotlin Multiplatform.

-

toolchain — you are here — 현재 위치

The Gradle plugin and tcl: the vocabulary for building the Python half of an app.

Gradle 플러그인과 tcl: 앱의 Python 절반을 빌드하는 어휘.

+

toolchain: you are here: 현재 위치

The Gradle plugin and tcl: the vocabulary for building the Python half of an app.

Gradle 플러그인과 tcl: 앱의 Python 절반을 빌드하는 어휘.

pypackpack

The work: Python acquisition, dependency resolution with uv, cross-compilation, bundling.

작업: Python 획득, uv 의존성 해석, 크로스 컴파일, 번들링.

python-multiplatform

The runtime: CPython embedded in Kotlin Multiplatform, and the language boundary.

런타임: Kotlin Multiplatform 에 내장된 CPython 과 언어 경계.

pythonx-compose

Compose Multiplatform, usable from Python.

Python 에서 쓰는 Compose Multiplatform.

@@ -37,13 +37,13 @@

Ecosystem생태계

Where toolchain stopstoolchain 의 경계

-

If a feature acquires Python, resolves a dependency or builds a bundle, it belongs in pypackpack. If it runs at app start-up, it belongs in python-multiplatform. toolchain's job is the Gradle side in between — and saying clearly when the other side is not there yet.

Python 을 얻거나, 의존성을 해석하거나, 번들을 만드는 기능은 pypackpack 의 것입니다. 앱 시작 시점에 도는 것은 python-multiplatform 의 것입니다. toolchain 의 일은 그 사이의 Gradle 쪽 — 그리고 반대편이 아직 없을 때 그렇다고 분명히 말하는 것입니다.

+

If a feature acquires Python, resolves a dependency or builds a bundle, it belongs in pypackpack. If it runs at app start-up, it belongs in python-multiplatform. toolchain's job is the Gradle side in between: and saying clearly when the other side is not there yet.

Python 을 얻거나, 의존성을 해석하거나, 번들을 만드는 기능은 pypackpack 의 것입니다. 앱 시작 시점에 도는 것은 python-multiplatform 의 것입니다. toolchain 의 일은 그 사이의 Gradle 쪽: 그리고 반대편이 아직 없을 때 그렇다고 분명히 말하는 것입니다.

diff --git a/docs/guide/getting-started.html b/docs/guide/getting-started.html index bbb1e64..b5176d8 100644 --- a/docs/guide/getting-started.html +++ b/docs/guide/getting-started.html @@ -81,7 +81,7 @@

5. What you get5. 결과물 diff --git a/docs/guide/guide-dependencies.html b/docs/guide/guide-dependencies.html index aaf7ca4..3f478d8 100644 --- a/docs/guide/guide-dependencies.html +++ b/docs/guide/guide-dependencies.html @@ -95,7 +95,7 @@

ComposeCompose

diff --git a/docs/guide/guide-staging.html b/docs/guide/guide-staging.html index 189d8da..c9203e7 100644 --- a/docs/guide/guide-staging.html +++ b/docs/guide/guide-staging.html @@ -30,7 +30,7 @@

Into the app앱 안으로What staging does스테이징이 하는 일

stagePythonBundleAndroid, …Ios and …Desktop each copy a bundle's python/ directory into build/pythonStaging/<platform>/python/. stagePythonBundle runs all three.

stagePythonBundleAndroid, …Ios, …Desktop 은 각각 번들의 python/ 디렉터리를 build/pythonStaging/<platform>/python/ 로 복사합니다. stagePythonBundle 은 셋을 모두 실행합니다.

    -
  • Only python/ travels — the manifest is build metadata.python/ 만 옮깁니다 — 매니페스트는 빌드 메타데이터입니다.
  • +
  • Only python/ travels: the manifest is build metadata.python/ 만 옮깁니다: 매니페스트는 빌드 메타데이터입니다.
  • The previous payload is deleted first, so a module you removed stops shipping.이전 페이로드를 먼저 지우므로, 지운 모듈은 더 이상 실리지 않습니다.
  • The payload is a declared input: change your Python and the next build re-stages.페이로드가 선언된 입력이라, Python 을 바꾸면 다음 빌드가 다시 스테이징합니다.
  • Everything stays under build/; nothing is generated into src/.모든 것이 build/ 아래에 있고, src/ 에는 아무것도 생성하지 않습니다.
  • @@ -39,7 +39,7 @@

    What staging does스테이

    Which bundle goes where어느 번들이 어디로

    -

    A packaging step takes one directory, so one variant is chosen per destination — only from variants of the active build type:

    패키징 단계는 디렉터리 하나를 받으므로 목적지마다 변형 하나를 고릅니다 — 활성 빌드 타입의 변형 중에서만.

    +

    A packaging step takes one directory, so one variant is chosen per destination: only from variants of the active build type:

    패키징 단계는 디렉터리 하나를 받으므로 목적지마다 변형 하나를 고릅니다; 활성 빌드 타입의 변형 중에서만.

    Destination목적지Chosen variant고르는 변형Handed to넘겨받는 곳
    Desktop데스크톱the one whose triple is the host's, else the first declared호스트와 트리플이 같은 것, 없으면 처음 선언된 것the JVM target's processResources → jarJVM 타깃의 processResources → jarpartial부분
    AndroidAndroidthe first declared처음 선언된 것android.sourceSets.main.assets → APKandroid.sourceSets.main.assets → APKpartial부분
    iOSiOSthe first declared처음 선언된 것stagePythonBundleIosForXcode prints the path; an Xcode build phase copies itstagePythonBundleIosForXcode 가 경로를 출력하고 Xcode 빌드 단계가 복사합니다partial부분

    Without a declared platform variant, every destination gets the single host bundle and the build logs that it did so. A destination with no matching variant stages nothing rather than a bundle built for another platform.

    선언된 플랫폼 변형이 없으면 모든 목적지가 호스트 번들 하나를 받고, 빌드가 그 사실을 기록합니다. 맞는 변형이 없는 목적지는 다른 플랫폼용 번들 대신 아무것도 스테이징하지 않습니다.

    @@ -58,13 +58,13 @@

    iOS: the Xcode build phasei

    After staging스테이징 이후

    -

    Putting the staged python/ on sys.path when the app starts is the runtime's job — python-multiplatform's side of the boundary, not this plugin's.

    앱이 시작할 때 스테이징된 python/ 을 sys.path 에 올리는 것은 런타임의 일입니다 — 이 플러그인이 아니라 python-multiplatform 쪽 경계입니다.

    +

    Putting the staged python/ on sys.path when the app starts is the runtime's job: python-multiplatform's side of the boundary, not this plugin's.

    앱이 시작할 때 스테이징된 python/ 을 sys.path 에 올리는 것은 런타임의 일입니다: 이 플러그인이 아니라 python-multiplatform 쪽 경계입니다.

    diff --git a/docs/guide/guide-tcl.html b/docs/guide/guide-tcl.html index 0962b9a..b966b97 100644 --- a/docs/guide/guide-tcl.html +++ b/docs/guide/guide-tcl.html @@ -37,7 +37,7 @@

    Install a package패키지 diff --git a/docs/guide/guide-variants.html b/docs/guide/guide-variants.html index 1f9074a..4d0240b 100644 --- a/docs/guide/guide-variants.html +++ b/docs/guide/guide-variants.html @@ -78,13 +78,13 @@

    Project flavors프로젝트

    Choosing the active build type활성 빌드 타입 고르기

    Without platforms there are no per-variant task names to ask for, so a project property picks the build type. It defaults to debug; a name you did not declare fails the build.

    플랫폼이 없으면 고를 변형 태스크 이름이 없으므로 프로젝트 속성으로 빌드 타입을 정합니다. 기본값은 debug 이고, 선언하지 않은 이름은 빌드를 실패시킵니다.

    ./gradlew packagePython -Ppython.buildType=release
    -

    The same property decides which variant's bundle is staged into your app — see Into the app.

    같은 속성이 앱에 스테이징될 변형도 정합니다 — 앱 안으로를 보세요.

    +

    The same property decides which variant's bundle is staged into your app: see Into the app.

    같은 속성이 앱에 스테이징될 변형도 정합니다: 앱 안으로를 보세요.

diff --git a/docs/guide/index.html b/docs/guide/index.html index 133041c..fb9fa0b 100644 --- a/docs/guide/index.html +++ b/docs/guide/index.html @@ -27,12 +27,12 @@

Gradle plugin · Kotlin Multiplatform · Python

Gradle 플러그인 · Kotlin Multiplatform · Python

Declare the Python half of your app in Gradle.앱의 Python 절반을 Gradle 에 선언하세요.

-

toolchain turns one python { } block into installed dependencies, a bundle per target, and a payload inside your jar and APK — by delegating the work, not redoing it.

toolchain 은 python { } 블록 하나를 설치된 의존성, 타깃별 번들, 그리고 jar 와 APK 안의 페이로드로 바꿉니다. 작업을 다시 만들지 않고 위임해서요.

+

toolchain turns one python { } block into installed dependencies, a bundle per target, and a payload inside your jar and APK: by delegating the work, not redoing it.

toolchain 은 python { } 블록 하나를 설치된 의존성, 타깃별 번들, 그리고 jar 와 APK 안의 페이로드로 바꿉니다. 작업을 다시 만들지 않고 위임해서요.

-
python { }you declare선언한다toolchaintranslates번역한다pypackpackuv · bundleruv · 번들러pythonStagingper platform플랫폼별your app내 앱jar · APK · iOSjar · APK · iOSruntime런타임python-multiplatformpython-multiplatformbuild time — this repository + pypackpack빌드 시점 — 이 저장소 + pypackpackrun time실행 시점one python { } block → an app that carries its Pythonpython { } 블록 하나 → Python 을 품은 앱 +
python { }you declare선언한다toolchaintranslates번역한다pypackpackuv · bundleruv · 번들러pythonStagingper platform플랫폼별your app내 앱jar · APK · iOSjar · APK · iOSruntime런타임python-multiplatformpython-multiplatformbuild time: this repository + pypackpack빌드 시점: 이 저장소 + pypackpackrun time실행 시점one python { } block → an app that carries its Pythonpython { } 블록 하나 → Python 을 품은 앱
toolchain owns the Gradle vocabulary. pypackpack owns the work. python-multiplatform owns the runtime.toolchain 은 Gradle 어휘를, pypackpack 은 작업을, python-multiplatform 은 런타임을 맡습니다.
@@ -44,13 +44,13 @@

Three ideas the plugin is built on02

Delegate the work작업은 위임한다

Installing a package is a real uv add through pypackpack. Bundling is pypackpack's resource bundler. The plugin translates Gradle into those calls and nothing more.

패키지 설치는 pypackpack 을 거친 실제 uv add 이고, 번들링은 pypackpack 의 리소스 번들러입니다. 플러그인은 Gradle 을 그 호출로 번역할 뿐입니다.

03

Loud, never silent조용히 넘기지 않는다

-

Every DSL value is either read by a task or rejected with a reason. An unmapped platform or an unsupported compile level fails — and only the variant that asked for it.

모든 DSL 값은 태스크가 읽거나, 이유와 함께 거부됩니다. 매핑되지 않는 플랫폼이나 지원되지 않는 컴파일 레벨은 실패합니다 — 그것을 요청한 변형만.

+

Every DSL value is either read by a task or rejected with a reason. An unmapped platform or an unsupported compile level fails: and only the variant that asked for it.

모든 DSL 값은 태스크가 읽거나, 이유와 함께 거부됩니다. 매핑되지 않는 플랫폼이나 지원되지 않는 컴파일 레벨은 실패합니다: 그것을 요청한 변형만.

Where it is going가고 있는 곳

-

This is the shape of the DSL the maintainer wrote by hand — the target the plugin grows toward. Part of it is live today; the status page says exactly which part.

아래는 메인테이너가 직접 쓴 DSL 의 모습, 즉 플러그인이 향하는 목표입니다. 일부는 지금 동작하고, 정확히 어느 부분인지는 상태 페이지에 있습니다.

+

This is the shape of the DSL the maintainer wrote by hand: the target the plugin grows toward. Part of it is live today; the status page says exactly which part.

아래는 메인테이너가 직접 쓴 DSL 의 모습, 즉 플러그인이 향하는 목표입니다. 일부는 지금 동작하고, 정확히 어느 부분인지는 상태 페이지에 있습니다.

python {
     compileSdk = "3.11.9-alpha"
 
@@ -85,7 +85,7 @@ 

Where it is going가고 있 diff --git a/docs/guide/status.html b/docs/guide/status.html index cd7fef1..dcf8867 100644 --- a/docs/guide/status.html +++ b/docs/guide/status.html @@ -28,7 +28,7 @@

Status & FAQ상태와 F

Feature status기능 상태

-
+
Feature기능Status상태Evidence근거
Applying the plugin: python extension and tasks플러그인 적용: python 확장과 태스크implemented구현됨PythonPluginApplyTest
compileSdk parsing: alpha · rc · normalcompileSdk 파싱: alpha · rc · normalimplemented구현됨PythonVersionTest
Platform → triple mapping, Kotlin target check, min SDK플랫폼 → 트리플 매핑, Kotlin 타깃 확인, 최소 SDKimplemented구현됨PythonExtensionPlatformDslTest, PlatformTargetMappingTest, PythonPluginPlatformsTest
debug / release, -Ppython.buildTypedebug / release, -Ppython.buildTypeimplemented구현됨PythonPluginBuildTypeTest
Per-variant task graph변형별 태스크 그래프implemented구현됨PythonPluginVariantGraphTest
compileLevel = instant · bytecode (.venv and compileSdk minor checked); others rejected per variantcompileLevel = instant · bytecode (.venv 와 compileSdk 마이너 버전 확인); 나머지는 변형별 거부implemented구현됨PythonPluginBuildLevelTest, BytecodeLevelTest
srcDirs / metaDirs / libDirssrcDirs / metaDirs / libDirsimplemented구현됨PythonPluginSourceSetTest
implementation / integration → uv addimplementation / integration → uv addimplemented구현됨PythonPluginDependencyTest, InstallDependenciesTaskTest
Bundling via pypackpack's resource bundlerpypackpack 리소스 번들러로 번들링implemented구현됨BuildPythonArtifactTaskTest
packagePython zippackagePython zipimplemented구현됨AssemblePythonPackageTaskTest
Staging copy rule and variant selection스테이징 복사 규칙과 변형 선택implemented구현됨StagePythonBundleTaskTest, PythonPluginStagingTest
tcl installtcl installimplemented구현됨CliArgsTest, InstallerTest
Staging into the desktop jar데스크톱 jar 로의 스테이징implemented구현됨PythonPluginAttachmentTest
Staging into the APKAPK 로의 스테이징partial부분asset root tested (PythonPluginAttachmentTest); task dependency untested에셋 루트는 테스트됨 (PythonPluginAttachmentTest), 태스크 의존성은 테스트 없음
Hot reload핫 리로드partial부분Android, adb only; serverHost unusedAndroid, adb 만; serverHost 미사용
Code push코드 푸시partial부분validation only검증만
Dependencies per source set, installed for each variant's own triple into its bundle소스셋별 의존성을 변형마다 그 트리플용으로 설치해 번들에 포함implemented구현됨TargetDependenciesTest, PythonPluginTargetDependenciesTest, InstallTargetDependenciesTaskTest
integration(): warn when the wheel has no KLIBDEPENSintegration(): 휠에 KLIBDEPENS 가 없으면 경고implemented구현됨KlibDepensTest, PythonPluginIntegrationListTest
native / mixednative / mixedplanned계획needs pypackpack#19pypackpack#19 선행 필요
iOS: attaching the staged payload (stagePythonBundleIosForXcode prints the path; the Xcode phase is python-multiplatform's, #59)iOS: 스테이징된 페이로드 연결 (stagePythonBundleIosForXcode 가 경로 출력, Xcode 단계는 python-multiplatform #59 소유)partial부분StagePythonBundleIosForXcodeTaskTest, StagePythonPayloadScriptTest
buildFeatures { metaclass } and excludeMetaclass: metaDirs forwarded or droppedbuildFeatures { metaclass } 와 excludeMetaclass: metaDirs 전달 또는 제외implemented구현됨BuildFeaturesTest, PythonPluginBuildFeaturesTest
buildFeatures { compose }: installs pythonx-compose, adds python-multiplatform-compose to commonMainbuildFeatures { compose }: pythonx-compose 설치, commonMain 에 python-multiplatform-compose 추가implemented구현됨BuildFeaturesTest, PythonPluginBuildFeaturesTest; locations from two Gradle propertiesBuildFeaturesTest, PythonPluginBuildFeaturesTest; 위치는 Gradle 속성 두 개로 지정
embedLevel: 0/1/2 resolved per platform (Android and iOS raise to 2 with a warning), -Ppython.embedLevel override, recorded in embed.json with the expected interpreter version; level 2 means python-multiplatform embeds the interpreter, and the bundle stays python/ onlyembedLevel: 플랫폼별 0/1/2 결정 (Android, iOS 는 경고와 함께 2 로 상향), -Ppython.embedLevel 재정의, 기대 인터프리터 버전과 함께 embed.json 에 기록; 레벨 2 는 python-multiplatform 이 인터프리터를 내장한다는 뜻이고, 번들은 python/ 만 담음implemented구현됨EmbedLevelTest, PythonPluginEmbedLevelTest, PythonPluginPythonOnlyTest
useCodeMinifieruseCodeMinifierplanned계획declared, unread선언만, 읽히지 않음
defaultConfig { pip }: central · local · autoUpdatedefaultConfig { pip }: central · local · autoUpdateimplemented구현됨PipRepositoriesTest
pip { jit }pip { jit }planned계획—
compileSdk constants (PY3_14_7); unprovided versions refused per taskcompileSdk 상수(PY3_14_7); 제공되지 않는 버전은 태스크에서 거부implemented구현됨PythonSdkTest, BuildPythonSdkRejectionTest
projectFlavors in the variant graph; -Ppython.flavor변형 그래프의 projectFlavors; -Ppython.flavorimplemented구현됨PythonPluginFlavorsTest
defaultConfig { versionCode, versionName } → bundle manifestdefaultConfig { versionCode, versionName } → 번들 manifestimplemented구현됨PythonPluginPayloadVersionTest, BuildPythonArtifactTaskTest
toolchain ships only python/: compileSdk selects the wheels' Python version and fetches no interpreter; the interpreter and stdlib come from python-multiplatformtoolchain 은 python/ 만 실음: compileSdk 는 wheel 의 Python 버전을 고를 뿐 인터프리터를 받지 않음; 인터프리터와 stdlib 는 python-multiplatform 이 제공implemented구현됨PythonPluginTargetDependenciesTest, PythonPluginPythonOnlyTest
Feature기능Status상태Evidence근거
Applying the plugin: python extension and tasks플러그인 적용: python 확장과 태스크implemented구현됨PythonPluginApplyTest
compileSdk parsing: alpha · rc · normalcompileSdk 파싱: alpha · rc · normalimplemented구현됨PythonVersionTest
Platform → triple mapping, Kotlin target check, min SDK플랫폼 → 트리플 매핑, Kotlin 타깃 확인, 최소 SDKimplemented구현됨PythonExtensionPlatformDslTest, PlatformTargetMappingTest, PythonPluginPlatformsTest
debug / release, -Ppython.buildTypedebug / release, -Ppython.buildTypeimplemented구현됨PythonPluginBuildTypeTest
Per-variant task graph변형별 태스크 그래프implemented구현됨PythonPluginVariantGraphTest
compileLevel = instant · bytecode (.venv and compileSdk minor checked); others rejected per variantcompileLevel = instant · bytecode (.venv 와 compileSdk 마이너 버전 확인); 나머지는 변형별 거부implemented구현됨PythonPluginBuildLevelTest, BytecodeLevelTest
srcDirs / metaDirs / libDirssrcDirs / metaDirs / libDirsimplemented구현됨PythonPluginSourceSetTest
implementation / integration → uv addimplementation / integration → uv addimplemented구현됨PythonPluginDependencyTest, InstallDependenciesTaskTest
Bundling via pypackpack's resource bundlerpypackpack 리소스 번들러로 번들링implemented구현됨BuildPythonArtifactTaskTest
packagePython zippackagePython zipimplemented구현됨AssemblePythonPackageTaskTest
Staging copy rule and variant selection스테이징 복사 규칙과 변형 선택implemented구현됨StagePythonBundleTaskTest, PythonPluginStagingTest
tcl installtcl installimplemented구현됨CliArgsTest, InstallerTest
Staging into the desktop jar데스크톱 jar 로의 스테이징implemented구현됨PythonPluginAttachmentTest
Staging into the APKAPK 로의 스테이징partial부분asset root tested (PythonPluginAttachmentTest); task dependency untested에셋 루트는 테스트됨 (PythonPluginAttachmentTest), 태스크 의존성은 테스트 없음
Hot reload핫 리로드partial부분Android, adb only; serverHost unusedAndroid, adb 만; serverHost 미사용
Code push코드 푸시partial부분validation only검증만
Dependencies per source set, installed for each variant's own triple into its bundle소스셋별 의존성을 변형마다 그 트리플용으로 설치해 번들에 포함implemented구현됨TargetDependenciesTest, PythonPluginTargetDependenciesTest, InstallTargetDependenciesTaskTest
integration(): warn when the wheel has no KLIBDEPENSintegration(): 휠에 KLIBDEPENS 가 없으면 경고implemented구현됨KlibDepensTest, PythonPluginIntegrationListTest
native / mixednative / mixedplanned계획needs pypackpack#19pypackpack#19 선행 필요
iOS: attaching the staged payload (stagePythonBundleIosForXcode prints the path; the Xcode phase is python-multiplatform's, #59)iOS: 스테이징된 페이로드 연결 (stagePythonBundleIosForXcode 가 경로 출력, Xcode 단계는 python-multiplatform #59 소유)partial부분StagePythonBundleIosForXcodeTaskTest, StagePythonPayloadScriptTest
buildFeatures { metaclass } and excludeMetaclass: metaDirs forwarded or droppedbuildFeatures { metaclass } 와 excludeMetaclass: metaDirs 전달 또는 제외implemented구현됨BuildFeaturesTest, PythonPluginBuildFeaturesTest
buildFeatures { compose }: installs pythonx-compose, adds python-multiplatform-compose to commonMainbuildFeatures { compose }: pythonx-compose 설치, commonMain 에 python-multiplatform-compose 추가implemented구현됨BuildFeaturesTest, PythonPluginBuildFeaturesTest; locations from two Gradle propertiesBuildFeaturesTest, PythonPluginBuildFeaturesTest; 위치는 Gradle 속성 두 개로 지정
embedLevel: 0/1/2 resolved per platform (Android and iOS raise to 2 with a warning), -Ppython.embedLevel override, recorded in embed.json with the expected interpreter version; level 2 means python-multiplatform embeds the interpreter, and the bundle stays python/ onlyembedLevel: 플랫폼별 0/1/2 결정 (Android, iOS 는 경고와 함께 2 로 상향), -Ppython.embedLevel 재정의, 기대 인터프리터 버전과 함께 embed.json 에 기록; 레벨 2 는 python-multiplatform 이 인터프리터를 내장한다는 뜻이고, 번들은 python/ 만 담음implemented구현됨EmbedLevelTest, PythonPluginEmbedLevelTest, PythonPluginPythonOnlyTest
useCodeMinifieruseCodeMinifierplanned계획declared, unread선언만, 읽히지 않음
defaultConfig { pip }: central · local · autoUpdatedefaultConfig { pip }: central · local · autoUpdateimplemented구현됨PipRepositoriesTest
pip { jit }pip { jit }planned계획-
compileSdk constants (PY3_14_7); unprovided versions refused per taskcompileSdk 상수(PY3_14_7); 제공되지 않는 버전은 태스크에서 거부implemented구현됨PythonSdkTest, BuildPythonSdkRejectionTest
projectFlavors in the variant graph; -Ppython.flavor변형 그래프의 projectFlavors; -Ppython.flavorimplemented구현됨PythonPluginFlavorsTest
defaultConfig { versionCode, versionName } → bundle manifestdefaultConfig { versionCode, versionName } → 번들 manifestimplemented구현됨PythonPluginPayloadVersionTest, BuildPythonArtifactTaskTest
toolchain ships only python/: compileSdk selects the wheels' Python version and fetches no interpreter; the interpreter and stdlib come from python-multiplatformtoolchain 은 python/ 만 실음: compileSdk 는 wheel 의 Python 버전을 고를 뿐 인터프리터를 받지 않음; 인터프리터와 stdlib 는 python-multiplatform 이 제공implemented구현됨PythonPluginTargetDependenciesTest, PythonPluginPythonOnlyTest
compileSdk checked against python-multiplatform's pythonVersion at level 2 (a mismatch fails only that variant's buildPython…); read from -Ppython.multiplatform.pythonVersion, the :python-multiplatform project's extension, or the published dependency's metadata or jar레벨 2 에서 compileSdk 를 python-multiplatform 의 pythonVersion 과 대조 (다르면 그 변형의 buildPython… 만 실패); -Ppython.multiplatform.pythonVersion, :python-multiplatform 프로젝트의 확장, 또는 배포된 의존의 메타데이터나 jar 에서 읽음implemented구현됨PythonVersionAgreementTest, PythonPluginPythonOnlyTest, PublishedPythonVersionTest
@@ -46,7 +46,7 @@

Is the DSL final?DSL 은

Contributing기여하기

-

Changes go intent → spec → test → code. A behaviour change begins as a change to the specification; its test is written first and seen failing; then the code makes it pass. A DSL value is never accepted silently — it is wired to a task, or rejected with a reason.

변경은 의도 → 스펙 → 테스트 → 코드 순서로 갑니다. 동작 변경은 스펙 변경에서 시작하고, 테스트를 먼저 써서 실패를 확인한 뒤, 코드로 통과시킵니다. DSL 값은 조용히 받아들이지 않습니다 — 태스크에 연결하거나, 이유와 함께 거부합니다.

+

Changes go intent → spec → test → code. A behaviour change begins as a change to the specification; its test is written first and seen failing; then the code makes it pass. A DSL value is never accepted silently: it is wired to a task, or rejected with a reason.

변경은 의도 → 스펙 → 테스트 → 코드 순서로 갑니다. 동작 변경은 스펙 변경에서 시작하고, 테스트를 먼저 써서 실패를 확인한 뒤, 코드로 통과시킵니다. DSL 값은 조용히 받아들이지 않습니다: 태스크에 연결하거나, 이유와 함께 거부합니다.

./gradlew :toolchain-gradle-plugin:test --rerun     # plugin; needs packpack in Maven Local, uv and network
 ./gradlew :tcl:test --rerun           # tcl; needs uv and network
 python3 docs/guide/check_guide.py     # this site
@@ -56,7 +56,7 @@

Contributing diff --git a/docs/locale/README_ko.md b/docs/locale/README_ko.md index 5ac74f3..63dfaef 100644 --- a/docs/locale/README_ko.md +++ b/docs/locale/README_ko.md @@ -6,7 +6,7 @@ **Kotlin Multiplatform 앱의 Python 절반을 Gradle 에 선언하고, 그대로 배포하세요.** -[![License: MIT](https://img.shields.io/badge/license-MIT-0f9d76.svg)](../../LICENSE) +[![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-0f9d76.svg)](../../LICENSE) ![Kotlin](https://img.shields.io/badge/Kotlin-2.1-7f52ff.svg?logo=kotlin&logoColor=white) ![Gradle](https://img.shields.io/badge/Gradle-8.9-02303a.svg?logo=gradle&logoColor=white) ![Targets](https://img.shields.io/badge/targets-Android%20%7C%20iOS%20%7C%20Desktop-0f9d76.svg) @@ -21,8 +21,8 @@ ## 💡 왜 필요한가 Python 을 내장한 Kotlin Multiplatform 앱에는 맞물려 돌아가야 하는 빌드가 둘 있습니다. Gradle 이 -이미 아는 Kotlin 빌드, 그리고 Gradle 이 모르는 Python 빌드 — 인터프리터 버전, 패키지, 플랫폼별 -번들, 에셋 — 입니다. `toolchain` 은 그 간극을 메우는 Gradle 플러그인입니다. `kotlin { }` 블록 옆에 +이미 아는 Kotlin 빌드, 그리고 Gradle 이 모르는 Python 빌드(인터프리터 버전, 패키지, 플랫폼별 +번들, 에셋)입니다. `toolchain` 은 그 간극을 메우는 Gradle 플러그인입니다. `kotlin { }` 블록 옆에 `python { }` 블록 하나를 쓰면, 빌드가 Python 의존성을 설치하고, 타깃마다 코드를 번들링하고, 각 플랫폼의 패키징 단계가 가져갈 자리에 페이로드를 놓습니다. @@ -33,17 +33,17 @@ Python 을 내장한 Kotlin Multiplatform 앱에는 맞물려 돌아가야 하 ## ✨ 기능 -- 🧩 **DSL 블록 하나** — `compileSdk`, 플랫폼, `buildTypes`, `sourceSets`, `packaging` 을 모두 +- 🧩 **DSL 블록 하나**: `compileSdk`, 플랫폼, `buildTypes`, `sourceSets`, `packaging` 을 모두 `python { }` 안에. -- 📦 **실제 의존성 설치** — `implementation("pkg")` 와 `integration("pkg")` 가 `pypackpack` 을 거쳐 +- 📦 **실제 의존성 설치**: `implementation("pkg")` 와 `integration("pkg")` 가 `pypackpack` 을 거쳐 실제 `uv add` 가 됩니다. -- 🚀 **변형마다 태스크 하나** — 플랫폼 변형 × 빌드 타입마다 +- 🚀 **변형마다 태스크 하나**: 플랫폼 변형 × 빌드 타입마다 `buildPython` / `packagePython` 가 생기고, 지원되지 않는 변형은 그것만 실패합니다. -- 🔌 **산출물에 실제로 들어감** — 번들이 데스크톱 jar 의 리소스와 APK 의 `assets/` 로 스테이징됩니다. -- 🧪 **조용히 넘어가지 않음** — 알 수 없는 버전 문자열, 매핑되지 않는 플랫폼, 지원되지 않는 컴파일 +- 🔌 **산출물에 실제로 들어감**: 번들이 데스크톱 jar 의 리소스와 APK 의 `assets/` 로 스테이징됩니다. +- 🧪 **조용히 넘어가지 않음**: 알 수 없는 버전 문자열, 매핑되지 않는 플랫폼, 지원되지 않는 컴파일 레벨은 컴파일만 되고 아무 일도 안 하는 대신, 이유를 밝히며 거부됩니다. -- 🐍 **Python 사용자를 위한 `tcl`** — Gradle 프로젝트 없이 `tcl install `. +- 🐍 **Python 사용자를 위한 `tcl`**: Gradle 프로젝트 없이 `tcl install `. ## 🚀 빠른 시작 @@ -137,17 +137,17 @@ flowchart LR | `uv` 를 통한 `implementation` / `integration` 의존성, 소스셋별로 변형마다 그 트리플용으로 설치해 번들에 포함 | ✅ 구현됨 | | `instant` 레벨의 `pypackpack` 번들링 | ✅ 구현됨 | | `bytecode` 컴파일 레벨 (`.pyc`, `compileSdk` 와 맞는 `.venv` 필요) | ✅ 구현됨 | -| `native` / `mixed` 컴파일 레벨 | ⏳ 계획 — 현재는 명시적으로 거부 | -| 데스크톱 jar 와 APK 로의 스테이징 | 🟡 부분 — iOS 는 스테이징만 되고 연결되지 않음 | -| 핫 리로드 | 🟡 부분 — Android 전용, `adb` 경유 | -| 코드 푸시 | 🟡 부분 — 검증만, 업로드 없음 | -| `buildFeatures { metaclass, compose }` | ✅ 구현됨 — compose 위치는 Gradle 속성 두 개로 지정 | +| `native` / `mixed` 컴파일 레벨 | ⏳ 계획: 현재는 명시적으로 거부 | +| 데스크톱 jar 와 APK 로의 스테이징 | 🟡 부분: iOS 는 스테이징만 되고 연결되지 않음 | +| 핫 리로드 | 🟡 부분: Android 전용, `adb` 경유 | +| 코드 푸시 | 🟡 부분: 검증만, 업로드 없음 | +| `buildFeatures { metaclass, compose }` | ✅ 구현됨: compose 위치는 Gradle 속성 두 개로 지정 | | `embedLevel`, `projectFlavors`, `pip { }` | ⏳ 계획 | | `tcl install` | ✅ 구현됨 | ## 📖 문서 -- **[가이드](../guide/index.html)** — 개념, 작업별 가이드, 상태. 영어와 한국어. +- **[가이드](../guide/index.html)**: 개념, 작업별 가이드, 상태. 영어와 한국어. - **[English README](../../README.md)** ## 🤝 기여 @@ -158,4 +158,4 @@ flowchart LR ## 📄 라이선스 -[MIT](../../LICENSE) © 2024 thisisthepy +[Apache-2.0](../../LICENSE) © 2024 thisisthepy diff --git a/pyproject.toml b/pyproject.toml index c0a92ca..11efa29 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["flit_core>=3.4,<4"] +requires = ["flit_core>=3.12,<4"] build-backend = "flit_core.buildapi" [project] @@ -8,7 +8,8 @@ version = "0.0.1" description = "Python Multiplatform Build Plugin/Tool with Kotlin Multiplatform Mobile" readme = "README.md" requires-python = ">=3.9, <3.14" -license = {file = "LICENSE"} +license = "Apache-2.0" +license-files = ["LICENSE"] authors = [ {name = "THISISTHEPY", email = "thisisthepy@gmail.com"} ] @@ -16,7 +17,6 @@ classifiers = [ "Development Status :: 3 - Alpha", "Environment :: Console", "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.9", diff --git a/toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/DSLFlavors.kt b/toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/DSLFlavors.kt index 3297032..cdf9dca 100644 --- a/toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/DSLFlavors.kt +++ b/toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/DSLFlavors.kt @@ -1,7 +1,7 @@ package org.thisisthepy.python.multiplatform.toolchain.dsl /** - * `projectFlavors { create("free"); create("paid") }` — `(플러그인예시)build.gradle.kts` declares the + * `projectFlavors { create("free"); create("paid") }`: `(플러그인예시)build.gradle.kts` declares the * block (empty); its meaning follows AGP's product flavors (decided 2026-10-03). Each flavor is * crossed into the variant graph between platform and build type * (`buildPythonAndroidArm64FreeDebug`), and its own dependencies go in a `Main` source set. From 74581ee24bbb87297c42c2b62d6dfff81d378255 Mon Sep 17 00:00:00 2001 From: BREW <81044362+b-re-w@users.noreply.github.com> Date: Sat, 3 Oct 2026 23:29:37 +0900 Subject: [PATCH 06/12] CI: ios-e2e.yml -> test-ios.yml; drop main-source-guard and protect-main.sh - CI file names (lead, 2026-10-03): tests live in test.yml; a separate file is test-.yml, and iOS needs one (macOS runner, path filters, informational). The job id stays ios-e2e, so the check keeps its name. - main is protected by the maintainer's settings only, locked (user, 2026-10-03), as in pypackpack #55 and python-multiplatform #122/#127: main-source-guard.yml and protect-main.sh deleted, release-sync's guard-status step and statuses: write gone, the release README and AGENTS.md say only that the maintainer protects main and merges the release PR. Closes #67 --- .github/scripts/release/README.md | 18 ++++------ .github/scripts/release/protect-main.sh | 34 ------------------- .github/workflows/main-source-guard.yml | 24 ------------- .github/workflows/release-sync.yml | 15 -------- .../workflows/{ios-e2e.yml => test-ios.yml} | 0 AGENTS.md | 2 +- 6 files changed, 7 insertions(+), 86 deletions(-) delete mode 100755 .github/scripts/release/protect-main.sh delete mode 100644 .github/workflows/main-source-guard.yml rename .github/workflows/{ios-e2e.yml => test-ios.yml} (100%) diff --git a/.github/scripts/release/README.md b/.github/scripts/release/README.md index 9b72a96..697c383 100644 --- a/.github/scripts/release/README.md +++ b/.github/scripts/release/README.md @@ -14,13 +14,11 @@ ignored and overwritten. The push uses `--force-with-lease` pinned to the remote sha the run observed. 2. The workflow opens (or updates) a pull request `release` -> `main` with the default `GITHUB_TOKEN`. This needs the repository setting *Actions -> General -> Allow GitHub - Actions to create and approve pull requests*. A PR opened with `GITHUB_TOKEN` - triggers no workflows, so `main-source-guard` would never run on it; the workflow - therefore sets the `only-release-into-main` commit status on the release commit - itself, which satisfies the required check. No PAT is needed; an optional + Actions to create and approve pull requests*. No PAT is needed; an optional `RELEASE_PR_TOKEN` secret is used for the PR step when present. -3. Merging that PR is the only way to change `main`. `main-source-guard.yml` fails - any PR into `main` whose head is not `release`. +3. Merging that PR is the only way to change `main`. The maintainer protects `main` in the + repository settings (locked) and merges the `release` PR after checking that its head is + `release` (maintainer decision 2026-10-03: settings only, no scripts or workflows for it). 4. On push to `main`, `pages.yml` deploys `docs/guide/` to GitHub Pages. ## What is dropped on release @@ -43,9 +41,5 @@ It refuses to run while `release` is checked out. ## Protecting main - .github/scripts/release/protect-main.sh # print what would be applied - .github/scripts/release/protect-main.sh --apply # apply via gh api (needs admin) - -This requires a pull request, blocks force-push and deletion. Classic protection cannot -restrict a PR's source branch, so add the check `only-release-into-main` as required -(the script does; the workflow must have run once first). +`main`'s protection is set by the maintainer in the repository settings (the branch is locked; +the maintainer merges the `release` pull request). Agents do not script or change it. diff --git a/.github/scripts/release/protect-main.sh b/.github/scripts/release/protect-main.sh deleted file mode 100755 index 5d3588f..0000000 --- a/.github/scripts/release/protect-main.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env bash -# Print (default) or apply (--apply) branch protection for main. -# Usage: protect-main.sh [--apply] [--repo OWNER/NAME] [--branch main] -set -eu -APPLY=0; REPO=""; BRANCH=main -while [ $# -gt 0 ]; do - case "$1" in - --apply) APPLY=1; shift ;; - --repo) REPO="$2"; shift 2 ;; - --branch) BRANCH="$2"; shift 2 ;; - *) echo "unknown argument: $1" >&2; exit 2 ;; - esac -done -[ -n "$REPO" ] || REPO="{owner}/{repo}" # gh expands these placeholders from the current repo - -BODY='{ - "required_status_checks": {"strict": true, "contexts": ["only-release-into-main"]}, - "enforce_admins": true, - "required_pull_request_reviews": {"required_approving_review_count": 0}, - "restrictions": null, - "allow_force_pushes": false, - "allow_deletions": false -}' -CMD="gh api --method PUT repos/$REPO/branches/$BRANCH/protection --input -" - -if [ "$APPLY" -eq 1 ]; then - printf '%s' "$BODY" | gh api --method PUT "repos/$REPO/branches/$BRANCH/protection" --input - - echo "applied protection to $BRANCH" -else - echo "[dry run] would run: $CMD" - echo "$BODY" - echo "Re-run with --apply to apply. The check 'only-release-into-main' comes from .github/workflows/main-source-guard.yml;" - echo "it must have run once before GitHub accepts it as a required check." -fi diff --git a/.github/workflows/main-source-guard.yml b/.github/workflows/main-source-guard.yml deleted file mode 100644 index 4eb5d20..0000000 --- a/.github/workflows/main-source-guard.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Main source guard - -# Make the job "only-release-into-main" a required status check on main. -on: - pull_request: - branches: [main] - types: [opened, reopened, synchronize, edited] - -permissions: - contents: read - -jobs: - only-release-into-main: - runs-on: ubuntu-latest - steps: - - name: Require head branch "release" - env: - HEAD_REF: ${{ github.head_ref }} - HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} - run: | - if [ "$HEAD_REF" != "release" ] || [ "$HEAD_REPO" != "${{ github.repository }}" ]; then - echo "::error::PRs into main must come from the release branch of this repository (got '$HEAD_REPO:$HEAD_REF')." - exit 1 - fi diff --git a/.github/workflows/release-sync.yml b/.github/workflows/release-sync.yml index cf35f65..8914610 100644 --- a/.github/workflows/release-sync.yml +++ b/.github/workflows/release-sync.yml @@ -10,7 +10,6 @@ on: permissions: contents: write pull-requests: write - statuses: write concurrency: group: release-sync @@ -51,17 +50,3 @@ jobs: else gh pr create --head release --base main --title "$title" --body "$body" fi - - # A PR opened with GITHUB_TOKEN triggers no workflows, so main-source-guard - # never runs on it. Report the guard's verdict for the release commit here: - # this job just generated it from develop, which is exactly what the guard - # requires. A status set with GITHUB_TOKEN satisfies a required check. - - name: Mark release as passing the main source guard - env: - GH_TOKEN: ${{ github.token }} - run: | - set -eu - if ! git rev-parse -q --verify refs/heads/release >/dev/null; then exit 0; fi - gh api "repos/${GITHUB_REPOSITORY}/statuses/$(git rev-parse release)" \ - -f state=success -f context=only-release-into-main \ - -f description="generated from develop ${GITHUB_SHA::7} by release-sync" diff --git a/.github/workflows/ios-e2e.yml b/.github/workflows/test-ios.yml similarity index 100% rename from .github/workflows/ios-e2e.yml rename to .github/workflows/test-ios.yml diff --git a/AGENTS.md b/AGENTS.md index 3e3a38e..73ac19c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -60,7 +60,7 @@ build caches, model weights, `node_modules`) into every worktree is how 86 workt | `feat/` | You. All work happens here. Never name a branch `work/...`. | | `develop` | Merged into from `feat/` branches after verification. Never commit to it directly. | | `release` | **CI only.** Not a standing branch: CI regenerates it from every push to `develop`, in the main-only file layout, and opens the PR into `main`. It may not exist. Never write to it. | -| `main` | **Pull request from `release` only.** Never push or merge to it directly. | +| `main` | **Pull request from `release` only.** Never push or merge to it directly. The maintainer protects it in the repository settings (locked) and merges the `release` PR after checking its head; no script or workflow does this, and agents do not change those settings (decision 2026-10-03). | Only `main`, `develop` and `release` are standing branches. A `feat/` branch lives until its pull request merges: merge with `gh pr merge --delete-branch`, then delete the local branch and its From fac4d5d2c6cc86963e25e8b24b4218d8e1a5db18 Mon Sep 17 00:00:00 2001 From: BREW <81044362+b-re-w@users.noreply.github.com> Date: Sat, 3 Oct 2026 23:29:46 +0900 Subject: [PATCH 07/12] CI: test-ios.yml's path filter names its new file --- .github/workflows/test-ios.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-ios.yml b/.github/workflows/test-ios.yml index 379876f..8f4f752 100644 --- a/.github/workflows/test-ios.yml +++ b/.github/workflows/test-ios.yml @@ -13,7 +13,7 @@ on: - "sample/build.gradle.kts" - "sample/gradle/**" - "toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/**" - - ".github/workflows/ios-e2e.yml" + - ".github/workflows/test-ios.yml" workflow_dispatch: permissions: From 6b09877eb1d2c587e0a1353bc019c611decb1c62 Mon Sep 17 00:00:00 2001 From: BREW <81044362+b-re-w@users.noreply.github.com> Date: Sun, 4 Oct 2026 00:08:10 +0900 Subject: [PATCH 08/12] CI: Release toolchain-lite 0.1.0 on PyPI as native tcl wheels User decision (via the lead): publish toolchain-lite 0.1.0, Apache-2.0, in pypackpack 0.2.0's shape (pypackpack #53). - tcl --help / -h and tcl --version; tcl gets its own version, 0.1.0, read from a resource the build generates (CliArgsTest; the version test compares against the build's version). - A GraalVM native image of tcl (graalvm buildtools 0.10.6, pypackpack's settings: slf4j initialized at build time, macOS minimum 11.0). - pyproject.toml is toolchain-lite 0.1.0, Apache-2.0, setuptools, with the launcher package toolchain_lite (tcl/src/main/python) and its own PyPI page (docs/pypi/README.md, absolute links). The old flit stub named a module that never existed. - publish-pypi.yml and .github/scripts/pypi/, adapted from pypackpack: published-Release trigger, trusted publishing from environment pypi (v* tags only), gates tag == pyproject == tcl version, version new on PyPI, fresh-venv smoke (tcl --help, --version, python -m toolchain_lite, tcl install six). Each wheel job publishes packpack from pypackpack develop first. - SPEC 2.2 and 2.3, AGENTS 12, the guide's tcl and Status pages. Closes #69 --- .github/scripts/pypi/build_wheel.py | 148 ++++++++++++++++++ .github/scripts/pypi/smoke_wheel.py | 62 ++++++++ .github/workflows/publish-pypi.yml | 136 ++++++++++++++++ AGENTS.md | 8 +- docs/SPEC.md | 33 +++- docs/guide/guide-tcl.html | 6 +- docs/guide/status.html | 2 +- docs/pypi/README.md | 43 +++++ pyproject.toml | 75 ++++----- tcl/build.gradle.kts | 50 +++++- .../python/multiplatform/tcl/BuildInfo.kt | 17 ++ .../python/multiplatform/tcl/Cli.kt | 8 + tcl/src/main/python/__init__.py | 5 + tcl/src/main/python/__main__.py | 41 +++++ .../python/multiplatform/tcl/CliArgsTest.kt | 29 ++++ 15 files changed, 606 insertions(+), 57 deletions(-) create mode 100644 .github/scripts/pypi/build_wheel.py create mode 100644 .github/scripts/pypi/smoke_wheel.py create mode 100644 .github/workflows/publish-pypi.yml create mode 100644 docs/pypi/README.md create mode 100644 tcl/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/BuildInfo.kt create mode 100644 tcl/src/main/python/__init__.py create mode 100644 tcl/src/main/python/__main__.py diff --git a/.github/scripts/pypi/build_wheel.py b/.github/scripts/pypi/build_wheel.py new file mode 100644 index 0000000..f90bda4 --- /dev/null +++ b/.github/scripts/pypi/build_wheel.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +"""Assemble the platform wheel that carries the native `tcl` binary (publish-pypi.yml). + + python3 .github/scripts/pypi/build_wheel.py --binary tcl/build/native/nativeCompile/tcl --out dist + +Adapted from pypackpack's .github/scripts/pypi/build_wheel.py (pypackpack #53). The wheel holds: + toolchain_lite/__init__.py, __main__.py tcl/src/main/python/ (`python -m toolchain_lite`) + toolchain_lite-.data/scripts/tcl the native binary (tcl.exe on Windows) + dist-info: METADATA from pyproject.toml's [project], LICENSE, WHEEL, RECORD + +The platform tag is read from the binary, never assumed: the highest GLIBC_x.y symbol it needs on +Linux (manylinux_x_y_), its LC_BUILD_VERSION minos on macOS (macosx___), +win_amd64 / win_arm64 on Windows. On Linux every NEEDED library must be in ALLOWED_LINUX_LIBS. +""" +from __future__ import annotations + +import argparse +import base64 +import hashlib +import platform +import re +import subprocess +import sys +import tomllib +import zipfile +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[3] +PACKAGE_SRC = ROOT / "tcl/src/main/python" +IMPORT_NAME = "toolchain_lite" +BINARY = "tcl" +# glibc's own libraries plus libz, which every glibc distribution ships and Native Image links. +ALLOWED_LINUX_LIBS = { + "libc.so.6", "libm.so.6", "libdl.so.2", "libpthread.so.0", "librt.so.1", "libz.so.1", + "ld-linux-x86-64.so.2", "ld-linux-aarch64.so.1", +} +FIXED_DATE = (1980, 1, 1, 0, 0, 0) + + +def run(*cmd: str) -> str: + return subprocess.run(cmd, check=True, capture_output=True, text=True).stdout + + +def linux_tag(binary: Path) -> str: + needed = set(re.findall(r"\(NEEDED\)\s+Shared library: \[([^\]]+)\]", run("readelf", "-d", str(binary)))) + extra = needed - ALLOWED_LINUX_LIBS + if extra: + sys.exit(f"{binary} needs libraries a manylinux wheel may not assume: {sorted(extra)}") + versions = {tuple(map(int, v)) for v in re.findall(r"GLIBC_(\d+)\.(\d+)", run("objdump", "-T", str(binary)))} + major, minor = max(versions) if versions else (2, 17) + machine = platform.machine().lower() + arch = {"amd64": "x86_64", "arm64": "aarch64"}.get(machine, machine) + return f"manylinux_{major}_{minor}_{arch}" + + +def macos_tag(binary: Path) -> str: + match = re.search(r"cmd LC_BUILD_VERSION.*?minos (\d+)\.(\d+)", run("otool", "-l", str(binary)), re.S) + if not match: + sys.exit(f"{binary} has no LC_BUILD_VERSION; cannot tell its minimum macOS") + major, minor = int(match[1]), int(match[2]) + if major >= 11: + minor = 0 # pip matches macosx_11+ tags on the major version only + arch = "arm64" if platform.machine() == "arm64" else "x86_64" + return f"macosx_{major}_{minor}_{arch}" + + +def windows_tag() -> str: + return "win_arm64" if platform.machine().lower() in ("arm64", "aarch64") else "win_amd64" + + +def platform_tag(binary: Path) -> str: + if sys.platform.startswith("linux"): + return linux_tag(binary) + if sys.platform == "darwin": + return macos_tag(binary) + if sys.platform == "win32": + return windows_tag() + sys.exit(f"no wheel platform tag for {sys.platform}") + + +def metadata(project: dict) -> str: + lines = [ + "Metadata-Version: 2.4", + f"Name: {project['name']}", + f"Version: {project['version']}", + f"Summary: {project['description']}", + ] + for author in project.get("authors", []): + lines.append(f"Author-email: {author['name']} <{author['email']}>") + lines.append(f"License-Expression: {project['license']}") + lines += [f"License-File: {f}" for f in project.get("license-files", [])] + lines += [f"Classifier: {c}" for c in project.get("classifiers", [])] + if project.get("keywords"): + lines.append(f"Keywords: {','.join(project['keywords'])}") + lines.append(f"Requires-Python: {project['requires-python']}") + lines += [f"Project-URL: {label}, {url}" for label, url in project.get("urls", {}).items()] + lines.append("Description-Content-Type: text/markdown") + readme = (ROOT / project["readme"]).read_text(encoding="utf-8") + return "\n".join(lines) + "\n\n" + readme + + +def record_line(arcname: str, data: bytes) -> str: + digest = base64.urlsafe_b64encode(hashlib.sha256(data).digest()).rstrip(b"=").decode() + return f"{arcname},sha256={digest},{len(data)}" + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--binary", type=Path, required=True) + parser.add_argument("--out", type=Path, required=True) + args = parser.parse_args() + + project = tomllib.loads((ROOT / "pyproject.toml").read_text(encoding="utf-8"))["project"] + name, version = project["name"], project["version"] + dist = re.sub(r"[-_.]+", "_", name).lower() # wheel and dist-info names use the normalized form + tag = f"py3-none-{platform_tag(args.binary)}" + dist_info = f"{dist}-{version}.dist-info" + exe = f"{BINARY}.exe" if sys.platform == "win32" else BINARY + + files: list[tuple[str, bytes, int]] = [] # (arcname, data, unix mode) + for source in sorted(PACKAGE_SRC.glob("*.py")): + files.append((f"{IMPORT_NAME}/{source.name}", source.read_bytes(), 0o644)) + files.append((f"{dist}-{version}.data/scripts/{exe}", args.binary.read_bytes(), 0o755)) + files.append((f"{dist_info}/METADATA", metadata(project).encode(), 0o644)) + for license_file in project.get("license-files", []): + files.append((f"{dist_info}/licenses/{license_file}", (ROOT / license_file).read_bytes(), 0o644)) + scripts = "".join(f"{k} = {v}\n" for k, v in project.get("scripts", {}).items()) + if scripts: + files.append((f"{dist_info}/entry_points.txt", f"[console_scripts]\n{scripts}".encode(), 0o644)) + wheel = f"Wheel-Version: 1.0\nGenerator: toolchain build_wheel.py\nRoot-Is-Purelib: false\nTag: {tag}\n" + files.append((f"{dist_info}/WHEEL", wheel.encode(), 0o644)) + record = "\n".join(record_line(a, d) for a, d, _ in files) + f"\n{dist_info}/RECORD,,\n" + files.append((f"{dist_info}/RECORD", record.encode(), 0o644)) + + args.out.mkdir(parents=True, exist_ok=True) + path = args.out / f"{dist}-{version}-{tag}.whl" + with zipfile.ZipFile(path, "w", zipfile.ZIP_DEFLATED) as archive: + for arcname, data, mode in files: + info = zipfile.ZipInfo(arcname, FIXED_DATE) + info.external_attr = (0o100000 | mode) << 16 + info.compress_type = zipfile.ZIP_DEFLATED + archive.writestr(info, data) + print(path) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.github/scripts/pypi/smoke_wheel.py b/.github/scripts/pypi/smoke_wheel.py new file mode 100644 index 0000000..92971bf --- /dev/null +++ b/.github/scripts/pypi/smoke_wheel.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +"""Install a built wheel into a fresh venv and check that what a PyPI user runs works. + + python3 .github/scripts/pypi/smoke_wheel.py dist/toolchain_lite--.whl + +Adapted from pypackpack's smoke_wheel.py (pypackpack #53). Fails (exit 1) unless, from that venv: + 1. `tcl --help` exits 0 and prints the usage line; + 2. `tcl --version` and `python -m toolchain_lite --version` print "tcl "; + 3. `tcl install six` in an empty directory creates a pyproject.toml that depends on six (a real + install, which needs uv: tcl finds it on PATH or pypackpack downloads it into ~/.pypackpack/uv). +""" +from __future__ import annotations + +import os +import subprocess +import sys +import tempfile +import tomllib +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[3] + + +def check(cmd: list[str], expect: str | None, cwd: Path | None = None) -> None: + result = subprocess.run(cmd, cwd=cwd, capture_output=True, text=True) + shown = " ".join(Path(c).name if i == 0 else c for i, c in enumerate(cmd)) + if result.returncode != 0 or (expect and expect not in result.stdout): + print(f"FAIL {shown}: exit {result.returncode}\n{result.stdout}{result.stderr}") + sys.exit(1) + print(f"ok {shown}") + + +def main() -> int: + wheel = Path(sys.argv[1]).resolve() + version = tomllib.loads((ROOT / "pyproject.toml").read_text(encoding="utf-8"))["project"]["version"] + with tempfile.TemporaryDirectory() as tmp: + venv = Path(tmp) / "venv" + subprocess.run([sys.executable, "-m", "venv", str(venv)], check=True) + bin_dir = venv / ("Scripts" if os.name == "nt" else "bin") + exe = ".exe" if os.name == "nt" else "" + python = bin_dir / f"python{exe}" + subprocess.run([str(python), "-m", "pip", "install", "--quiet", "--no-index", str(wheel)], check=True) + + tcl = str(bin_dir / f"tcl{exe}") + check([tcl, "--help"], "Usage: tcl install ") + expected = f"tcl {version}" + check([tcl, "--version"], expected) + check([str(python), "-m", "toolchain_lite", "--version"], expected) + + work = Path(tmp) / "work" + work.mkdir() + check([tcl, "install", "six"], None, cwd=work) + pyproject = work / "pyproject.toml" + if not pyproject.is_file() or "six" not in pyproject.read_text(encoding="utf-8"): + print("FAIL tcl install six: no pyproject.toml that depends on six") + return 1 + print("ok tcl install six wrote pyproject.toml") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml new file mode 100644 index 0000000..ecaddee --- /dev/null +++ b/.github/workflows/publish-pypi.yml @@ -0,0 +1,136 @@ +name: Publish to PyPI + +# toolchain-lite (the PyPI distribution of tcl). Adapted from pypackpack's publish-pypi.yml +# (pypackpack #53), with the same gates. Publishing a GitHub Release `v` builds the native +# tcl on each platform, wraps each binary in a wheel (.github/scripts/pypi/build_wheel.py), installs +# and smoke-tests every wheel (smoke_wheel.py), and only then uploads them all through trusted +# publishing (environment `pypi`, no token). A pull request that touches the publishing files, or a +# manual run, does everything except the upload. +# +# A published Release, not a pushed `v*` tag: a PyPI version can never be uploaded twice, so the +# trigger is a deliberate act. No sdist: building needs a JDK and GraalVM, so an sdist would install +# the launcher without the binary. +# +# tcl links pypackpack's packpack library from mavenLocal, so each wheel job first publishes it +# from pypackpack's develop branch, as test.yml does. +on: + release: + types: [published] + pull_request: + paths: + - .github/workflows/publish-pypi.yml + - .github/scripts/pypi/** + - pyproject.toml + - docs/pypi/** + - tcl/src/main/python/** + - tcl/build.gradle.kts + workflow_dispatch: + +permissions: + contents: read + +# A newer push supersedes a dry run; a release run is never cancelled. +concurrency: + group: publish-pypi-${{ github.ref }} + cancel-in-progress: ${{ github.event_name != 'release' }} + +jobs: + version: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Tag, pyproject.toml and tcl agree; the version is new on PyPI + env: + TAG: ${{ github.event.release.tag_name }} + IS_RELEASE: ${{ github.event_name == 'release' }} + run: | + py=$(python3 -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])') + cli=$(sed -n 's/^version = "\(.*\)"$/\1/p' tcl/build.gradle.kts) + echo "pyproject.toml: $py tcl/build.gradle.kts: $cli tag: ${TAG:-none (dry run)}" + if [ "$py" != "$cli" ]; then + echo "::error::pyproject.toml ($py) and tcl/build.gradle.kts ($cli) must carry the same version"; exit 1 + fi + if [ "$IS_RELEASE" = true ] && [ "$TAG" != "v$py" ]; then + echo "::error::release tag '$TAG' is not 'v$py'"; exit 1 + fi + if curl -fsS -o /dev/null "https://pypi.org/pypi/toolchain-lite/$py/json"; then + if [ "$IS_RELEASE" = true ]; then echo "::error::toolchain-lite $py is already on PyPI"; exit 1; fi + echo "::warning::toolchain-lite $py is already on PyPI; bump the version before releasing" + fi + + wheel: + needs: version + name: wheel (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + timeout-minutes: 40 + strategy: + fail-fast: false + matrix: + # ubuntu-22.04: the oldest glibc (2.35) a hosted runner offers, so the widest manylinux tag. + os: [ubuntu-22.04, ubuntu-22.04-arm, macos-latest, windows-latest] + defaults: + run: + shell: bash + steps: + - uses: actions/checkout@v4 + + - uses: graalvm/setup-graalvm@v1 + with: + java-version: "21" + distribution: graalvm-community + github-token: ${{ secrets.GITHUB_TOKEN }} + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - uses: gradle/actions/setup-gradle@v4 + + - uses: actions/checkout@v4 + with: + repository: thisisthepy/pypackpack + ref: develop + path: .ci/pypackpack + + - name: packpack into mavenLocal + working-directory: .ci/pypackpack + run: ./gradlew --no-daemon :packpack:publishToMavenLocal + + - name: Native image + run: ./gradlew --no-daemon :tcl:nativeCompile + + - name: Wheel + run: | + exe=tcl/build/native/nativeCompile/tcl + [ "$RUNNER_OS" = Windows ] && exe="$exe.exe" + python .github/scripts/pypi/build_wheel.py --binary "$exe" --out dist + + - name: Install and smoke-test the wheel + run: python .github/scripts/pypi/smoke_wheel.py dist/*.whl + + - uses: actions/upload-artifact@v4 + with: + name: wheel-${{ matrix.os }} + path: dist/*.whl + if-no-files-found: error + + publish: + if: github.event_name == 'release' + needs: wheel + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/project/toolchain-lite/ + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v4 + with: + pattern: wheel-* + merge-multiple: true + path: dist + - name: One wheel per platform + run: | + ls -la dist + test "$(ls dist/*.whl | wc -l)" -eq 4 + - uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/AGENTS.md b/AGENTS.md index d9bdaa0..00ab8e5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -184,9 +184,11 @@ GitHub issues `thisisthepy/toolchain#2` (plugin checklist) and `#1` (`tcl`), it | `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/`. | | `.github/scripts/release/` | The develop → release → main sync (rule 4). | -`pyproject.toml` at the root is toolchain-lite's PyPI build: `toolchain-lite` 0.0.1 on PyPI came -from it. Keep it. (It still declares the name `toolchain` and a flit module that is not in the -repository; fixing that belongs to the toolchain-lite publishing work, not to a cleanup.) +`pyproject.toml` at the root is toolchain-lite, the PyPI distribution of `tcl` (#69): one platform +wheel per OS carrying the native `tcl` binary, plus the launcher package `toolchain_lite` +(`tcl/src/main/python/`). `.github/workflows/publish-pypi.yml` builds, smoke-tests and uploads the +wheels; its version must equal `tcl/build.gradle.kts`'s, 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 diff --git a/docs/SPEC.md b/docs/SPEC.md index 285b0d5..62ed1fa 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -537,6 +537,35 @@ prints `Usage: tcl install `. **Status: implemented**. `tcl/Cli.kt`, `tcl/Installer.kt`; `tcl/CliArgsTest.kt`, `tcl/InstallerTest.kt` (needs `uv` and network). Run with `./gradlew :tcl:run --args="install "`. +### 2.2 `tcl --help` and `tcl --version` +`--help` (or `-h`) prints the usage line on stdout and exits 0. `--version` prints `tcl ` +and exits 0. The version is tcl's own (`tcl/build.gradle.kts`, 0.1.0), separate from the plugin's, +written by the build into `build-info.properties` and read by `BuildInfo`. + +**Status: implemented**. `tcl/Cli.kt`, `tcl/BuildInfo.kt`; `tcl/CliArgsTest.kt` (the version test +compares against the version the build passes to the test JVM). + +### 2.3 toolchain-lite on PyPI +```shell +uv tool install toolchain-lite +tcl --help +``` +- One wheel per platform, `py3-none-`: Linux x86_64 and aarch64 (manylinux), macOS arm64 + (11+), Windows x86_64. Each carries the GraalVM native image `tcl` as a script, plus the launcher + package `toolchain_lite` (`tcl/src/main/python/`) for `python -m toolchain_lite`. +- The platform tag is read from the binary (`.github/scripts/pypi/build_wheel.py`). No sdist: + building needs a JDK and GraalVM. +- `.github/workflows/publish-pypi.yml` runs on a published GitHub Release only and uploads through + trusted publishing (environment `pypi`). Before uploading it requires the tag to be `v`, + `pyproject.toml` and `tcl/build.gradle.kts` to carry that version, the version to be new on PyPI, + and every wheel to pass `smoke_wheel.py` in a fresh venv (`tcl --help`, `tcl --version`, + `python -m toolchain_lite --version`, and `tcl install six` writing a `pyproject.toml`). + Pull requests that touch these files run everything but the upload. +- PyPI's 0.0.1 (2025-06-12) packed the old `toolchain/` Gradle directory and installs no `tcl`. + +**Status: partial**. Built and smoke-tested by CI on every pull request that touches it; nothing +uploaded yet. + --- ## Outside intent: needs a decision @@ -556,6 +585,4 @@ These exist in the code but are not asked for by the example build file or the i wired), `bundle/PythonLocalLoader.kt`, `dependency/DependencyType.kt`, and `BinariesExtension` / `FrozenPackConfig` / `BuildTypeEnum` in `dsl/DSLPlatforms.kt`. Nothing calls any of them. -5. **`pyproject.toml`** is toolchain-lite's PyPI build (`toolchain-lite` 0.0.1, 2025-06-12). It - still declares a flit-built package named `toolchain` (Python 3.9–3.13) whose module is not in - the repository, and no CI builds or publishes it yet. +5. **`pyproject.toml`** is toolchain-lite's PyPI build, §2.3. diff --git a/docs/guide/guide-tcl.html b/docs/guide/guide-tcl.html index b966b97..a06ee02 100644 --- a/docs/guide/guide-tcl.html +++ b/docs/guide/guide-tcl.html @@ -28,7 +28,11 @@

tcl for Python usersPython

Install a package패키지 설치

-
./gradlew :tcl:run --args="install pythonx-compose"
+
uv tool install toolchain-lite    # once 0.1.0 is on PyPI
+tcl install pythonx-compose
+tcl --version
+
+./gradlew :tcl:run --args="install pythonx-compose"    # from a checkout

tcl install <package> looks for a pyproject.toml in the current directory and its parents. If there is none it runs uv init --bare first; then it adds the package with uv add, through the same pypackpack backend the Gradle plugin uses.

tcl install <package> 는 현재 디렉터리와 그 상위에서 pyproject.toml 을 찾습니다. 없으면 먼저 uv init --bare 를 실행하고, Gradle 플러그인과 같은 pypackpack 백엔드로 uv add 를 해서 패키지를 추가합니다.

Invocation호출Result결과
tcl install pkgexit 0, uv output on stdout종료 코드 0, stdout 에 uv 출력
tclexit 1, Usage: tcl install <package>종료 코드 1, Usage: tcl install <package>
tcl installexit 1, missing argument종료 코드 1, 인자 누락
tcl otherexit 1, unknown command종료 코드 1, 알 수 없는 명령

install is the only command, matching issue #1. tcl needs uv on PATH and network access to PyPI.

명령은 이슈 #1 그대로 install 하나뿐입니다. tcl 은 PATH 의 uv 와 PyPI 네트워크 접근이 필요합니다.

diff --git a/docs/guide/status.html b/docs/guide/status.html index dcf8867..b67ed71 100644 --- a/docs/guide/status.html +++ b/docs/guide/status.html @@ -28,7 +28,7 @@

Status & FAQ상태와 F

Feature status기능 상태

-
+
Feature기능Status상태Evidence근거
Applying the plugin: python extension and tasks플러그인 적용: python 확장과 태스크implemented구현됨PythonPluginApplyTest
compileSdk parsing: alpha · rc · normalcompileSdk 파싱: alpha · rc · normalimplemented구현됨PythonVersionTest
Platform → triple mapping, Kotlin target check, min SDK플랫폼 → 트리플 매핑, Kotlin 타깃 확인, 최소 SDKimplemented구현됨PythonExtensionPlatformDslTest, PlatformTargetMappingTest, PythonPluginPlatformsTest
debug / release, -Ppython.buildTypedebug / release, -Ppython.buildTypeimplemented구현됨PythonPluginBuildTypeTest
Per-variant task graph변형별 태스크 그래프implemented구현됨PythonPluginVariantGraphTest
compileLevel = instant · bytecode (.venv and compileSdk minor checked); others rejected per variantcompileLevel = instant · bytecode (.venv 와 compileSdk 마이너 버전 확인); 나머지는 변형별 거부implemented구현됨PythonPluginBuildLevelTest, BytecodeLevelTest
srcDirs / metaDirs / libDirssrcDirs / metaDirs / libDirsimplemented구현됨PythonPluginSourceSetTest
implementation / integration → uv addimplementation / integration → uv addimplemented구현됨PythonPluginDependencyTest, InstallDependenciesTaskTest
Bundling via pypackpack's resource bundlerpypackpack 리소스 번들러로 번들링implemented구현됨BuildPythonArtifactTaskTest
packagePython zippackagePython zipimplemented구현됨AssemblePythonPackageTaskTest
Staging copy rule and variant selection스테이징 복사 규칙과 변형 선택implemented구현됨StagePythonBundleTaskTest, PythonPluginStagingTest
tcl installtcl installimplemented구현됨CliArgsTest, InstallerTest
Staging into the desktop jar데스크톱 jar 로의 스테이징implemented구현됨PythonPluginAttachmentTest
Staging into the APKAPK 로의 스테이징partial부분asset root tested (PythonPluginAttachmentTest); task dependency untested에셋 루트는 테스트됨 (PythonPluginAttachmentTest), 태스크 의존성은 테스트 없음
Hot reload핫 리로드partial부분Android, adb only; serverHost unusedAndroid, adb 만; serverHost 미사용
Code push코드 푸시partial부분validation only검증만
Dependencies per source set, installed for each variant's own triple into its bundle소스셋별 의존성을 변형마다 그 트리플용으로 설치해 번들에 포함implemented구현됨TargetDependenciesTest, PythonPluginTargetDependenciesTest, InstallTargetDependenciesTaskTest
integration(): warn when the wheel has no KLIBDEPENSintegration(): 휠에 KLIBDEPENS 가 없으면 경고implemented구현됨KlibDepensTest, PythonPluginIntegrationListTest
native / mixednative / mixedplanned계획needs pypackpack#19pypackpack#19 선행 필요
iOS: attaching the staged payload (stagePythonBundleIosForXcode prints the path; the Xcode phase is python-multiplatform's, #59)iOS: 스테이징된 페이로드 연결 (stagePythonBundleIosForXcode 가 경로 출력, Xcode 단계는 python-multiplatform #59 소유)partial부분StagePythonBundleIosForXcodeTaskTest, StagePythonPayloadScriptTest
buildFeatures { metaclass } and excludeMetaclass: metaDirs forwarded or droppedbuildFeatures { metaclass } 와 excludeMetaclass: metaDirs 전달 또는 제외implemented구현됨BuildFeaturesTest, PythonPluginBuildFeaturesTest
buildFeatures { compose }: installs pythonx-compose, adds python-multiplatform-compose to commonMainbuildFeatures { compose }: pythonx-compose 설치, commonMain 에 python-multiplatform-compose 추가implemented구현됨BuildFeaturesTest, PythonPluginBuildFeaturesTest; locations from two Gradle propertiesBuildFeaturesTest, PythonPluginBuildFeaturesTest; 위치는 Gradle 속성 두 개로 지정
embedLevel: 0/1/2 resolved per platform (Android and iOS raise to 2 with a warning), -Ppython.embedLevel override, recorded in embed.json with the expected interpreter version; level 2 means python-multiplatform embeds the interpreter, and the bundle stays python/ onlyembedLevel: 플랫폼별 0/1/2 결정 (Android, iOS 는 경고와 함께 2 로 상향), -Ppython.embedLevel 재정의, 기대 인터프리터 버전과 함께 embed.json 에 기록; 레벨 2 는 python-multiplatform 이 인터프리터를 내장한다는 뜻이고, 번들은 python/ 만 담음implemented구현됨EmbedLevelTest, PythonPluginEmbedLevelTest, PythonPluginPythonOnlyTest
useCodeMinifieruseCodeMinifierplanned계획declared, unread선언만, 읽히지 않음
defaultConfig { pip }: central · local · autoUpdatedefaultConfig { pip }: central · local · autoUpdateimplemented구현됨PipRepositoriesTest
pip { jit }pip { jit }planned계획-
compileSdk constants (PY3_14_7); unprovided versions refused per taskcompileSdk 상수(PY3_14_7); 제공되지 않는 버전은 태스크에서 거부implemented구현됨PythonSdkTest, BuildPythonSdkRejectionTest
projectFlavors in the variant graph; -Ppython.flavor변형 그래프의 projectFlavors; -Ppython.flavorimplemented구현됨PythonPluginFlavorsTest
defaultConfig { versionCode, versionName } → bundle manifestdefaultConfig { versionCode, versionName } → 번들 manifestimplemented구현됨PythonPluginPayloadVersionTest, BuildPythonArtifactTaskTest
toolchain ships only python/: compileSdk selects the wheels' Python version and fetches no interpreter; the interpreter and stdlib come from python-multiplatformtoolchain 은 python/ 만 실음: compileSdk 는 wheel 의 Python 버전을 고를 뿐 인터프리터를 받지 않음; 인터프리터와 stdlib 는 python-multiplatform 이 제공implemented구현됨PythonPluginTargetDependenciesTest, PythonPluginPythonOnlyTest
Feature기능Status상태Evidence근거
Applying the plugin: python extension and tasks플러그인 적용: python 확장과 태스크implemented구현됨PythonPluginApplyTest
compileSdk parsing: alpha · rc · normalcompileSdk 파싱: alpha · rc · normalimplemented구현됨PythonVersionTest
Platform → triple mapping, Kotlin target check, min SDK플랫폼 → 트리플 매핑, Kotlin 타깃 확인, 최소 SDKimplemented구현됨PythonExtensionPlatformDslTest, PlatformTargetMappingTest, PythonPluginPlatformsTest
debug / release, -Ppython.buildTypedebug / release, -Ppython.buildTypeimplemented구현됨PythonPluginBuildTypeTest
Per-variant task graph변형별 태스크 그래프implemented구현됨PythonPluginVariantGraphTest
compileLevel = instant · bytecode (.venv and compileSdk minor checked); others rejected per variantcompileLevel = instant · bytecode (.venv 와 compileSdk 마이너 버전 확인); 나머지는 변형별 거부implemented구현됨PythonPluginBuildLevelTest, BytecodeLevelTest
srcDirs / metaDirs / libDirssrcDirs / metaDirs / libDirsimplemented구현됨PythonPluginSourceSetTest
implementation / integration → uv addimplementation / integration → uv addimplemented구현됨PythonPluginDependencyTest, InstallDependenciesTaskTest
Bundling via pypackpack's resource bundlerpypackpack 리소스 번들러로 번들링implemented구현됨BuildPythonArtifactTaskTest
packagePython zippackagePython zipimplemented구현됨AssemblePythonPackageTaskTest
Staging copy rule and variant selection스테이징 복사 규칙과 변형 선택implemented구현됨StagePythonBundleTaskTest, PythonPluginStagingTest
tcl installtcl installimplemented구현됨CliArgsTest, InstallerTest
tcl --help, tcl --versiontcl --help, tcl --versionimplemented구현됨CliArgsTest
toolchain-lite on PyPI (uv tool install toolchain-lite)PyPI 의 toolchain-lite (uv tool install toolchain-lite)partial부분CI builds and smoke-tests the wheels; not uploaded yetCI 가 휠을 빌드하고 스모크 테스트함. 아직 업로드 안 됨
Staging into the desktop jar데스크톱 jar 로의 스테이징implemented구현됨PythonPluginAttachmentTest
Staging into the APKAPK 로의 스테이징partial부분asset root tested (PythonPluginAttachmentTest); task dependency untested에셋 루트는 테스트됨 (PythonPluginAttachmentTest), 태스크 의존성은 테스트 없음
Hot reload핫 리로드partial부분Android, adb only; serverHost unusedAndroid, adb 만; serverHost 미사용
Code push코드 푸시partial부분validation only검증만
Dependencies per source set, installed for each variant's own triple into its bundle소스셋별 의존성을 변형마다 그 트리플용으로 설치해 번들에 포함implemented구현됨TargetDependenciesTest, PythonPluginTargetDependenciesTest, InstallTargetDependenciesTaskTest
integration(): warn when the wheel has no KLIBDEPENSintegration(): 휠에 KLIBDEPENS 가 없으면 경고implemented구현됨KlibDepensTest, PythonPluginIntegrationListTest
native / mixednative / mixedplanned계획needs pypackpack#19pypackpack#19 선행 필요
iOS: attaching the staged payload (stagePythonBundleIosForXcode prints the path; the Xcode phase is python-multiplatform's, #59)iOS: 스테이징된 페이로드 연결 (stagePythonBundleIosForXcode 가 경로 출력, Xcode 단계는 python-multiplatform #59 소유)partial부분StagePythonBundleIosForXcodeTaskTest, StagePythonPayloadScriptTest
buildFeatures { metaclass } and excludeMetaclass: metaDirs forwarded or droppedbuildFeatures { metaclass } 와 excludeMetaclass: metaDirs 전달 또는 제외implemented구현됨BuildFeaturesTest, PythonPluginBuildFeaturesTest
buildFeatures { compose }: installs pythonx-compose, adds python-multiplatform-compose to commonMainbuildFeatures { compose }: pythonx-compose 설치, commonMain 에 python-multiplatform-compose 추가implemented구현됨BuildFeaturesTest, PythonPluginBuildFeaturesTest; locations from two Gradle propertiesBuildFeaturesTest, PythonPluginBuildFeaturesTest; 위치는 Gradle 속성 두 개로 지정
embedLevel: 0/1/2 resolved per platform (Android and iOS raise to 2 with a warning), -Ppython.embedLevel override, recorded in embed.json with the expected interpreter version; level 2 means python-multiplatform embeds the interpreter, and the bundle stays python/ onlyembedLevel: 플랫폼별 0/1/2 결정 (Android, iOS 는 경고와 함께 2 로 상향), -Ppython.embedLevel 재정의, 기대 인터프리터 버전과 함께 embed.json 에 기록; 레벨 2 는 python-multiplatform 이 인터프리터를 내장한다는 뜻이고, 번들은 python/ 만 담음implemented구현됨EmbedLevelTest, PythonPluginEmbedLevelTest, PythonPluginPythonOnlyTest
useCodeMinifieruseCodeMinifierplanned계획declared, unread선언만, 읽히지 않음
defaultConfig { pip }: central · local · autoUpdatedefaultConfig { pip }: central · local · autoUpdateimplemented구현됨PipRepositoriesTest
pip { jit }pip { jit }planned계획-
compileSdk constants (PY3_14_7); unprovided versions refused per taskcompileSdk 상수(PY3_14_7); 제공되지 않는 버전은 태스크에서 거부implemented구현됨PythonSdkTest, BuildPythonSdkRejectionTest
projectFlavors in the variant graph; -Ppython.flavor변형 그래프의 projectFlavors; -Ppython.flavorimplemented구현됨PythonPluginFlavorsTest
defaultConfig { versionCode, versionName } → bundle manifestdefaultConfig { versionCode, versionName } → 번들 manifestimplemented구현됨PythonPluginPayloadVersionTest, BuildPythonArtifactTaskTest
toolchain ships only python/: compileSdk selects the wheels' Python version and fetches no interpreter; the interpreter and stdlib come from python-multiplatformtoolchain 은 python/ 만 실음: compileSdk 는 wheel 의 Python 버전을 고를 뿐 인터프리터를 받지 않음; 인터프리터와 stdlib 는 python-multiplatform 이 제공implemented구현됨PythonPluginTargetDependenciesTest, PythonPluginPythonOnlyTest
compileSdk checked against python-multiplatform's pythonVersion at level 2 (a mismatch fails only that variant's buildPython…); read from -Ppython.multiplatform.pythonVersion, the :python-multiplatform project's extension, or the published dependency's metadata or jar레벨 2 에서 compileSdk 를 python-multiplatform 의 pythonVersion 과 대조 (다르면 그 변형의 buildPython… 만 실패); -Ppython.multiplatform.pythonVersion, :python-multiplatform 프로젝트의 확장, 또는 배포된 의존의 메타데이터나 jar 에서 읽음implemented구현됨PythonVersionAgreementTest, PythonPluginPythonOnlyTest, PublishedPythonVersionTest
diff --git a/docs/pypi/README.md b/docs/pypi/README.md new file mode 100644 index 0000000..0b2fe9e --- /dev/null +++ b/docs/pypi/README.md @@ -0,0 +1,43 @@ +# toolchain-lite + +**`tcl`: add Python packages to a project from the command line, no Gradle project needed.** + +toolchain-lite is the command-line side of [toolchain](https://github.com/thisisthepy/toolchain), the +Gradle plugin that declares the Python half of a Kotlin Multiplatform app. `tcl` gives Python users +the same dependency step without a Gradle build. + +## Install + +```shell +uv tool install toolchain-lite # puts tcl on PATH +tcl --help +uvx --from toolchain-lite tcl --version # or run it without installing +``` + +The wheel carries `tcl` as a native binary (GraalVM Native Image), one wheel per platform: macOS 11+ +on Apple silicon, Linux x86_64 and aarch64 (glibc), Windows x86_64. There is no source distribution: +building needs a JDK and GraalVM. + +`uv` is needed at run time. When none is on `PATH`, `tcl` downloads one into `~/.pypackpack/uv`. + +## Use + +```shell +mkdir myapp && cd myapp +tcl install six # creates pyproject.toml (uv init --bare) if there is none, then adds six +``` + +`tcl install ` finds the nearest `pyproject.toml` at or above the current directory, creates +one if there is none, and adds the package with uv. It exits 0 on success and 1 with a message +otherwise. + +## Status + +`tcl install` is implemented and tested. Everything else in the plan for `tcl` +([issue #1](https://github.com/thisisthepy/toolchain/issues/1)) is not built yet. + +## Links + +- [Guide: tcl for Python users](https://thisisthepy.github.io/toolchain/guide-tcl.html) +- [Source](https://github.com/thisisthepy/toolchain/tree/develop/tcl) +- [Licence: Apache-2.0](https://github.com/thisisthepy/toolchain/blob/develop/LICENSE) diff --git a/pyproject.toml b/pyproject.toml index 11efa29..997c6fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,65 +1,44 @@ +# toolchain-lite, the PyPI distribution of `tcl`. The wheel carries the native `tcl` command line +# (GraalVM Native Image) and is assembled per platform by .github/scripts/pypi/build_wheel.py in +# publish-pypi.yml, which reads [project] below. Its version must equal tcl/build.gradle.kts's: the +# workflow refuses to publish unless the installed binary prints it. +# +# Installing from source (`uv pip install .`) gives only the Python package, without the binary; +# `python -m toolchain_lite` then says so and exits. [build-system] -requires = ["flit_core>=3.12,<4"] -build-backend = "flit_core.buildapi" +requires = ["setuptools>=77"] +build-backend = "setuptools.build_meta" [project] -name = "toolchain" -version = "0.0.1" -description = "Python Multiplatform Build Plugin/Tool with Kotlin Multiplatform Mobile" -readme = "README.md" -requires-python = ">=3.9, <3.14" +name = "toolchain-lite" +version = "0.1.0" +description = "tcl: install Python packages for a Kotlin Multiplatform app's Python half, no Gradle project needed" +readme = "docs/pypi/README.md" license = "Apache-2.0" license-files = ["LICENSE"] authors = [ - {name = "THISISTHEPY", email = "thisisthepy@gmail.com"} + { name = "thisisthepy", email = "thisisthepy@gmail.com" }, ] +keywords = ["kotlin-multiplatform", "python", "uv", "packaging"] classifiers = [ "Development Status :: 3 - Alpha", "Environment :: Console", "Intended Audience :: Developers", - "Operating System :: OS Independent", + "Operating System :: MacOS", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", "Topic :: Software Development :: Build Tools", ] -dependencies = [ -] - -[project.optional-dependencies] -dev = [ - "pytest>=7.0.0", - "black>=23.0.0", - "isort>=5.12.0", - "mypy>=1.0.0", -] -docs = [ - "sphinx>=6.0.0", - "sphinx-rtd-theme>=1.2.0", -] +requires-python = ">=3.9" +dependencies = [] [project.urls] -"Homepage" = "https://github.com/thisisthepy/toolchain" -"Bug Tracker" = "https://github.com/thisisthepy/toolchain/issues" -"Documentation" = "https://thisisthepy.github.io/" - -[tool.flit.module] -name = "toolchain" - -[tool.black] -line-length = 88 -target-version = ["py39", "py310", "py311", "py312"] - -[tool.isort] -profile = "black" -line_length = 88 +Homepage = "https://github.com/thisisthepy/toolchain" +Documentation = "https://thisisthepy.github.io/toolchain/guide-tcl.html" +Repository = "https://github.com/thisisthepy/toolchain" +Issues = "https://github.com/thisisthepy/toolchain/issues" -[tool.mypy] -python_version = "3.9" -warn_return_any = true -warn_unused_configs = true -disallow_untyped_defs = true -disallow_incomplete_defs = true +[tool.setuptools] +package-dir = { toolchain_lite = "tcl/src/main/python" } +packages = ["toolchain_lite"] diff --git a/tcl/build.gradle.kts b/tcl/build.gradle.kts index 8f37309..4ca4f03 100644 --- a/tcl/build.gradle.kts +++ b/tcl/build.gradle.kts @@ -1,5 +1,8 @@ group = rootProject.group -version = rootProject.version +// tcl's own version, which `tcl --version` prints and the toolchain-lite wheel carries: +// publish-pypi.yml refuses to publish unless it equals pyproject.toml's. The plugin keeps +// rootProject.version. +version = "0.1.0" plugins { // No explicit version: `org.jetbrains.kotlin.jvm` is already on this settings' shared plugin @@ -8,6 +11,7 @@ plugins { // already resolved ("plugin is already on the classpath with an unknown version"). id("org.jetbrains.kotlin.jvm") application + id("org.graalvm.buildtools.native") version "0.10.6" } dependencies { @@ -36,3 +40,47 @@ application { tasks.withType().configureEach { compilerOptions.freeCompilerArgs.add("-Xskip-metadata-version-check") } + +tasks.test { + systemProperty("tcl.expectedVersion", project.version.toString()) +} + +// `BuildInfo` reads this resource, so the version is not a literal in the source. +val generateBuildInfo by tasks.registering { + val tclVersion = project.version.toString() + val outputDir = layout.buildDirectory.dir("generated/build-info") + inputs.property("version", tclVersion) + outputs.dir(outputDir) + doLast { + val file = outputDir.get().file("org/thisisthepy/python/multiplatform/tcl/build-info.properties").asFile + file.parentFile.mkdirs() + file.writeText("version=$tclVersion\n") + } +} +sourceSets.main { resources.srcDir(generateBuildInfo) } + +// The native `tcl` binary that the toolchain-lite wheel carries (publish-pypi.yml). Same settings +// as pypackpack's CLI, which this links: io.ktor reaches org.slf4j (slf4j-nop, no I/O at init), +// which GraalVM 21 otherwise refuses to initialize at build time. +graalvmNative { + binaries { + named("main") { + imageName.set("tcl") + mainClass.set("org.thisisthepy.python.multiplatform.tcl.CliKt") + buildArgs.addAll( + "--no-fallback", + "--install-exit-handlers", + "--initialize-at-build-time=kotlin,kotlinx.coroutines,io.ktor,kotlinx.io,org.slf4j", + "-H:+AddAllCharsets", + "--gc=serial", + ) + if (System.getProperty("os.name").lowercase().contains("mac")) { + // Without this the binary's minimum macOS is the build host's. + buildArgs.add("-H:NativeLinkerOption=-mmacosx-version-min=11.0") + } + resources.autodetect() + } + } + // native-image comes from GRAALVM_HOME/JAVA_HOME, not a toolchain Gradle might pick. + toolchainDetection.set(false) +} diff --git a/tcl/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/BuildInfo.kt b/tcl/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/BuildInfo.kt new file mode 100644 index 0000000..ce71324 --- /dev/null +++ b/tcl/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/BuildInfo.kt @@ -0,0 +1,17 @@ +package org.thisisthepy.python.multiplatform.tcl + +import java.util.Properties + +/** + * Facts the build writes into tcl's resources (`generateBuildInfo` in `tcl/build.gradle.kts`). + * + * [version] is tcl's version, and so the toolchain-lite wheel's: `publish-pypi.yml` refuses to + * publish unless the installed binary prints the version in `pyproject.toml` (docs/SPEC.md 2.2). + */ +internal object BuildInfo { + val version: String by lazy { + BuildInfo::class.java.getResourceAsStream("build-info.properties") + ?.use { stream -> Properties().apply { load(stream) }.getProperty("version") } + ?: error("build-info.properties is missing from tcl's resources") + } +} diff --git a/tcl/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Cli.kt b/tcl/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Cli.kt index 393cd6d..f869251 100644 --- a/tcl/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Cli.kt +++ b/tcl/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Cli.kt @@ -26,6 +26,14 @@ fun execute( } return when (args[0]) { + "--help", "-h" -> { + out(USAGE) + 0 + } + "--version" -> { + out("tcl ${BuildInfo.version}") + 0 + } "install" -> { val packageName = args.getOrNull(1) if (packageName.isNullOrBlank()) { diff --git a/tcl/src/main/python/__init__.py b/tcl/src/main/python/__init__.py new file mode 100644 index 0000000..cd7c584 --- /dev/null +++ b/tcl/src/main/python/__init__.py @@ -0,0 +1,5 @@ +"""toolchain-lite on PyPI. + +The wheel installs the native `tcl` command line (a GraalVM Native Image) as a script. This package +only lets `python -m toolchain_lite` find and run that binary. +""" diff --git a/tcl/src/main/python/__main__.py b/tcl/src/main/python/__main__.py new file mode 100644 index 0000000..4880750 --- /dev/null +++ b/tcl/src/main/python/__main__.py @@ -0,0 +1,41 @@ +"""Run the native `tcl` binary installed beside this interpreter (`python -m toolchain_lite`).""" +import os +import subprocess +import sys +import sysconfig +from pathlib import Path + +_NAME = "tcl.exe" if os.name == "nt" else "tcl" + + +def _candidates(): + yield Path(sysconfig.get_path("scripts")) / _NAME + user_scheme = getattr(sysconfig, "get_preferred_scheme", lambda _: f"{os.name}_user")("user") + try: + yield Path(sysconfig.get_path("scripts", user_scheme)) / _NAME + except KeyError: + pass + + +def _binary() -> Path: + tried = [] + for path in _candidates(): + if path.is_file(): + return path + tried.append(str(path)) + sys.exit( + "tcl: the native binary is not installed beside this Python " + f"(looked for {', '.join(tried)}). Install a platform wheel: uv tool install toolchain-lite" + ) + + +def main() -> None: + exe = _binary() + args = [str(exe), *sys.argv[1:]] + if os.name == "nt": + sys.exit(subprocess.call(args)) + os.execv(str(exe), args) + + +if __name__ == "__main__": + main() diff --git a/tcl/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/CliArgsTest.kt b/tcl/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/CliArgsTest.kt index 9693d0d..df09c21 100644 --- a/tcl/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/CliArgsTest.kt +++ b/tcl/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/CliArgsTest.kt @@ -72,4 +72,33 @@ class CliArgsTest { assertEquals(1, code) assertTrue(errors.any { it.contains("network down") }) } + + @Test + fun `--help prints usage on stdout and exits zero`() { + val out = mutableListOf() + + val code = execute(arrayOf("--help"), anyDir, install = mustNotInstall, out = out::add) + + assertEquals(0, code) + assertTrue(out.any { it.contains("Usage: tcl install ") }, out.toString()) + } + + @Test + fun `--version prints the build's version and exits zero`() { + val out = mutableListOf() + + val code = execute(arrayOf("--version"), anyDir, install = mustNotInstall, out = out::add) + + assertEquals(0, code) + assertEquals(listOf("tcl ${BuildInfo.version}"), out) + } + + // cli/build.gradle.kts passes tcl's own version to the test JVM; publish-pypi.yml then checks it + // against pyproject.toml, so it must not be a literal (docs/SPEC.md section 2.2). + @Test + fun `the version is the one the build declares`() { + val expected = System.getProperty("tcl.expectedVersion") + assertTrue(!expected.isNullOrBlank(), "the build passes no tcl.expectedVersion") + assertEquals(expected, BuildInfo.version) + } } From c15f92c2e471f0d7bb3ba625908cdbbfd0f4d77f Mon Sep 17 00:00:00 2001 From: BREW <81044362+b-re-w@users.noreply.github.com> Date: Sun, 4 Oct 2026 00:28:34 +0900 Subject: [PATCH 09/12] CI: Tag toolchain-lite releases toolchain-lite-v Lead decision (option A): this repository also releases the Gradle plugin, so toolchain-lite's Release tags are toolchain-lite-v and bare v* stays the plugin's. publish-pypi.yml requires that tag and skips itself for any other Release; the pypi environment accepts only toolchain-lite-v* tags. Closes #71 --- .github/workflows/publish-pypi.yml | 9 ++++++--- docs/SPEC.md | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index ecaddee..39b337a 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -1,7 +1,8 @@ name: Publish to PyPI # toolchain-lite (the PyPI distribution of tcl). Adapted from pypackpack's publish-pypi.yml -# (pypackpack #53), with the same gates. Publishing a GitHub Release `v` builds the native +# (pypackpack #53), with the same gates. Publishing a GitHub Release `toolchain-lite-v` +# (#71: bare `v*` tags belong to the Gradle plugin, which this repository also releases) builds the native # tcl on each platform, wraps each binary in a wheel (.github/scripts/pypi/build_wheel.py), installs # and smoke-tests every wheel (smoke_wheel.py), and only then uploads them all through trusted # publishing (environment `pypi`, no token). A pull request that touches the publishing files, or a @@ -36,6 +37,8 @@ concurrency: jobs: version: + # Another product's Release (the Gradle plugin's `v*`) skips this workflow; skipped jobs skip their dependants. + if: github.event_name != 'release' || startsWith(github.event.release.tag_name, 'toolchain-lite-v') runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -50,8 +53,8 @@ jobs: if [ "$py" != "$cli" ]; then echo "::error::pyproject.toml ($py) and tcl/build.gradle.kts ($cli) must carry the same version"; exit 1 fi - if [ "$IS_RELEASE" = true ] && [ "$TAG" != "v$py" ]; then - echo "::error::release tag '$TAG' is not 'v$py'"; exit 1 + if [ "$IS_RELEASE" = true ] && [ "$TAG" != "toolchain-lite-v$py" ]; then + echo "::error::release tag '$TAG' is not 'toolchain-lite-v$py'"; exit 1 fi if curl -fsS -o /dev/null "https://pypi.org/pypi/toolchain-lite/$py/json"; then if [ "$IS_RELEASE" = true ]; then echo "::error::toolchain-lite $py is already on PyPI"; exit 1; fi diff --git a/docs/SPEC.md b/docs/SPEC.md index 62ed1fa..8866ef4 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -556,7 +556,8 @@ tcl --help - The platform tag is read from the binary (`.github/scripts/pypi/build_wheel.py`). No sdist: building needs a JDK and GraalVM. - `.github/workflows/publish-pypi.yml` runs on a published GitHub Release only and uploads through - trusted publishing (environment `pypi`). Before uploading it requires the tag to be `v`, + trusted publishing (environment `pypi`, which accepts only `toolchain-lite-v*` tags). Before uploading + it requires the tag to be `toolchain-lite-v` (bare `v*` tags are the Gradle plugin's, #71), `pyproject.toml` and `tcl/build.gradle.kts` to carry that version, the version to be new on PyPI, and every wheel to pass `smoke_wheel.py` in a fresh venv (`tcl --help`, `tcl --version`, `python -m toolchain_lite --version`, and `tcl install six` writing a `pyproject.toml`). From 7a0658fc53bb310fbf06ecd1603b638428cbd54a Mon Sep 17 00:00:00 2001 From: BREW <81044362+b-re-w@users.noreply.github.com> Date: Sun, 4 Oct 2026 00:31:46 +0900 Subject: [PATCH 10/12] Docs: Absolute README links, status words, AGENTS layout table, release test scratch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lead directive (item B, 2026-10-04) and the writing standard (pythonx-compose docs/style/writing.md): - README.md and README_ko.md link the guide site, the Korean/English README and LICENSE by absolute URL; AGENTS section 17 says README links are absolute. - Status words: 구현 / 부분 / 계획 (37 occurrences of 구현됨). - AGENTS section 2's table: CI-only scripts in .github/scripts/, no benchmarks/ or tools/. - test-sync-release.sh resolves the repository root three levels up since #62 moved it, so its scratch is the root .tmp/ again, not .github/.tmp/. Closes #74 --- .github/scripts/release/test-sync-release.sh | 2 +- AGENTS.md | 7 ++--- PROJECT.md | 2 +- README.md | 16 +++++----- docs/guide/guide-variants.html | 2 +- docs/guide/status.html | 6 ++-- docs/locale/README_ko.md | 32 ++++++++++---------- 7 files changed, 33 insertions(+), 34 deletions(-) diff --git a/.github/scripts/release/test-sync-release.sh b/.github/scripts/release/test-sync-release.sh index cba1d31..cb7e18d 100755 --- a/.github/scripts/release/test-sync-release.sh +++ b/.github/scripts/release/test-sync-release.sh @@ -2,7 +2,7 @@ # Tests for sync-release.sh. Builds a throwaway repo under /.tmp/. set -u HERE="$(cd "$(dirname "$0")" && pwd)" -ROOT="$(cd "$HERE/../.." && pwd)" +ROOT="$(cd "$HERE/../../.." && pwd)" # .github/scripts/release -> repository root SYNC="$HERE/sync-release.sh" SCRATCH="$ROOT/.tmp/release-sync-test.$$" FAILS=0; PASSES=0 diff --git a/AGENTS.md b/AGENTS.md index 00ab8e5..6ee10da 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,8 +21,7 @@ files) lives **inside this repository's root directory.** |---|---| | Worktrees | `.worktrees/` (git-ignored) | | Temporary files | `.tmp/` (git-ignored); delete when done | -| Benchmarks | `benchmarks/` | -| Developer tooling | `tools/` | +| CI-only scripts | `.github/scripts/` | Before writing a file, check that its absolute path starts with this repository's root. If it does not, stop. The only exceptions are a path the user names explicitly, and caches that build tools @@ -49,7 +48,7 @@ build caches, model weights, `node_modules`) into every worktree is how 86 workt - Create worktrees under `.worktrees/`. - **Symlink** large untracked directories from the main checkout instead of copying or rebuilding - them. If `tools/worktree-add.sh` exists, use it: it does the linking. + them. - Delete a worktree once its branch is merged: `git worktree remove .worktrees/`. - Periodically delete `build/` directories inside worktrees; they only grow. @@ -271,7 +270,7 @@ python3 docs/guide/check_guide.py | File | Language | Holds | |---|---|---| -| `README.md` | English | The public face. Links only to `docs/guide/`, `docs/locale/`, `docs//` and `LICENSE` (rule 4: other root and `docs/*.md` files do not exist on `main`). | +| `README.md` | English | The public face. Links only to the guide site, `docs/locale/`, `docs//` and `LICENSE`, always as absolute URLs (the file is read on GitHub, PyPI and elsewhere; rule 4: other root and `docs/*.md` files do not exist on `main`). | | `docs/locale/README_ko.md` | Korean | A faithful translation of `README.md`. Change both together. | | `PROJECT.md` | Korean | Status, structure, how to build, decisions, open questions. | | `docs/INTENT.md` | English | Why the project exists and what it is not. | diff --git a/PROJECT.md b/PROJECT.md index a5ac3b1..7d35331 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -15,7 +15,7 @@ Kotlin Multiplatform 앱의 Python 부분을 Gradle `python { }` 블록으로 ## 현재 상태 (2026-10-03, 코드와 테스트를 읽고 판정) -**구현됨 (테스트 있음)** +**구현 (테스트 있음)** - `compileSdk` 버전 문자열 파싱 (alpha / rc / normal 채널), 그리고 이름 상수 `PY3_14_7`·`PY3_13_0` (python-multiplatform 이 런타임을 제공하는 버전만). 제공되지 않는 버전 문자열은 `buildPython…` 에서 이유와 함께 실패 - `python { }` 최상위의 `android("android") { }` / `ios { }` / `desktop()` 과 변형 호출 diff --git a/README.md b/README.md index ab88eeb..7578b0a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -English | [한국어](docs/locale/README_ko.md) +English | [한국어](https://github.com/thisisthepy/toolchain/blob/develop/docs/locale/README_ko.md)
@@ -6,13 +6,13 @@ English | [한국어](docs/locale/README_ko.md) **Declare the Python half of your Kotlin Multiplatform app in Gradle, and ship it.** -[![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-0f9d76.svg)](LICENSE) +[![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-0f9d76.svg)](https://github.com/thisisthepy/toolchain/blob/develop/LICENSE) ![Kotlin](https://img.shields.io/badge/Kotlin-2.1-7f52ff.svg?logo=kotlin&logoColor=white) ![Gradle](https://img.shields.io/badge/Gradle-8.9-02303a.svg?logo=gradle&logoColor=white) ![Targets](https://img.shields.io/badge/targets-Android%20%7C%20iOS%20%7C%20Desktop-0f9d76.svg) ![Status](https://img.shields.io/badge/status-alpha-orange.svg) -[Guide](docs/guide/index.html) · [Getting started](docs/guide/getting-started.html) · [Status](docs/guide/status.html) · [Ecosystem](docs/guide/ecosystem.html) +[Guide](https://thisisthepy.github.io/toolchain/index.html) · [Getting started](https://thisisthepy.github.io/toolchain/getting-started.html) · [Status](https://thisisthepy.github.io/toolchain/status.html) · [Ecosystem](https://thisisthepy.github.io/toolchain/ecosystem.html)
@@ -129,7 +129,7 @@ flowchart LR ## 📊 Status An honest summary: the full contract, item by item, is on the guide's -[Status page](docs/guide/status.html). +[Status page](https://thisisthepy.github.io/toolchain/status.html). | Area | State | |---|---| @@ -149,15 +149,15 @@ An honest summary: the full contract, item by item, is on the guide's ## 📖 Documentation -- **[Guide](docs/guide/index.html)**: concepts, task guides, status, in English and 한국어. -- **[한국어 README](docs/locale/README_ko.md)** +- **[Guide](https://thisisthepy.github.io/toolchain/index.html)**: concepts, task guides, status, in English and 한국어. +- **[한국어 README](https://github.com/thisisthepy/toolchain/blob/develop/docs/locale/README_ko.md)** ## 🤝 Contributing Work here runs intent → spec → test → code: a behaviour change starts as a spec change, and its test is written and seen failing before the implementation. The guide's -[contributing notes](docs/guide/status.html#contributing) say how to run the tests. +[contributing notes](https://thisisthepy.github.io/toolchain/status.html#contributing) say how to run the tests. ## 📄 License -[Apache-2.0](LICENSE) © 2024 thisisthepy +[Apache-2.0](https://github.com/thisisthepy/toolchain/blob/develop/LICENSE) © 2024 thisisthepy diff --git a/docs/guide/guide-variants.html b/docs/guide/guide-variants.html index 4d0240b..0a84b83 100644 --- a/docs/guide/guide-variants.html +++ b/docs/guide/guide-variants.html @@ -56,7 +56,7 @@

Build types빌드 타입

Only debug and release exist. useCodeMinifier, excludeMetaclass and enableCodePush are release-only: setting them on debug throws.

debug 와 release 만 있습니다. useCodeMinifier, excludeMetaclass, enableCodePush 는 release 전용이며 debug 에 설정하면 예외가 납니다.

-
compileLevelcompileLevelMeaning의미Today지금
instant (or blank)(또는 빈 값).py only.py 만implemented구현됨
bytecodedebug: .py + .pyc; release: .pyc onlydebug: .py + .pyc, release: .pyc 만implemented구현됨
nativenative code only네이티브 코드만planned (pypackpack#19)계획 (pypackpack#19)
mixedyour code as bytecode, libraries native내 코드는 바이트코드, 라이브러리는 네이티브planned (pypackpack#19)계획 (pypackpack#19)
+
compileLevelcompileLevelMeaning의미Today지금
instant (or blank)(또는 빈 값).py only.py 만implemented구현
bytecodedebug: .py + .pyc; release: .pyc onlydebug: .py + .pyc, release: .pyc 만implemented구현
nativenative code only네이티브 코드만planned (pypackpack#19)계획 (pypackpack#19)
mixedyour code as bytecode, libraries native내 코드는 바이트코드, 라이브러리는 네이티브planned (pypackpack#19)계획 (pypackpack#19)

bytecode compiles with the Python in a .venv at or above your package directory. installPythonDependencies leaves one there (uv add creates <package>/.venv) once you declare a dependency; otherwise run uv venv --python 3.14 in the package directory. Its minor version must match compileSdk, because a .pyc only loads on the CPython minor version that wrote it; a mismatch fails the variant. Without a declared compileSdk, or a pyvenv.cfg to read, the versions are not compared.

bytecode 는 패키지 디렉터리나 그 위의 .venv 에 있는 Python 으로 컴파일합니다. 의존성을 하나라도 선언하면 installPythonDependencies 가 그것을 만들어 둡니다(uv add 가 <package>/.venv 를 만듭니다). 그렇지 않으면 패키지 디렉터리에서 uv venv --python 3.14 를 실행하세요. 그 마이너 버전은 compileSdk 와 같아야 합니다. .pyc 는 그것을 쓴 CPython 마이너 버전에서만 로드되기 때문이며, 다르면 그 변형이 실패합니다. compileSdk 를 선언하지 않았거나 읽을 pyvenv.cfg 가 없으면 버전을 비교하지 않습니다.

A planned level fails only its own variant's buildPython… task, so ./gradlew packagePython --continue still builds every other variant.

계획 단계의 레벨은 그 변형의 buildPython… 태스크만 실패시키므로, ./gradlew packagePython --continue 는 나머지 변형을 모두 빌드합니다.

diff --git a/docs/guide/status.html b/docs/guide/status.html index b67ed71..bd58f5a 100644 --- a/docs/guide/status.html +++ b/docs/guide/status.html @@ -24,12 +24,12 @@

Status & FAQ상태와 FAQ

-

What works today, measured against the maintainer's example build file. “Implemented” means a test in this repository exercises it.

지금 되는 것을 메인테이너의 예시 빌드 파일에 대어 본 것입니다. “구현됨” 은 이 저장소의 테스트가 그것을 실행한다는 뜻입니다.

+

What works today, measured against the maintainer's example build file. “Implemented” means a test in this repository exercises it.

지금 되는 것을 메인테이너의 예시 빌드 파일에 대어 본 것입니다. “구현” 은 이 저장소의 테스트가 그것을 실행한다는 뜻입니다.

Feature status기능 상태

-
-
Feature기능Status상태Evidence근거
Applying the plugin: python extension and tasks플러그인 적용: python 확장과 태스크implemented구현됨PythonPluginApplyTest
compileSdk parsing: alpha · rc · normalcompileSdk 파싱: alpha · rc · normalimplemented구현됨PythonVersionTest
Platform → triple mapping, Kotlin target check, min SDK플랫폼 → 트리플 매핑, Kotlin 타깃 확인, 최소 SDKimplemented구현됨PythonExtensionPlatformDslTest, PlatformTargetMappingTest, PythonPluginPlatformsTest
debug / release, -Ppython.buildTypedebug / release, -Ppython.buildTypeimplemented구현됨PythonPluginBuildTypeTest
Per-variant task graph변형별 태스크 그래프implemented구현됨PythonPluginVariantGraphTest
compileLevel = instant · bytecode (.venv and compileSdk minor checked); others rejected per variantcompileLevel = instant · bytecode (.venv 와 compileSdk 마이너 버전 확인); 나머지는 변형별 거부implemented구현됨PythonPluginBuildLevelTest, BytecodeLevelTest
srcDirs / metaDirs / libDirssrcDirs / metaDirs / libDirsimplemented구현됨PythonPluginSourceSetTest
implementation / integration → uv addimplementation / integration → uv addimplemented구현됨PythonPluginDependencyTest, InstallDependenciesTaskTest
Bundling via pypackpack's resource bundlerpypackpack 리소스 번들러로 번들링implemented구현됨BuildPythonArtifactTaskTest
packagePython zippackagePython zipimplemented구현됨AssemblePythonPackageTaskTest
Staging copy rule and variant selection스테이징 복사 규칙과 변형 선택implemented구현됨StagePythonBundleTaskTest, PythonPluginStagingTest
tcl installtcl installimplemented구현됨CliArgsTest, InstallerTest
tcl --help, tcl --versiontcl --help, tcl --versionimplemented구현됨CliArgsTest
toolchain-lite on PyPI (uv tool install toolchain-lite)PyPI 의 toolchain-lite (uv tool install toolchain-lite)partial부분CI builds and smoke-tests the wheels; not uploaded yetCI 가 휠을 빌드하고 스모크 테스트함. 아직 업로드 안 됨
Staging into the desktop jar데스크톱 jar 로의 스테이징implemented구현됨PythonPluginAttachmentTest
Staging into the APKAPK 로의 스테이징partial부분asset root tested (PythonPluginAttachmentTest); task dependency untested에셋 루트는 테스트됨 (PythonPluginAttachmentTest), 태스크 의존성은 테스트 없음
Hot reload핫 리로드partial부분Android, adb only; serverHost unusedAndroid, adb 만; serverHost 미사용
Code push코드 푸시partial부분validation only검증만
Dependencies per source set, installed for each variant's own triple into its bundle소스셋별 의존성을 변형마다 그 트리플용으로 설치해 번들에 포함implemented구현됨TargetDependenciesTest, PythonPluginTargetDependenciesTest, InstallTargetDependenciesTaskTest
integration(): warn when the wheel has no KLIBDEPENSintegration(): 휠에 KLIBDEPENS 가 없으면 경고implemented구현됨KlibDepensTest, PythonPluginIntegrationListTest
native / mixednative / mixedplanned계획needs pypackpack#19pypackpack#19 선행 필요
iOS: attaching the staged payload (stagePythonBundleIosForXcode prints the path; the Xcode phase is python-multiplatform's, #59)iOS: 스테이징된 페이로드 연결 (stagePythonBundleIosForXcode 가 경로 출력, Xcode 단계는 python-multiplatform #59 소유)partial부분StagePythonBundleIosForXcodeTaskTest, StagePythonPayloadScriptTest
buildFeatures { metaclass } and excludeMetaclass: metaDirs forwarded or droppedbuildFeatures { metaclass } 와 excludeMetaclass: metaDirs 전달 또는 제외implemented구현됨BuildFeaturesTest, PythonPluginBuildFeaturesTest
buildFeatures { compose }: installs pythonx-compose, adds python-multiplatform-compose to commonMainbuildFeatures { compose }: pythonx-compose 설치, commonMain 에 python-multiplatform-compose 추가implemented구현됨BuildFeaturesTest, PythonPluginBuildFeaturesTest; locations from two Gradle propertiesBuildFeaturesTest, PythonPluginBuildFeaturesTest; 위치는 Gradle 속성 두 개로 지정
embedLevel: 0/1/2 resolved per platform (Android and iOS raise to 2 with a warning), -Ppython.embedLevel override, recorded in embed.json with the expected interpreter version; level 2 means python-multiplatform embeds the interpreter, and the bundle stays python/ onlyembedLevel: 플랫폼별 0/1/2 결정 (Android, iOS 는 경고와 함께 2 로 상향), -Ppython.embedLevel 재정의, 기대 인터프리터 버전과 함께 embed.json 에 기록; 레벨 2 는 python-multiplatform 이 인터프리터를 내장한다는 뜻이고, 번들은 python/ 만 담음implemented구현됨EmbedLevelTest, PythonPluginEmbedLevelTest, PythonPluginPythonOnlyTest
useCodeMinifieruseCodeMinifierplanned계획declared, unread선언만, 읽히지 않음
defaultConfig { pip }: central · local · autoUpdatedefaultConfig { pip }: central · local · autoUpdateimplemented구현됨PipRepositoriesTest
pip { jit }pip { jit }planned계획-
compileSdk constants (PY3_14_7); unprovided versions refused per taskcompileSdk 상수(PY3_14_7); 제공되지 않는 버전은 태스크에서 거부implemented구현됨PythonSdkTest, BuildPythonSdkRejectionTest
projectFlavors in the variant graph; -Ppython.flavor변형 그래프의 projectFlavors; -Ppython.flavorimplemented구현됨PythonPluginFlavorsTest
defaultConfig { versionCode, versionName } → bundle manifestdefaultConfig { versionCode, versionName } → 번들 manifestimplemented구현됨PythonPluginPayloadVersionTest, BuildPythonArtifactTaskTest
toolchain ships only python/: compileSdk selects the wheels' Python version and fetches no interpreter; the interpreter and stdlib come from python-multiplatformtoolchain 은 python/ 만 실음: compileSdk 는 wheel 의 Python 버전을 고를 뿐 인터프리터를 받지 않음; 인터프리터와 stdlib 는 python-multiplatform 이 제공implemented구현됨PythonPluginTargetDependenciesTest, PythonPluginPythonOnlyTest
compileSdk checked against python-multiplatform's pythonVersion at level 2 (a mismatch fails only that variant's buildPython…); read from -Ppython.multiplatform.pythonVersion, the :python-multiplatform project's extension, or the published dependency's metadata or jar레벨 2 에서 compileSdk 를 python-multiplatform 의 pythonVersion 과 대조 (다르면 그 변형의 buildPython… 만 실패); -Ppython.multiplatform.pythonVersion, :python-multiplatform 프로젝트의 확장, 또는 배포된 의존의 메타데이터나 jar 에서 읽음implemented구현됨PythonVersionAgreementTest, PythonPluginPythonOnlyTest, PublishedPythonVersionTest
+
+
Feature기능Status상태Evidence근거
Applying the plugin: python extension and tasks플러그인 적용: python 확장과 태스크implemented구현PythonPluginApplyTest
compileSdk parsing: alpha · rc · normalcompileSdk 파싱: alpha · rc · normalimplemented구현PythonVersionTest
Platform → triple mapping, Kotlin target check, min SDK플랫폼 → 트리플 매핑, Kotlin 타깃 확인, 최소 SDKimplemented구현PythonExtensionPlatformDslTest, PlatformTargetMappingTest, PythonPluginPlatformsTest
debug / release, -Ppython.buildTypedebug / release, -Ppython.buildTypeimplemented구현PythonPluginBuildTypeTest
Per-variant task graph변형별 태스크 그래프implemented구현PythonPluginVariantGraphTest
compileLevel = instant · bytecode (.venv and compileSdk minor checked); others rejected per variantcompileLevel = instant · bytecode (.venv 와 compileSdk 마이너 버전 확인); 나머지는 변형별 거부implemented구현PythonPluginBuildLevelTest, BytecodeLevelTest
srcDirs / metaDirs / libDirssrcDirs / metaDirs / libDirsimplemented구현PythonPluginSourceSetTest
implementation / integration → uv addimplementation / integration → uv addimplemented구현PythonPluginDependencyTest, InstallDependenciesTaskTest
Bundling via pypackpack's resource bundlerpypackpack 리소스 번들러로 번들링implemented구현BuildPythonArtifactTaskTest
packagePython zippackagePython zipimplemented구현AssemblePythonPackageTaskTest
Staging copy rule and variant selection스테이징 복사 규칙과 변형 선택implemented구현StagePythonBundleTaskTest, PythonPluginStagingTest
tcl installtcl installimplemented구현CliArgsTest, InstallerTest
tcl --help, tcl --versiontcl --help, tcl --versionimplemented구현CliArgsTest
toolchain-lite on PyPI (uv tool install toolchain-lite)PyPI 의 toolchain-lite (uv tool install toolchain-lite)partial부분CI builds and smoke-tests the wheels; not uploaded yetCI 가 휠을 빌드하고 스모크 테스트함. 아직 업로드 안 됨
Staging into the desktop jar데스크톱 jar 로의 스테이징implemented구현PythonPluginAttachmentTest
Staging into the APKAPK 로의 스테이징partial부분asset root tested (PythonPluginAttachmentTest); task dependency untested에셋 루트는 테스트됨 (PythonPluginAttachmentTest), 태스크 의존성은 테스트 없음
Hot reload핫 리로드partial부분Android, adb only; serverHost unusedAndroid, adb 만; serverHost 미사용
Code push코드 푸시partial부분validation only검증만
Dependencies per source set, installed for each variant's own triple into its bundle소스셋별 의존성을 변형마다 그 트리플용으로 설치해 번들에 포함implemented구현TargetDependenciesTest, PythonPluginTargetDependenciesTest, InstallTargetDependenciesTaskTest
integration(): warn when the wheel has no KLIBDEPENSintegration(): 휠에 KLIBDEPENS 가 없으면 경고implemented구현KlibDepensTest, PythonPluginIntegrationListTest
native / mixednative / mixedplanned계획needs pypackpack#19pypackpack#19 선행 필요
iOS: attaching the staged payload (stagePythonBundleIosForXcode prints the path; the Xcode phase is python-multiplatform's, #59)iOS: 스테이징된 페이로드 연결 (stagePythonBundleIosForXcode 가 경로 출력, Xcode 단계는 python-multiplatform #59 소유)partial부분StagePythonBundleIosForXcodeTaskTest, StagePythonPayloadScriptTest
buildFeatures { metaclass } and excludeMetaclass: metaDirs forwarded or droppedbuildFeatures { metaclass } 와 excludeMetaclass: metaDirs 전달 또는 제외implemented구현BuildFeaturesTest, PythonPluginBuildFeaturesTest
buildFeatures { compose }: installs pythonx-compose, adds python-multiplatform-compose to commonMainbuildFeatures { compose }: pythonx-compose 설치, commonMain 에 python-multiplatform-compose 추가implemented구현BuildFeaturesTest, PythonPluginBuildFeaturesTest; locations from two Gradle propertiesBuildFeaturesTest, PythonPluginBuildFeaturesTest; 위치는 Gradle 속성 두 개로 지정
embedLevel: 0/1/2 resolved per platform (Android and iOS raise to 2 with a warning), -Ppython.embedLevel override, recorded in embed.json with the expected interpreter version; level 2 means python-multiplatform embeds the interpreter, and the bundle stays python/ onlyembedLevel: 플랫폼별 0/1/2 결정 (Android, iOS 는 경고와 함께 2 로 상향), -Ppython.embedLevel 재정의, 기대 인터프리터 버전과 함께 embed.json 에 기록; 레벨 2 는 python-multiplatform 이 인터프리터를 내장한다는 뜻이고, 번들은 python/ 만 담음implemented구현EmbedLevelTest, PythonPluginEmbedLevelTest, PythonPluginPythonOnlyTest
useCodeMinifieruseCodeMinifierplanned계획declared, unread선언만, 읽히지 않음
defaultConfig { pip }: central · local · autoUpdatedefaultConfig { pip }: central · local · autoUpdateimplemented구현PipRepositoriesTest
pip { jit }pip { jit }planned계획-
compileSdk constants (PY3_14_7); unprovided versions refused per taskcompileSdk 상수(PY3_14_7); 제공되지 않는 버전은 태스크에서 거부implemented구현PythonSdkTest, BuildPythonSdkRejectionTest
projectFlavors in the variant graph; -Ppython.flavor변형 그래프의 projectFlavors; -Ppython.flavorimplemented구현PythonPluginFlavorsTest
defaultConfig { versionCode, versionName } → bundle manifestdefaultConfig { versionCode, versionName } → 번들 manifestimplemented구현PythonPluginPayloadVersionTest, BuildPythonArtifactTaskTest
toolchain ships only python/: compileSdk selects the wheels' Python version and fetches no interpreter; the interpreter and stdlib come from python-multiplatformtoolchain 은 python/ 만 실음: compileSdk 는 wheel 의 Python 버전을 고를 뿐 인터프리터를 받지 않음; 인터프리터와 stdlib 는 python-multiplatform 이 제공implemented구현PythonPluginTargetDependenciesTest, PythonPluginPythonOnlyTest
compileSdk checked against python-multiplatform's pythonVersion at level 2 (a mismatch fails only that variant's buildPython…); read from -Ppython.multiplatform.pythonVersion, the :python-multiplatform project's extension, or the published dependency's metadata or jar레벨 2 에서 compileSdk 를 python-multiplatform 의 pythonVersion 과 대조 (다르면 그 변형의 buildPython… 만 실패); -Ppython.multiplatform.pythonVersion, :python-multiplatform 프로젝트의 확장, 또는 배포된 의존의 메타데이터나 jar 에서 읽음implemented구현PythonVersionAgreementTest, PythonPluginPythonOnlyTest, PublishedPythonVersionTest
diff --git a/docs/locale/README_ko.md b/docs/locale/README_ko.md index 63dfaef..ddd7d39 100644 --- a/docs/locale/README_ko.md +++ b/docs/locale/README_ko.md @@ -1,4 +1,4 @@ -[English](../../README.md) | 한국어 +[English](https://github.com/thisisthepy/toolchain/blob/develop/README.md) | 한국어
@@ -6,13 +6,13 @@ **Kotlin Multiplatform 앱의 Python 절반을 Gradle 에 선언하고, 그대로 배포하세요.** -[![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-0f9d76.svg)](../../LICENSE) +[![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-0f9d76.svg)](https://github.com/thisisthepy/toolchain/blob/develop/LICENSE) ![Kotlin](https://img.shields.io/badge/Kotlin-2.1-7f52ff.svg?logo=kotlin&logoColor=white) ![Gradle](https://img.shields.io/badge/Gradle-8.9-02303a.svg?logo=gradle&logoColor=white) ![Targets](https://img.shields.io/badge/targets-Android%20%7C%20iOS%20%7C%20Desktop-0f9d76.svg) ![Status](https://img.shields.io/badge/status-alpha-orange.svg) -[가이드](../guide/index.html) · [시작하기](../guide/getting-started.html) · [상태](../guide/status.html) · [생태계](../guide/ecosystem.html) +[가이드](https://thisisthepy.github.io/toolchain/index.html) · [시작하기](https://thisisthepy.github.io/toolchain/getting-started.html) · [상태](https://thisisthepy.github.io/toolchain/status.html) · [생태계](https://thisisthepy.github.io/toolchain/ecosystem.html)
@@ -127,35 +127,35 @@ flowchart LR ## 📊 현재 상태 -솔직한 요약입니다. 항목별 전체 계약은 가이드의 [상태 페이지](../guide/status.html)에 있습니다. +솔직한 요약입니다. 항목별 전체 계약은 가이드의 [상태 페이지](https://thisisthepy.github.io/toolchain/status.html)에 있습니다. | 영역 | 상태 | |---|---| -| `compileSdk` 파싱 (alpha / rc / normal) | ✅ 구현됨 | -| 플랫폼 → 타깃 트리플, Kotlin 타깃 대조, 최소 SDK | ✅ 구현됨 | -| `debug` / `release` 와 변형별 태스크 그래프 | ✅ 구현됨 | -| `uv` 를 통한 `implementation` / `integration` 의존성, 소스셋별로 변형마다 그 트리플용으로 설치해 번들에 포함 | ✅ 구현됨 | -| `instant` 레벨의 `pypackpack` 번들링 | ✅ 구현됨 | -| `bytecode` 컴파일 레벨 (`.pyc`, `compileSdk` 와 맞는 `.venv` 필요) | ✅ 구현됨 | +| `compileSdk` 파싱 (alpha / rc / normal) | ✅ 구현 | +| 플랫폼 → 타깃 트리플, Kotlin 타깃 대조, 최소 SDK | ✅ 구현 | +| `debug` / `release` 와 변형별 태스크 그래프 | ✅ 구현 | +| `uv` 를 통한 `implementation` / `integration` 의존성, 소스셋별로 변형마다 그 트리플용으로 설치해 번들에 포함 | ✅ 구현 | +| `instant` 레벨의 `pypackpack` 번들링 | ✅ 구현 | +| `bytecode` 컴파일 레벨 (`.pyc`, `compileSdk` 와 맞는 `.venv` 필요) | ✅ 구현 | | `native` / `mixed` 컴파일 레벨 | ⏳ 계획: 현재는 명시적으로 거부 | | 데스크톱 jar 와 APK 로의 스테이징 | 🟡 부분: iOS 는 스테이징만 되고 연결되지 않음 | | 핫 리로드 | 🟡 부분: Android 전용, `adb` 경유 | | 코드 푸시 | 🟡 부분: 검증만, 업로드 없음 | -| `buildFeatures { metaclass, compose }` | ✅ 구현됨: compose 위치는 Gradle 속성 두 개로 지정 | +| `buildFeatures { metaclass, compose }` | ✅ 구현: compose 위치는 Gradle 속성 두 개로 지정 | | `embedLevel`, `projectFlavors`, `pip { }` | ⏳ 계획 | -| `tcl install` | ✅ 구현됨 | +| `tcl install` | ✅ 구현 | ## 📖 문서 -- **[가이드](../guide/index.html)**: 개념, 작업별 가이드, 상태. 영어와 한국어. -- **[English README](../../README.md)** +- **[가이드](https://thisisthepy.github.io/toolchain/index.html)**: 개념, 작업별 가이드, 상태. 영어와 한국어. +- **[English README](https://github.com/thisisthepy/toolchain/blob/develop/README.md)** ## 🤝 기여 이 저장소의 작업은 의도 → 스펙 → 테스트 → 코드 순서로 진행합니다. 동작을 바꾸려면 스펙부터 바꾸고, 그 테스트를 먼저 작성해 실패하는 것을 확인한 뒤 구현합니다. 테스트 실행 방법은 가이드의 -[기여 안내](../guide/status.html#contributing)에 있습니다. +[기여 안내](https://thisisthepy.github.io/toolchain/status.html#contributing)에 있습니다. ## 📄 라이선스 -[Apache-2.0](../../LICENSE) © 2024 thisisthepy +[Apache-2.0](https://github.com/thisisthepy/toolchain/blob/develop/LICENSE) © 2024 thisisthepy From 3833cb356541568a621b3ac0d6b62080b1fb1c78 Mon Sep 17 00:00:00 2001 From: BREW <81044362+b-re-w@users.noreply.github.com> Date: Sun, 4 Oct 2026 01:18:11 +0900 Subject: [PATCH 11/12] Refactor: Restore toolchain/ and move tcl into it as cli source sets User correction (via the lead): the #62 restructure relayed the lead's proposal table as a user directive, and the user never approved the name toolchain-gradle-plugin, nor tcl as a root folder. - toolchain-gradle-plugin/ -> toolchain/ (:toolchain; plugin id unchanged; the implementation artifact is ...:toolchain again). Settings, CI, sample comments and docs follow. - tcl/ leaves the root: its sources are toolchain's src/cliMain/kotlin, the PyPI launcher src/cliMain/python, tests src/cliTest/kotlin (as pypackpack's CLI, pypackpack #74). The cli source sets depend on packpack and coroutines only and never extend main's configurations, which carry the Gradle API through java-gradle-plugin; the plugin publication carries no tcl, and test.yml checks the jar and the POM. - toolchain/build.gradle.kts gets the cliTest task, runTcl (./gradlew :toolchain:runTcl --args=...), tclVersion 0.1.0 and the GraalVM native image (:toolchain:nativeCompile, same arguments as before). The plugin keeps rootProject.version. - test.yml runs :toolchain:test (job toolchain) and :toolchain:cliTest (job tcl); publish-pypi.yml reads tclVersion and builds :toolchain:nativeCompile; build_wheel.py and pyproject's package-dir point at toolchain/src/cliMain/python. PyPI name toolchain-lite and the command tcl are unchanged. Closes #78 --- .github/scripts/pypi/build_wheel.py | 6 +- .github/workflows/publish-pypi.yml | 16 +-- .github/workflows/test-ios.yml | 4 +- .github/workflows/test.yml | 35 ++++-- AGENTS.md | 17 ++- PROJECT.md | 8 +- README.md | 8 +- docs/SPEC.md | 20 ++-- docs/guide/getting-started.html | 2 +- docs/guide/guide-tcl.html | 2 +- docs/guide/status.html | 4 +- docs/locale/README_ko.md | 8 +- pyproject.toml | 4 +- sample/build.gradle.kts | 2 +- sample/gradle/libs.versions.toml | 4 +- sample/settings.gradle.kts | 2 +- settings.gradle.kts | 3 +- tcl/build.gradle.kts | 86 --------------- .../build.gradle.kts | 101 ++++++++++++++++++ .../python/multiplatform/tcl/BuildInfo.kt | 2 +- .../python/multiplatform/tcl/Cli.kt | 0 .../python/multiplatform/tcl/Installer.kt | 2 +- .../src/cliMain}/python/__init__.py | 0 .../src/cliMain}/python/__main__.py | 0 .../python/multiplatform/tcl/CliArgsTest.kt | 2 +- .../python/multiplatform/tcl/InstallerTest.kt | 2 +- .../multiplatform/toolchain/BuildFeatures.kt | 0 .../multiplatform/toolchain/EmbedLevel.kt | 0 .../toolchain/PublishedPythonVersion.kt | 0 .../toolchain/PythonMultiplatformPlugin.kt | 0 .../multiplatform/toolchain/PythonPlugin.kt | 2 +- .../toolchain/PythonVersionAgreement.kt | 0 .../toolchain/TargetDependencies.kt | 0 .../bundle/AssemblePythonPackageTask.kt | 0 .../bundle/BuildPythonArtifactTask.kt | 0 .../toolchain/bundle/PythonLocalLoader.kt | 0 .../StagePythonBundleIosForXcodeTask.kt | 0 .../toolchain/bundle/StagePythonBundleTask.kt | 0 .../toolchain/dependency/DependencyType.kt | 0 .../lang/kotlin/decompileKotlinMeta.kt | 0 .../lang/python/InstallDependenciesTask.kt | 0 .../python/InstallTargetDependenciesTask.kt | 0 .../dependency/lang/python/KlibDepens.kt | 0 .../dependency/lang/python/PipRepositories.kt | 0 .../toolchain/dependency/reslover.kt | 0 .../multiplatform/toolchain/dsl/DSLBuild.kt | 0 .../multiplatform/toolchain/dsl/DSLCore.kt | 0 .../multiplatform/toolchain/dsl/DSLFlavors.kt | 0 .../toolchain/dsl/DSLPackaging.kt | 0 .../toolchain/dsl/DSLPlatforms.kt | 0 .../multiplatform/toolchain/dsl/PythonSdk.kt | 0 .../toolchain/dsl/PythonVersion.kt | 0 .../hotreload/CodePushPendingTask.kt | 0 .../toolchain/hotreload/HotReloadConfig.kt | 0 .../toolchain/hotreload/HotReloadPushTask.kt | 0 .../toolchain/hotreload/WatchAndPush.kt | 0 .../typedpython/TypedpythonCheckTask.kt | 0 .../toolchain/BuildFeaturesTest.kt | 0 .../multiplatform/toolchain/EmbedLevelTest.kt | 0 .../toolchain/PublishedPythonVersionTest.kt | 0 .../toolchain/PythonPluginApplyTest.kt | 0 .../toolchain/PythonPluginAttachmentTest.kt | 0 .../PythonPluginBuildFeaturesTest.kt | 0 .../toolchain/PythonPluginBuildLevelTest.kt | 0 .../toolchain/PythonPluginBuildTypeTest.kt | 0 .../toolchain/PythonPluginCodePushTest.kt | 0 .../toolchain/PythonPluginDependencyTest.kt | 0 .../toolchain/PythonPluginEmbedLevelTest.kt | 0 .../toolchain/PythonPluginFlavorsTest.kt | 0 .../toolchain/PythonPluginHotReloadTest.kt | 0 .../PythonPluginIntegrationListTest.kt | 0 .../PythonPluginPayloadVersionTest.kt | 0 .../toolchain/PythonPluginPlatformsTest.kt | 0 .../toolchain/PythonPluginPythonOnlyTest.kt | 0 .../toolchain/PythonPluginSourceSetTest.kt | 0 .../toolchain/PythonPluginStagingTest.kt | 0 .../PythonPluginTargetDependenciesTest.kt | 0 .../toolchain/PythonPluginVariantGraphTest.kt | 0 .../toolchain/PythonVersionAgreementTest.kt | 0 .../toolchain/SourceSetGettingDelegateTest.kt | 0 .../toolchain/TargetDependenciesTest.kt | 0 .../bundle/AssemblePythonPackageTaskTest.kt | 0 .../bundle/BuildPythonArtifactTaskTest.kt | 0 .../bundle/BuildPythonSdkRejectionTest.kt | 0 .../toolchain/bundle/BytecodeLevelTest.kt | 0 .../StagePythonBundleIosForXcodeTaskTest.kt | 0 .../bundle/StagePythonBundleTaskTest.kt | 0 .../python/InstallDependenciesTaskTest.kt | 0 .../InstallTargetDependenciesTaskTest.kt | 0 .../dependency/lang/python/KlibDepensTest.kt | 0 .../lang/python/PipRepositoriesTest.kt | 0 .../dsl/PlatformTargetMappingTest.kt | 0 .../dsl/PythonExtensionPlatformDslTest.kt | 0 .../toolchain/dsl/PythonSdkTest.kt | 0 .../toolchain/dsl/PythonVersionTest.kt | 0 .../toolchain/hotreload/WatchAndPushTest.kt | 0 .../typedpython/TypedpythonCheckTaskTest.kt | 2 +- .../typedpython/TypedpythonDecisionsTest.kt | 0 .../typedpython/TypedpythonNoWheelTest.kt | 0 99 files changed, 186 insertions(+), 158 deletions(-) delete mode 100644 tcl/build.gradle.kts rename {toolchain-gradle-plugin => toolchain}/build.gradle.kts (56%) rename {tcl/src/main => toolchain/src/cliMain}/kotlin/org/thisisthepy/python/multiplatform/tcl/BuildInfo.kt (94%) rename {tcl/src/main => toolchain/src/cliMain}/kotlin/org/thisisthepy/python/multiplatform/tcl/Cli.kt (100%) rename {tcl/src/main => toolchain/src/cliMain}/kotlin/org/thisisthepy/python/multiplatform/tcl/Installer.kt (97%) rename {tcl/src/main => toolchain/src/cliMain}/python/__init__.py (100%) rename {tcl/src/main => toolchain/src/cliMain}/python/__main__.py (100%) rename {tcl/src/test => toolchain/src/cliTest}/kotlin/org/thisisthepy/python/multiplatform/tcl/CliArgsTest.kt (97%) rename {tcl/src/test => toolchain/src/cliTest}/kotlin/org/thisisthepy/python/multiplatform/tcl/InstallerTest.kt (96%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/BuildFeatures.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/EmbedLevel.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PublishedPythonVersion.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonMultiplatformPlugin.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPlugin.kt (99%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonVersionAgreement.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/TargetDependencies.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/AssemblePythonPackageTask.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/BuildPythonArtifactTask.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/PythonLocalLoader.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/StagePythonBundleIosForXcodeTask.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/StagePythonBundleTask.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/DependencyType.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/kotlin/decompileKotlinMeta.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/InstallDependenciesTask.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/InstallTargetDependenciesTask.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/KlibDepens.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/PipRepositories.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/reslover.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/DSLBuild.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/DSLCore.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/DSLFlavors.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/DSLPackaging.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/DSLPlatforms.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/PythonSdk.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/PythonVersion.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/hotreload/CodePushPendingTask.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/hotreload/HotReloadConfig.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/hotreload/HotReloadPushTask.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/hotreload/WatchAndPush.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/typedpython/TypedpythonCheckTask.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/BuildFeaturesTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/EmbedLevelTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PublishedPythonVersionTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginApplyTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginAttachmentTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginBuildFeaturesTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginBuildLevelTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginBuildTypeTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginCodePushTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginDependencyTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginEmbedLevelTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginFlavorsTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginHotReloadTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginIntegrationListTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginPayloadVersionTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginPlatformsTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginPythonOnlyTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginSourceSetTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginStagingTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginTargetDependenciesTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPluginVariantGraphTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonVersionAgreementTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/SourceSetGettingDelegateTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/TargetDependenciesTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/AssemblePythonPackageTaskTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/BuildPythonArtifactTaskTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/BuildPythonSdkRejectionTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/BytecodeLevelTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/StagePythonBundleIosForXcodeTaskTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/StagePythonBundleTaskTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/InstallDependenciesTaskTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/InstallTargetDependenciesTaskTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/KlibDepensTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dependency/lang/python/PipRepositoriesTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/PlatformTargetMappingTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/PythonExtensionPlatformDslTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/PythonSdkTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/dsl/PythonVersionTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/hotreload/WatchAndPushTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/typedpython/TypedpythonCheckTaskTest.kt (99%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/typedpython/TypedpythonDecisionsTest.kt (100%) rename {toolchain-gradle-plugin => toolchain}/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/typedpython/TypedpythonNoWheelTest.kt (100%) diff --git a/.github/scripts/pypi/build_wheel.py b/.github/scripts/pypi/build_wheel.py index f90bda4..b59f0a1 100644 --- a/.github/scripts/pypi/build_wheel.py +++ b/.github/scripts/pypi/build_wheel.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 """Assemble the platform wheel that carries the native `tcl` binary (publish-pypi.yml). - python3 .github/scripts/pypi/build_wheel.py --binary tcl/build/native/nativeCompile/tcl --out dist + python3 .github/scripts/pypi/build_wheel.py --binary toolchain/build/native/nativeCompile/tcl --out dist Adapted from pypackpack's .github/scripts/pypi/build_wheel.py (pypackpack #53). The wheel holds: - toolchain_lite/__init__.py, __main__.py tcl/src/main/python/ (`python -m toolchain_lite`) + toolchain_lite/__init__.py, __main__.py toolchain/src/cliMain/python/ (`python -m toolchain_lite`) toolchain_lite-.data/scripts/tcl the native binary (tcl.exe on Windows) dist-info: METADATA from pyproject.toml's [project], LICENSE, WHEEL, RECORD @@ -26,7 +26,7 @@ from pathlib import Path ROOT = Path(__file__).resolve().parents[3] -PACKAGE_SRC = ROOT / "tcl/src/main/python" +PACKAGE_SRC = ROOT / "toolchain/src/cliMain/python" IMPORT_NAME = "toolchain_lite" BINARY = "tcl" # glibc's own libraries plus libz, which every glibc distribution ships and Native Image links. diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 39b337a..3c3973e 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -23,8 +23,8 @@ on: - .github/scripts/pypi/** - pyproject.toml - docs/pypi/** - - tcl/src/main/python/** - - tcl/build.gradle.kts + - toolchain/src/cliMain/** + - toolchain/build.gradle.kts workflow_dispatch: permissions: @@ -48,10 +48,10 @@ jobs: IS_RELEASE: ${{ github.event_name == 'release' }} run: | py=$(python3 -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])') - cli=$(sed -n 's/^version = "\(.*\)"$/\1/p' tcl/build.gradle.kts) - echo "pyproject.toml: $py tcl/build.gradle.kts: $cli tag: ${TAG:-none (dry run)}" - if [ "$py" != "$cli" ]; then - echo "::error::pyproject.toml ($py) and tcl/build.gradle.kts ($cli) must carry the same version"; exit 1 + cli=$(sed -n 's/^val tclVersion = "\(.*\)"$/\1/p' toolchain/build.gradle.kts) + echo "pyproject.toml: $py toolchain/build.gradle.kts tclVersion: $cli tag: ${TAG:-none (dry run)}" + if [ -z "$cli" ] || [ "$py" != "$cli" ]; then + echo "::error::pyproject.toml ($py) and toolchain/build.gradle.kts tclVersion ($cli) must carry the same version"; exit 1 fi if [ "$IS_RELEASE" = true ] && [ "$TAG" != "toolchain-lite-v$py" ]; then echo "::error::release tag '$TAG' is not 'toolchain-lite-v$py'"; exit 1 @@ -100,11 +100,11 @@ jobs: run: ./gradlew --no-daemon :packpack:publishToMavenLocal - name: Native image - run: ./gradlew --no-daemon :tcl:nativeCompile + run: ./gradlew --no-daemon :toolchain:nativeCompile - name: Wheel run: | - exe=tcl/build/native/nativeCompile/tcl + exe=toolchain/build/native/nativeCompile/tcl [ "$RUNNER_OS" = Windows ] && exe="$exe.exe" python .github/scripts/pypi/build_wheel.py --binary "$exe" --out dist diff --git a/.github/workflows/test-ios.yml b/.github/workflows/test-ios.yml index 8f4f752..82a9ca6 100644 --- a/.github/workflows/test-ios.yml +++ b/.github/workflows/test-ios.yml @@ -12,7 +12,7 @@ on: - "sample/src/iosMain/**" - "sample/build.gradle.kts" - "sample/gradle/**" - - "toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/**" + - "toolchain/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/bundle/**" - ".github/workflows/test-ios.yml" workflow_dispatch: @@ -53,7 +53,7 @@ jobs: - name: Publish packpack, the toolchain plugin and python-multiplatform (iOS simulator) run: | (cd .ci/pypackpack && ./gradlew :packpack:publishToMavenLocal --console=plain) - ./gradlew :toolchain-gradle-plugin:publishToMavenLocal --console=plain + ./gradlew :toolchain:publishToMavenLocal --console=plain (cd .ci/python-multiplatform && ./gradlew \ :python-multiplatform:publishIosSimulatorArm64PublicationToMavenLocal \ :python-multiplatform:publishKotlinMultiplatformPublicationToMavenLocal \ diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 354874e..33cd036 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,13 +22,17 @@ concurrency: jobs: test: - name: ${{ matrix.module }} + name: ${{ matrix.name }} runs-on: ubuntu-latest timeout-minutes: 40 strategy: fail-fast: false matrix: - module: [toolchain-gradle-plugin, tcl] + # Both live in :toolchain (#78): the plugin's `test` and tcl's `cliTest`, run as separate + # invocations (AGENTS.md section 8). + include: + - { name: toolchain, task: test } + - { name: tcl, task: cliTest } steps: - uses: actions/checkout@v4 @@ -55,8 +59,19 @@ jobs: working-directory: .ci/pypackpack run: ./gradlew :packpack:publishToMavenLocal --console=plain - - name: Test ${{ matrix.module }} - run: ./gradlew :${{ matrix.module }}:test --rerun --console=plain + - name: Test ${{ matrix.name }} + run: ./gradlew :toolchain:${{ matrix.task }} --rerun --console=plain + + - name: The plugin's publication carries no tcl + if: matrix.task == 'test' + run: | + ./gradlew :toolchain:jar :toolchain:generatePomFileForPluginMavenPublication --console=plain + jar=$(ls toolchain/build/libs/toolchain-*.jar | grep -v -- '-sources\|-javadoc' | head -1) + pom=toolchain/build/publications/pluginMaven/pom-default.xml + ls -la "$jar" "$pom" + if unzip -l "$jar" | grep -n "multiplatform/tcl/\|toolchain_lite"; then echo "::error::tcl classes are in the plugin jar (#78)"; exit 1; fi + if grep -n "tcl\|toolchain-lite\|clikt" "$pom"; then echo "::error::the plugin POM depends on tcl (#78)"; exit 1; fi + echo "plugin jar and POM carry no tcl" - name: Count results if: always() @@ -64,7 +79,7 @@ jobs: python3 - <<'PY' import glob, re t = f = e = s = 0 - for x in glob.glob("${{ matrix.module }}/build/test-results/test/*.xml"): + for x in glob.glob("toolchain/build/test-results/${{ matrix.task }}/*.xml"): m = re.search(r'tests="(\d+)" skipped="(\d+)" failures="(\d+)" errors="(\d+)"', open(x).read(4000)) t += int(m[1]); s += int(m[2]); f += int(m[3]); e += int(m[4]) print(f"tests={t} skipped={s} failures={f} errors={e}") @@ -74,8 +89,8 @@ jobs: if: failure() uses: actions/upload-artifact@v4 with: - name: ${{ matrix.module }}-test-reports - path: ${{ matrix.module }}/build/reports/tests/test/ + name: ${{ matrix.name }}-test-reports + path: toolchain/build/reports/tests/${{ matrix.task }}/ # 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 @@ -112,7 +127,7 @@ jobs: - name: Publish packpack, the toolchain plugin and python-multiplatform to mavenLocal run: | (cd .ci/pypackpack && ./gradlew :packpack:publishToMavenLocal --console=plain) - ./gradlew :toolchain-gradle-plugin:publishToMavenLocal --console=plain + ./gradlew :toolchain:publishToMavenLocal --console=plain # Targeted, not publishAllToMavenLocal: that currently fails on the Android release Java # resources (generatePythonProperties has no declared consumer; reported to the lead). (cd .ci/python-multiplatform && ./gradlew \ @@ -169,7 +184,7 @@ jobs: - name: Publish packpack, the toolchain plugin and python-multiplatform to mavenLocal run: | (cd .ci/pypackpack && ./gradlew :packpack:publishToMavenLocal --console=plain) - ./gradlew :toolchain-gradle-plugin:publishToMavenLocal --console=plain + ./gradlew :toolchain:publishToMavenLocal --console=plain # Targeted, not publishAllToMavenLocal: that currently fails on the Android release Java # resources (generatePythonProperties has no declared consumer; reported to the lead). (cd .ci/python-multiplatform && ./gradlew \ @@ -230,7 +245,7 @@ jobs: - name: Publish packpack, the toolchain plugin and python-multiplatform (Android too) run: | (cd .ci/pypackpack && ./gradlew :packpack:publishToMavenLocal --console=plain) - ./gradlew :toolchain-gradle-plugin:publishToMavenLocal --console=plain + ./gradlew :toolchain:publishToMavenLocal --console=plain (cd .ci/python-multiplatform && ./gradlew \ :python-multiplatform:publishAndroidReleasePublicationToMavenLocal \ :python-multiplatform:publishKotlinMultiplatformPublicationToMavenLocal \ diff --git a/AGENTS.md b/AGENTS.md index 6ee10da..a609f58 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -178,15 +178,14 @@ GitHub issues `thisisthepy/toolchain#2` (plugin checklist) and `#1` (`tcl`), it | Module | What it is | |---|---| -| `toolchain-gradle-plugin/` (`:toolchain-gradle-plugin`) | The Gradle plugin (id `org.thisisthepy.python.multiplatform`, class `PythonPlugin`). DSL in `dsl/`, tasks in `bundle/`, `dependency/`, `hotreload/`. | -| `tcl/` (`:tcl`) | toolchain-lite, a CLI application (`tcl install `). | +| `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 ` 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/`. | | `.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 wheel per OS carrying the native `tcl` binary, plus the launcher package `toolchain_lite` -(`tcl/src/main/python/`). `.github/workflows/publish-pypi.yml` builds, smoke-tests and uploads the -wheels; its version must equal `tcl/build.gradle.kts`'s, and `docs/pypi/README.md` is its PyPI page. +(`toolchain/src/cliMain/python/`). `.github/workflows/publish-pypi.yml` builds, smoke-tests and uploads the +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 @@ -235,7 +234,7 @@ history removing. - `-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 - `includeBuild`. After changing `:toolchain-gradle-plugin`, run `:toolchain-gradle-plugin:publishToMavenLocal` before building + `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 1.11.1, AGP 8.10.1, Gradle 8.11.1), because a Kotlin 2.1 compiler cannot read python-multiplatform's @@ -252,10 +251,10 @@ Prerequisites: `org.thisisthepy.python.multiplatform:packpack:0.1.0` in `~/.m2` Run each module separately (rule 8), with output to a file under `.tmp/`: ```bash -rm -rf toolchain-gradle-plugin/build/test-results -./gradlew :toolchain-gradle-plugin:test --rerun --console=plain > .tmp/toolchain-test.log 2>&1; echo "EXIT=$?" -rm -rf tcl/build/test-results -./gradlew :tcl:test --rerun --console=plain > .tmp/tcl-test.log 2>&1; echo "EXIT=$?" +rm -rf toolchain/build/test-results +./gradlew :toolchain:test --rerun --console=plain > .tmp/toolchain-test.log 2>&1; echo "EXIT=$?" +rm -rf toolchain/build/test-results/cliTest +./gradlew :toolchain:cliTest --rerun --console=plain > .tmp/tcl-test.log 2>&1; echo "EXIT=$?" ``` Count results from `/build/test-results/test/*.xml`, not from the log. diff --git a/PROJECT.md b/PROJECT.md index 7d35331..6ad2bc2 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -98,7 +98,7 @@ Python 코드와 의존성이 실려 빌드·실행되는 상태. 기한을 맞 ``` toolchain/ Gradle 플러그인 (dsl/, bundle/, dependency/, hotreload/) -tcl/ toolchain-lite CLI (`:tcl`) +toolchain/src/cliMain, src/cliTest toolchain-lite CLI `tcl` (`:toolchain` 의 별도 소스셋, #78) sample/ 별도 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) @@ -109,9 +109,9 @@ docs/ INTENT.md, SPEC.md, locale/, guide/ (GitHub Pages) 전제: `~/.m2` 에 `packpack:0.1.0` (pypackpack 에서 `publishToMavenLocal`), `PATH` 에 `uv`, 네트워크. ```bash -./gradlew :toolchain-gradle-plugin:test --rerun --console=plain > .tmp/toolchain-test.log 2>&1; echo "EXIT=$?" -./gradlew :tcl:test --rerun --console=plain > .tmp/tcl-test.log 2>&1; echo "EXIT=$?" -./gradlew :toolchain-gradle-plugin:publishToMavenLocal # sample 빌드 전에 반드시 +./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 빌드 전에 반드시 python3 docs/guide/check_guide.py # 가이드 검사 # sample 은 별도 빌드: python-multiplatform 체크아웃에서 ./gradlew publishAllToMavenLocal 후 ./gradlew -p sample packagePython diff --git a/README.md b/README.md index 7578b0a..72139f8 100644 --- a/README.md +++ b/README.md @@ -53,14 +53,14 @@ owns the Gradle vocabulary in between. ```shell # in pypackpack: ./gradlew :packpack:publishToMavenLocal -./gradlew :toolchain-gradle-plugin:publishToMavenLocal +./gradlew :toolchain:publishToMavenLocal ``` | Coordinates | What | |---|---| | plugin id `org.thisisthepy.python.multiplatform` | the Gradle plugin; apply it by id (unchanged) | -| `org.thisisthepy.python.multiplatform:toolchain-gradle-plugin` | the plugin's implementation artifact (was `…:toolchain` before #62) | -| `org.thisisthepy.python.multiplatform:tcl` | the CLI, toolchain-lite (tcl) | +| `org.thisisthepy.python.multiplatform:toolchain` | the plugin's implementation artifact | +| 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: @@ -108,7 +108,7 @@ python { Python only? Skip Gradle builds entirely: ```shell -./gradlew :tcl:run --args="install pythonx-compose" +./gradlew :toolchain:runTcl --args="install pythonx-compose" ``` ## 🧭 Architecture at a glance diff --git a/docs/SPEC.md b/docs/SPEC.md index 8866ef4..a39cbc0 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -12,11 +12,11 @@ Each item carries a status: - **planned**: the example build file or an issue asks for it; the code does not do it (or only declares a DSL property that nothing reads). -Status was assigned on 2026-10-02 by reading `toolchain-gradle-plugin/src` (then `toolchain/src`), `tcl/src` and their tests. No status +Status was assigned on 2026-10-02 by reading `toolchain/src`, `tcl/src` (now `toolchain/src/cliMain`, #78) and their tests. No status here comes from a roadmap, a commit message or an issue checkbox. -Paths below are abbreviated: `plugin/` = `toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/`, -`ptest/` = the matching `toolchain-gradle-plugin/src/test/kotlin/...` directory, `tcl/` = `tcl/src/{main,test}/kotlin/org/thisisthepy/python/multiplatform/tcl/`. +Paths below are abbreviated: `plugin/` = `toolchain/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/`, +`ptest/` = the matching `toolchain/src/test/kotlin/...` directory, `tcl/` = `toolchain/src/{cliMain,cliTest}/kotlin/org/thisisthepy/python/multiplatform/tcl/`. --- @@ -24,9 +24,9 @@ Paths below are abbreviated: `plugin/` = `toolchain-gradle-plugin/src/main/kotli ### 1.1 Applying the plugin Plugin id `org.thisisthepy.python.multiplatform`, implementation class `PythonPlugin` -(`toolchain-gradle-plugin/build.gradle.kts` `gradlePlugin { }`). Applying it creates the `python` extension +(`toolchain/build.gradle.kts` `gradlePlugin { }`). Applying it creates the `python` extension (`PythonExtension`) and registers the tasks of §1.10–§1.15 and §1.18, all in group `python`. -Consumers resolve it from `mavenLocal()` after `./gradlew :toolchain-gradle-plugin:publishToMavenLocal`; the plugin +Consumers resolve it from `mavenLocal()` after `./gradlew :toolchain:publishToMavenLocal`; the plugin itself depends on `org.thisisthepy.python.multiplatform:packpack:0.1.0` from `mavenLocal()`. **Status: implemented**. `plugin/PythonPlugin.kt`; `ptest/PythonPluginApplyTest.kt` applies the @@ -378,7 +378,7 @@ Hand-off to the platform's packaging step: real `com.android.application` (AGP 8.5.2, test classpath only) and checks the staged root is a `main` asset source directory. Not tested: the `preBuild` / `merge*Assets` → `stagePythonBundleAndroid` dependency (AGP creates those tasks only when the project is evaluated against an Android SDK, - which `:toolchain-gradle-plugin:test` does not require), and the built APK. + which `:toolchain:test` does not require), and the built APK. - iOS: toolchain stages and exposes the path; python-multiplatform (#59) owns the Xcode build phase that attaches it. `stagePythonBundleIosForXcode` (group `python`) depends on `stagePythonBundleIos` and prints exactly one line `PYTHON_PAYLOAD_DIR=` (stdout, visible under `--quiet`). @@ -535,11 +535,11 @@ message on stderr for a missing argument, an unknown command or a failed install prints `Usage: tcl install `. **Status: implemented**. `tcl/Cli.kt`, `tcl/Installer.kt`; `tcl/CliArgsTest.kt`, -`tcl/InstallerTest.kt` (needs `uv` and network). Run with `./gradlew :tcl:run --args="install "`. +`tcl/InstallerTest.kt` (needs `uv` and network). Run with `./gradlew :toolchain:runTcl --args="install "`. ### 2.2 `tcl --help` and `tcl --version` `--help` (or `-h`) prints the usage line on stdout and exits 0. `--version` prints `tcl ` -and exits 0. The version is tcl's own (`tcl/build.gradle.kts`, 0.1.0), separate from the plugin's, +and exits 0. The version is tcl's own (`tclVersion` in `toolchain/build.gradle.kts`, 0.1.0), separate from the plugin's, written by the build into `build-info.properties` and read by `BuildInfo`. **Status: implemented**. `tcl/Cli.kt`, `tcl/BuildInfo.kt`; `tcl/CliArgsTest.kt` (the version test @@ -552,13 +552,13 @@ tcl --help ``` - One wheel per platform, `py3-none-`: Linux x86_64 and aarch64 (manylinux), macOS arm64 (11+), Windows x86_64. Each carries the GraalVM native image `tcl` as a script, plus the launcher - package `toolchain_lite` (`tcl/src/main/python/`) for `python -m toolchain_lite`. + package `toolchain_lite` (`toolchain/src/cliMain/python/`) for `python -m toolchain_lite`. - The platform tag is read from the binary (`.github/scripts/pypi/build_wheel.py`). No sdist: building needs a JDK and GraalVM. - `.github/workflows/publish-pypi.yml` runs on a published GitHub Release only and uploads through trusted publishing (environment `pypi`, which accepts only `toolchain-lite-v*` tags). Before uploading it requires the tag to be `toolchain-lite-v` (bare `v*` tags are the Gradle plugin's, #71), - `pyproject.toml` and `tcl/build.gradle.kts` to carry that version, the version to be new on PyPI, + `pyproject.toml` and `toolchain/build.gradle.kts`'s `tclVersion` to carry that version, the version to be new on PyPI, and every wheel to pass `smoke_wheel.py` in a fresh venv (`tcl --help`, `tcl --version`, `python -m toolchain_lite --version`, and `tcl install six` writing a `pyproject.toml`). Pull requests that touch these files run everything but the upload. diff --git a/docs/guide/getting-started.html b/docs/guide/getting-started.html index b5176d8..fcd9817 100644 --- a/docs/guide/getting-started.html +++ b/docs/guide/getting-started.html @@ -33,7 +33,7 @@

1. Prerequisites1. 준비 ./gradlew :packpack:publishToMavenLocal # in this repository -./gradlew :toolchain-gradle-plugin:publishToMavenLocal +./gradlew :toolchain:publishToMavenLocal

The plugin is pre-release and resolved from mavenLocal(). Add mavenLocal() to pluginManagement.repositories in your settings.gradle.kts.

플러그인은 출시 전이며 mavenLocal() 에서 받습니다. settings.gradle.kts 의 pluginManagement.repositories 에 mavenLocal() 을 추가하세요.

diff --git a/docs/guide/guide-tcl.html b/docs/guide/guide-tcl.html index a06ee02..ba9df2f 100644 --- a/docs/guide/guide-tcl.html +++ b/docs/guide/guide-tcl.html @@ -32,7 +32,7 @@

Install a package패키지 tcl install pythonx-compose tcl --version -./gradlew :tcl:run --args="install pythonx-compose" # from a checkout +./gradlew :toolchain:runTcl --args="install pythonx-compose" # from a checkout

tcl install <package> looks for a pyproject.toml in the current directory and its parents. If there is none it runs uv init --bare first; then it adds the package with uv add, through the same pypackpack backend the Gradle plugin uses.

tcl install <package> 는 현재 디렉터리와 그 상위에서 pyproject.toml 을 찾습니다. 없으면 먼저 uv init --bare 를 실행하고, Gradle 플러그인과 같은 pypackpack 백엔드로 uv add 를 해서 패키지를 추가합니다.

Invocation호출Result결과
tcl install pkgexit 0, uv output on stdout종료 코드 0, stdout 에 uv 출력
tclexit 1, Usage: tcl install <package>종료 코드 1, Usage: tcl install <package>
tcl installexit 1, missing argument종료 코드 1, 인자 누락
tcl otherexit 1, unknown command종료 코드 1, 알 수 없는 명령

install is the only command, matching issue #1. tcl needs uv on PATH and network access to PyPI.

명령은 이슈 #1 그대로 install 하나뿐입니다. tcl 은 PATH 의 uv 와 PyPI 네트워크 접근이 필요합니다.

diff --git a/docs/guide/status.html b/docs/guide/status.html index bd58f5a..b6223cc 100644 --- a/docs/guide/status.html +++ b/docs/guide/status.html @@ -47,8 +47,8 @@

Is the DSL final?DSL 은

Contributing기여하기

Changes go intent → spec → test → code. A behaviour change begins as a change to the specification; its test is written first and seen failing; then the code makes it pass. A DSL value is never accepted silently: it is wired to a task, or rejected with a reason.

변경은 의도 → 스펙 → 테스트 → 코드 순서로 갑니다. 동작 변경은 스펙 변경에서 시작하고, 테스트를 먼저 써서 실패를 확인한 뒤, 코드로 통과시킵니다. DSL 값은 조용히 받아들이지 않습니다: 태스크에 연결하거나, 이유와 함께 거부합니다.

-
./gradlew :toolchain-gradle-plugin:test --rerun     # plugin; needs packpack in Maven Local, uv and network
-./gradlew :tcl:test --rerun           # tcl; needs uv and network
+
./gradlew :toolchain:test --rerun     # plugin; needs packpack in Maven Local, uv and network
+./gradlew :toolchain:cliTest --rerun  # tcl; needs uv and network
 python3 docs/guide/check_guide.py     # this site

Run each module on its own, and count results from build/test-results/ rather than from the console.

모듈은 따로따로 실행하고, 결과는 콘솔이 아니라 build/test-results/ 에서 세세요.

diff --git a/docs/locale/README_ko.md b/docs/locale/README_ko.md index ddd7d39..8086994 100644 --- a/docs/locale/README_ko.md +++ b/docs/locale/README_ko.md @@ -52,14 +52,14 @@ Python 을 내장한 Kotlin Multiplatform 앱에는 맞물려 돌아가야 하 ```shell # pypackpack 에서: ./gradlew :packpack:publishToMavenLocal -./gradlew :toolchain-gradle-plugin:publishToMavenLocal +./gradlew :toolchain:publishToMavenLocal ``` | 좌표 | 무엇 | |---|---| | 플러그인 id `org.thisisthepy.python.multiplatform` | Gradle 플러그인; id 로 적용 (변경 없음) | -| `org.thisisthepy.python.multiplatform:toolchain-gradle-plugin` | 플러그인 구현 아티팩트 (#62 이전에는 `…:toolchain`) | -| `org.thisisthepy.python.multiplatform:tcl` | CLI, toolchain-lite (tcl) | +| `org.thisisthepy.python.multiplatform:toolchain` | 플러그인 구현 아티팩트 | +| PyPI `toolchain-lite` | Python 사용자를 위한 명령줄 도구 `tcl` (`uv tool install toolchain-lite`) | Kotlin Multiplatform 옆에 플러그인을 적용하고 `pypackpack` 패키지를 가리키게 합니다. 아래는 `sample/build.gradle.kts` 를 줄인 것입니다. @@ -107,7 +107,7 @@ python { Python 만 쓰나요? Gradle 빌드 없이: ```shell -./gradlew :tcl:run --args="install pythonx-compose" +./gradlew :toolchain:runTcl --args="install pythonx-compose" ``` ## 🧭 한눈에 보는 구조 diff --git a/pyproject.toml b/pyproject.toml index 997c6fa..f871f0a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ # toolchain-lite, the PyPI distribution of `tcl`. The wheel carries the native `tcl` command line # (GraalVM Native Image) and is assembled per platform by .github/scripts/pypi/build_wheel.py in -# publish-pypi.yml, which reads [project] below. Its version must equal tcl/build.gradle.kts's: the +# publish-pypi.yml, which reads [project] below. Its version must equal toolchain/build.gradle.kts's tclVersion: the # workflow refuses to publish unless the installed binary prints it. # # Installing from source (`uv pip install .`) gives only the Python package, without the binary; @@ -40,5 +40,5 @@ Repository = "https://github.com/thisisthepy/toolchain" Issues = "https://github.com/thisisthepy/toolchain/issues" [tool.setuptools] -package-dir = { toolchain_lite = "tcl/src/main/python" } +package-dir = { toolchain_lite = "toolchain/src/cliMain/python" } packages = ["toolchain_lite"] diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts index d78d0de..34b8c40 100644 --- a/sample/build.gradle.kts +++ b/sample/build.gradle.kts @@ -14,7 +14,7 @@ plugins { alias(libs.plugins.compose.multiplatform) alias(libs.plugins.compose.compiler) // sample is its own build (settings.gradle.kts here). The toolchain plugin is resolved by - // coordinate from mavenLocal (`./gradlew :toolchain-gradle-plugin:publishToMavenLocal` in ../), as is + // coordinate from mavenLocal (`./gradlew :toolchain:publishToMavenLocal` in ../), as is // python-multiplatform's bindings plugin, which stages and packages the CPython stdlib (#22). alias(libs.plugins.toolchain) alias(libs.plugins.python.multiplatform.bindings) diff --git a/sample/gradle/libs.versions.toml b/sample/gradle/libs.versions.toml index c11606c..25d1651 100644 --- a/sample/gradle/libs.versions.toml +++ b/sample/gradle/libs.versions.toml @@ -1,6 +1,6 @@ # sample/ is its own build (toolchain#22): a consumer of python-multiplatform, so it uses # python-multiplatform's toolchain (its gradle/libs.versions.toml), not the toolchain plugin's. -# The plugin itself (../toolchain-gradle-plugin) stays on its own Kotlin for its consumers. +# The plugin itself (../toolchain) stays on its own Kotlin for its consumers. [versions] agp = "8.10.1" android-compileSdk = "36" @@ -14,7 +14,7 @@ kotlinx-coroutines = "1.10.1" # python-multiplatform's coordinates today (its #70 will unify the plugin's version with the library's). python-multiplatform = "3.14.7-alpha01" python-multiplatform-plugin = "3.13.0" -# The toolchain plugin, from ../toolchain-gradle-plugin via mavenLocal (`./gradlew :toolchain-gradle-plugin:publishToMavenLocal`). +# The toolchain plugin, from ../toolchain via mavenLocal (`./gradlew :toolchain:publishToMavenLocal`). toolchain = "1.0.0-alpha" [libraries] diff --git a/sample/settings.gradle.kts b/sample/settings.gradle.kts index 20191c3..0a3439c 100644 --- a/sample/settings.gradle.kts +++ b/sample/settings.gradle.kts @@ -1,6 +1,6 @@ // sample/ is its own Gradle build (toolchain#22, #62), run with the repository's wrapper: // ./gradlew -p sample …. It consumes python-multiplatform, which is built with Kotlin 2.4, so it uses -// that toolchain (gradle/libs.versions.toml here); the plugin in ../toolchain-gradle-plugin stays on +// that toolchain (gradle/libs.versions.toml here); the plugin in ../toolchain stays on // its own Kotlin for its consumers. Both plugins and the // python-multiplatform library come from mavenLocal: publish them first (see AGENTS.md §16). pluginManagement { diff --git a/settings.gradle.kts b/settings.gradle.kts index 730cc1c..50894b3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -31,7 +31,6 @@ dependencyResolutionManagement { rootProject.name = "ToolchainProject" -include(":toolchain-gradle-plugin") +include(":toolchain") // sample/ is its own build, run with this wrapper: ./gradlew -p sample …. It uses python-multiplatform's // Kotlin; the plugin here stays on its own for its consumers (toolchain#22, #62). -include(":tcl") diff --git a/tcl/build.gradle.kts b/tcl/build.gradle.kts deleted file mode 100644 index 4ca4f03..0000000 --- a/tcl/build.gradle.kts +++ /dev/null @@ -1,86 +0,0 @@ -group = rootProject.group -// tcl's own version, which `tcl --version` prints and the toolchain-lite wheel carries: -// publish-pypi.yml refuses to publish unless it equals pyproject.toml's. The plugin keeps -// rootProject.version. -version = "0.1.0" - -plugins { - // No explicit version: `org.jetbrains.kotlin.jvm` is already on this settings' shared plugin - // classpath (pulled in transitively by the other subprojects' Kotlin Multiplatform/`kotlin-dsl` - // plugins), and Gradle rejects a second, explicitly-versioned request for a plugin ID it has - // already resolved ("plugin is already on the classpath with an unknown version"). - id("org.jetbrains.kotlin.jvm") - application - id("org.graalvm.buildtools.native") version "0.10.6" -} - -dependencies { - implementation(libs.kotlinx.coroutines.core) - - // `tcl` ("toolchain-lite", GitHub issue thisisthepy/toolchain#1) is a thin CLI layer over - // `pypackpack`'s dependency backend -- the same artifact `:toolchain-gradle-plugin` already resolves through - // `mavenLocal()` (see that module's `build.gradle.kts` for the `publishToMavenLocal` - // prerequisite this also depends on). - implementation("org.thisisthepy.python.multiplatform:packpack:0.1.0") - - testImplementation(libs.kotlin.test) - testImplementation(libs.kotlin.test.junit) -} - -application { - // `main()` lives in `Cli.kt`, so its Kotlin-generated facade class is `CliKt`, not `MainKt`. - mainClass.set("org.thisisthepy.python.multiplatform.tcl.CliKt") -} - -// `packpack` is built with Kotlin 2.3.0 while this module's own `compileKotlin` resolves to -// Kotlin 2.1.0 (whichever Kotlin Gradle Plugin version wins the shared plugin classpath across -// this settings). Same escape hatch `:toolchain-gradle-plugin/build.gradle.kts` already uses for the same -// mismatch, for the same reason: this only relaxes the metadata-format version gate, not -// language/API level compatibility. -tasks.withType().configureEach { - compilerOptions.freeCompilerArgs.add("-Xskip-metadata-version-check") -} - -tasks.test { - systemProperty("tcl.expectedVersion", project.version.toString()) -} - -// `BuildInfo` reads this resource, so the version is not a literal in the source. -val generateBuildInfo by tasks.registering { - val tclVersion = project.version.toString() - val outputDir = layout.buildDirectory.dir("generated/build-info") - inputs.property("version", tclVersion) - outputs.dir(outputDir) - doLast { - val file = outputDir.get().file("org/thisisthepy/python/multiplatform/tcl/build-info.properties").asFile - file.parentFile.mkdirs() - file.writeText("version=$tclVersion\n") - } -} -sourceSets.main { resources.srcDir(generateBuildInfo) } - -// The native `tcl` binary that the toolchain-lite wheel carries (publish-pypi.yml). Same settings -// as pypackpack's CLI, which this links: io.ktor reaches org.slf4j (slf4j-nop, no I/O at init), -// which GraalVM 21 otherwise refuses to initialize at build time. -graalvmNative { - binaries { - named("main") { - imageName.set("tcl") - mainClass.set("org.thisisthepy.python.multiplatform.tcl.CliKt") - buildArgs.addAll( - "--no-fallback", - "--install-exit-handlers", - "--initialize-at-build-time=kotlin,kotlinx.coroutines,io.ktor,kotlinx.io,org.slf4j", - "-H:+AddAllCharsets", - "--gc=serial", - ) - if (System.getProperty("os.name").lowercase().contains("mac")) { - // Without this the binary's minimum macOS is the build host's. - buildArgs.add("-H:NativeLinkerOption=-mmacosx-version-min=11.0") - } - resources.autodetect() - } - } - // native-image comes from GRAALVM_HOME/JAVA_HOME, not a toolchain Gradle might pick. - toolchainDetection.set(false) -} diff --git a/toolchain-gradle-plugin/build.gradle.kts b/toolchain/build.gradle.kts similarity index 56% rename from toolchain-gradle-plugin/build.gradle.kts rename to toolchain/build.gradle.kts index ee20c67..7dd8896 100644 --- a/toolchain-gradle-plugin/build.gradle.kts +++ b/toolchain/build.gradle.kts @@ -6,6 +6,8 @@ plugins { `kotlin-dsl` `maven-publish` `java-gradle-plugin` + // The native `tcl` (toolchain-lite) that the PyPI wheels carry; see the cli source sets below. + id("org.graalvm.buildtools.native") version "0.10.6" } dependencies { @@ -102,3 +104,102 @@ publishing { mavenLocal() } } + +// --------------------------------------------------------------------------------------------- +// tcl (toolchain-lite): the command line for Python users, `tcl install ` +// +// It lives inside this module, in its own source sets (#78; it was a root `tcl/` module from +// 9560853 until then): `src/cliMain/kotlin`, the PyPI launcher package in `src/cliMain/python`, and +// tests in `src/cliTest/kotlin`. These source sets do not extend `main`'s configurations: through +// `java-gradle-plugin`, `main` carries the Gradle API and the Kotlin Gradle plugin, which tcl +// neither uses nor may drag into its native image. In the other direction, the plugin's +// publication is `main` alone, so it carries no tcl (test.yml checks the jar). +// +// `tclVersion` is tcl's own version, which `tcl --version` prints and the toolchain-lite wheel +// carries; publish-pypi.yml refuses to publish unless it equals pyproject.toml's. The plugin keeps +// rootProject.version. +val tclVersion = "0.1.0" +val tclMainClass = "org.thisisthepy.python.multiplatform.tcl.CliKt" + +val cliMain: SourceSet by sourceSets.creating +val cliTest: SourceSet by sourceSets.creating +cliTest.compileClasspath += cliMain.output +cliTest.runtimeClasspath += cliMain.output +configurations[cliTest.implementationConfigurationName].extendsFrom(configurations[cliMain.implementationConfigurationName]) +configurations[cliTest.runtimeOnlyConfigurationName].extendsFrom(configurations[cliMain.runtimeOnlyConfigurationName]) + +dependencies { + // A thin CLI over pypackpack's dependency backend: the same packpack artifact `main` resolves + // from mavenLocal (see this module's dependencies above). + "cliMainImplementation"(libs.kotlinx.coroutines.core) + "cliMainImplementation"("org.thisisthepy.python.multiplatform:packpack:0.1.0") + "cliTestImplementation"(libs.kotlin.test) + "cliTestImplementation"(libs.kotlin.test.junit) +} + +kotlin.target.compilations.getByName("cliTest").associateWith(kotlin.target.compilations.getByName("cliMain")) + +val cliTestTask = tasks.register("cliTest") { + description = "Runs tcl's tests (src/cliTest). InstallerTest needs uv and network access." + group = "verification" + testClassesDirs = cliTest.output.classesDirs + classpath = cliTest.runtimeClasspath + systemProperty("tcl.expectedVersion", tclVersion) +} +tasks.check { dependsOn(cliTestTask) } + +// `./gradlew :toolchain:runTcl --args="install "` +tasks.register("runTcl") { + description = "Runs tcl from source." + group = "application" + mainClass.set(tclMainClass) + classpath = cliMain.runtimeClasspath + workingDir = rootDir +} + +// `BuildInfo` reads this resource, so the version is not a literal in the source. +val generateBuildInfo by tasks.registering { + val version = tclVersion + val outputDir = layout.buildDirectory.dir("generated/build-info") + inputs.property("version", version) + outputs.dir(outputDir) + doLast { + val file = outputDir.get().file("org/thisisthepy/python/multiplatform/tcl/build-info.properties").asFile + file.parentFile.mkdirs() + file.writeText("version=$version\n") + } +} +cliMain.resources.srcDir(generateBuildInfo) + +val cliJar = tasks.register("cliJar") { + archiveBaseName.set("tcl") + archiveVersion.set(tclVersion) + from(cliMain.output) +} + +// The native `tcl` binary that the toolchain-lite wheel carries (publish-pypi.yml). The same +// settings as pypackpack's CLI, which tcl links: io.ktor reaches org.slf4j (slf4j-nop, no I/O at +// init), which GraalVM 21 otherwise refuses to initialize at build time. +graalvmNative { + binaries { + named("main") { + imageName.set("tcl") + mainClass.set(tclMainClass) + classpath.setFrom(cliJar, configurations[cliMain.runtimeClasspathConfigurationName]) + buildArgs.addAll( + "--no-fallback", + "--install-exit-handlers", + "--initialize-at-build-time=kotlin,kotlinx.coroutines,io.ktor,kotlinx.io,org.slf4j", + "-H:+AddAllCharsets", + "--gc=serial", + ) + if (System.getProperty("os.name").lowercase().contains("mac")) { + // Without this the binary's minimum macOS is the build host's. + buildArgs.add("-H:NativeLinkerOption=-mmacosx-version-min=11.0") + } + resources.autodetect() + } + } + // native-image comes from GRAALVM_HOME/JAVA_HOME, not a toolchain Gradle might pick. + toolchainDetection.set(false) +} diff --git a/tcl/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/BuildInfo.kt b/toolchain/src/cliMain/kotlin/org/thisisthepy/python/multiplatform/tcl/BuildInfo.kt similarity index 94% rename from tcl/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/BuildInfo.kt rename to toolchain/src/cliMain/kotlin/org/thisisthepy/python/multiplatform/tcl/BuildInfo.kt index ce71324..1f8d5d6 100644 --- a/tcl/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/BuildInfo.kt +++ b/toolchain/src/cliMain/kotlin/org/thisisthepy/python/multiplatform/tcl/BuildInfo.kt @@ -3,7 +3,7 @@ package org.thisisthepy.python.multiplatform.tcl import java.util.Properties /** - * Facts the build writes into tcl's resources (`generateBuildInfo` in `tcl/build.gradle.kts`). + * Facts the build writes into tcl's resources (`generateBuildInfo` in `toolchain/build.gradle.kts`). * * [version] is tcl's version, and so the toolchain-lite wheel's: `publish-pypi.yml` refuses to * publish unless the installed binary prints the version in `pyproject.toml` (docs/SPEC.md 2.2). diff --git a/tcl/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Cli.kt b/toolchain/src/cliMain/kotlin/org/thisisthepy/python/multiplatform/tcl/Cli.kt similarity index 100% rename from tcl/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Cli.kt rename to toolchain/src/cliMain/kotlin/org/thisisthepy/python/multiplatform/tcl/Cli.kt diff --git a/tcl/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Installer.kt b/toolchain/src/cliMain/kotlin/org/thisisthepy/python/multiplatform/tcl/Installer.kt similarity index 97% rename from tcl/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Installer.kt rename to toolchain/src/cliMain/kotlin/org/thisisthepy/python/multiplatform/tcl/Installer.kt index 38340b4..b483bfc 100644 --- a/tcl/src/main/kotlin/org/thisisthepy/python/multiplatform/tcl/Installer.kt +++ b/toolchain/src/cliMain/kotlin/org/thisisthepy/python/multiplatform/tcl/Installer.kt @@ -33,7 +33,7 @@ fun findProjectRoot(startDir: File): File? { * call the issue's usage example shows. * * Deliberately calls `dependency.backend.BackendInterface` directly -- the same layer - * `:toolchain-gradle-plugin`'s `InstallDependenciesTask.installWithPackpack` uses -- rather than the + * `:toolchain`'s `InstallDependenciesTask.installWithPackpack` uses -- rather than the * `dependency.frontend`/`middleware` layers `pypackpack`'s own CLI is built on. Those resolve the * project root from the JVM-global `user.dir` system property (`pypackpack`'s internal * `findProjectRoot()`); the backend layer takes `workingDir: File?` as an explicit parameter diff --git a/tcl/src/main/python/__init__.py b/toolchain/src/cliMain/python/__init__.py similarity index 100% rename from tcl/src/main/python/__init__.py rename to toolchain/src/cliMain/python/__init__.py diff --git a/tcl/src/main/python/__main__.py b/toolchain/src/cliMain/python/__main__.py similarity index 100% rename from tcl/src/main/python/__main__.py rename to toolchain/src/cliMain/python/__main__.py diff --git a/tcl/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/CliArgsTest.kt b/toolchain/src/cliTest/kotlin/org/thisisthepy/python/multiplatform/tcl/CliArgsTest.kt similarity index 97% rename from tcl/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/CliArgsTest.kt rename to toolchain/src/cliTest/kotlin/org/thisisthepy/python/multiplatform/tcl/CliArgsTest.kt index df09c21..f731768 100644 --- a/tcl/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/CliArgsTest.kt +++ b/toolchain/src/cliTest/kotlin/org/thisisthepy/python/multiplatform/tcl/CliArgsTest.kt @@ -93,7 +93,7 @@ class CliArgsTest { assertEquals(listOf("tcl ${BuildInfo.version}"), out) } - // cli/build.gradle.kts passes tcl's own version to the test JVM; publish-pypi.yml then checks it + // toolchain/build.gradle.kts passes `tclVersion` to the test JVM; publish-pypi.yml then checks it // against pyproject.toml, so it must not be a literal (docs/SPEC.md section 2.2). @Test fun `the version is the one the build declares`() { diff --git a/tcl/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/InstallerTest.kt b/toolchain/src/cliTest/kotlin/org/thisisthepy/python/multiplatform/tcl/InstallerTest.kt similarity index 96% rename from tcl/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/InstallerTest.kt rename to toolchain/src/cliTest/kotlin/org/thisisthepy/python/multiplatform/tcl/InstallerTest.kt index 9c344c6..a6f42cd 100644 --- a/tcl/src/test/kotlin/org/thisisthepy/python/multiplatform/tcl/InstallerTest.kt +++ b/toolchain/src/cliTest/kotlin/org/thisisthepy/python/multiplatform/tcl/InstallerTest.kt @@ -11,7 +11,7 @@ import kotlin.test.assertTrue * (toolchain-lite, `tcl install `): a python-only user may or may not already have a * `pypackpack` project at the target directory, and `install` must handle both. Uses real `uv` * and real network access (PyPI), the same tradeoff `InstallDependenciesTaskTest` - * (`:toolchain-gradle-plugin`) documents and accepts for the same reason: a fake backend would only prove this + * (`:toolchain`) documents and accepts for the same reason: a fake backend would only prove this * module calls the right function, not that the resulting `pyproject.toml` is real. * * Pre-implementation state: this file fails to compile (no `Installer`, no `installBlocking`, no diff --git a/toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/BuildFeatures.kt b/toolchain/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/BuildFeatures.kt similarity index 100% rename from toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/BuildFeatures.kt rename to toolchain/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/BuildFeatures.kt diff --git a/toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/EmbedLevel.kt b/toolchain/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/EmbedLevel.kt similarity index 100% rename from toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/EmbedLevel.kt rename to toolchain/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/EmbedLevel.kt diff --git a/toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PublishedPythonVersion.kt b/toolchain/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PublishedPythonVersion.kt similarity index 100% rename from toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PublishedPythonVersion.kt rename to toolchain/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PublishedPythonVersion.kt diff --git a/toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonMultiplatformPlugin.kt b/toolchain/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonMultiplatformPlugin.kt similarity index 100% rename from toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonMultiplatformPlugin.kt rename to toolchain/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonMultiplatformPlugin.kt diff --git a/toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPlugin.kt b/toolchain/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPlugin.kt similarity index 99% rename from toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPlugin.kt rename to toolchain/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPlugin.kt index 835879d..fab36ba 100644 --- a/toolchain-gradle-plugin/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPlugin.kt +++ b/toolchain/src/main/kotlin/org/thisisthepy/python/multiplatform/toolchain/PythonPlugin.kt @@ -628,7 +628,7 @@ private fun registerStagingTasks(project: Project): Map` (`toolchain-gradle-plugin/build.gradle.kts` forwards it as a system property). + * `-Ptypedpython.wheelDir=` (`toolchain/build.gradle.kts` forwards it as a system property). * Without it these tests are skipped with that reason -- CI has no wheel directory. * * Every test project lives under this module's `build/` (AGENTS.md §2), and so does the TestKit diff --git a/toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/typedpython/TypedpythonDecisionsTest.kt b/toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/typedpython/TypedpythonDecisionsTest.kt similarity index 100% rename from toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/typedpython/TypedpythonDecisionsTest.kt rename to toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/typedpython/TypedpythonDecisionsTest.kt diff --git a/toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/typedpython/TypedpythonNoWheelTest.kt b/toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/typedpython/TypedpythonNoWheelTest.kt similarity index 100% rename from toolchain-gradle-plugin/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/typedpython/TypedpythonNoWheelTest.kt rename to toolchain/src/test/kotlin/org/thisisthepy/python/multiplatform/toolchain/typedpython/TypedpythonNoWheelTest.kt From 7a85593a6de17ecc037f14e83386092e2f76b8b3 Mon Sep 17 00:00:00 2001 From: BREW <81044362+b-re-w@users.noreply.github.com> Date: Sun, 4 Oct 2026 01:23:02 +0900 Subject: [PATCH 12/12] Fix: Build tcl's native image as an executable, not a shared library The native plugin defaults the main binary to a shared library when java-library is applied, and java-gradle-plugin applies it to this module: the dry run produced tcl.dll and no tcl.exe. --- toolchain/build.gradle.kts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/toolchain/build.gradle.kts b/toolchain/build.gradle.kts index 7dd8896..d299eca 100644 --- a/toolchain/build.gradle.kts +++ b/toolchain/build.gradle.kts @@ -185,6 +185,9 @@ graalvmNative { named("main") { imageName.set("tcl") mainClass.set(tclMainClass) + // An executable, not a library: the native plugin defaults to a shared library when + // `java-library` is applied, which `java-gradle-plugin` does for this module. + sharedLibrary.set(false) classpath.setFrom(cliJar, configurations[cliMain.runtimeClasspathConfigurationName]) buildArgs.addAll( "--no-fallback",