4
4
5
5
Environment variables:
6
6
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.
9
10
10
11
MPLCAIRO_NO_UNITY_BUILD
11
12
If set, compile the various cpp files separately, instead of as a single
23
24
import subprocess
24
25
from subprocess import CalledProcessError
25
26
import sys
27
+ from tempfile import TemporaryDirectory
28
+ import tokenize
26
29
import urllib .request
27
30
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
37
38
38
39
39
40
MIN_CAIRO_VERSION = "1.13.1" # Also in _feature_tests.cpp.
40
41
MIN_RAQM_VERSION = "0.7.0"
41
- MANYLINUX = bool (os .environ .get ("MPLCAIRO_MANYLINUX" , "" ))
42
42
UNITY_BUILD = not bool (os .environ .get ("MPLCAIRO_NO_UNITY_BUILD" ))
43
43
44
44
45
45
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
48
103
49
104
50
105
@functools .lru_cache (1 )
@@ -61,69 +116,7 @@ def paths_from_link_libpaths():
61
116
return paths
62
117
63
118
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 ):
127
120
def _copy_dlls_to (self , dest ):
128
121
if os .name == "nt" :
129
122
for dll in ["cairo.dll" , "freetype.dll" ]:
@@ -142,41 +135,43 @@ def copy_extensions_to_source(self):
142
135
self .get_finalized_command ("build_py" ).get_package_dir ("mplcairo" ))
143
136
144
137
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