From 39c6c2b97ffce6ef3c9bd7557ef2ab8ba07caba9 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 20 Jun 2024 02:22:30 -0400 Subject: [PATCH] feat: Python 3.7+ (#249) * feat: Python 3.7+ Signed-off-by: Henry Schreiner * fix: mention Python version support Signed-off-by: Henry Schreiner * Update tests/test_ninja.py --------- Signed-off-by: Henry Schreiner --- .github/workflows/build.yml | 12 +-------- .pre-commit-config.yaml | 21 +++++++++++++++- CONTRIBUTING.rst | 2 +- README.rst | 5 ++++ noxfile.py | 12 ++++++--- pyproject.toml | 44 +++++++++++++++++++++++---------- scripts/repair_wheel.py | 3 ++- scripts/update_ninja_version.py | 36 +++++++++++++-------------- src/ninja/__init__.py | 18 ++++++++------ src/ninja/__init__.pyi | 10 -------- src/ninja/__main__.py | 3 ++- src/ninja/ninja_syntax.pyi | 37 +++++++++++++++++++++++++++ tests/__init__.py | 3 ++- tests/test_ninja.py | 5 ++-- 14 files changed, 140 insertions(+), 71 deletions(-) delete mode 100644 src/ninja/__init__.pyi create mode 100644 src/ninja/ninja_syntax.pyi diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a3eda16..ef73db0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,7 +40,7 @@ jobs: fail-fast: false matrix: include: - - os: ubuntu-20.04 + - os: ubuntu-22.04 arch: "x86_64" - os: ubuntu-22.04 arch: "i686" @@ -78,16 +78,6 @@ jobs: name: cibw-wheels-${{ matrix.os }}-${{ matrix.arch }} path: ./wheelhouse/*.whl - - name: Install Ubuntu Python 2.7 - if: matrix.os == 'ubuntu-20.04' - run: | - sudo apt-get update - sudo apt-get install -y --no-install-recommends python2 python3-virtualenv - virtualenv -p python2 ${HOME}/cp27 - ${HOME}/cp27/bin/python -m pip install -U pip - ${HOME}/cp27/bin/python -m pip install -U setuptools wheel - echo "${HOME}/cp27/bin" >> $GITHUB_PATH - - name: Test wheel on host Linux if: runner.os == 'Linux' && matrix.arch == 'x86_64' run: | diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7d51ef6..b9181a9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,10 +12,29 @@ repos: - id: mixed-line-ending - id: requirements-txt-fixer - id: trailing-whitespace - - id: fix-encoding-pragma - repo: https://github.com/astral-sh/ruff-pre-commit rev: "v0.4.5" hooks: - id: ruff args: [--fix, --show-fixes] + +- repo: https://github.com/pre-commit/mirrors-mypy + rev: "v1.10.0" + hooks: + - id: mypy + files: src + args: [] + +- repo: https://github.com/abravalheri/validate-pyproject + rev: "v0.16" + hooks: + - id: validate-pyproject + additional_dependencies: ["validate-pyproject-schema-store[all]"] + +- repo: https://github.com/python-jsonschema/check-jsonschema + rev: "0.28.2" + hooks: + - id: check-dependabot + - id: check-github-workflows + - id: check-readthedocs diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index bd51586..08cfb57 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -102,7 +102,7 @@ Before you submit a pull request, check that it meets these guidelines: your new functionality into a function with a docstring, and add the feature to the list in `README.rst`. -3. The pull request should work for Python 2.7, and 3.6+. +3. The pull request should work for Python 3.7+. Check `GitHub Actions`_ and make sure that the tests pass for all supported Python versions. diff --git a/README.rst b/README.rst index 076ba5b..3d57bfa 100644 --- a/README.rst +++ b/README.rst @@ -48,6 +48,11 @@ Miscellaneous * Source code: https://github.com/scikit-build/ninja-python-distributions * Mailing list: https://groups.google.com/forum/#!forum/scikit-build +Python Version Support +---------------------- + +Versions after 1.11.1 no longer support Python 2-3.6, and require manylinux2010+ on linux. + License ------- diff --git a/noxfile.py b/noxfile.py index 359b425..6392934 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,10 +1,13 @@ -# -*- coding: utf-8 -*- +from __future__ import annotations + import argparse import sys from pathlib import Path import nox +nox.needs_version = ">=2024.4.15" +nox.options.default_venv_backend = "uv|virtualenv" nox.options.sessions = ["lint", "build", "tests"] if sys.platform.startswith("darwin"): @@ -25,7 +28,7 @@ def build(session: nox.Session) -> str: """ Make an SDist and a wheel. Only runs once. """ - global built + global built # noqa: PLW0603 if not built: session.log( "The files produced locally by this job are not intended to be redistributable" @@ -48,7 +51,7 @@ def lint(session: nox.Session) -> str: Run linters on the codebase. """ session.install("pre-commit") - session.run("pre-commit", "run", "-a") + session.run("pre-commit", "run", "-a", *session.posargs) @nox.session @@ -92,7 +95,8 @@ def bump(session: nox.Session) -> None: else: version = args.version - session.install("requests") + deps = nox.project.load_toml("scripts/update_ninja_version.py")["dependencies"] + session.install(*deps) extra = ["--quiet"] if args.commit else [] session.run("python", "scripts/update_ninja_version.py", "--upstream-repository", args.upstream_repository, version, *extra) diff --git a/pyproject.toml b/pyproject.toml index 3e85911..643d29c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,13 +31,14 @@ classifiers = [ "Topic :: Software Development :: Build Tools", "Typing :: Typed", ] +requires-python = ">=3.7" [project.optional-dependencies] test = [ "coverage>=4.2", "importlib_metadata>=2.0", - "pytest-cov>=2.7.1", - "pytest>=4.5.0", + "pytest-cov>=3", + "pytest>=6.0", ] [project.urls] @@ -51,7 +52,7 @@ Homepage = "http://ninja-build.org/" [tool.scikit-build] minimum-version = "0.9" build-dir = "build/{wheel_tag}" -wheel.py-api = "py2.py3" +wheel.py-api = "py3" wheel.expand-macos-universal-tags = true metadata.version.provider = "scikit_build_core.metadata.setuptools_scm" metadata.readme.provider = "scikit_build_core.metadata.fancy_pypi_readme" @@ -101,18 +102,40 @@ MACOSX_DEPLOYMENT_TARGET = "10.9" select = "*-musllinux_*" environment = { LDFLAGS = "-static-libstdc++ -static-libgcc" } + +[tool.pytest.ini_options] +minversion = "6.0" +addopts = ["-ra", "--strict-markers", "--strict-config"] +xfail_strict = true +filterwarnings = ["error"] +log_cli_level = "info" +testpaths = [ "tests" ] + + +[tool.mypy] +warn_unused_configs = true +files = "src" +python_version = "3.8" +strict = true +enable_error_code = ["ignore-without-code", "truthy-bool", "redundant-expr"] +warn_unreachable = true + + [tool.ruff] src = ["src"] [tool.ruff.lint] extend-select = [ - "B", # flake8-bugbear - "I", # isort "ARG", # flake8-unused-arguments + "B", # flake8-bugbear "C4", # flake8-comprehensions + "EXE", # flake8-executable + "G", # flake8-logging-format + "I", # isort "ICN", # flake8-import-conventions "ISC", # flake8-implicit-str-concat - "G", # flake8-logging-format + "NPY", # NumPy specific rules + "PD", # pandas-vet "PGH", # pygrep-hooks "PIE", # flake8-pie "PL", # pylint @@ -120,22 +143,17 @@ extend-select = [ "RET", # flake8-return "RUF", # Ruff-specific "SIM", # flake8-simplify - "EXE", # flake8-executable - "NPY", # NumPy specific rules - "PD", # pandas-vet + "UP", # pyupgrade ] ignore = [ "PLR", # Design related pylint codes "E501", # Line too long - "RUF005", # Python 3 needed - "B904", # Python 3 needed -# "SIM105", # Python 3 needed ] +isort.required-imports = ["from __future__ import annotations"] flake8-unused-arguments.ignore-variadic-names = true [tool.ruff.lint.per-file-ignores] "*.pyi" = ["ARG001"] -"noxfile.py" = ["PLW0603"] # Could be fixed if Python 2 dropped [tool.pytest.ini_config] testpaths = ["tests"] diff --git a/scripts/repair_wheel.py b/scripts/repair_wheel.py index b7e14a0..f4c8ae5 100644 --- a/scripts/repair_wheel.py +++ b/scripts/repair_wheel.py @@ -1,4 +1,5 @@ -# -*- coding: utf-8 -*- +from __future__ import annotations + import argparse import re import shutil diff --git a/scripts/update_ninja_version.py b/scripts/update_ninja_version.py index ef5873d..5904037 100644 --- a/scripts/update_ninja_version.py +++ b/scripts/update_ninja_version.py @@ -1,8 +1,12 @@ -# -*- coding: utf-8 -*- +# /// script +# dependencies = ["requests"] +# /// + """ Command line executable allowing to update NinjaUrls.cmake, documentation and tests given a Ninja version. """ +from __future__ import annotations import argparse import contextlib @@ -12,13 +16,7 @@ import tempfile import textwrap -try: - from requests import request -except ImportError: - raise SystemExit( - "requests not available: " - "consider installing it running 'pip install requests'" - ) +from requests import request ROOT_DIR = os.path.join(os.path.dirname(__file__), "..") @@ -31,7 +29,7 @@ def _log(txt, verbose=True): print(txt) yield if verbose: - print("%s - done" % txt) + print(f"{txt} - done") def _download_file(download_url, filename): @@ -67,7 +65,7 @@ def _hash_sum(filepath, algorithm="sha256", block_size=2 ** 20): def _download_and_compute_sha256(url, filename): filepath = os.path.join(tempfile.gettempdir(), filename) - with _log("Downloading %s" % url): + with _log(f"Downloading {url}"): _download_file(url, filepath) sha256 = _hash_sum(filepath, algorithm="sha256") return url, sha256 @@ -77,7 +75,7 @@ def get_ninja_archive_urls_and_sha256s(upstream_repository, version, verbose=Fal tag_name = f"v{version}" files_base_url = f"https://github.com/{upstream_repository}/archive/{tag_name}" - with _log("Collecting URLs and SHA256s from '%s'" % files_base_url): + with _log(f"Collecting URLs and SHA256s from '{files_base_url}'"): # Get SHA256s and URLs urls = { @@ -87,7 +85,7 @@ def get_ninja_archive_urls_and_sha256s(upstream_repository, version, verbose=Fal if verbose: for identifier, (url, sha256) in urls.items(): - print("[{}]\n{}\n{}\n".format(identifier, url, sha256)) + print(f"[{identifier}]\n{url}\n{sha256}\n") return urls @@ -97,8 +95,8 @@ def generate_cmake_variables(urls_and_sha256s): # Get SHA256s and URLs for var_prefix, urls_and_sha256s_values in urls_and_sha256s.items(): - template_inputs["%s_url" % var_prefix] = urls_and_sha256s_values[0] - template_inputs["%s_sha256" % var_prefix] = urls_and_sha256s_values[1] + template_inputs[f"{var_prefix}_url"] = urls_and_sha256s_values[0] + template_inputs[f"{var_prefix}_sha256"] = urls_and_sha256s_values[1] return textwrap.dedent( """ @@ -118,16 +116,16 @@ def update_cmake_urls_script(upstream_repository, version): cmake_urls_filename = "NinjaUrls.cmake" cmake_urls_filepath = os.path.join(ROOT_DIR, cmake_urls_filename) - msg = "Updating '{}' with Ninja version {}".format(cmake_urls_filename, version) + msg = f"Updating '{cmake_urls_filename}' with Ninja version {version}" with _log(msg), open(cmake_urls_filepath, "w") as cmake_file: cmake_file.write(content) def _update_file(filepath, regex, replacement, verbose=True): - msg = "Updating %s" % os.path.relpath(filepath, ROOT_DIR) + msg = f"Updating {os.path.relpath(filepath, ROOT_DIR)}" with _log(msg, verbose=verbose): pattern = re.compile(regex) - with open(filepath, "r") as doc_file: + with open(filepath) as doc_file: lines = doc_file.readlines() updated_content = [] for line in lines: @@ -138,7 +136,7 @@ def _update_file(filepath, regex, replacement, verbose=True): def update_docs(upstream_repository, version): pattern = re.compile(r"ninja \d+.\d+.\d+(\.[\w\-]+)*") - replacement = "ninja %s" % version + replacement = f"ninja {version}" _update_file( os.path.join(ROOT_DIR, "README.rst"), pattern, replacement) @@ -171,7 +169,7 @@ def update_tests(version): version = ".".join(parts) pattern = re.compile(r'expected_version = "\d+.\d+.\d+(\.[\w\-]+)*"') - replacement = 'expected_version = "%s"' % version + replacement = f'expected_version = "{version}"' _update_file(os.path.join( ROOT_DIR, "tests/test_ninja.py"), pattern, replacement) diff --git a/src/ninja/__init__.py b/src/ninja/__init__.py index ec23419..5a143f8 100644 --- a/src/ninja/__init__.py +++ b/src/ninja/__init__.py @@ -1,8 +1,11 @@ -# -*- coding: utf-8 -*- +from __future__ import annotations + import os import subprocess import sys import sysconfig +from collections.abc import Iterable +from typing import NoReturn from ._version import version as __version__ from .ninja_syntax import Writer, escape, expand @@ -10,11 +13,11 @@ __all__ = ["__version__", "DATA", "BIN_DIR", "ninja", "Writer", "escape", "expand"] -def __dir__(): +def __dir__() -> list[str]: return __all__ -def _get_ninja_dir(): +def _get_ninja_dir() -> str: ninja_exe = "ninja" + sysconfig.get_config_var("EXE") # Default path @@ -27,7 +30,7 @@ def _get_ninja_dir(): user_scheme = sysconfig.get_preferred_scheme("user") elif os.name == "nt": user_scheme = "nt_user" - elif sys.platform.startswith("darwin") and sys._framework: + elif sys.platform.startswith("darwin") and getattr(sys, "_framework", None): user_scheme = "osx_framework_user" else: user_scheme = "posix_user" @@ -47,10 +50,11 @@ def _get_ninja_dir(): BIN_DIR = _get_ninja_dir() -def _program(name, args): + +def _program(name: str, args: Iterable[str]) -> int: cmd = os.path.join(BIN_DIR, name) - return subprocess.call([cmd] + args, close_fds=False) + return subprocess.call([cmd, *args], close_fds=False) -def ninja(): +def ninja() -> NoReturn: raise SystemExit(_program('ninja', sys.argv[1:])) diff --git a/src/ninja/__init__.pyi b/src/ninja/__init__.pyi deleted file mode 100644 index d4c91b2..0000000 --- a/src/ninja/__init__.pyi +++ /dev/null @@ -1,10 +0,0 @@ -from typing import Iterable, NoReturn - -__version__ = tuple[int, int, int] | tuple[int, int, int, int | str] - -DATA = str -BIN_DIR = str - -def _program(name: str, args: Iterable[str]) -> int: ... - -def ninja() -> NoReturn: ... diff --git a/src/ninja/__main__.py b/src/ninja/__main__.py index 76fb29c..87772ab 100644 --- a/src/ninja/__main__.py +++ b/src/ninja/__main__.py @@ -1,4 +1,5 @@ -# -*- coding: utf-8 -*- +from __future__ import annotations + from ninja import ninja if __name__ == '__main__': diff --git a/src/ninja/ninja_syntax.pyi b/src/ninja/ninja_syntax.pyi new file mode 100644 index 0000000..8f84bf7 --- /dev/null +++ b/src/ninja/ninja_syntax.pyi @@ -0,0 +1,37 @@ +from collections.abc import Mapping, Sequence +from os import PathLike + +def escape_path(word: str) -> str: ... + +class Writer: + output: str + width: int + + def __init__(self, output: str, width: int = ...): ... + def newline(self) -> None: ... + def comment(self, text: str) -> None: ... + def variable(self, key: str, value: list[str] | str, indent: int = ...) -> None: ... + def pool(self, name: str, depth: int) -> None: ... + def rule(self, name: str, command: str, description: str | None = None, + depfile: str | None = None, generator: bool = False, + pool: str | None = None, restat: bool = False, + rspfile: str | None = None, rspfile_content: str | None = None, + deps: str | None = None) -> None: + ... + + def build(self, outputs: list[str], rule: str, inputs: list[str] | None = None, + implicit: list[str] | None = None, order_only: list[str] | None = None, + variables: dict[str, str] | None = None, + implicit_outputs: list[str] | None = None, + pool: str | None = None, dyndep: str | None = None) -> None: + ... + def include(self, path: str | PathLike[str]) -> None: ... + def subninja(self, path: str | PathLike[str]) -> None: ... + def default(self, paths: Sequence[str | PathLike[str]]) -> None: ... + def close(self) -> None: ... + +def as_list(input: None | list[str] | str) -> list[str]: ... + +def escape(string: str) -> str: ... + +def expand(string: str, vars: Mapping[str, str], local_vars: Mapping[str, str]=...) -> str: ... diff --git a/tests/__init__.py b/tests/__init__.py index 1edb21d..6381083 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,4 +1,5 @@ -# -*- coding: utf-8 -*- +from __future__ import annotations + import sys from contextlib import contextmanager diff --git a/tests/test_ninja.py b/tests/test_ninja.py index 57ddf85..65b2fba 100644 --- a/tests/test_ninja.py +++ b/tests/test_ninja.py @@ -1,4 +1,5 @@ -# -*- coding: utf-8 -*- +from __future__ import annotations + import os import subprocess import sys @@ -14,7 +15,7 @@ def _run(program, args): func = getattr(ninja, program) - args = ["%s.py" % program] + args + args = [f"{program}.py", *args] with push_argv(args), pytest.raises(SystemExit) as excinfo: func() assert excinfo.value.code == 0