Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,8 @@ jobs:
"3.13t",
"3.14",
"3.14t",
"3.15-dev",
"3.15t-dev",
"pypy3.11",
"graalpy25.0",
]
Expand Down
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ abi3-py310 = ["abi3-py311", "pyo3-build-config/abi3-py310", "pyo3-ffi/abi3-py310
abi3-py311 = ["abi3-py312", "pyo3-build-config/abi3-py311", "pyo3-ffi/abi3-py311"]
abi3-py312 = ["abi3-py313", "pyo3-build-config/abi3-py312", "pyo3-ffi/abi3-py312"]
abi3-py313 = ["abi3-py314", "pyo3-build-config/abi3-py313", "pyo3-ffi/abi3-py313"]
abi3-py314 = ["abi3", "pyo3-build-config/abi3-py314", "pyo3-ffi/abi3-py314"]
abi3-py314 = ["abi3-py315", "pyo3-build-config/abi3-py314", "pyo3-ffi/abi3-py314"]
abi3-py315 = ["abi3", "pyo3-build-config/abi3-py315", "pyo3-ffi/abi3-py315"]

# Automatically generates `python3.dll` import libraries for Windows targets.
generate-import-lib = ["pyo3-ffi/generate-import-lib"]
Expand Down
1 change: 1 addition & 0 deletions newsfragments/5518-packaging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add 3.15 to CI for preliminary testing
106 changes: 58 additions & 48 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,18 @@
import sysconfig
import tarfile
import tempfile
from collections.abc import (
Callable,
Iterable,
Iterator,
)
from contextlib import ExitStack, contextmanager
from functools import lru_cache
from glob import glob
from pathlib import Path
from typing import (
Any,
Callable,
Dict,
Iterable,
Iterator,
List,
Optional,
Tuple,
IO,
)

import nox.command
Expand Down Expand Up @@ -54,7 +53,7 @@ def _get_output(*args: str) -> str:

def _parse_supported_interpreter_version(
python_impl: str, # Literal["cpython", "pypy"], TODO update after 3.7 dropped
) -> Tuple[str, str]:
) -> tuple[str, str]:
output = _get_output("cargo", "metadata", "--format-version=1", "--no-deps")
cargo_packages = json.loads(output)["packages"]
# Check Python interpreter version support in package metadata
Expand All @@ -68,7 +67,7 @@ def _parse_supported_interpreter_version(

def _supported_interpreter_versions(
python_impl: str, # Literal["cpython", "pypy"], TODO update after 3.7 dropped
) -> List[str]:
) -> list[str]:
min_version, max_version = _parse_supported_interpreter_version(python_impl)
major = int(min_version.split(".")[0])
assert major == 3, f"unsupported Python major version {major}"
Expand Down Expand Up @@ -97,7 +96,7 @@ def test_rust(session: nox.Session):
_run_cargo_test(session, package="pyo3-macros-backend")
_run_cargo_test(session, package="pyo3-macros")

extra_flags = []
extra_flags: list[str] = []
# pypy and graalpy don't have Py_Initialize APIs, so we can only
# build the main tests, not run them
if sys.implementation.name in ("pypy", "graalpy"):
Expand Down Expand Up @@ -214,14 +213,14 @@ def rumdl(session: nox.Session):


@nox.session(name="clippy", venv_backend="none")
def clippy(session: nox.Session) -> bool:
def clippy(session: nox.Session) -> None:
if not (_clippy(session) and _clippy_additional_workspaces(session)):
session.error("one or more jobs failed")


