calibre/setup/extensions.py
Kovid Goyal 8da98e8ba4 Start work on a headless QPA plugin to allow Qt to be used without an X server in linux.
Useful for the various command line tools that require
Qt. For example conversion with PDF Output or MOBI output +
rasterization of SVG images. Or ebook-meta with rendering of first page
of EPUB as cover. Or downloading news using a WebKit browser as the
backend.

Makes it unnecessary to use xvfb and will hopefully end the pointless
bug reports from people that try to use calibre command line tools in
environments without X.
2014-08-08 18:31:09 +05:30

633 lines
24 KiB
Python

#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import with_statement
__license__ = 'GPL v3'
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import textwrap, os, shlex, subprocess, glob, shutil, re, sys
from distutils import sysconfig
from multiprocessing import cpu_count
from setup import Command, islinux, isbsd, isosx, SRC, iswindows, __version__
from setup.build_environment import (chmlib_inc_dirs,
podofo_inc, podofo_lib, podofo_error, pyqt, OSX_SDK, NMAKE, QMAKE,
msvc, MT, win_inc, win_lib, win_ddk, magick_inc_dirs, magick_lib_dirs,
magick_libs, chmlib_lib_dirs, sqlite_inc_dirs, icu_inc_dirs,
icu_lib_dirs, win_ddk_lib_dirs, ft_libs, ft_lib_dirs, ft_inc_dirs,
zlib_libs, zlib_lib_dirs, zlib_inc_dirs, is64bit)
MT
isunix = islinux or isosx or isbsd
make = 'make' if isunix else NMAKE
py_lib_dir = os.path.join(sys.prefix, 'lib')
class Extension(object):
@classmethod
def absolutize(cls, paths):
return list(set([x if os.path.isabs(x) else os.path.join(SRC, x.replace('/',
os.sep)) for x in paths]))
def __init__(self, name, sources, **kwargs):
self.name = name
self.needs_cxx = bool([1 for x in sources if os.path.splitext(x)[1] in
('.cpp', '.c++', '.cxx')])
self.sources = self.absolutize(sources)
self.headers = self.absolutize(kwargs.get('headers', []))
self.sip_files = self.absolutize(kwargs.get('sip_files', []))
self.inc_dirs = self.absolutize(kwargs.get('inc_dirs', []))
self.lib_dirs = self.absolutize(kwargs.get('lib_dirs', []))
self.extra_objs = self.absolutize(kwargs.get('extra_objs', []))
self.error = kwargs.get('error', None)
self.libraries = kwargs.get('libraries', [])
self.cflags = kwargs.get('cflags', [])
self.ldflags = kwargs.get('ldflags', [])
self.optional = kwargs.get('optional', False)
self.needs_ddk = kwargs.get('needs_ddk', False)
of = kwargs.get('optimize_level', None)
if of is None:
of = '/Ox' if iswindows else '-O3'
else:
flag = '/O%d' if iswindows else '-O%d'
of = flag % of
self.cflags.insert(0, of)
self.qt_private_headers = kwargs.get('qt_private', [])
def preflight(self, obj_dir, compiler, linker, builder, cflags, ldflags):
pass
reflow_sources = glob.glob(os.path.join(SRC, 'calibre', 'ebooks', 'pdf', '*.cpp'))
reflow_headers = glob.glob(os.path.join(SRC, 'calibre', 'ebooks', 'pdf', '*.h'))
icu_libs = ['icudata', 'icui18n', 'icuuc', 'icuio']
icu_cflags = []
if iswindows:
icu_libs = ['icudt', 'icuin', 'icuuc', 'icuio']
if isosx:
icu_libs = ['icucore']
icu_cflags = ['-DU_DISABLE_RENAMING'] # Needed to use system libicucore.dylib
extensions = [
Extension('hunspell',
['hunspell/'+x for x in
'affentry.cxx affixmgr.cxx csutil.cxx dictmgr.cxx filemgr.cxx hashmgr.cxx hunspell.cxx phonet.cxx replist.cxx suggestmgr.cxx'.split()
] + ['calibre/utils/spell/hunspell_wrapper.cpp',],
inc_dirs=['hunspell'],
cflags='/DHUNSPELL_STATIC /D_CRT_SECURE_NO_WARNINGS /DUNICODE /D_UNICODE'.split() if iswindows else ['-DHUNSPELL_STATIC'],
optimize_level=2,
),
Extension('_regex',
['regex/_regex.c', 'regex/_regex_unicode.c'],
headers=['regex/_regex.h'],
optimize_level=2,
),
Extension('speedup',
['calibre/utils/speedup.c'],
),
Extension('html',
['calibre/gui2/tweak_book/editor/syntax/html.c'],
),
Extension('tokenizer',
['tinycss/tokenizer.c'],
),
Extension('_patiencediff_c',
['calibre/gui2/tweak_book/diff/_patiencediff_c.c'],
),
Extension('icu',
['calibre/utils/icu.c'],
headers=['calibre/utils/icu_calibre_utils.h'],
libraries=icu_libs,
lib_dirs=icu_lib_dirs,
inc_dirs=icu_inc_dirs,
cflags=icu_cflags
),
Extension('sqlite_custom',
['calibre/library/sqlite_custom.c'],
inc_dirs=sqlite_inc_dirs
),
Extension('chmlib',
['calibre/utils/chm/swig_chm.c'],
libraries=['ChmLib' if iswindows else 'chm'],
inc_dirs=chmlib_inc_dirs,
lib_dirs=chmlib_lib_dirs,
cflags=["-DSWIG_COBJECT_TYPES"]),
Extension('chm_extra',
['calibre/utils/chm/extra.c'],
libraries=['ChmLib' if iswindows else 'chm'],
inc_dirs=chmlib_inc_dirs,
lib_dirs=chmlib_lib_dirs,
cflags=["-D__PYTHON__"]),
Extension('magick',
['calibre/utils/magick/magick.c'],
headers=['calibre/utils/magick/magick_constants.h'],
libraries=magick_libs,
lib_dirs=magick_lib_dirs,
inc_dirs=magick_inc_dirs,
cflags=['-DMAGICKCORE_QUANTUM_DEPTH=16', '-DMAGICKCORE_HDRI_ENABLE=0']
),
Extension('lzx',
['calibre/utils/lzx/lzxmodule.c',
'calibre/utils/lzx/compressor.c',
'calibre/utils/lzx/lzxd.c',
'calibre/utils/lzx/lzc.c',
'calibre/utils/lzx/lzxc.c'],
headers=['calibre/utils/lzx/msstdint.h',
'calibre/utils/lzx/lzc.h',
'calibre/utils/lzx/lzxmodule.h',
'calibre/utils/lzx/system.h',
'calibre/utils/lzx/lzxc.h',
'calibre/utils/lzx/lzxd.h',
'calibre/utils/lzx/mspack.h'],
inc_dirs=['calibre/utils/lzx']),
Extension('freetype',
['calibre/utils/fonts/freetype.cpp'],
inc_dirs=ft_inc_dirs,
libraries=ft_libs,
lib_dirs=ft_lib_dirs),
Extension('woff',
['calibre/utils/fonts/woff/main.c',
'calibre/utils/fonts/woff/woff.c'],
headers=[
'calibre/utils/fonts/woff/woff.h',
'calibre/utils/fonts/woff/woff-private.h'],
libraries=zlib_libs,
lib_dirs=zlib_lib_dirs,
inc_dirs=zlib_inc_dirs,
),
Extension('msdes',
['calibre/utils/msdes/msdesmodule.c',
'calibre/utils/msdes/des.c'],
headers=['calibre/utils/msdes/spr.h',
'calibre/utils/msdes/d3des.h'],
inc_dirs=['calibre/utils/msdes']),
Extension('cPalmdoc',
['calibre/ebooks/compression/palmdoc.c']),
Extension('bzzdec',
['calibre/ebooks/djvu/bzzdecoder.c'],
inc_dirs=(['calibre/utils/chm'] if iswindows else []) # For stdint.h
),
Extension('matcher',
['calibre/utils/matcher.c'],
headers=['calibre/utils/icu_calibre_utils.h'],
libraries=icu_libs,
lib_dirs=icu_lib_dirs,
cflags=icu_cflags,
inc_dirs=icu_inc_dirs
),
Extension('podofo',
[
'calibre/utils/podofo/utils.cpp',
'calibre/utils/podofo/output.cpp',
'calibre/utils/podofo/doc.cpp',
'calibre/utils/podofo/outline.cpp',
'calibre/utils/podofo/podofo.cpp',
],
headers=[
'calibre/utils/podofo/global.h',
],
libraries=['podofo'],
lib_dirs=[podofo_lib],
inc_dirs=[podofo_inc, os.path.dirname(podofo_inc)],
error=podofo_error),
Extension('pictureflow',
['calibre/gui2/pictureflow/pictureflow.cpp'],
inc_dirs=['calibre/gui2/pictureflow'],
headers=['calibre/gui2/pictureflow/pictureflow.h'],
sip_files=['calibre/gui2/pictureflow/pictureflow.sip']
),
Extension('progress_indicator',
['calibre/gui2/progress_indicator/QProgressIndicator.cpp'],
inc_dirs=['calibre/gui2/progress_indicator'],
headers=['calibre/gui2/progress_indicator/QProgressIndicator.h'],
sip_files=['calibre/gui2/progress_indicator/QProgressIndicator.sip']
),
Extension('qt_hack',
['calibre/ebooks/pdf/render/qt_hack.cpp'],
inc_dirs=['calibre/ebooks/pdf/render'],
headers=['calibre/ebooks/pdf/render/qt_hack.h'],
qt_private=['core', 'gui'],
sip_files=['calibre/ebooks/pdf/render/qt_hack.sip']
),
Extension('unrar',
['unrar/%s.cpp'%(x.partition('.')[0]) for x in '''
rar.o strlist.o strfn.o pathfn.o savepos.o smallfn.o global.o file.o
filefn.o filcreat.o archive.o arcread.o unicode.o system.o
isnt.o crypt.o crc.o rawread.o encname.o resource.o match.o
timefn.o rdwrfn.o consio.o options.o ulinks.o errhnd.o rarvm.o
secpassword.o rijndael.o getbits.o sha1.o extinfo.o extract.o
volume.o list.o find.o unpack.o cmddata.o filestr.o scantree.o
'''.split()] + ['calibre/utils/unrar.cpp'],
inc_dirs=['unrar'],
cflags=[('/' if iswindows else '-') + x for x in (
'DSILENT', 'DRARDLL', 'DUNRAR')] + (
[] if iswindows else ['-D_FILE_OFFSET_BITS=64',
'-D_LARGEFILE_SOURCE']),
optimize_level=2,
libraries=['User32', 'Advapi32', 'kernel32', 'Shell32'] if iswindows else []
),
]
if iswindows:
extensions.extend([
Extension('winutil',
['calibre/utils/windows/winutil.c'],
libraries=['shell32', 'setupapi', 'wininet'],
cflags=['/X']
),
Extension('wpd',
[
'calibre/devices/mtp/windows/utils.cpp',
'calibre/devices/mtp/windows/device_enumeration.cpp',
'calibre/devices/mtp/windows/content_enumeration.cpp',
'calibre/devices/mtp/windows/device.cpp',
'calibre/devices/mtp/windows/wpd.cpp',
],
headers=[
'calibre/devices/mtp/windows/global.h',
],
libraries=['ole32', 'oleaut32', 'portabledeviceguids', 'user32'],
# needs_ddk=True,
cflags=['/X']
),
Extension('winfonts',
['calibre/utils/fonts/winfonts.cpp'],
libraries=['Gdi32', 'User32'],
cflags=['/X']
),
])
if isosx:
extensions.append(Extension('usbobserver',
['calibre/devices/usbobserver/usbobserver.c'],
ldflags=['-framework', 'CoreServices', '-framework', 'IOKit'])
)
if islinux or isosx:
extensions.append(Extension('libusb',
['calibre/devices/libusb/libusb.c'],
libraries=['usb-1.0']
))
extensions.append(Extension('libmtp',
[
'calibre/devices/mtp/unix/devices.c',
'calibre/devices/mtp/unix/libmtp.c'
],
headers=[
'calibre/devices/mtp/unix/devices.h',
'calibre/devices/mtp/unix/upstream/music-players.h',
'calibre/devices/mtp/unix/upstream/device-flags.h',
],
libraries=['mtp']
))
if isunix:
cc = os.environ.get('CC', 'gcc')
cxx = os.environ.get('CXX', 'g++')
debug = ''
# debug = '-ggdb'
cflags = os.environ.get('OVERRIDE_CFLAGS',
'-Wall -DNDEBUG %s -fno-strict-aliasing -pipe' % debug)
cflags = shlex.split(cflags) + ['-fPIC']
ldflags = os.environ.get('OVERRIDE_LDFLAGS', '-Wall')
ldflags = shlex.split(ldflags)
cflags += shlex.split(os.environ.get('CFLAGS', ''))
ldflags += shlex.split(os.environ.get('LDFLAGS', ''))
if islinux:
cflags.append('-pthread')
ldflags.append('-shared')
cflags.append('-I'+sysconfig.get_python_inc())
ldflags.append('-lpython'+sysconfig.get_python_version())
if isbsd:
cflags.append('-pthread')
ldflags.append('-shared')
cflags.append('-I'+sysconfig.get_python_inc())
ldflags.append('-lpython'+sysconfig.get_python_version())
if isosx:
x, p = ('i386', 'x86_64')
archs = ['-arch', x, '-arch', p, '-isysroot',
OSX_SDK]
cflags.append('-D_OSX')
cflags.extend(archs)
ldflags.extend(archs)
ldflags.extend('-bundle -undefined dynamic_lookup'.split())
cflags.extend(['-fno-common', '-dynamic'])
cflags.append('-I'+sysconfig.get_python_inc())
if iswindows:
cc = cxx = msvc.cc
cflags = '/c /nologo /MD /W3 /EHsc /DNDEBUG'.split()
ldflags = '/DLL /nologo /INCREMENTAL:NO /NODEFAULTLIB:libcmt.lib'.split()
# cflags = '/c /nologo /Ox /MD /W3 /EHsc /Zi'.split()
# ldflags = '/DLL /nologo /INCREMENTAL:NO /DEBUG'.split()
if is64bit:
cflags.append('/GS-')
for p in win_inc:
cflags.append('-I'+p)
for p in win_lib:
ldflags.append('/LIBPATH:'+p)
cflags.append('-I%s'%sysconfig.get_python_inc())
ldflags.append('/LIBPATH:'+os.path.join(sysconfig.PREFIX, 'libs'))
class Build(Command):
short_description = 'Build calibre C/C++ extension modules'
description = textwrap.dedent('''\
calibre depends on several python extensions written in C/C++.
This command will compile them. You can influence the compile
process by several environment variables, listed below:
CC - C Compiler defaults to gcc
CXX - C++ Compiler, defaults to g++
CFLAGS - Extra compiler flags
LDFLAGS - Extra linker flags
POPPLER_INC_DIR - poppler header files
POPPLER_LIB_DIR - poppler-qt4 library
PODOFO_INC_DIR - podofo header files
PODOFO_LIB_DIR - podofo library files
QMAKE - Path to qmake
VS90COMNTOOLS - Location of Microsoft Visual Studio 9 Tools (windows only)
''')
def add_options(self, parser):
choices = [e.name for e in extensions]+['all', 'headless']
parser.add_option('-1', '--only', choices=choices, default='all',
help=('Build only the named extension. Available: '+
', '.join(choices)+'. Default:%default'))
parser.add_option('--no-compile', default=False, action='store_true',
help='Skip compiling all C/C++ extensions.')
def run(self, opts):
if opts.no_compile:
self.info('--no-compile specified, skipping compilation')
return
self.obj_dir = os.path.join(os.path.dirname(SRC), 'build', 'objects')
if not os.path.exists(self.obj_dir):
os.makedirs(self.obj_dir)
for ext in extensions:
if opts.only != 'all' and opts.only != ext.name:
continue
if ext.error is not None:
if ext.optional:
self.warn(ext.error)
continue
else:
raise Exception(ext.error)
dest = self.dest(ext)
if not os.path.exists(self.d(dest)):
os.makedirs(self.d(dest))
self.info('\n####### Building extension', ext.name, '#'*7)
self.build(ext, dest)
if opts.only in {'all', 'headless'}:
self.build_headless()
def dest(self, ext):
ex = '.pyd' if iswindows else '.so'
return os.path.join(SRC, 'calibre', 'plugins', getattr(ext, 'name', ext))+ex
def inc_dirs_to_cflags(self, dirs):
return ['-I'+x for x in dirs]
def lib_dirs_to_ldflags(self, dirs):
pref = '/LIBPATH:' if iswindows else '-L'
return [pref+x for x in dirs]
def libraries_to_ldflags(self, dirs):
pref = '' if iswindows else '-l'
suff = '.lib' if iswindows else ''
return [pref+x+suff for x in dirs]
def build(self, ext, dest):
if ext.sip_files:
return self.build_pyqt_extension(ext, dest)
compiler = cxx if ext.needs_cxx else cc
linker = msvc.linker if iswindows else compiler
objects = []
obj_dir = self.j(self.obj_dir, ext.name)
ext.preflight(obj_dir, compiler, linker, self, cflags, ldflags)
einc = self.inc_dirs_to_cflags(ext.inc_dirs)
if ext.needs_ddk:
ddk_flags = ['-I'+x for x in win_ddk]
cflags.extend(ddk_flags)
ldflags.extend(['/LIBPATH:'+x for x in win_ddk_lib_dirs])
if not os.path.exists(obj_dir):
os.makedirs(obj_dir)
for src in ext.sources:
obj = self.j(obj_dir, os.path.splitext(self.b(src))[0]+'.o')
objects.append(obj)
if self.newer(obj, [src]+ext.headers):
inf = '/Tp' if src.endswith('.cpp') or src.endswith('.cxx') else '/Tc'
sinc = [inf+src] if iswindows else ['-c', src]
oinc = ['/Fo'+obj] if iswindows else ['-o', obj]
cmd = [compiler] + cflags + ext.cflags + einc + sinc + oinc
self.info(' '.join(cmd))
self.check_call(cmd)
dest = self.dest(ext)
elib = self.lib_dirs_to_ldflags(ext.lib_dirs)
xlib = self.libraries_to_ldflags(ext.libraries)
if self.newer(dest, objects+ext.extra_objs):
print 'Linking', ext.name
cmd = [linker]
if iswindows:
cmd += ldflags + ext.ldflags + elib + xlib + \
['/EXPORT:init'+ext.name] + objects + ext.extra_objs + ['/OUT:'+dest]
else:
cmd += objects + ext.extra_objs + ['-o', dest] + ldflags + ext.ldflags + elib + xlib
self.info('\n\n', ' '.join(cmd), '\n\n')
self.check_call(cmd)
if iswindows:
# manifest = dest+'.manifest'
# cmd = [MT, '-manifest', manifest, '-outputresource:%s;2'%dest]
# self.info(*cmd)
# self.check_call(cmd)
# os.remove(manifest)
for x in ('.exp', '.lib'):
x = os.path.splitext(dest)[0]+x
if os.path.exists(x):
os.remove(x)
def check_call(self, *args, **kwargs):
"""print cmdline if an error occured
If something is missing (qmake e.g.) you get a non-informative error
self.check_call(qmc + [ext.name+'.pro'])
so you would have to look a the source to see the actual command.
"""
try:
subprocess.check_call(*args, **kwargs)
except:
cmdline = ' '.join(['"%s"' % (arg) if ' ' in arg else arg for arg in args[0]])
print "Error while executing: %s\n" % (cmdline)
raise
def build_headless(self):
if iswindows or isosx:
return # Dont have headless operation on these platforms
self.info('\n####### Building headless QPA plugin', '#'*7)
a = Extension.absolutize
headers = a(['calibre/headless/headless_backingstore.h', 'calibre/headless/headless_integration.h'])
sources = a(['calibre/headless/main.cpp', 'calibre/headless/headless_backingstore.cpp', 'calibre/headless/headless_integration.cpp'])
others = a(['calibre/headless/headless.json'])
target = self.dest('headless')
if not self.newer(target, headers + sources + others):
return
pro = textwrap.dedent(
'''\
TARGET = headless
PLUGIN_TYPE = platforms
PLUGIN_CLASS_NAME = HeadlessIntegrationPlugin
load(qt_plugin)
QT += core-private gui-private platformsupport-private
HEADERS = {headers}
SOURCES = {sources}
OTHER_FILES = {others}
DESTDIR = {destdir}
CONFIG -= create_cmake # Prevent qmake from generating a cmake build file which it puts in the calibre src directory
''').format(
headers=' '.join(headers), sources=' '.join(sources), others=' '.join(others), destdir=self.d(target))
bdir = self.j(self.d(self.SRC), 'build', 'headless')
if not os.path.exists(bdir):
os.makedirs(bdir)
pf = self.j(bdir, 'headless.pro')
open(self.j(bdir, '.qmake.conf'), 'wb').close()
with open(pf, 'wb') as f:
f.write(pro.encode('utf-8'))
cwd = os.getcwd()
os.chdir(bdir)
try:
self.check_call([QMAKE] + [self.b(pf)])
self.check_call([make] + ['-j%d'%(cpu_count() or 1)])
finally:
os.chdir(cwd)
def build_sip_files(self, ext, src_dir):
sip_files = ext.sip_files
ext.sip_files = []
sipf = sip_files[0]
sbf = self.j(src_dir, self.b(sipf)+'.sbf')
if self.newer(sbf, [sipf]+ext.headers):
cmd = [pyqt['sip_bin'], '-w', '-c', src_dir, '-b', sbf, '-I'+
pyqt['default_sip_dir']+'/sip/PyQt5'] + shlex.split(pyqt['sip_flags']) + [sipf]
self.info(' '.join(cmd))
self.check_call(cmd)
self.info('')
raw = open(sbf, 'rb').read().decode('utf-8')
def read(x):
ans = re.search('^%s\s*=\s*(.+)$' % x, raw, flags=re.M).group(1).strip()
if x != 'target':
ans = ans.split()
return ans
return {x:read(x) for x in ('target', 'sources', 'headers')}
def build_pyqt_extension(self, ext, dest):
pyqt_dir = self.j(self.d(self.SRC), 'build', 'pyqt')
src_dir = self.j(pyqt_dir, ext.name)
if not os.path.exists(src_dir):
os.makedirs(src_dir)
sip = self.build_sip_files(ext, src_dir)
pro = textwrap.dedent(
'''\
TEMPLATE = lib
CONFIG += release plugin
QT += widgets
TARGET = {target}
HEADERS = {headers}
SOURCES = {sources}
INCLUDEPATH += {sipinc} {pyinc}
VERSION = {ver}
win32 {{
LIBS += {py_lib_dir}
}}
macx {{
QMAKE_LFLAGS += "-undefined dynamic_lookup"
}}
''').format(
target=sip['target'], headers=' '.join(sip['headers'] + ext.headers), sources=' '.join(ext.sources + sip['sources']),
sipinc=pyqt['sip_inc_dir'], pyinc=sysconfig.get_python_inc(), py_lib_dir=py_lib_dir,
ver=__version__
)
for incdir in ext.inc_dirs:
pro += '\nINCLUDEPATH += ' + incdir
if not iswindows and not isosx:
# Ensure that only the init symbol is exported
pro += '\nQMAKE_LFLAGS += -Wl,--version-script=%s.exp' % sip['target']
with open(os.path.join(src_dir, sip['target'] + '.exp'), 'wb') as f:
f.write(('{ global: init%s; local: *; };' % sip['target']).encode('utf-8'))
if ext.qt_private_headers:
qph = ' '.join(x + '-private' for x in ext.qt_private_headers)
pro += '\nQT += ' + qph
proname = '%s.pro' % sip['target']
with open(os.path.join(src_dir, proname), 'wb') as f:
f.write(pro.encode('utf-8'))
cwd = os.getcwdu()
qmc = []
if iswindows:
qmc += ['-spec', 'win32-msvc2008']
fext = 'dll' if iswindows else 'dylib' if isosx else 'so'
name = '%s%s.%s' % ('' if iswindows else 'lib', sip['target'], fext)
try:
os.chdir(src_dir)
if self.newer(dest, sip['headers'] + sip['sources'] + ext.sources + ext.headers):
self.check_call([QMAKE] + qmc + [proname])
self.check_call([make]+([] if iswindows else ['-j%d'%(cpu_count() or 1)]))
shutil.copy2(os.path.realpath(name), dest)
finally:
os.chdir(cwd)
def clean(self):
for ext in extensions:
dest = self.dest(ext)
for x in (dest, dest+'.manifest'):
if os.path.exists(x):
os.remove(x)
build_dir = self.j(self.d(self.SRC), 'build')
if os.path.exists(build_dir):
shutil.rmtree(build_dir)