diff --git a/kson-lib/build.gradle.kts b/kson-lib/build.gradle.kts index 26d76bcf..2a8f03bf 100644 --- a/kson-lib/build.gradle.kts +++ b/kson-lib/build.gradle.kts @@ -267,6 +267,18 @@ tasks.register("buildWithGraalVmNativeImage") { val ksonCoreJarTask = project.rootProject.tasks.named("jvmJar") dependsOn(ksonLibJarTask, ksonCoreJarTask, "generateJniBindingsJvm") + val nativeImageOutputDir = project.projectDir.resolve("build/kotlin/compileGraalVmNativeImage") + val jniConfig = project.projectDir.resolve("build/kotlin/krossover/metadata/jni-config.json") + + // ksonLibJarTask is this project's own JAR (not in its own runtime classpath). + // ksonCoreJarTask (root project) is a dependency, so it's covered by jvmRuntimeClasspath. + inputs.files(ksonLibJarTask.map { it.archiveFile }) + inputs.files(configurations.named("jvmRuntimeClasspath")) + inputs.file(jniConfig) + // Declare the specific binary rather than the whole directory, since krossover's + // generateJniBindingsJvm also writes jni_simplified.h into nativeImageOutputDir. + outputs.file(nativeImageOutputDir.resolve(BinaryArtifactPaths.binaryFileName())) + // Configure the command at configuration time using providers command.set(provider { val graalHome = GraalVmHelper.getGraalVMHome() @@ -277,7 +289,7 @@ tasks.register("buildWithGraalVmNativeImage") { } // Ensure build dir exists - val buildDir = project.projectDir.resolve("build/kotlin/compileGraalVmNativeImage").toPath() + val buildDir = nativeImageOutputDir.toPath() buildDir.createDirectories() val buildArtifactPath = buildDir.resolve(BinaryArtifactPaths.binaryFileNameWithoutExtension()).toAbsolutePath().pathString @@ -305,9 +317,6 @@ tasks.register("buildWithGraalVmNativeImage") { } val classPath = jars.joinToString(cpSeparator) - // The `jniConfig` file tells graal which classes should be publicly exposed - val jniConfig = project.projectDir.resolve("build/kotlin/krossover/metadata/jni-config.json") - listOf( nativeImageExe.absolutePath, "--shared", diff --git a/lib-python/.gitignore b/lib-python/.gitignore index 9cdbe3db..65886729 100644 --- a/lib-python/.gitignore +++ b/lib-python/.gitignore @@ -8,6 +8,7 @@ kson-sdist *.h *.egg-info *.pyd +LICENSE # uvw generated files .uv \ No newline at end of file diff --git a/lib-python/build.gradle.kts b/lib-python/build.gradle.kts index d15d13f6..8f4aded0 100644 --- a/lib-python/build.gradle.kts +++ b/lib-python/build.gradle.kts @@ -105,11 +105,16 @@ tasks { exclude(".gradle/**") } - // Copy lib-python (build files and source) + // Copy lib-python (build files and source, excluding native binaries + // which are platform-specific and must be built from source) + // Keep in sync with PLATFORM_NATIVE_LIBRARIES in build_backend.py from(project.projectDir) { into("lib-python") include("build.gradle.kts") include("src/**") + exclude("src/kson/kson.dll") + exclude("src/kson/libkson.dylib") + exclude("src/kson/libkson.so") } } @@ -133,12 +138,7 @@ tasks { errorOutput = System.err isIgnoreExitValue = false - // Configure cibuildwheel - environment("CIBW_BUILD", "cp310-*") // Build for Python 3.10+ - environment("CIBW_SKIP", "*-musllinux_*") // Skip musl Linux builds - environment("CIBW_ARCHS", "native") // Build only for native architecture - environment("CIBW_TEST_REQUIRES", "pytest") // Install pytest for testing - environment("CIBW_TEST_COMMAND", "pytest -v {project}/tests") + // cibuildwheel configuration lives in pyproject.toml under [tool.cibuildwheel] doLast { println("Successfully built platform-specific wheel using cibuildwheel") diff --git a/lib-python/build_backend.py b/lib-python/build_backend.py index 46b50dd6..ee306975 100644 --- a/lib-python/build_backend.py +++ b/lib-python/build_backend.py @@ -6,10 +6,31 @@ import os import subprocess import shutil +import sys from pathlib import Path from setuptools import build_meta as _orig from setuptools.build_meta import * +# See also LIBRARY_NAMES in src/kson/__init__.py +PLATFORM_NATIVE_LIBRARIES = { + "win32": "kson.dll", + "darwin": "libkson.dylib", + "linux": "libkson.so", +} + + +def _platform_native_library(): + """Return the native library filename for the current platform.""" + lib = PLATFORM_NATIVE_LIBRARIES.get(sys.platform) + if lib is None: + raise RuntimeError(f"Unsupported platform: {sys.platform}") + return lib + + +def _required_artifacts(): + """Return the filenames that must be present for a working installation.""" + return [_platform_native_library(), "jni_simplified.h"] + def _ensure_native_artifacts(): """Build native artifacts using the bundled Gradle setup.""" @@ -18,55 +39,60 @@ def _ensure_native_artifacts(): src_dir = lib_python_dir / "src" src_kson_dir = src_dir / "kson" - # Check if native artifacts already exist - native_files = ["kson.dll", "libkson.dylib", "libkson.so", "kson_api.h"] - artifacts_exist = any((src_kson_dir / f).exists() for f in native_files) + # Only check for the native library needed on *this* platform + required = _required_artifacts() + artifacts_exist = all((src_kson_dir / f).exists() for f in required) if not artifacts_exist and kson_copy_dir.exists(): - print("Building native artifacts with bundled Gradle setup...") - # Run gradle from the kson-sdist directory - original_dir = os.getcwd() - try: - os.chdir(kson_copy_dir) - - # Run the Gradle build - gradlew = "./gradlew" if os.name != "nt" else "gradlew.bat" - result = subprocess.run( - [gradlew, "lib-python:build"], capture_output=True, text=True - ) - - if result.returncode != 0: - print(f"Gradle build failed:\n{result.stderr}") - raise RuntimeError("Failed to build native artifacts") - - print("Native artifacts built successfully") - - # Replace the entire src directory with the one from kson-sdist - kson_copy_src = kson_copy_dir / "lib-python" / "src" - if kson_copy_src.exists(): - print("Replacing src directory with built artifacts...") - # Save _marker.c if it exists - marker_c = src_kson_dir / "_marker.c" - marker_c_content = None - if marker_c.exists(): - marker_c_content = marker_c.read_bytes() - - shutil.rmtree(src_dir, ignore_errors=True) - shutil.copytree(kson_copy_src, src_dir) - - # Restore _marker.c if it existed - if marker_c_content is not None: - marker_c_new = src_kson_dir / "_marker.c" - marker_c_new.parent.mkdir(parents=True, exist_ok=True) - marker_c_new.write_bytes(marker_c_content) - - finally: - os.chdir(original_dir) + print(f"Building native artifacts for {sys.platform} with bundled Gradle setup...") + + gradlew = "./gradlew" if os.name != "nt" else "gradlew.bat" + result = subprocess.run( + [gradlew, "lib-python:build"], + capture_output=True, + text=True, + cwd=kson_copy_dir, + ) + + if result.returncode != 0: + print(f"Gradle build stdout:\n{result.stdout}") + print(f"Gradle build stderr:\n{result.stderr}") + raise RuntimeError("Failed to build native artifacts") + + print("Native artifacts built successfully") + + # Replace the entire src directory with the one from kson-sdist + kson_copy_src = kson_copy_dir / "lib-python" / "src" + if kson_copy_src.exists(): + print("Replacing src directory with built artifacts...") + # Save _marker.c if it exists + marker_c = src_kson_dir / "_marker.c" + marker_c_content = None + if marker_c.exists(): + marker_c_content = marker_c.read_bytes() + + shutil.rmtree(src_dir, ignore_errors=True) + shutil.copytree(kson_copy_src, src_dir) + + # Restore _marker.c if it existed + if marker_c_content is not None: + marker_c_new = src_kson_dir / "_marker.c" + marker_c_new.parent.mkdir(parents=True, exist_ok=True) + marker_c_new.write_bytes(marker_c_content) # Clean up kson-sdist after successful build print("Cleaning up build files...") shutil.rmtree(kson_copy_dir, ignore_errors=True) + # Post-condition: verify all required artifacts are present + missing = [f for f in required if not (src_kson_dir / f).exists()] + if missing: + raise RuntimeError( + f"Required native artifacts missing for {sys.platform}: {', '.join(missing)}. " + f"Install from a pre-built wheel instead, or ensure a JDK is available " + f"so the Gradle build can produce them." + ) + def build_sdist(sdist_directory, config_settings=None): """Build source distribution.""" diff --git a/lib-python/pyproject.toml b/lib-python/pyproject.toml index 6fceaa8b..eff38f60 100644 --- a/lib-python/pyproject.toml +++ b/lib-python/pyproject.toml @@ -48,11 +48,29 @@ package-dir = {"" = "src"} [tool.setuptools.package-data] kson = ["kson_api.h", "kson.dll", "libkson.dylib", "libkson.so", "jni_simplified.h"] +[tool.pytest.ini_options] +pythonpath = ["."] + +[tool.cibuildwheel] +# One wheel targeting 3.10 is sufficient: the abi3 stable ABI tag (see setup.py) +# makes it installable on any CPython >= 3.10. +build = "cp310-*" +skip = "*-musllinux_*" +archs = "native" +test-requires = "pytest" +test-command = "pytest -v {project}/tests --ignore={project}/tests/test_build_backend.py" + +[tool.cibuildwheel.macos] +# Pin deployment target so builds don't break when the host macOS is +# newer than what the Python packaging tools recognize yet. +environment = { MACOSX_DEPLOYMENT_TARGET = "11.0" } + [dependency-groups] dev = [ "pyright>=1.1.403", "pytest>=8.4.1", "ruff>=0.12.8", + "setuptools>=61.0", "cibuildwheel>=2.16.0", "twine>=6.2.0", ] diff --git a/lib-python/tests/test_build_backend.py b/lib-python/tests/test_build_backend.py new file mode 100644 index 00000000..50d9ac83 --- /dev/null +++ b/lib-python/tests/test_build_backend.py @@ -0,0 +1,137 @@ +"""Tests for the custom build backend's platform-aware native artifact detection.""" + +import subprocess +import sys +from unittest.mock import patch + +import pytest + +import build_backend + + +class TestPlatformNativeLibrary: + def test_darwin(self): + with patch.object(sys, "platform", "darwin"): + assert build_backend._platform_native_library() == "libkson.dylib" + + def test_linux(self): + with patch.object(sys, "platform", "linux"): + assert build_backend._platform_native_library() == "libkson.so" + + def test_windows(self): + with patch.object(sys, "platform", "win32"): + assert build_backend._platform_native_library() == "kson.dll" + + def test_unsupported_platform(self): + with patch.object(sys, "platform", "freebsd"): + with pytest.raises(RuntimeError, match="Unsupported platform: freebsd"): + build_backend._platform_native_library() + + +class TestEnsureNativeArtifacts: + """Verify that _ensure_native_artifacts only considers the current platform's library.""" + + def _place_all_required_artifacts(self, directory): + """Place all required artifacts for the current platform.""" + for f in build_backend._required_artifacts(): + (directory / f).touch() + + def test_skips_build_when_current_platform_artifacts_exist(self, tmp_path): + """Build is skipped when all required artifacts for *this* platform are present.""" + src_kson = tmp_path / "src" / "kson" + src_kson.mkdir(parents=True) + kson_sdist = tmp_path / "kson-sdist" + kson_sdist.mkdir() + + self._place_all_required_artifacts(src_kson) + + with patch.object(build_backend, "__file__", str(tmp_path / "build_backend.py")): + with patch.object(build_backend.subprocess, "run") as mock_run: + build_backend._ensure_native_artifacts() + mock_run.assert_not_called() + + def test_triggers_build_when_only_foreign_artifact_exists(self, tmp_path): + """Build is triggered when only a *different* platform's library is present.""" + src_kson = tmp_path / "src" / "kson" + src_kson.mkdir(parents=True) + kson_sdist = tmp_path / "kson-sdist" + kson_sdist.mkdir() + + # Place foreign platform libraries (but not the current one) + current_lib = build_backend._platform_native_library() + for lib in build_backend.PLATFORM_NATIVE_LIBRARIES.values(): + if lib != current_lib: + (src_kson / lib).touch() + (src_kson / "jni_simplified.h").touch() + + with patch.object(build_backend, "__file__", str(tmp_path / "build_backend.py")): + with patch.object(build_backend.subprocess, "run") as mock_run: + mock_run.return_value = subprocess.CompletedProcess( + args=[], returncode=1, stdout="", stderr="fail", + ) + with pytest.raises(RuntimeError, match="Failed to build native artifacts"): + build_backend._ensure_native_artifacts() + mock_run.assert_called_once() + + def test_triggers_build_when_header_missing(self, tmp_path): + """Build is triggered when the native lib exists but jni_simplified.h is missing.""" + src_kson = tmp_path / "src" / "kson" + src_kson.mkdir(parents=True) + kson_sdist = tmp_path / "kson-sdist" + kson_sdist.mkdir() + + current_lib = build_backend._platform_native_library() + (src_kson / current_lib).touch() + + with patch.object(build_backend, "__file__", str(tmp_path / "build_backend.py")): + with patch.object(build_backend.subprocess, "run") as mock_run: + mock_run.return_value = subprocess.CompletedProcess( + args=[], returncode=1, stdout="", stderr="fail", + ) + with pytest.raises(RuntimeError, match="Failed to build native artifacts"): + build_backend._ensure_native_artifacts() + mock_run.assert_called_once() + + def test_errors_when_artifacts_missing_and_no_kson_sdist(self, tmp_path): + """Raises when artifacts are missing and there's no kson-sdist to build from.""" + src_kson = tmp_path / "src" / "kson" + src_kson.mkdir(parents=True) + + with patch.object(build_backend, "__file__", str(tmp_path / "build_backend.py")): + with pytest.raises(RuntimeError, match="Required native artifacts missing"): + build_backend._ensure_native_artifacts() + + def test_successful_build_replaces_src_and_preserves_marker(self, tmp_path): + """On successful build, src is replaced with kson-sdist output and _marker.c is preserved.""" + src_kson = tmp_path / "src" / "kson" + src_kson.mkdir(parents=True) + kson_sdist = tmp_path / "kson-sdist" + + # Set up the kson-sdist build output that Gradle would produce + native_lib = build_backend._platform_native_library() + kson_sdist_src = kson_sdist / "lib-python" / "src" / "kson" + kson_sdist_src.mkdir(parents=True) + (kson_sdist_src / "__init__.py").write_text("# built") + (kson_sdist_src / native_lib).write_bytes(b"built-lib") + (kson_sdist_src / "jni_simplified.h").write_text("/* header */") + + # Place _marker.c in the original src (should be preserved) + marker_content = b"/* platform marker */" + (src_kson / "_marker.c").write_bytes(marker_content) + + with patch.object(build_backend, "__file__", str(tmp_path / "build_backend.py")): + with patch.object(build_backend.subprocess, "run") as mock_run: + mock_run.return_value = subprocess.CompletedProcess( + args=[], returncode=0, stdout="", stderr="", + ) + build_backend._ensure_native_artifacts() + + # src was replaced with kson-sdist output + assert (tmp_path / "src" / "kson" / "__init__.py").read_text() == "# built" + assert (tmp_path / "src" / "kson" / native_lib).read_bytes() == b"built-lib" + + # _marker.c was preserved + assert (tmp_path / "src" / "kson" / "_marker.c").read_bytes() == marker_content + + # kson-sdist was cleaned up + assert not kson_sdist.exists() diff --git a/lib-python/uv.lock b/lib-python/uv.lock index 0dfc977f..07337615 100644 --- a/lib-python/uv.lock +++ b/lib-python/uv.lock @@ -298,10 +298,8 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2f/53/f0b865a971e4e8b3e90e648b6f828950dea4c221bb699421e82ef45f0ef9/cryptography-46.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d1eccae15d5c28c74b2bea228775c63ac5b6c36eedb574e002440c0bc28750d3", size = 4571982, upload-time = "2025-09-16T21:05:57.322Z" }, { url = "https://files.pythonhosted.org/packages/d4/c8/035be5fd63a98284fd74df9e04156f9fed7aa45cef41feceb0d06cbdadd0/cryptography-46.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:1b4fba84166d906a22027f0d958e42f3a4dbbb19c28ea71f0fb7812380b04e3c", size = 4307996, upload-time = "2025-09-16T21:05:59.043Z" }, { url = "https://files.pythonhosted.org/packages/aa/4a/dbb6d7d0a48b95984e2d4caf0a4c7d6606cea5d30241d984c0c02b47f1b6/cryptography-46.0.0-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:523153480d7575a169933f083eb47b1edd5fef45d87b026737de74ffeb300f69", size = 4015692, upload-time = "2025-09-16T21:06:01.324Z" }, - { url = "https://files.pythonhosted.org/packages/65/48/aafcffdde716f6061864e56a0a5908f08dcb8523dab436228957c8ebd5df/cryptography-46.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:f09a3a108223e319168b7557810596631a8cb864657b0c16ed7a6017f0be9433", size = 4982192, upload-time = "2025-09-16T21:06:03.367Z" }, { url = "https://files.pythonhosted.org/packages/4c/ab/1e73cfc181afc3054a09e5e8f7753a8fba254592ff50b735d7456d197353/cryptography-46.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c1f6ccd6f2eef3b2eb52837f0463e853501e45a916b3fc42e5d93cf244a4b97b", size = 4603944, upload-time = "2025-09-16T21:06:05.29Z" }, { url = "https://files.pythonhosted.org/packages/3a/02/d71dac90b77c606c90c366571edf264dc8bd37cf836e7f902253cbf5aa77/cryptography-46.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:80a548a5862d6912a45557a101092cd6c64ae1475b82cef50ee305d14a75f598", size = 4308149, upload-time = "2025-09-16T21:06:07.006Z" }, - { url = "https://files.pythonhosted.org/packages/29/e6/4dcb67fdc6addf4e319a99c4bed25776cb691f3aa6e0c4646474748816c6/cryptography-46.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6c39fd5cd9b7526afa69d64b5e5645a06e1b904f342584b3885254400b63f1b3", size = 4947449, upload-time = "2025-09-16T21:06:11.244Z" }, { url = "https://files.pythonhosted.org/packages/26/04/91e3fad8ee33aa87815c8f25563f176a58da676c2b14757a4d3b19f0253c/cryptography-46.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:d5c0cbb2fb522f7e39b59a5482a1c9c5923b7c506cfe96a1b8e7368c31617ac0", size = 4603549, upload-time = "2025-09-16T21:06:13.268Z" }, { url = "https://files.pythonhosted.org/packages/9c/6e/caf4efadcc8f593cbaacfbb04778f78b6d0dac287b45cec25e5054de38b7/cryptography-46.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6d8945bc120dcd90ae39aa841afddaeafc5f2e832809dc54fb906e3db829dfdc", size = 4435976, upload-time = "2025-09-16T21:06:16.514Z" }, { url = "https://files.pythonhosted.org/packages/c1/c0/704710f349db25c5b91965c3662d5a758011b2511408d9451126429b6cd6/cryptography-46.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:88c09da8a94ac27798f6b62de6968ac78bb94805b5d272dbcfd5fdc8c566999f", size = 4709447, upload-time = "2025-09-16T21:06:19.246Z" }, @@ -309,10 +307,8 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5f/b9/07aec6b183ef0054b5f826ae43f0b4db34c50b56aff18f67babdcc2642a3/cryptography-46.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:da7f93551d39d462263b6b5c9056c49f780b9200bf9fc2656d7c88c7bdb9b363", size = 4545583, upload-time = "2025-09-16T21:06:31.914Z" }, { url = "https://files.pythonhosted.org/packages/39/4a/7d25158be8c607e2b9ebda49be762404d675b47df335d0d2a3b979d80213/cryptography-46.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:be7479f9504bfb46628544ec7cb4637fe6af8b70445d4455fbb9c395ad9b7290", size = 4299196, upload-time = "2025-09-16T21:06:33.724Z" }, { url = "https://files.pythonhosted.org/packages/15/3f/65c8753c0dbebe769cc9f9d87d52bce8b74e850ef2818c59bfc7e4248663/cryptography-46.0.0-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f85e6a7d42ad60024fa1347b1d4ef82c4df517a4deb7f829d301f1a92ded038c", size = 3994419, upload-time = "2025-09-16T21:06:35.877Z" }, - { url = "https://files.pythonhosted.org/packages/d5/b4/69a271873cfc333a236443c94aa07e0233bc36b384e182da2263703b5759/cryptography-46.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:d349af4d76a93562f1dce4d983a4a34d01cb22b48635b0d2a0b8372cdb4a8136", size = 4960228, upload-time = "2025-09-16T21:06:38.182Z" }, { url = "https://files.pythonhosted.org/packages/af/e0/ab62ee938b8d17bd1025cff569803cfc1c62dfdf89ffc78df6e092bff35f/cryptography-46.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:35aa1a44bd3e0efc3ef09cf924b3a0e2a57eda84074556f4506af2d294076685", size = 4577257, upload-time = "2025-09-16T21:06:39.998Z" }, { url = "https://files.pythonhosted.org/packages/49/67/09a581c21da7189676678edd2bd37b64888c88c2d2727f2c3e0350194fba/cryptography-46.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:c457ad3f151d5fb380be99425b286167b358f76d97ad18b188b68097193ed95a", size = 4299023, upload-time = "2025-09-16T21:06:42.182Z" }, - { url = "https://files.pythonhosted.org/packages/af/28/2cb6d3d0d2c8ce8be4f19f4d83956c845c760a9e6dfe5b476cebed4f4f00/cryptography-46.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:399ef4c9be67f3902e5ca1d80e64b04498f8b56c19e1bc8d0825050ea5290410", size = 4925802, upload-time = "2025-09-16T21:06:44.31Z" }, { url = "https://files.pythonhosted.org/packages/88/0b/1f31b6658c1dfa04e82b88de2d160e0e849ffb94353b1526dfb3a225a100/cryptography-46.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:378eff89b040cbce6169528f130ee75dceeb97eef396a801daec03b696434f06", size = 4577107, upload-time = "2025-09-16T21:06:46.324Z" }, { url = "https://files.pythonhosted.org/packages/c2/af/507de3a1d4ded3068ddef188475d241bfc66563d99161585c8f2809fee01/cryptography-46.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c3648d6a5878fd1c9a22b1d43fa75efc069d5f54de12df95c638ae7ba88701d0", size = 4422506, upload-time = "2025-09-16T21:06:47.963Z" }, { url = "https://files.pythonhosted.org/packages/47/aa/08e514756504d92334cabfe7fe792d10d977f2294ef126b2056b436450eb/cryptography-46.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2fc30be952dd4334801d345d134c9ef0e9ccbaa8c3e1bc18925cbc4247b3e29c", size = 4684081, upload-time = "2025-09-16T21:06:49.667Z" }, @@ -320,10 +316,8 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/da/94/f1c1f30110c05fa5247bf460b17acfd52fa3f5c77e94ba19cff8957dc5e6/cryptography-46.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c3cd09b1490c1509bf3892bde9cef729795fae4a2fee0621f19be3321beca7e4", size = 4562561, upload-time = "2025-09-16T21:07:03.386Z" }, { url = "https://files.pythonhosted.org/packages/5d/54/8decbf2f707350bedcd525833d3a0cc0203d8b080d926ad75d5c4de701ba/cryptography-46.0.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d14eaf1569d6252280516bedaffdd65267428cdbc3a8c2d6de63753cf0863d5e", size = 4301974, upload-time = "2025-09-16T21:07:04.962Z" }, { url = "https://files.pythonhosted.org/packages/82/63/c34a2f3516c6b05801f129616a5a1c68a8c403b91f23f9db783ee1d4f700/cryptography-46.0.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ab3a14cecc741c8c03ad0ad46dfbf18de25218551931a23bca2731d46c706d83", size = 4009462, upload-time = "2025-09-16T21:07:06.569Z" }, - { url = "https://files.pythonhosted.org/packages/cd/c5/92ef920a4cf8ff35fcf9da5a09f008a6977dcb9801c709799ec1bf2873fb/cryptography-46.0.0-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:8e8b222eb54e3e7d3743a7c2b1f7fa7df7a9add790307bb34327c88ec85fe087", size = 4980769, upload-time = "2025-09-16T21:07:08.269Z" }, { url = "https://files.pythonhosted.org/packages/a9/8f/1705f7ea3b9468c4a4fef6cce631db14feb6748499870a4772993cbeb729/cryptography-46.0.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7f3f88df0c9b248dcc2e76124f9140621aca187ccc396b87bc363f890acf3a30", size = 4591812, upload-time = "2025-09-16T21:07:10.288Z" }, { url = "https://files.pythonhosted.org/packages/34/b9/2d797ce9d346b8bac9f570b43e6e14226ff0f625f7f6f2f95d9065e316e3/cryptography-46.0.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:9aa85222f03fdb30defabc7a9e1e3d4ec76eb74ea9fe1504b2800844f9c98440", size = 4301844, upload-time = "2025-09-16T21:07:12.522Z" }, - { url = "https://files.pythonhosted.org/packages/a8/2d/8efc9712997b46aea2ac8f74adc31f780ac4662e3b107ecad0d5c1a0c7f8/cryptography-46.0.0-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:f9aaf2a91302e1490c068d2f3af7df4137ac2b36600f5bd26e53d9ec320412d3", size = 4943257, upload-time = "2025-09-16T21:07:14.289Z" }, { url = "https://files.pythonhosted.org/packages/c4/0c/bc365287a97d28aa7feef8810884831b2a38a8dc4cf0f8d6927ad1568d27/cryptography-46.0.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:32670ca085150ff36b438c17f2dfc54146fe4a074ebf0a76d72fb1b419a974bc", size = 4591154, upload-time = "2025-09-16T21:07:16.271Z" }, { url = "https://files.pythonhosted.org/packages/51/3b/0b15107277b0c558c02027da615f4e78c892f22c6a04d29c6ad43fcddca6/cryptography-46.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0f58183453032727a65e6605240e7a3824fd1d6a7e75d2b537e280286ab79a52", size = 4428200, upload-time = "2025-09-16T21:07:18.118Z" }, { url = "https://files.pythonhosted.org/packages/cf/24/814d69418247ea2cfc985eec6678239013500d745bc7a0a35a32c2e2f3be/cryptography-46.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4bc257c2d5d865ed37d0bd7c500baa71f939a7952c424f28632298d80ccd5ec1", size = 4699862, upload-time = "2025-09-16T21:07:20.219Z" }, @@ -411,7 +405,7 @@ name = "importlib-metadata" version = "8.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "zipp" }, + { name = "zipp", marker = "python_full_version < '3.14' or platform_python_implementation == 'PyPy'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" } wheels = [ @@ -505,6 +499,7 @@ dev = [ { name = "pyright" }, { name = "pytest" }, { name = "ruff" }, + { name = "setuptools" }, { name = "twine" }, ] @@ -517,6 +512,7 @@ dev = [ { name = "pyright", specifier = ">=1.1.403" }, { name = "pytest", specifier = ">=8.4.1" }, { name = "ruff", specifier = ">=0.12.8" }, + { name = "setuptools", specifier = ">=61.0" }, { name = "twine", specifier = ">=6.2.0" }, ] @@ -610,10 +606,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b1/a7/8c4f86c78ec03db954d05fd9c57a114cc3a172a2d3e4a8b949cd5ff89471/patchelf-0.17.2.4-py3-none-macosx_10_9_universal2.whl", hash = "sha256:343bb1b94e959f9070ca9607453b04390e36bbaa33c88640b989cefad0aa049e", size = 184436, upload-time = "2025-07-23T21:16:20.578Z" }, { url = "https://files.pythonhosted.org/packages/7e/19/f7821ef31aab01fa7dc8ebe697ece88ec4f7a0fdd3155dab2dfee4b00e5c/patchelf-0.17.2.4-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.musllinux_1_1_x86_64.whl", hash = "sha256:d9b35ebfada70c02679ad036407d9724ffe1255122ba4ac5e4be5868618a5689", size = 482846, upload-time = "2025-07-23T21:16:23.73Z" }, { url = "https://files.pythonhosted.org/packages/d1/50/107fea848ecfd851d473b079cab79107487d72c4c3cdb25b9d2603a24ca2/patchelf-0.17.2.4-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:2931a1b5b85f3549661898af7bf746afbda7903c7c9a967cfc998a3563f84fad", size = 477811, upload-time = "2025-07-23T21:16:25.145Z" }, - { url = "https://files.pythonhosted.org/packages/89/a9/a9a2103e159fd65bffbc21ecc5c8c36e44eb34fe53b4ef85fb6d08c2a635/patchelf-0.17.2.4-py3-none-manylinux2014_armv7l.manylinux_2_17_armv7l.musllinux_1_1_armv7l.whl", hash = "sha256:ae44cb3c857d50f54b99e5697aa978726ada33a8a6129d4b8b7ffd28b996652d", size = 431226, upload-time = "2025-07-23T21:16:26.765Z" }, - { url = "https://files.pythonhosted.org/packages/87/93/897d612f6df7cfd987bdf668425127efeff8d8e4ad8bfbab1c69d2a0d861/patchelf-0.17.2.4-py3-none-manylinux2014_ppc64le.manylinux_2_17_ppc64le.musllinux_1_1_ppc64le.whl", hash = "sha256:680a266a70f60a7a4f4c448482c5bdba80cc8e6bb155a49dcc24238ba49927b0", size = 540276, upload-time = "2025-07-23T21:16:27.983Z" }, - { url = "https://files.pythonhosted.org/packages/5d/b8/2b92d11533482bac9ee989081d6880845287751b5f528adbd6bb27667fbd/patchelf-0.17.2.4-py3-none-manylinux2014_s390x.manylinux_2_17_s390x.musllinux_1_1_s390x.whl", hash = "sha256:d842b51f0401460f3b1f3a3a67d2c266a8f515a5adfbfa6e7b656cb3ac2ed8bc", size = 596632, upload-time = "2025-07-23T21:16:29.253Z" }, - { url = "https://files.pythonhosted.org/packages/14/e2/975d4bdb418f942b53e6187b95bd9e0d5e0488b7bc214685a1e43e2c2751/patchelf-0.17.2.4-py3-none-manylinux_2_31_riscv64.musllinux_1_1_riscv64.whl", hash = "sha256:7076d9e127230982e20a81a6e2358d3343004667ba510d9f822d4fdee29b0d71", size = 508281, upload-time = "2025-07-23T21:16:30.865Z" }, ] [[package]] @@ -812,6 +804,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/91/ff/2e2eed29e02c14a5cb6c57f09b2d5b40e65d6cc71f45b52e0be295ccbc2f/secretstorage-3.4.0-py3-none-any.whl", hash = "sha256:0e3b6265c2c63509fb7415717607e4b2c9ab767b7f344a57473b779ca13bd02e", size = 15272, upload-time = "2025-09-09T16:42:12.744Z" }, ] +[[package]] +name = "setuptools" +version = "82.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/f3/748f4d6f65d1756b9ae577f329c951cda23fb900e4de9f70900ced962085/setuptools-82.0.0.tar.gz", hash = "sha256:22e0a2d69474c6ae4feb01951cb69d515ed23728cf96d05513d36e42b62b37cb", size = 1144893, upload-time = "2026-02-08T15:08:40.206Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl", hash = "sha256:70b18734b607bd1da571d097d236cfcfacaf01de45717d59e6e04b96877532e0", size = 1003468, upload-time = "2026-02-08T15:08:38.723Z" }, +] + [[package]] name = "tomli" version = "2.2.1"