def _clippy(session: nox.Session, *, env: Dict[str, str] = None) -> bool:
def _clippy(session: nox.Session, *, env: dict[str, str] | None = None) -> bool:
success = True
env = env or os.environ
env = env or dict(os.environ)
for feature_set in _get_feature_sets():
try:
_run_cargo(
Expand Down Expand Up @@ -263,12 +262,12 @@ def _clippy_additional_workspaces(session: nox.Session) -> bool:


@nox.session(venv_backend="none")
def bench(session: nox.Session) -> bool:
def bench(session: nox.Session) -> None:
_run_cargo(session, "bench", _BENCHES, *session.posargs)


@nox.session()
def codspeed(session: nox.Session) -> bool:
def codspeed(session: nox.Session) -> None:
# rust benchmarks
os.chdir(PYO3_DIR / "pyo3-benches")
_run_cargo(session, "codspeed", "build")
Expand All @@ -283,7 +282,7 @@ def codspeed(session: nox.Session) -> bool:
def clippy_all(session: nox.Session) -> None:
success = True

def _clippy_with_config(env: Dict[str, str]) -> None:
def _clippy_with_config(env: dict[str, str]) -> None:
nonlocal success
success &= _clippy(session, env=env)

Expand All @@ -298,7 +297,7 @@ def _clippy_with_config(env: Dict[str, str]) -> None:
def check_all(session: nox.Session) -> None:
success = True

def _check(env: Dict[str, str]) -> None:
def _check(env: dict[str, str]) -> None:
nonlocal success
for feature_set in _get_feature_sets():
try:
Expand Down Expand Up @@ -347,7 +346,7 @@ def contributors(session: nox.Session) -> None:
if len(session.posargs) > 2:
raise Exception("too many arguments")

authors = set()
authors: set[str] | list[str] = set()

while True:
resp = requests.get(
Expand Down Expand Up @@ -387,9 +386,10 @@ def __init__(self):

self.pyversion = sys.version.split()[0]
self.pymajor, self.pyminor, self.pymicro = self.pyversion.split(".")
self.pymicro, self.pydev = re.match(
"([0-9]*)([^0-9].*)?", self.pymicro
).groups()
match = re.match("([0-9]*)([^0-9].*)?", self.pymicro)
if match:
self.pymicro, self.pydev = match.groups()

if self.pydev is None:
self.pydev = ""

Expand Down Expand Up @@ -441,7 +441,7 @@ def test_emscripten(session: nox.Session):
)
session.env["RUSTDOCFLAGS"] = session.env["RUSTFLAGS"]
session.env["CARGO_BUILD_TARGET"] = target
session.env["PYO3_CROSS_LIB_DIR"] = pythonlibdir
session.env["PYO3_CROSS_LIB_DIR"] = str(pythonlibdir)
_run(session, "rustup", "target", "add", target, "--toolchain", "stable")
_run(
session,
Expand Down Expand Up @@ -517,8 +517,8 @@ def test_cross_compilation_windows(session: nox.Session):
@nox.session(venv_backend="none")
def docs(session: nox.Session, nightly: bool = False, internal: bool = False) -> None:
rustdoc_flags = ["-Dwarnings"]
toolchain_flags = []
cargo_flags = []
toolchain_flags: list[str] = []
cargo_flags: list[str] = []

nightly = nightly or ("nightly" in session.posargs)
internal = internal or ("internal" in session.posargs)
Expand All @@ -539,7 +539,7 @@ def docs(session: nox.Session, nightly: bool = False, internal: bool = False) ->
else:
cargo_flags.extend(["--exclude=pyo3-macros", "--exclude=pyo3-macros-backend"])

rustdoc_flags.append(session.env.get("RUSTDOCFLAGS", ""))
rustdoc_flags.append(session.env.get("RUSTDOCFLAGS") or "")
session.env["RUSTDOCFLAGS"] = " ".join(rustdoc_flags)

features = "full"
Expand Down Expand Up @@ -579,6 +579,9 @@ def build_guide(session: nox.Session):

@nox.session(name="build-netlify-site")
def build_netlify_site(session: nox.Session):
if requests is None:
session.error("requests library is required for this session")

# Remove netlify_build directory if it exists
netlify_build = Path("netlify_build")
if netlify_build.exists():
Expand Down Expand Up @@ -726,7 +729,7 @@ def check_guide(session: nox.Session):
# rust docs
"(https://docs.rs/[^#]+)#[a-zA-Z0-9._-]*": "$1",
}
remap_args = []
remap_args: list[str] = []
for key, value in remaps.items():
remap_args.extend(("--remap", f"{key} {value}"))

Expand Down Expand Up @@ -767,7 +770,7 @@ def format_guide(session: nox.Session):
for path in Path("guide").glob("**/*.md"):
session.log("Working on %s", path)
lines = iter(path.read_text().splitlines(True))
new_lines = []
new_lines: list[str] = []

for line in lines:
new_lines.append(line)
Expand Down Expand Up @@ -889,7 +892,7 @@ def set_msrv_package_versions(session: nox.Session):
*(Path(p).parent for p in glob("examples/*/Cargo.toml")),
*(Path(p).parent for p in glob("pyo3-ffi/examples/*/Cargo.toml")),
)
min_pkg_versions = {}
min_pkg_versions: dict[str, str] = {}

