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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ jobs:
test_spin:
strategy:
matrix:
python_version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14-dev"]
python_version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
id: setup-python
with:
python-version: ${{ matrix.python_version }}
allow-prereleases: true
Expand All @@ -37,4 +38,4 @@ jobs:
sudo apt-get install -y gdb lcov
- name: Tests PyTest
run: |
pipx run nox --forcecolor -s test
pipx run --python '${{ steps.setup-python.outputs.python-path }}' nox --forcecolor -s test
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,9 @@ nox -s test -- -v
nox -s test -- -v spin/tests/test_meson.py
```

`spin` takes a slightly more conservative approach than [SPEC 0](https://scientific-python.org/specs/spec-0000/), and
supports all non-EOL versions of Python.

## History

The `dev.py` tool was [proposed for SciPy](https://github.com/scipy/scipy/issues/15489) by Ralf Gommers and [implemented](https://github.com/scipy/scipy/pull/15959) by Sayantika Banik, Eduardo Naufel Schettino, and Ralf Gommers (also see [Sayantika's blog post](https://labs.quansight.org/blog/the-evolution-of-the-scipy-developer-cli)).
Expand Down
2 changes: 1 addition & 1 deletion example_pkg_src/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[project]
name = "example_pkg"
version = "0.0dev0"
requires-python = ">=3.9"
requires-python = ">=3.10"
description = "spin Example Package"

[build-system]
Expand Down
3 changes: 1 addition & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "spin"
requires-python = ">=3.9"
requires-python = ">=3.10" # Oldest non-EOL Python here
description = "Developer tool for scientific Python libraries"
readme = "README.md"
license = {file = "LICENSE"}
Expand All @@ -15,7 +15,6 @@ classifiers = [
"License :: OSI Approved :: BSD 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",
Expand Down
3 changes: 1 addition & 2 deletions spin/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import sys
import textwrap
import traceback
from typing import Union

import click

Expand All @@ -31,7 +30,7 @@
)


def _detect_config_dir(path: pathlib.Path) -> Union[pathlib.Path, None]:
def _detect_config_dir(path: pathlib.Path) -> pathlib.Path | None:
path = path.resolve()
files = os.listdir(path)
if any(f in files for f in config_filenames):
Expand Down
7 changes: 3 additions & 4 deletions spin/cmds/meson.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import sys
from enum import Enum
from pathlib import Path
from typing import Union

import click

Expand Down Expand Up @@ -36,7 +35,7 @@ def _meson_cli():
return [meson_cli]


def editable_install_path(distname: str) -> Union[str, None]:
def editable_install_path(distname: str) -> str | None:
"""Return path of the editable install for package `distname`.

If the package is not an editable install, return None.
Expand Down Expand Up @@ -191,15 +190,15 @@ def _get_site_packages(build_dir: str) -> str:
return site_packages


def _meson_version() -> Union[str, None]:
def _meson_version() -> str | None:
try:
p = _run(_meson_cli() + ["--version"], output=False, echo=False)
return p.stdout.decode("ascii").strip()
except:
return None


def _meson_version_configured(build_dir: str) -> Union[str, None]:
def _meson_version_configured(build_dir: str) -> str | None:
try:
meson_info_fn = os.path.join(build_dir, "meson-info", "meson-info.json")
with open(meson_info_fn) as f:
Expand Down
12 changes: 8 additions & 4 deletions spin/tests/test_build_cmds.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import subprocess
import sys
import tempfile
from pathlib import Path
from pathlib import Path, PureWindowsPath

import pytest

Expand All @@ -19,6 +19,10 @@
)


def unix_path(p: str) -> str:
return PureWindowsPath(p).as_posix()


def test_basic_build(example_pkg):
"""Does the package build?"""
spin("build")
Expand All @@ -39,7 +43,7 @@ def test_debug_builds(example_pkg):

def test_prefix_builds(example_pkg):
"""does spin build --prefix create a build-install directory with the correct structure?"""
spin("build", "--prefix=/foobar/")
spin("build", f"--prefix={os.path.abspath('/foobar')}")
assert (Path("build-install") / Path("foobar")).exists()


Expand Down Expand Up @@ -178,7 +182,7 @@ def test_parallel_builds(example_pkg):
spin("build")
spin("build", "-C", "parallel/build")
p = spin("python", "--", "-c", "import example_pkg; print(example_pkg.__file__)")
example_pkg_path = stdout(p).split("\n")[-1]
example_pkg_path = unix_path(stdout(p).split("\n")[-1])
p = spin(
"python",
"-C",
Expand All @@ -187,7 +191,7 @@ def test_parallel_builds(example_pkg):
"-c",
"import example_pkg; print(example_pkg.__file__)",
)
example_pkg_parallel_path = stdout(p).split("\n")[-1]
example_pkg_parallel_path = unix_path(stdout(p).split("\n")[-1])
assert "build-install" in example_pkg_path
assert "parallel/build-install" in example_pkg_parallel_path
assert "parallel/build-install" not in example_pkg_path
13 changes: 11 additions & 2 deletions spin/tests/testutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,21 @@

def spin(*args, **user_kwargs):
args = (str(el) for el in args)
# Capture stdout, stderr separately
default_kwargs = {
"stdout": subprocess.PIPE,
"stderr": subprocess.PIPE,
"sys_exit": True,
"sys_exit": False,
}
return run(["spin"] + list(args), **{**default_kwargs, **user_kwargs})
p = run(["spin"] + list(args), **{**default_kwargs, **user_kwargs})
if p.returncode != 0:
print(p.stdout.decode("utf-8"), end="")
print(p.stderr.decode("utf-8"), end="")
# Exit unless the spin call explicitly asks us not to
# by setting sys_exit=False
if user_kwargs.get("sys_exit", True):
sys.exit(p.returncode)
return p


def stdout(p):
Expand Down