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
97 changes: 45 additions & 52 deletions .github/workflows/dist.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,21 @@ jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: psf/black@stable

check-mypy:
runs-on: macos-13
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
# - uses: jpetrucciani/mypy-check@0.930
# .. can't use that because we need to install pytest
- uses: actions/setup-python@v5
- uses: actions/setup-python@v6
with:
python-version: "3.8"
python-version: "3.10"
- name: Install requirements
run: |
pip --disable-pip-version-check install mypy .
pip --disable-pip-version-check install mypy .[test]
- name: Run mypy
run: |
mypy .
Expand All @@ -43,80 +43,73 @@ jobs:
build:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 0

- uses: actions/setup-python@v5
- uses: actions/setup-python@v6
with:
python-version: "3.8"
python-version: "3.10"

- run: pipx run build

- name: Upload build artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: dist
path: dist

# test:
# needs: [build]
# runs-on: ${{ matrix.os }}
# strategy:
# matrix:
# os: [windows-latest, macos-13, ubuntu-20.04]
# python_version: [3.6, 3.7, 3.8, 3.9, "3.10", "3.11", "3.12"]
# architecture: [x86, x64]
# exclude:
# - os: macos-13
# architecture: x86
# - os: ubuntu-20.04
# architecture: x86
test:
needs: [build]
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-22.04, macos-14, windows-2022]

# steps:
# - uses: actions/checkout@v4
# with:
# submodules: recursive
# fetch-depth: 0
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 0

# - uses: actions/setup-python@v5
# with:
# python-version: ${{ matrix.python_version }}
# architecture: ${{ matrix.architecture }}
- uses: actions/setup-python@v6
with:
python-version: "3.10"

# - name: Download build artifacts
# uses: actions/download-artifact@v4
# with:
# name: dist
# path: dist

# - name: Install test dependencies
# run: python -m pip --disable-pip-version-check install -r tests/requirements.txt
- name: Download build artifacts
uses: actions/download-artifact@v7
with:
name: dist
path: dist

# - name: Setup MSVC compiler
# uses: ilammy/msvc-dev-cmd@v1
# if: matrix.os == 'windows-latest'
- name: Setup MSVC
uses: bus1/cabuild/action/msdevshell@e22aba57d6e74891d059d66501b6b5aed8123c4d # v1
with:
architecture: x64
if: runner.os == 'Windows'

# - name: Test wheel
# shell: bash
# run: |
# cd dist
# python -m pip --disable-pip-version-check install *.whl
# cd ../tests
# python -m pytest
- name: Install wheel
shell: bash
run: |
cd dist
python -m pip --disable-pip-version-check install $(ls *.whl)[test]

- name: Test wheel
run: python -m pytest -v

publish:
runs-on: ubuntu-latest
# needs: [check, check-mypy, check-doc, build, test]
needs: [check, check-mypy, build]
needs: [check, check-mypy, build, test]
permissions:
id-token: write
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')

