Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Maintenance #3

Merged
merged 10 commits into from
Mar 25, 2024
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
135 changes: 86 additions & 49 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,14 @@ on:
options:
- all
- ubuntu-latest
- macos-13 # TODO(matt): switch to 14 once this is fixed: https://github.com/actions/setup-python/issues/825
- macos-14
- windows-latest
python_version:
type: choice
description: Python version to test with
default: '3.11'
default: '3.12'
options:
- 'all'
- '3.8'
- '3.9'
- '3.10'
- '3.11'
Expand All @@ -40,14 +39,29 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref_name }}-${{ github.event.pull_request.number }}
cancel-in-progress: true

permissions:
contents: read
checks: write

jobs:
generate-matrix:
name: Generate Matrix
runs-on: ubuntu-latest
outputs:
os: ${{ steps.generate-matrix.outputs.os }}
python-version: ${{ steps.generate-matrix.outputs.python-version }}
platform: ${{ steps.generate-matrix.outputs.platform }}
fail-fast: ${{ steps.generate-matrix.outputs.fail-fast }}
env:
OS_MATRIX: |
- ubuntu-latest
- macos-14
- windows-latest
PYTHON_VERSION: |
- '3.9'
- '3.10'
- '3.11'
- '3.12'
- 'pypy3.9'
- 'pypy3.10'
steps:
- uses: actions/setup-node@v4
with:
Expand All @@ -58,77 +72,98 @@ jobs:
uses: actions/github-script@v7
with:
script: |
const yaml = require('js-yaml')
const OS = yaml.load(process.env.OS_MATRIX)
const PYTHON_VERSIONS = yaml.load(process.env.PYTHON_VERSION)
const yaml = require("js-yaml");

const ALL_OS = yaml.load(process.env.OS_MATRIX);
const ALL_PYTHON_VERSIONS = yaml.load(process.env.PYTHON_VERSION);

let platforms = [];
let fail_fast = false;

let all_platforms = [];
ALL_OS.forEach(os => {
ALL_PYTHON_VERSIONS.forEach(python_version => {
if (os === "macos-14") {
if (python_version.startsWith("pypy")) {
return; // PyPy is only built for x64
} else if (parseInt(python_version.slice(2)) < 11) {
return; // macOS ARM runners only have Python 3.11+
}
}
all_platforms.push({
"os": os,
"python-version": python_version
})
})
});

core.info(`job triggered by: ${context.eventName}`);
if (context.eventName == 'workflow_dispatch') {
const input_os = "${{ github.event.inputs.os }}";
const input_python_version = "${{ github.event.inputs.python_version }}";
core.setOutput('os', input_os == "all" ? OS : [input_os]);
core.setOutput('python-version', input_python_version == "all" ? PYTHON_VERSIONS : [input_python_version]);
core.setOutput('fail-fast', "${{ github.event.inputs.fail_fast }}");
const INPUT_OS = "${{ github.event.inputs.os }}";
const INPUT_PYTHON_VERSION = "${{ github.event.inputs.python_version }}";
const INPUT_FAIL_FAST = "${{ github.event.inputs.fail_fast }}";

platforms = all_platforms.filter(platform => (
(INPUT_OS == "all" || INPUT_OS.includes(platform.os))
&& (INPUT_PYTHON_VERSION == "all" || INPUT_PYTHON_VERSION.includes(platform['python-version']))
));
fail_fast = INPUT_FAIL_FAST;

} else if (context.eventName == 'merge_group') {
core.setOutput('os', OS)
core.setOutput('python-version', PYTHON_VERSIONS)
core.setOutput('fail-fast', 'false')
platforms = all_platforms;
fail_fast = false;

} else if (context.eventName == 'pull_request') {
const { data: { labels: labels } } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number
})
const labelNames = labels.map(label => label.name)
if (labelNames.includes('CI-no-fail-fast')) {
core.setOutput('fail-fast', 'false')
});
const label_names = labels.map(label => label.name);

if (label_names.includes("CI-test-all")) {
platforms = all_platforms;
} else {
// assumes versions are listed in ascending order
const latest_pypy_index = ALL_PYTHON_VERSIONS.findLastIndex(version => version.startsWith('pypy'));
const latest_cpython_index = ALL_PYTHON_VERSIONS.findLastIndex(version => /^\d/.test(version));
const python_versions = [ALL_PYTHON_VERSIONS[latest_pypy_index], ALL_PYTHON_VERSIONS[latest_cpython_index]];
platforms = all_platforms.filter(platform => (
platform.os == "ubuntu-latest" && python_versions.includes(platform["python-version"])
));
}
// Only run latest CPython and PyPy tests on pull requests
const firstPyPy = PYTHON_VERSIONS.findIndex(version => version.startsWith('pypy'))
const pythonVersions = [PYTHON_VERSIONS[firstPyPy - 1], PYTHON_VERSIONS[PYTHON_VERSIONS.length - 1]]
core.setOutput('python-version', pythonVersions)
fail_fast = !label_names.includes("CI-no-fail-fast");
}
env:
OS_MATRIX: |
- ubuntu-latest
- macos-13
- windows-latest
PYTHON_VERSION: |
- '3.8'
- '3.9'
- '3.10'
- '3.11'
- '3.12'
- 'pypy3.9'
- 'pypy3.10'