# run cargo update first to ensure that everything is at highest
# possible version, so that this matches what CI will resolve to.
Expand All @@ -904,10 +907,12 @@ def set_msrv_package_versions(session: nox.Session):

lock_file = project / "Cargo.lock"

def load_pkg_versions():
def load_pkg_versions():
if toml is None:
session.error("requires `toml` to be installed")
cargo_lock = toml.loads(lock_file.read_text())
# Cargo allows to depends on multiple versions of the same package
pkg_versions = defaultdict(list)
pkg_versions: defaultdict[str, list[str]] = defaultdict(list)
for pkg in cargo_lock["package"]:
name = pkg["name"]
if name not in min_pkg_versions:
Expand All @@ -922,7 +927,7 @@ def load_pkg_versions():
if version != min_version:
pkg_id = pkg_name + ":" + version
_run_cargo_set_package_version(
session, pkg_id, min_version, project=project
session, pkg_id, min_version, project=str(project)
)
# assume `_run_cargo_set_package_version` has changed something
# and re-read `Cargo.lock`
Expand Down Expand Up @@ -955,8 +960,8 @@ def test_version_limits(session: nox.Session):
config_file.set("CPython", "3.6")
_run_cargo(session, "check", env=env, expect_error=True)

assert "3.15" not in PY_VERSIONS
config_file.set("CPython", "3.15")
assert "3.16" not in PY_VERSIONS
config_file.set("CPython", "3.16")
_run_cargo(session, "check", env=env, expect_error=True)

