3
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
4
__docformat__ = 'restructuredtext en'
7
Build PyQt extensions. Integrates with distutils (but uses the PyQt build system).
9
from distutils.core import Extension as _Extension
10
from distutils.command.build_ext import build_ext as _build_ext
11
from distutils.dep_util import newer_group
12
from distutils import log
14
import sipconfig, os, sys, string, glob, shutil
15
from PyQt4 import pyqtconfig
16
iswindows = 'win32' in sys.platform
17
QMAKE = os.path.expanduser('~/qt/bin/qmake') if 'darwin' in sys.platform else'qmake'
18
WINDOWS_PYTHON = ['C:/Python26/libs']
19
OSX_SDK = '/Developer/SDKs/MacOSX10.4u.sdk'
21
def replace_suffix(path, new_suffix):
22
return os.path.splitext(path)[0] + new_suffix
24
class Extension(_Extension):
28
from distutils import msvc9compiler
29
msvc = msvc9compiler.MSVCCompiler()
31
nmake = msvc.find_exe('nmake.exe')
32
rc = msvc.find_exe('rc.exe')
35
class PyQtExtension(Extension):
37
def __init__(self, name, sources, sip_sources, **kw):
39
:param sources: Qt .cpp and .h files needed for this extension
40
:param sip_sources: List of .sip files this extension depends on. The
41
first .sip file will be used toactually build the extension.
43
self.module_makefile = pyqtconfig.QtGuiModuleMakefile
44
self.sip_sources = map(lambda x: x.replace('/', os.sep), sip_sources)
45
Extension.__init__(self, name, sources, **kw)
48
class build_ext(_build_ext):
50
def make(self, makefile):
51
make = nmake if iswindows else 'make'
52
self.spawn([make, '-f', makefile])
54
def build_qt_objects(self, ext, bdir):
56
bdir = os.path.join(bdir, 'qt')
57
if not os.path.exists(bdir):
60
sources = map(os.path.abspath, ext.sources)
63
headers = set([f for f in sources if f.endswith('.h')])
64
sources = set(sources) - headers
65
name = ext.name.rpartition('.')[-1]
73
'''%(name, ' '.join(headers), ' '.join(sources))
74
open(name+'.pro', 'wb').write(pro)
75
self.spawn([QMAKE, '-o', 'Makefile.qt', name+'.pro'])
76
self.make('Makefile.qt')
77
pat = 'release\\*.obj' if iswindows else '*.o'
78
return map(os.path.abspath, glob.glob(pat))
82
def build_sbf(self, sip, sbf, bdir):
83
print '\tBuilding spf...'
84
sip_bin = self.sipcfg.sip_bin
88
'-I', self.pyqtcfg.pyqt_sip_dir,
89
] + self.pyqtcfg.pyqt_sip_flags.split()+
92
def build_pyqt(self, bdir, sbf, ext, qtobjs, headers):
93
makefile = ext.module_makefile(configuration=self.pyqtcfg,
94
build_file=sbf, dir=bdir,
95
makefile='Makefile.pyqt',
96
universal=OSX_SDK, qt=1)
97
if 'win32' in sys.platform:
98
makefile.extra_lib_dirs += WINDOWS_PYTHON
99
makefile.extra_include_dirs = list(set(map(os.path.dirname, headers)))
100
makefile.extra_lflags += qtobjs
105
self.make('Makefile.pyqt')
111
def build_extension(self, ext):
112
self.inplace = True # Causes extensions to be built in the source tree
114
fullname = self.get_ext_fullname(ext.name)
116
# ignore build-lib -- put the compiled extension into
117
# the source tree along with pure Python modules
119
modpath = string.split(fullname, '.')
120
package = string.join(modpath[0:-1], '.')
123
build_py = self.get_finalized_command('build_py')
124
package_dir = build_py.get_package_dir(package)
125
ext_filename = os.path.join(package_dir,
126
self.get_ext_filename(base))
128
ext_filename = os.path.join(self.build_lib,
129
self.get_ext_filename(fullname))
130
bdir = os.path.abspath(os.path.join(self.build_temp, fullname))
131
if not os.path.exists(bdir):
134
if not isinstance(ext, PyQtExtension):
136
return _build_ext.build_extension(self, ext)
138
c_sources = [f for f in ext.sources if os.path.splitext(f)[1].lower() in ('.c', '.cpp', '.cxx')]
139
compile_args = '/c /nologo /Ox /MD /W3 /GX /DNDEBUG'.split()
140
compile_args += ext.extra_compile_args
142
inc_dirs = self.include_dirs + [x.replace('/', '\\') for x in ext.include_dirs]
143
cc = [msvc.cc] + compile_args + ['-I%s'%x for x in list(set(inc_dirs))]
146
o = os.path.join(bdir, os.path.basename(f)+'.obj')
148
compiler = cc + ['/Tc'+f, '/Fo'+o]
150
out = os.path.join(bdir, base+'.pyd')
151
linker = [msvc.linker] + '/DLL /nologo /INCREMENTAL:NO'.split()
152
linker += ['/LIBPATH:'+x for x in self.library_dirs]
153
linker += [x+'.lib' for x in ext.libraries]
154
linker += ['/EXPORT:init'+base] + objects + ['/OUT:'+out]
156
for src in (out, out+'.manifest'):
157
shutil.copyfile(src, os.path.join('src', 'calibre', 'plugins', os.path.basename(src)))
162
if not os.path.exists(bdir):
164
ext.sources2 = map(os.path.abspath, ext.sources)
165
qt_dir = 'qt\\release' if iswindows else 'qt'
166
objects = set(map(lambda x: os.path.join(bdir, qt_dir, replace_suffix(os.path.basename(x), '.o')),
167
[s for s in ext.sources2 if not s.endswith('.h')]))
169
for object in objects:
170
if newer_group(ext.sources2, object, missing='newer'):
173
headers = [f for f in ext.sources2 if f.endswith('.h')]
174
if self.force or newer:
175
log.info('building \'%s\' extension', ext.name)
176
objects = self.build_qt_objects(ext, bdir)
178
self.sipcfg = sipconfig.Configuration()
179
self.pyqtcfg = pyqtconfig.Configuration()
181
for sip in ext.sip_sources:
182
sipbasename = os.path.basename(sip)
183
sbf = os.path.join(bdir, replace_suffix(sipbasename, ".sbf"))
184
sbf_sources.append(sbf)
185
if self.force or newer_group(ext.sip_sources, sbf, 'newer'):
186
self.build_sbf(sip, sbf, bdir)
187
generated_sources = []
188
for sbf in sbf_sources:
189
generated_sources += self.get_sip_output_list(sbf, bdir)
191
depends = generated_sources + list(objects)
192
mod = os.path.join(bdir, os.path.basename(ext_filename))
194
if self.force or newer_group(depends, mod, 'newer'):
195
self.build_pyqt(bdir, sbf_sources[0], ext, list(objects), headers)
197
if self.force or newer_group([mod], ext_filename, 'newer'):
198
if os.path.exists(ext_filename):
199
os.unlink(ext_filename)
200
shutil.copyfile(mod, ext_filename)
201
shutil.copymode(mod, ext_filename)
203
def get_sip_output_list(self, sbf, bdir):
205
Parse the sbf file specified to extract the name of the generated source
206
files. Make them absolute assuming they reside in the temp directory.
209
key, value = L.split("=", 1)
210
if key.strip() == "sources":
212
for o in value.split():
213
out.append(os.path.join(bdir, o))
216
raise RuntimeError, "cannot parse SIP-generated '%s'" % sbf
218
def run_sip(self, sip_files):
219
sip_bin = self.sipcfg.sip_bin
220
sip_sources = [i[0] for i in sip_files]
221
generated_sources = []
222
for sip, sbf in sip_files:
223
if not (self.force or newer_group(sip_sources, sbf, 'newer')):
224
log.info(sbf + ' is up to date')
227
"-c", self.build_temp,
229
'-I', self.pyqtcfg.pyqt_sip_dir,
230
] + self.pyqtcfg.pyqt_sip_flags.split()+
232
generated_sources += self.get_sip_output_list(sbf)
233
return generated_sources