Skip to content

Commit 68b4558

Browse files
committed
Move to pyproject.toml; CI python 3.12.
1 parent e771c74 commit 68b4558

35 files changed

+258
-270
lines changed

.github/workflows/build.yml

+3-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ jobs:
88
fail-fast: false
99
matrix:
1010
os: [ubuntu-20.04, macos-11, windows-2019]
11-
python-version: ["3.8", "3.9", "3.10", "3.11"]
11+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
1212
runs-on: ${{ matrix.os }}
1313
steps:
1414
- uses: actions/checkout@v3
@@ -21,7 +21,7 @@ jobs:
2121
set -x &&
2222
2323
export DISTUTILS_DEBUG=1 &&
24-
python -mpip install --upgrade pip setuptools &&
24+
python -mpip install --upgrade pip &&
2525
2626
case "$(python -c 'import sys; print(sys.platform)')" in
2727
linux)
@@ -41,6 +41,7 @@ jobs:
4141
win32)
4242
# Don't install inkscape (see note in run-mpl-test-suite.py).
4343
# Skip known-bad version of numpy.
44+
python -mpip install setuptools &&
4445
python tools/build-windows-wheel.py &&
4546
choco install --no-progress ghostscript &&
4647
pip install 'numpy!=1.19.4'
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

src/_os.cpp renamed to ext/_os.cpp

File renamed without changes.

src/_os.h renamed to ext/_os.h

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

src/_raqm.h renamed to ext/_raqm.h

File renamed without changes.
File renamed without changes.
File renamed without changes.

src/_util.h renamed to ext/_util.h

File renamed without changes.

lib/mplcairo/.gitignore

-2
This file was deleted.

pyproject.toml

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
[build-system]
2+
requires = [
3+
"setuptools>=62",
4+
"setuptools_scm[toml]>=6.2",
5+
"pybind11>=2.8.0",
6+
"pycairo>=1.16.0; os_name == 'posix'", # Removed for manylinux build.
7+
]
8+
build-backend = "setuptools.build_meta"
9+
10+
[project]
11+
name = "mplcairo"
12+
description = "A (new) cairo backend for Matplotlib."
13+
readme = "README.rst"
14+
authors = [{name = "Antony Lee"}]
15+
license = {text = "MIT"}
16+
classifiers = [
17+
"Development Status :: 4 - Beta",
18+
"Framework :: Matplotlib",
19+
"License :: OSI Approved :: MIT License",
20+
"Programming Language :: Python :: 3",
21+
]
22+
requires-python = ">=3.8"
23+
dependencies = [
24+
"matplotlib>=2.2",
25+
"pillow", # Already a dependency of mpl>=3.3.
26+
"pycairo>=1.16.0; os_name == 'posix'",
27+
]
28+
dynamic = ["version"]
29+
30+
[tool.setuptools_scm]
31+
version_scheme = "post-release"
32+
local_scheme = "node-and-date"
33+
fallback_version = "0+unknown"
34+
35+
[tool.coverage.run]
36+
branch = true
37+
source_pkgs = ["mplcairo"]
38+
39+
[tool.pytest.ini_options]
40+
filterwarnings = [
41+
"error",
42+
"ignore::DeprecationWarning",
43+
"error::DeprecationWarning:mplcairo",
44+
]

run-mpl-test-suite.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import pytest
2626

2727

28+
_UNUSED_XFAILS = []
2829
_IGNORED_FAILURES = {}
2930

3031