# 3.15 CPython should build if abi3 is explicitly requested
Expand Down Expand Up @@ -1103,7 +1108,7 @@ def update_ui_tests(session: nox.Session):
def test_introspection(session: nox.Session):
session.install("maturin")
session.install("ruff")
options = []
options: list[str] = []
target = os.environ.get("CARGO_BUILD_TARGET")
if target is not None:
options += ("--target", target)
Expand All @@ -1124,6 +1129,8 @@ def test_introspection(session: nox.Session):
for file in Path(session.virtualenv.location).rglob("pyo3_pytests.*"):
if file.is_file():
lib_file = str(file.resolve())
if lib_file is None:
session.error("Could not find built pyo3_pytests")
_run_cargo_test(
session,
package="pyo3-introspection",
Expand All @@ -1139,19 +1146,21 @@ def _build_docs_for_ffi_check(session: nox.Session) -> None:


@lru_cache()
def _get_rust_info() -> Tuple[str, ...]:
def _get_rust_info() -> tuple[str, ...]:
output = _get_output("rustc", "-vV")

return tuple(output.splitlines())


def get_rust_version() -> Tuple[int, int, int, List[str]]:
def get_rust_version() -> tuple[int, int, int, list[str]]:
for line in _get_rust_info():
if line.startswith(_RELEASE_LINE_START):
version = line[len(_RELEASE_LINE_START) :].strip()
# e.g. 1.67.0-beta.2
(version_number, *extra) = version.split("-", maxsplit=1)
return (*map(int, version_number.split(".")), extra)
major, minor, patch = map(int, version_number.split("."))
return (major, minor, patch, extra)
raise RuntimeError("Could not parse Rust version")


def is_rust_nightly() -> bool:
Expand All @@ -1165,10 +1174,11 @@ def _get_rust_default_target() -> str:
for line in _get_rust_info():
if line.startswith(_HOST_LINE_START):
return line[len(_HOST_LINE_START) :].strip()
raise RuntimeError("Could not get Rust default target")


@lru_cache()
def _get_feature_sets() -> Tuple[Optional[str], ...]:
def _get_feature_sets() -> tuple[str | None, ...]:
"""Returns feature sets to use for Rust jobs"""
cargo_target = os.getenv("CARGO_BUILD_TARGET", "")

Expand All @@ -1188,8 +1198,8 @@ def _get_feature_sets() -> Tuple[Optional[str], ...]:
_HOST_LINE_START = "host: "


def _get_coverage_env() -> Dict[str, str]:
env = {}
def _get_coverage_env() -> dict[str, str]:
env: dict[str, str] = {}
output = _get_output("cargo", "llvm-cov", "show-env")

for line in output.strip().splitlines():
Expand Down Expand Up @@ -1242,10 +1252,10 @@ def _run_cargo(
def _run_cargo_test(
session: nox.Session,
*,
package: Optional[str] = None,
features: Optional[str] = None,
env: Optional[Dict[str, str]] = None,
extra_flags: Optional[List[str]] = None,
package: str | None = None,
features: str | None = None,
env: dict[str, str] | None = None,
extra_flags: list[str] | None = None,
) -> None:
command = ["cargo"]
if "careful" in session.posargs:
Expand Down Expand Up @@ -1276,7 +1286,7 @@ def _run_cargo_set_package_version(
pkg_id: str,
version: str,
*,
project: Optional[str] = None,
project: str | None = None,
) -> None:
command = ["cargo", "update", "-p", pkg_id, "--precise", version, "--workspace"]
if project:
Expand All @@ -1285,13 +1295,13 @@ def _run_cargo_set_package_version(


def _for_all_version_configs(
session: nox.Session, job: Callable[[Dict[str, str]], None]
session: nox.Session, job: Callable[[dict[str, str]], None]
) -> None:
env = os.environ.copy()
with _config_file() as config_file:
env["PYO3_CONFIG_FILE"] = config_file.name

def _job_with_config(implementation, version):
def _job_with_config(implementation: str, version: str) -> None:
session.log(f"{implementation} {version}")
config_file.set(implementation, version)
job(env)
Expand All @@ -1304,7 +1314,7 @@ def _job_with_config(implementation, version):


class _ConfigFile:
def __init__(self, config_file) -> None:
def __init__(self, config_file: IO[str]) -> None:
self._config_file = config_file

def set(
Expand Down
3 changes: 2 additions & 1 deletion pyo3-build-config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ abi3-py310 = ["abi3-py311"]
abi3-py311 = ["abi3-py312"]
abi3-py312 = ["abi3-py313"]
abi3-py313 = ["abi3-py314"]
abi3-py314 = ["abi3"]
abi3-py314 = ["abi3-py315"]
abi3-py315 = ["abi3"]

[package.metadata.docs.rs]
features = ["resolve-config"]
5 changes: 3 additions & 2 deletions pyo3-ffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ abi3-py310 = ["abi3-py311", "pyo3-build-config/abi3-py310"]
abi3-py311 = ["abi3-py312", "pyo3-build-config/abi3-py311"]
abi3-py312 = ["abi3-py313", "pyo3-build-config/abi3-py312"]
abi3-py313 = ["abi3-py314", "pyo3-build-config/abi3-py313"]
abi3-py314 = ["abi3", "pyo3-build-config/abi3-py314"]
abi3-py314 = ["abi3-py315", "pyo3-build-config/abi3-py314"]
abi3-py315 = ["abi3", "pyo3-build-config/abi3-py315"]

# Automatically generates `python3.dll` import libraries for Windows targets.
generate-import-lib = ["pyo3-build-config/generate-import-lib"]
Expand All @@ -51,7 +52,7 @@ workspace = true

[package.metadata.cpython]
min-version = "3.7"
max-version = "3.14" # inclusive
max-version = "3.15" # inclusive

[package.metadata.pypy]
min-version = "3.11"
Expand Down
Loading
Loading