core.info(`platforms = ${JSON.stringify(platforms)}`);
core.setOutput("platform", platforms);

core.info(`fail fast = ${fail_fast}`);
core.setOutput("fail-fast", fail_fast);

test:
name: Test
needs: [generate-matrix]
strategy:
fail-fast: ${{ needs.generate-matrix.outputs.fail-fast != 'false' }}
matrix:
os: ${{ fromJson(needs.generate-matrix.outputs.os) }}
python-version: ${{ fromJson(needs.generate-matrix.outputs.python-version) }}
runs-on: ${{ matrix.os }}
platform: ${{ fromJson(needs.generate-matrix.outputs.platform) }}
runs-on: ${{ matrix.platform.os }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
architecture: "x64"
python-version: ${{ matrix.platform.python-version }}
cache: "pip"
- uses: dtolnay/rust-toolchain@stable
id: rustup
- name: Install aarch64-apple-darwin Rust target
if: startsWith(matrix.os, 'macos')
if: startsWith(matrix.platform.os, 'macos')
run: rustup target add aarch64-apple-darwin
- name: Setup Xcode env
if: startsWith(matrix.os, 'macos')
if: startsWith(matrix.platform.os, 'macos')
shell: bash
run: |
set -ex
Expand All @@ -139,7 +174,7 @@ jobs:
echo "SDKROOT=$(xcrun --sdk macosx --show-sdk-path)" >> "${GITHUB_ENV}"
# To save disk space
- name: Disable debuginfo on Windows
if: startsWith(matrix.os, 'windows')
if: startsWith(matrix.platform.os, 'windows')
run: echo "RUSTFLAGS="-C debuginfo=0"" >> $GITHUB_ENV
- name: Install test requirements
run: cd tests && pip install --disable-pip-version-check -r requirements.txt
Expand All @@ -153,21 +188,23 @@ jobs:

python tests/runner.py \
--workspace ./test_workspace \
--name "${{ matrix.os }}_${{ matrix.python-version }}" \
--name "${{ matrix.platform.os }}_${{ matrix.platform.python-version }}" \
${EXTRA_ARGS} \
"${{ github.event.inputs.test_specification || 'tests/test_import_hook' }}"
- name: Upload HTML test report
uses: actions/upload-artifact@v4
if: failure()
with:
name: ${{ matrix.os }}-${{ matrix.python-version }}-test-report.html
name: ${{ matrix.platform.os }}-${{ matrix.platform.python-version }}-test-report.html
path: './test_workspace/report.html'
- name: Publish Test Report
uses: mikepenz/action-junit-report@v4
if: success() || failure()
if: always()
with:
report_paths: './test_workspace/reports/*.xml'
test_files_prefix: "${{ matrix.os }}_${{ matrix.python-version }}"
test_files_prefix: "${{ matrix.platform.os }}_${{ matrix.platform.python-version }}"
check_annotations: false
job_summary: true


conclusion:
Expand Down
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ repos:
- id: trailing-whitespace
- id: mixed-line-ending
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.2.2
rev: v0.3.4
hooks:
- id: ruff-format
- id: ruff
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.8.0
rev: v1.9.0
hooks:
# note: mypy runs in an isolated environment and so has no access to third party packages
- id: mypy
Expand Down
7 changes: 3 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ authors = [
]
readme = "README.md"
version = "0.1.0"
requires-python = ">=3.8"
requires-python = ">=3.9"
dependencies = [
"filelock",
"tomli>=1.1.0 ; python_version<'3.11'"
Expand All @@ -23,7 +23,6 @@ classifiers = [

[project.urls]
Homepage = "https://github.com/PyO3/maturin-import-hook"
# TODO(matt): should the import hook have its own documentation
Documentation = "https://maturin.rs"
Repository = "https://github.com/PyO3/maturin-import-hook.git"
Issues = "https://github.com/PyO3/maturin-import-hook/issues"
Expand All @@ -34,7 +33,7 @@ where = ["src"]

[tool.ruff]
line-length = 120
target-version = "py38"
target-version = "py39"

[tool.ruff.format]
preview = true
Expand Down Expand Up @@ -70,7 +69,7 @@ ignore = [
]

[tool.mypy]
python_version = "3.8"
python_version = "3.9"
strict = true
allow_redefinition = true
exclude = [
Expand Down
3 changes: 1 addition & 2 deletions src/maturin_import_hook/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import shutil
import subprocess
from pathlib import Path
from typing import Dict

from maturin_import_hook._building import get_default_build_dir
from maturin_import_hook._site import (
Expand Down Expand Up @@ -119,7 +118,7 @@ def _dir_size_mib(dir_path: Path) -> str:
return f"{cache_size / (1024 * 1024):.2f} MiB"


def _print_info(info: Dict[str, object], format_name: str) -> None:
def _print_info(info: dict[str, object], format_name: str) -> None:
if format_name == "text":
for k, v in info.items():
print(f"{k}: {v}")
Expand Down
44 changes: 8 additions & 36 deletions src/maturin_import_hook/_building.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
import platform
import re
import shutil
import site
import subprocess
import sys
import zipfile
from collections.abc import Generator, Iterable
from contextlib import contextmanager
from dataclasses import dataclass
from operator import itemgetter
from pathlib import Path
from typing import Any, Dict, Generator, Iterable, List, Optional, Tuple
from typing import Any, Optional

import filelock

Expand All @@ -31,10 +31,10 @@ class BuildStatus:

build_mtime: float
source_path: Path
maturin_args: List[str]
maturin_args: list[str]
maturin_output: str

def to_json(self) -> Dict[str, Any]:
def to_json(self) -> dict[str, Any]:
return {
"build_mtime": self.build_mtime,
"source_path": str(self.source_path),
Expand All @@ -43,7 +43,7 @@ def to_json(self) -> Dict[str, Any]:
}

@staticmethod
def from_json(json_data: Dict[Any, Any]) -> Optional["BuildStatus"]:
def from_json(json_data: dict[Any, Any]) -> Optional["BuildStatus"]:
try:
return BuildStatus(
build_mtime=json_data["build_mtime"],
Expand Down Expand Up @@ -186,35 +186,7 @@ def develop_build_project(
return output


# TODO(matt): remove once a maturin release can create editable installs and raise minimum supported version
def fix_direct_url(project_dir: Path, package_name: str) -> None:
"""Seemingly due to a bug, installing with `pip install -e` will write the correct entry into `direct_url.json` to
point at the project directory, but calling `maturin develop` does not currently write this value correctly.
"""
logger.debug("fixing direct_url for %s", package_name)
for path in site.getsitepackages():
dist_info = next(Path(path).glob(f"{package_name}-*.dist-info"), None)
if dist_info is None:
continue
direct_url_path = dist_info / "direct_url.json"
try:
with direct_url_path.open() as f:
direct_url = json.load(f)
except OSError:
continue
url = project_dir.as_uri()
if direct_url.get("url") != url:
logger.debug("fixing direct_url.json for package %s", package_name)
logger.debug('"%s" -> "%s"', direct_url.get("url"), url)
direct_url = {"dir_info": {"editable": True}, "url": url}
try:
with direct_url_path.open("w") as f:
json.dump(direct_url, f)
except OSError:
return


def find_maturin(lower_version: Tuple[int, int, int], upper_version: Tuple[int, int, int]) -> Path:
def find_maturin(lower_version: tuple[int, int, int], upper_version: tuple[int, int, int]) -> Path:
logger.debug("searching for maturin")
maturin_path_str = shutil.which("maturin")
if maturin_path_str is None:
Expand All @@ -231,7 +203,7 @@ def find_maturin(lower_version: Tuple[int, int, int], upper_version: Tuple[int,
raise MaturinError(msg)


def get_maturin_version(maturin_path: Path) -> Tuple[int, int, int]:
def get_maturin_version(maturin_path: Path) -> tuple[int, int, int]:
success, output = run_maturin(maturin_path, ["--version"])
if not success:
msg = f'running "{maturin_path} --version" failed'
Expand All @@ -243,7 +215,7 @@ def get_maturin_version(maturin_path: Path) -> Tuple[int, int, int]:
return int(match.group(1)), int(match.group(2)), int(match.group(3))


def run_maturin(maturin_path: Path, args: List[str]) -> Tuple[bool, str]:
def run_maturin(maturin_path: Path, args: list[str]) -> tuple[bool, str]:
command = [str(maturin_path), *args]
if logger.isEnabledFor(logging.DEBUG):
logger.debug("running command: %s", subprocess.list2cmdline(command))
Expand Down
Loading
Loading