@@ -242,19 +243,19 @@ def pytest_collection_modifyitems(session, config, items):
242243
markers.append(item)
243244
item.add_marker(marker)
244245
if config.getoption("file_or_dir") == ["matplotlib"]:
245-
invalid_markers = (
246+
_UNUSED_XFAILS[:] = (
246247
({*module_markers} - {item.module.__name__ for item in markers})
247248
| ({*nodeid_markers}
248249
- {item.nodeid for item in markers}
249250
- {item.nodeid.split("[")[0] + "[" for item in markers
250251
if "[" in item.nodeid}))
251-
if invalid_markers:
252-
warnings.warn("Unused xfails:\n {}"
253-
.format("\n ".join(sorted(invalid_markers))))
254252

255253

256254
def pytest_terminal_summary(terminalreporter, exitstatus):
257255
write = terminalreporter.write
256+
if _UNUSED_XFAILS:
257+
write("Unused xfails:\n {}"
258+
.format("\n ".join(sorted(_UNUSED_XFAILS))))
258259
if _IGNORED_FAILURES:
259260
write("\n"
260261
"Ignored the following image comparison failures:\n"

setup.py

+110-115
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
55
Environment variables:
66
7-
MPLCAIRO_MANYLINUX
8-
If set, build a manylinux wheel: pycairo is not declared as setup_requires.
7+
MPLCAIRO_NO_PYCAIRO
8+
If set, pycairo is not a build requirement; its include path is configured
9+
externally.
910
1011
MPLCAIRO_NO_UNITY_BUILD
1112
If set, compile the various cpp files separately, instead of as a single
@@ -23,28 +24,82 @@
2324
import subprocess
2425
from subprocess import CalledProcessError
2526
import sys
27+
from tempfile import TemporaryDirectory
28+
import tokenize
2629
import urllib.request
2730

28-
if sys.platform == "darwin":
29-
os.environ.setdefault("CC", "clang")
30-
# Funnily enough, distutils uses $CC to compile c++ extensions but
31-
# $CXX to *link* such extensions... (Moreover, it does some funky
32-
# changes to $CXX if either $CC or $CXX has multiple words -- see e.g.
33-
# https://bugs.python.org/issue6863.)
34-
os.environ.setdefault("CXX", "clang")
35-
36-
from setupext import Extension, build_ext, find_packages, setup
31+
import setuptools
32+
from setuptools import Distribution
33+
from pybind11.setup_helpers import Pybind11Extension
34+
if os.environ.get("MPLCAIRO_NO_PYCAIRO", ""):
35+
cairo = None
36+
else:
37+
import cairo
3738

3839

3940
MIN_CAIRO_VERSION = "1.13.1" # Also in _feature_tests.cpp.
4041
MIN_RAQM_VERSION = "0.7.0"
41-
MANYLINUX = bool(os.environ.get("MPLCAIRO_MANYLINUX", ""))
4242
UNITY_BUILD = not bool(os.environ.get("MPLCAIRO_NO_UNITY_BUILD"))
4343

4444

4545
def get_pkgconfig(*args):
46-
return shlex.split(subprocess.check_output(["pkg-config", *args],
47-
universal_newlines=True))
46+
return shlex.split(
47+
subprocess.check_output(["pkg-config", *args], text=True))
48+
49+
50+
def gen_extension(tmpdir):
51+
ext = Pybind11Extension(
52+
"mplcairo._mplcairo",
53+
sources=(
54+
["ext/_unity_build.cpp"] if UNITY_BUILD else
55+
sorted({*map(str, Path("ext").glob("*.cpp"))}
56+
- {"ext/_unity_build.cpp"})),
57+
depends=[
58+
"setup.py",
59+
*map(str, Path("ext").glob("*.h")),
60+
*map(str, Path("ext").glob("*.cpp")),
61+
],
62+
cxx_std=17,
63+
include_dirs=[cairo.get_include()] if cairo else [],
64+
)
65+
66+
# NOTE: Versions <= 8.2 of Arch Linux's python-pillow package included
67+
# *into a non-overridable distutils header directory* a ``raqm.h`` that
68+
# is both invalid (https://bugs.archlinux.org/task/57492) and outdated
69+
# (missing a declaration for `raqm_version_string`). It is thus not
70+
# possible to build mplcairo with such an old distro package installed.
71+
try:
72+
get_pkgconfig(f"raqm >= {MIN_RAQM_VERSION}")
73+
except (FileNotFoundError, CalledProcessError):
74+
(tmpdir / "raqm-version.h").write_text("") # Touch it.
75+
with urllib.request.urlopen(
76+
f"https://raw.githubusercontent.com/HOST-Oman/libraqm/"
77+
f"v{MIN_RAQM_VERSION}/src/raqm.h") as request:
78+
(tmpdir / "raqm.h").write_bytes(request.read())
79+
ext.include_dirs += [tmpdir]
80+
else:
81+
ext.extra_compile_args += get_pkgconfig("--cflags", "raqm")
82+
83+
if os.name == "posix":
84+
get_pkgconfig(f"cairo >= {MIN_CAIRO_VERSION}")
85+
ext.extra_compile_args += [
86+
"-flto", "-Wall", "-Wextra", "-Wpedantic",
87+
*get_pkgconfig("--cflags", "cairo"),
88+
]
89+
ext.extra_link_args += ["-flto"]
90+
91+
elif os.name == "nt":
92+
# Windows conda path for FreeType.
93+
ext.include_dirs += [Path(sys.prefix, "Library/include")]
94+
ext.extra_compile_args += [
95+
"/experimental:preprocessor",
96+
"/wd4244", "/wd4267", # cf. gcc -Wconversion.
97+
]
98+
ext.libraries += ["psapi", "cairo", "freetype"]
99+
# Windows conda path for FreeType -- needs to be str, not Path.
100+
ext.library_dirs += [str(Path(sys.prefix, "Library/lib"))]
101+
102+
return ext
48103

49104

50105
@functools.lru_cache(1)
@@ -61,69 +116,7 @@ def paths_from_link_libpaths():
61116
return paths
62117

63118

64-
class build_ext(build_ext):
65-
66-
def finalize_options(self):
67-
import cairo
68-
from pybind11.setup_helpers import Pybind11Extension
69-
70-
self.distribution.ext_modules[:] = ext, = [Pybind11Extension(
71-
"mplcairo._mplcairo",
72-
sources=(
73-
["src/_unity_build.cpp"] if UNITY_BUILD else
74-
sorted({*map(str, Path("src").glob("*.cpp"))}
75-
- {"src/_unity_build.cpp"})),
76-
depends=[
77-
"setup.py",
78-
*map(str, Path("src").glob("*.h")),
79-
*map(str, Path("src").glob("*.cpp")),
80-
],
81-
cxx_std=17,
82-
include_dirs=[cairo.get_include()],
83-
)]
84-
85-
# NOTE: Versions <= 8.2 of Arch Linux's python-pillow package included
86-
# *into a non-overridable distutils header directory* a ``raqm.h`` that
87-
# is both invalid (https://bugs.archlinux.org/task/57492) and outdated
88-
# (missing a declaration for `raqm_version_string`). It is thus not
89-
# possible to build mplcairo with such an old distro package installed.
90-
try:
91-
get_pkgconfig(f"raqm >= {MIN_RAQM_VERSION}")
92-
except (FileNotFoundError, CalledProcessError):
93-
tmp_include_dir = Path(
94-
self.get_finalized_command("build").build_base, "include")
95-
tmp_include_dir.mkdir(parents=True, exist_ok=True)
96-
(tmp_include_dir / "raqm-version.h").write_text("") # Touch it.
97-
with urllib.request.urlopen(
98-
f"https://raw.githubusercontent.com/HOST-Oman/libraqm/"
99-
f"v{MIN_RAQM_VERSION}/src/raqm.h") as request, \
100-
(tmp_include_dir / "raqm.h").open("wb") as file:
101-
file.write(request.read())
102-
ext.include_dirs += [tmp_include_dir]
103-
else:
104-
ext.extra_compile_args += get_pkgconfig("--cflags", "raqm")
105-
106-
if os.name == "posix":
107-
get_pkgconfig(f"cairo >= {MIN_CAIRO_VERSION}")
108-
ext.extra_compile_args += [
109-
"-flto", "-Wall", "-Wextra", "-Wpedantic",
110-
*get_pkgconfig("--cflags", "cairo"),
111-
]
112-
ext.extra_link_args += ["-flto"]
113-
114-
elif os.name == "nt":
115-
# Windows conda path for FreeType.
116-
ext.include_dirs += [Path(sys.prefix, "Library/include")]
117-
ext.extra_compile_args += [
118-
"/experimental:preprocessor",
119-
"/wd4244", "/wd4267", # cf. gcc -Wconversion.
120-
]
121-
ext.libraries += ["psapi", "cairo", "freetype"]
122-
# Windows conda path for FreeType -- needs to be str, not Path.
123-
ext.library_dirs += [str(Path(sys.prefix, "Library/lib"))]
124-
125-
super().finalize_options()
126-
119+
class build_ext(setuptools.command.build_ext.build_ext):
127120
def _copy_dlls_to(self, dest):
128121
if os.name == "nt":
129122
for dll in ["cairo.dll", "freetype.dll"]:
@@ -142,41 +135,43 @@ def copy_extensions_to_source(self):
142135
self.get_finalized_command("build_py").get_package_dir("mplcairo"))
143136

144137

145-
setup.register_pth_hook("setup_mplcairo_pth.py", "mplcairo.pth")
146-
147-
148-
setup(
149-
name="mplcairo",
150-
description="A (new) cairo backend for Matplotlib.",
151-
long_description=open("README.rst", encoding="utf-8").read(),
152-
author="Antony Lee",
153-
url="https://github.com/matplotlib/mplcairo",
154-
license="MIT",
155-
classifiers=[
156-
"Development Status :: 4 - Beta",
157-
"Framework :: Matplotlib",
158-
"License :: OSI Approved :: MIT License",
159-
"Programming Language :: Python :: 3",
160-
],
161-
cmdclass={"build_ext": build_ext},
162-
packages=find_packages("lib"),
163-
package_dir={"": "lib"},
164-
ext_modules=[Extension("", [])],
165-
python_requires=">=3.8",
166-
setup_requires=[
167-
"setuptools>=36.7", # setup_requires early install.
168-
"setuptools_scm",
169-
"pybind11>=2.8.0",
170-
*(["pycairo>=1.16.0; os_name == 'posix'"] if not MANYLINUX else []),
171-
],
172-
use_scm_version={ # xref __init__.py
173-
"version_scheme": "post-release",
174-
"local_scheme": "node-and-date",
175-
"write_to": "lib/mplcairo/_version.py",
176-
},
177-
install_requires=[
178-
"matplotlib>=2.2",
179-
"pillow", # Already a dependency of mpl>=3.3.
180-
"pycairo>=1.16.0; os_name == 'posix'",
181-
],
182-
)
138+
def register_pth_hook(source_path, pth_name):
139+
"""
140+
::
141+
setup.register_pth_hook("hook_source.py", "hook_name.pth") # Add hook.
142+
"""
143+
with tokenize.open(source_path) as file:
144+
source = file.read()
145+
_pth_hook_mixin._pth_hooks.append((pth_name, source))
146+
147+
148+
class _pth_hook_mixin:
149+
_pth_hooks = []
150+
151+
def run(self):
152+
super().run()
153+
for pth_name, source in self._pth_hooks:
154+
with Path(self.install_dir, pth_name).open("w") as file:
155+
file.write(f"import os; exec({source!r})")
156+
157+
def get_outputs(self):
158+
return (super().get_outputs()
159+
+ [str(Path(self.install_dir, pth_name))
160+
for pth_name, _ in self._pth_hooks])
161+
162+
163+
register_pth_hook("setup_mplcairo_pth.py", "mplcairo.pth")
164+
165+
166+
gcc = Distribution().get_command_class
167+
with TemporaryDirectory() as tmpdir:
168+
setuptools.setup(
169+
cmdclass={
170+
"build_ext": build_ext,
171+
"develop": type(
172+
"develop_wph", (_pth_hook_mixin, gcc("develop")), {}),
173+
"install_lib": type(
174+
"install_lib_wph", (_pth_hook_mixin, gcc("install_lib")), {}),
175+
},
176+
ext_modules=[gen_extension(tmpdir=Path(tmpdir))],
177+
)

0 commit comments

Comments
 (0)