1#! python 2 3project = dict( 4 name = 'python-poppler-qt5', 5 version = '0.75.0', 6 description = 'A Python binding to Poppler-Qt5', 7 long_description = ( 8 'A Python binding to Poppler-Qt5 that aims for ' 9 'completeness and for being actively maintained. ' 10 'Using this module you can access the contents of PDF files ' 11 'inside PyQt5 applications.' 12 ), 13 maintainer = 'Wilbert Berendsen', 14 maintainer_email = 'wbsoft@xs4all.nl', 15 url = 'https://github.com/frescobaldi/python-poppler-qt5', 16 license = 'LGPL', 17 classifiers = [ 18 'Development Status :: 5 - Production/Stable', 19 'Intended Audience :: Developers', 20 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', 21 'Operating System :: MacOS :: MacOS X', 22 'Operating System :: Microsoft :: Windows', 23 'Operating System :: POSIX', 24 'Programming Language :: Python :: 3', 25 'Topic :: Multimedia :: Graphics :: Viewers', 26 ], 27 cmdclass={} 28) 29 30import os 31import re 32import shlex 33import subprocess 34import sys 35import platform 36 37try: 38 from setuptools import setup, Extension 39except ImportError: 40 from distutils.core import setup, Extension 41 42import sipdistutils 43 44### this circumvents a bug in sip < 4.14.2, where the file() builtin is used 45### instead of open() 46try: 47 import builtins 48 try: 49 builtins.file 50 except AttributeError: 51 builtins.file = open 52except ImportError: 53 pass 54### end 55 56 57def check_qtxml(): 58 """Return True if PyQt5.QtXml can be imported. 59 60 in some early releases of PyQt5, QtXml was missing because it was 61 thought QtXml was deprecated. 62 63 """ 64 import PyQt5 65 try: 66 import PyQt5.QtXml 67 except ImportError: 68 return False 69 return True 70 71 72def pkg_config(package, attrs=None, include_only=False): 73 """parse the output of pkg-config for a package. 74 75 returns the given or a new dictionary with one or more of these keys 76 'include_dirs', 'library_dirs', 'libraries'. Every key is a list of paths, 77 so that it can be used with distutils Extension objects. 78 79 """ 80 if attrs is None: 81 attrs = {} 82 cmd = ['pkg-config'] 83 if include_only: 84 cmd += ['--cflags-only-I'] 85 else: 86 cmd += ['--cflags', '--libs'] 87 cmd.append(package) 88 try: 89 output = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0] 90 except OSError: 91 return attrs 92 flag_map = {'-I': 'include_dirs', '-L': 'library_dirs', '-l': 'libraries'} 93 # for python3 turn bytes back to string 94 if sys.version_info[0] > 2: 95 output = output.decode('utf-8') 96 for flag in shlex.split(output): 97 option, path = flag[:2], flag[2:] 98 if option in flag_map: 99 l = attrs.setdefault(flag_map[option], []) 100 if path not in l: 101 l.append(path) 102 return attrs 103 104def pkg_config_version(package): 105 """Returns the version of the given package as a tuple of ints.""" 106 cmd = ['pkg-config', '--modversion', package] 107 try: 108 output = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0] 109 # for python3 turn bytes back to string 110 if sys.version_info[0] > 2: 111 output = output.decode('utf-8') 112 return tuple(map(int, re.findall(r'\d+', output))) 113 except OSError: 114 sys.stderr.write("Can't determine version of %s\n" % package) 115 116ext_args = {} 117ext_args['extra_compile_args'] = ['-std=c++11'] 118pkg_config('poppler-qt5', ext_args) 119 120if 'libraries' not in ext_args: 121 ext_args['libraries'] = ['poppler-qt5'] 122 123# hack to provide our options to sip on its invocation: 124build_ext_base = sipdistutils.build_ext 125class build_ext(build_ext_base): 126 127 description = "Builds the popplerqt5 module." 128 129 user_options = build_ext_base.user_options + [ 130 ('poppler-version=', None, "version of the poppler library"), 131 ('qmake-bin=', None, "Path to qmake binary"), 132 ('sip-bin=', None, "Path to sip binary"), 133 ('qt-include-dir=', None, "Path to Qt headers"), 134 ('pyqt-sip-dir=', None, "Path to PyQt's SIP files"), 135 ('pyqt-sip-flags=', None, "SIP flags used to generate PyQt bindings") 136 ] 137 138 def initialize_options (self): 139 build_ext_base.initialize_options(self) 140 self.poppler_version = None 141 self.qmake_bin = 'qmake' 142 self.sip_bin = None 143 self.qt_include_dir = None 144 self.pyqt_sip_dir = None 145 self.pyqt_sip_flags = None 146 147 def finalize_options (self): 148 build_ext_base.finalize_options(self) 149 150 if not self.qt_include_dir: 151 self.qt_include_dir = self.__find_qt_include_dir() 152 153 if not self.pyqt_sip_dir: 154 self.pyqt_sip_dir = self.__find_pyqt_sip_dir() 155 156 if not self.pyqt_sip_flags: 157 self.pyqt_sip_flags = self.__find_pyqt_sip_flags() 158 159 if not self.qt_include_dir: 160 raise SystemExit('Could not find Qt5 headers. ' 161 'Please specify via --qt-include-dir=') 162 163 if not self.pyqt_sip_dir: 164 raise SystemExit('Could not find PyQt SIP files. ' 165 'Please specify containing directory via ' 166 '--pyqt-sip-dir=') 167 168 if not self.pyqt_sip_flags: 169 raise SystemExit('Could not find PyQt SIP flags. ' 170 'Please specify via --pyqt-sip-flags=') 171 172 self.include_dirs += (self.qt_include_dir, 173 os.path.join(self.qt_include_dir, 'QtCore'), 174 os.path.join(self.qt_include_dir, 'QtGui'), 175 os.path.join(self.qt_include_dir, 'QtXml')) 176 177 if self.poppler_version is not None: 178 self.poppler_version = tuple(map(int, re.findall(r'\d+', self.poppler_version))) 179 180 def __find_qt_include_dir(self): 181 if self.pyqtconfig: 182 return self.pyqtconfig.qt_inc_dir 183 184 try: 185 qt_version = subprocess.check_output([self.qmake_bin, 186 '-query', 187 'QT_VERSION']) 188 qt_version = qt_version.strip().decode("ascii") 189 except (OSError, subprocess.CalledProcessError) as e: 190 raise SystemExit('Failed to determine Qt version (%s).' % e) 191 192 if not qt_version.startswith("5."): 193 raise SystemExit('Unsupported Qt version (%s). ' 194 'Try specifying the path to qmake manually via ' 195 '--qmake-bin=' % qt_version) 196 197 try: 198 result = subprocess.check_output([self.qmake_bin, 199 '-query', 200 'QT_INSTALL_HEADERS']) 201 return result.strip().decode(sys.getfilesystemencoding()) 202 except (OSError, subprocess.CalledProcessError) as e: 203 raise SystemExit('Failed to determine location of Qt headers (%s).' % e) 204 205 def __find_pyqt_sip_dir(self): 206 if self.pyqtconfig: 207 return self.pyqtconfig.pyqt_sip_dir 208 209 import sipconfig 210 211 return os.path.join(sipconfig.Configuration().default_sip_dir, 'PyQt5') 212 213 def __find_pyqt_sip_flags(self): 214 if self.pyqtconfig: 215 return self.pyqtconfig.pyqt_sip_flags 216 217 from PyQt5 import QtCore 218 219 return QtCore.PYQT_CONFIGURATION.get('sip_flags', '') 220 221 @property 222 def pyqtconfig(self): 223 if not hasattr(self, '_pyqtconfig'): 224 try: 225 from PyQt5 import pyqtconfig 226 227 self._pyqtconfig = pyqtconfig.Configuration() 228 except ImportError: 229 self._pyqtconfig = None 230 231 return self._pyqtconfig 232 233 def write_version_sip(self, poppler_qt5_version, python_poppler_qt5_version): 234 """Write a version.sip file. 235 236 The file contains code to make version information accessible from 237 the popplerqt5 Python module. 238 239 """ 240 with open('version.sip', 'w') as f: 241 f.write(version_sip_template.format( 242 vlen = 'i' * len(python_poppler_qt5_version), 243 vargs = ', '.join(map(format, python_poppler_qt5_version)), 244 pvlen = 'i' * len(poppler_qt5_version), 245 pvargs = ', '.join(map(format, poppler_qt5_version)))) 246 247 def _find_sip(self): 248 """override _find_sip to allow for manually speficied sip path.""" 249 return self.sip_bin or build_ext_base._find_sip(self) 250 251 def _sip_compile(self, sip_bin, source, sbf): 252 253 # First check manually specified poppler version 254 ver = self.poppler_version or pkg_config_version('poppler-qt5') or () 255 256 # our own version: 257 version = tuple(map(int, re.findall(r'\d+', project['version']))) 258 259 # make those accessible from the popplerqt5 module: 260 self.write_version_sip(ver, version) 261 262 # Disable features if older poppler-qt5 version is found. 263 # See the defined tags in %Timeline{} in timeline.sip. 264 tag = 'POPPLER_V0_20_0' 265 if ver: 266 with open("timeline.sip", "r") as f: 267 for m in re.finditer(r'POPPLER_V(\d+)_(\d+)_(\d+)', f.read()): 268 if ver < tuple(map(int, m.group(1, 2, 3))): 269 break 270 tag = m.group() 271 272 cmd = [sip_bin] 273 if hasattr(self, 'sip_opts'): 274 cmd += self.sip_opts 275 if hasattr(self, '_sip_sipfiles_dir'): 276 cmd += ['-I', self._sip_sipfiles_dir()] 277 if tag: 278 cmd += ['-t', tag] 279 if not check_qtxml(): 280 cmd += ["-x", "QTXML_AVAILABLE"] # mark QtXml not supported 281 cmd += [ 282 "-c", self.build_temp, 283 "-b", sbf, 284 "-I", self.pyqt_sip_dir] # find the PyQt5 stuff 285 cmd += shlex.split(self.pyqt_sip_flags) # use same SIP flags as for PyQt5 286 cmd.append(source) 287 self.spawn(cmd) 288 289if platform.system() == 'Windows': 290 # Enforce libraries to link against on Windows 291 ext_args['libraries'] = ['poppler-qt5', 'Qt5Core', 'Qt5Gui', 'Qt5Xml'] 292 293 class bdist_support(): 294 def __find_poppler_dll(self): 295 paths = os.environ['PATH'].split(";") 296 poppler_dll = None 297 298 for path in paths: 299 dll_path_candidate = os.path.join(path, "poppler-qt5.dll") 300 if os.path.exists(dll_path_candidate): 301 return dll_path_candidate 302 303 return None 304 305 def _copy_poppler_dll(self): 306 poppler_dll = self.__find_poppler_dll() 307 if poppler_dll is None: 308 self.warn("Could not find poppler-qt5.dll in any of the folders listed in the PATH environment variable.") 309 return False 310 311 self.mkpath(self.bdist_dir) 312 self.copy_file(poppler_dll, os.path.join(self.bdist_dir, "python-poppler5.dll")) 313 314 return True 315 316 import distutils.command.bdist_msi 317 class bdist_msi(distutils.command.bdist_msi.bdist_msi, bdist_support): 318 def run(self): 319 if not self._copy_poppler_dll(): 320 return 321 distutils.command.bdist_msi.bdist_msi.run(self) 322 323 project['cmdclass']['bdist_msi'] = bdist_msi 324 325 import distutils.command.bdist_wininst 326 class bdist_wininst(distutils.command.bdist_wininst.bdist_wininst, bdist_support): 327 def run(self): 328 if not self._copy_poppler_dll(): 329 return 330 distutils.command.bdist_wininst.bdist_wininst.run(self) 331 project['cmdclass']['bdist_wininst'] = bdist_wininst 332 333 import distutils.command.bdist_dumb 334 class bdist_dumb(distutils.command.bdist_dumb.bdist_dumb, bdist_support): 335 def run(self): 336 if not self._copy_poppler_dll(): 337 return 338 distutils.command.bdist_dumb.bdist_dumb.run(self) 339 project['cmdclass']['bdist_dumb'] = bdist_dumb 340 341 try: 342 # Attempt to patch bdist_egg if the setuptools/distribute extension is installed 343 import setuptools.command.bdist_egg 344 class bdist_egg(setuptools.command.bdist_egg.bdist_egg, bdist_support): 345 def run(self): 346 if not self._copy_poppler_dll(): 347 return 348 setuptools.command.bdist_egg.bdist_egg.run(self) 349 project['cmdclass']['bdist_egg'] = bdist_egg 350 except ImportError: 351 pass 352 353 354version_sip_template = r"""// Generated by setup.py -- Do not edit 355 356PyObject *version(); 357%Docstring 358The version of the popplerqt5 python module. 359%End 360 361PyObject *poppler_version(); 362%Docstring 363The version of the Poppler library. 364%End 365 366%ModuleCode 367 368PyObject *version() 369{{ return Py_BuildValue("({vlen})", {vargs}); }}; 370 371PyObject *poppler_version() 372{{ return Py_BuildValue("({pvlen})", {pvargs}); }}; 373 374%End 375""" 376 377### use full README.rst as long description 378with open('README.rst', 'rb') as f: 379 project["long_description"] = f.read().decode('utf-8') 380 381 382 383project['cmdclass']['build_ext'] = build_ext 384setup( 385 ext_modules = [Extension("popplerqt5", ["poppler-qt5.sip"], **ext_args)], 386 **project 387) 388