steps:
- name: Download build artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@v7
with:
name: dist
path: dist
Expand Down
18 changes: 17 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ name = "hatch-nativelib"
dynamic = ["version"]
description = "Hatchling plugin with utilities for native libraries"
readme = "README.md"
requires-python = ">=3.8"
requires-python = ">=3.10"
license = "BSD-3-Clause"
authors = [
{name = "Dustin Spicuzza", email = "dustin@virtualroadside.com"},
Expand All @@ -29,6 +29,15 @@ dependencies = [
[project.urls]
"Source code" = "https://github.com/robotpy/hatch-nativelib"

[project.optional-dependencies]
test = [
"build",
"environment-helpers",
"hatch-meson",
"pytest",
"ninja"
]

[project.entry-points.hatch]
nativelib = "hatch_nativelib.hooks"

Expand All @@ -38,6 +47,13 @@ source = "vcs"
[tool.hatch.build.hooks.vcs]
version-file = "src/hatch_nativelib/_version.py"

[tool.mypy]
exclude = ["^tests/packages/"]

[[tool.mypy.overrides]]
module = ["environment_helpers.*"]
follow_untyped_imports = true

[[tool.mypy.overrides]]
module = ["pkgconf.*"]
follow_untyped_imports = true
98 changes: 98 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
from __future__ import annotations

import os
import pathlib
import shutil
import subprocess
import sys
import textwrap
import zipfile
from dataclasses import dataclass

import pytest
from environment_helpers import Environment, VirtualEnvironment

REPO_ROOT = pathlib.Path(__file__).resolve().parents[1]
TEST_PACKAGES_ROOT = REPO_ROOT / "tests" / "packages"


def shared_library_filename(name: str) -> str:
if sys.platform == "win32":
return f"{name}.dll"
if sys.platform == "darwin":
return f"lib{name}.dylib"
return f"lib{name}.so"


def _write_text(path: pathlib.Path, content: str) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(textwrap.dedent(content).lstrip(), encoding="utf-8")


@dataclass
class TempProject:
root: pathlib.Path
env: Environment

def write(self, relative_path: str, content: str) -> pathlib.Path:
path = self.root / relative_path
_write_text(path, content)
return path

def read(self, relative_path: str) -> str:
return (self.root / relative_path).read_text(encoding="utf-8")

def copy_package_fixture(
self, fixture_name: str, destination: str | pathlib.Path = "."
) -> pathlib.Path:
src = TEST_PACKAGES_ROOT / fixture_name
if not src.is_dir():
raise FileNotFoundError(f"package fixture not found: {fixture_name}")

dst = self.root / destination
dst.mkdir(parents=True, exist_ok=True)
shutil.copytree(src, dst, dirs_exist_ok=True)
return dst

def run(
self, *args: str, cwd: pathlib.Path | None = None
) -> subprocess.CompletedProcess[str]:
return subprocess.run(
[str(self.env.interpreter), *args],
cwd=cwd or self.root,
env=dict(self.env.env),
text=True,
capture_output=True,
)

def install_current_project(self) -> None:
self.env.install([str(REPO_ROOT), "build", "hatch-meson", "ninja"])

def build_wheel(self) -> subprocess.CompletedProcess[str]:
return self.run("-m", "build", "--wheel", "--no-isolation")

def dist_wheels(self) -> list[pathlib.Path]:
return sorted((self.root / "dist").glob("*.whl"))

def wheel_contents(self, wheel: pathlib.Path) -> list[str]:
with zipfile.ZipFile(wheel) as zf:
return sorted(zf.namelist())

def installed_package_root(self, package: str) -> pathlib.Path:
code = (
"import importlib, pathlib; "
f"mod = importlib.import_module({package!r}); "
"print(pathlib.Path(mod.__file__).parent)"
)
result = self.run("-c", code)
result.check_returncode()
return pathlib.Path(result.stdout.strip())


@pytest.fixture
def project_factory(tmp_path: pathlib.Path) -> TempProject:
venv_path = tmp_path / ".venv"
env = VirtualEnvironment.create_venv(venv_path, with_pip=True)
project = TempProject(tmp_path, env)
project.install_current_project()
return project
37 changes: 37 additions & 0 deletions tests/packages/consumer-pkg/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
project('consumer-pkg', 'c')

py = import('python').find_installation(pure: false)
consumer_inc = include_directories('src/consumer_pkg/include')
provider_dep = dependency('provider', method: 'pkg-config')

if meson.get_compiler('c').get_id() in ['msvc', 'clang-cl', 'intel-cl']
export_dll_args = ['-DCONSUMER_DLL_EXPORTS']
import_dll_args = ['-DCONSUMER_DLL_IMPORTS']
else
export_dll_args = []
import_dll_args = []
endif

consumer_lib = shared_library(
'consumer',
'src/consumer_pkg/consumer.c',
c_args: export_dll_args,
dependencies: [provider_dep],
include_directories: consumer_inc,
install: true,
install_dir: py.get_install_dir() / 'consumer_pkg',
)

consumer_dep = declare_dependency(
compile_args: import_dll_args,
include_directories: consumer_inc,
link_with: consumer_lib,
)

py.extension_module(
'_consumer_ext',
'src/consumer_pkg/consumer_module.c',
dependencies: [consumer_dep],
install: true,
subdir: 'consumer_pkg',
)
19 changes: 19 additions & 0 deletions tests/packages/consumer-pkg/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[build-system]
requires = ["hatchling", "hatch-meson", "hatch-nativelib", "ninja"]
build-backend = "hatchling.build"

[project]
name = "consumer-pkg"
version = "1.0.0"
description = "Consumer package"

[tool.hatch.build.targets.wheel]
packages = ["src/consumer_pkg"]

[tool.hatch.build.hooks.meson]

[[tool.hatch.build.hooks.nativelib.pcfile]]
pcfile = "src/consumer_pkg/consumer.pc"
shared_libraries = ["consumer"]
requires = ["provider"]
extra_cflags = "-DCONSUMER_DLL_IMPORTS"
2 changes: 2 additions & 0 deletions tests/packages/consumer-pkg/src/consumer_pkg/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import consumer_pkg._init_consumer
from ._consumer_ext import consumer_twice_plus_one
6 changes: 6 additions & 0 deletions tests/packages/consumer-pkg/src/consumer_pkg/consumer.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#include "consumer.h"
#include "provider.h"

int consumer_twice_plus_one(int value) {
return provider_add(value, value) + 1;
}
29 changes: 29 additions & 0 deletions tests/packages/consumer-pkg/src/consumer_pkg/consumer_module.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "consumer.h"

static PyObject *wrap_consumer_twice_plus_one(PyObject *self, PyObject *args) {
int value;
if (!PyArg_ParseTuple(args, "i", &value)) {
return NULL;
}

return PyLong_FromLong(consumer_twice_plus_one(value));
}

static PyMethodDef consumer_methods[] = {
{"consumer_twice_plus_one", wrap_consumer_twice_plus_one, METH_VARARGS, "Compute a provider-backed value."},
{NULL, NULL, 0, NULL},
};

static struct PyModuleDef consumer_module = {
PyModuleDef_HEAD_INIT,
"_consumer_ext",
NULL,
-1,
consumer_methods,
};

PyMODINIT_FUNC PyInit__consumer_ext(void) {
return PyModule_Create(&consumer_module);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#pragma once

#include "consumer_dll.h"

CONSUMER_DLL int consumer_twice_plus_one(int value);
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#pragma once

#if defined(_WIN32) && defined(CONSUMER_DLL_EXPORTS)
#define CONSUMER_DLL __declspec(dllexport)
#elif defined(_WIN32) && defined(CONSUMER_DLL_IMPORTS)
#define CONSUMER_DLL __declspec(dllimport)
#else
#define CONSUMER_DLL
#endif
Loading
Loading