IGN:Fix various minor bugs and rationalize the build system

This commit is contained in:
Kovid Goyal 2008-08-31 18:30:32 -07:00
parent f839c4d9a9
commit d8e02cc76b
12 changed files with 393 additions and 399 deletions

View File

@ -10,7 +10,7 @@ from distutils.core import Extension
from distutils.command.build_ext import build_ext as _build_ext from distutils.command.build_ext import build_ext as _build_ext
from distutils.dep_util import newer_group from distutils.dep_util import newer_group
from distutils import log from distutils import log
import sipconfig, os, sys, string, glob, shutil import sipconfig, os, sys, string, glob, shutil
from PyQt4 import pyqtconfig from PyQt4 import pyqtconfig
iswindows = 'win32' in sys.platform iswindows = 'win32' in sys.platform
@ -22,7 +22,7 @@ def replace_suffix(path, new_suffix):
return os.path.splitext(path)[0] + new_suffix return os.path.splitext(path)[0] + new_suffix
class PyQtExtension(Extension): class PyQtExtension(Extension):
def __init__(self, name, sources, sip_sources, **kw): def __init__(self, name, sources, sip_sources, **kw):
''' '''
:param sources: Qt .cpp and .h files needed for this extension :param sources: Qt .cpp and .h files needed for this extension
@ -32,16 +32,16 @@ class PyQtExtension(Extension):
self.module_makefile = pyqtconfig.QtGuiModuleMakefile self.module_makefile = pyqtconfig.QtGuiModuleMakefile
self.sip_sources = map(lambda x: x.replace('/', os.sep), sip_sources) self.sip_sources = map(lambda x: x.replace('/', os.sep), sip_sources)
Extension.__init__(self, name, sources, **kw) Extension.__init__(self, name, sources, **kw)
class build_ext(_build_ext): class build_ext(_build_ext):
def make(self, makefile): def make(self, makefile):
make = 'make' make = 'make'
if iswindows: if iswindows:
make = 'mingw32-make' make = 'mingw32-make'
self.spawn([make, '-f', makefile]) self.spawn([make, '-f', makefile])
def build_qt_objects(self, ext, bdir): def build_qt_objects(self, ext, bdir):
if not iswindows: if not iswindows:
bdir = os.path.join(bdir, 'qt') bdir = os.path.join(bdir, 'qt')
@ -53,7 +53,7 @@ class build_ext(_build_ext):
try: try:
headers = set([f for f in sources if f.endswith('.h')]) headers = set([f for f in sources if f.endswith('.h')])
sources = set(sources) - headers sources = set(sources) - headers
name = ext.name.rpartition('.')[-1] name = ext.name.rpartition('.')[-1]
pro = '''\ pro = '''\
TARGET = %s TARGET = %s
TEMPLATE = lib TEMPLATE = lib
@ -69,7 +69,7 @@ CONFIG += x86 ppc
return map(os.path.abspath, glob.glob(pat)) return map(os.path.abspath, glob.glob(pat))
finally: finally:
os.chdir(cwd) os.chdir(cwd)
def build_sbf(self, sip, sbf, bdir): def build_sbf(self, sip, sbf, bdir):
sip_bin = self.sipcfg.sip_bin sip_bin = self.sipcfg.sip_bin
self.spawn([sip_bin, self.spawn([sip_bin,
@ -78,10 +78,10 @@ CONFIG += x86 ppc
'-I', self.pyqtcfg.pyqt_sip_dir, '-I', self.pyqtcfg.pyqt_sip_dir,
] + self.pyqtcfg.pyqt_sip_flags.split()+ ] + self.pyqtcfg.pyqt_sip_flags.split()+
[sip]) [sip])
def build_pyqt(self, bdir, sbf, ext, qtobjs, headers): def build_pyqt(self, bdir, sbf, ext, qtobjs, headers):
makefile = ext.module_makefile(configuration=self.pyqtcfg, makefile = ext.module_makefile(configuration=self.pyqtcfg,
build_file=sbf, dir=bdir, build_file=sbf, dir=bdir,
makefile='Makefile.pyqt', makefile='Makefile.pyqt',
universal=OSX_SDK, qt=1) universal=OSX_SDK, qt=1)
if 'win32' in sys.platform: if 'win32' in sys.platform:
@ -95,14 +95,14 @@ CONFIG += x86 ppc
self.make('Makefile.pyqt') self.make('Makefile.pyqt')
finally: finally:
os.chdir(cwd) os.chdir(cwd)
def build_extension(self, ext): def build_extension(self, ext):
self.inplace = True # Causes extensions to be built in the source tree self.inplace = True # Causes extensions to be built in the source tree
if not isinstance(ext, PyQtExtension): if not isinstance(ext, PyQtExtension):
return _build_ext.build_extension(self, ext) return _build_ext.build_extension(self, ext)
fullname = self.get_ext_fullname(ext.name) fullname = self.get_ext_fullname(ext.name)
if self.inplace: if self.inplace:
# ignore build-lib -- put the compiled extension into # ignore build-lib -- put the compiled extension into
@ -122,20 +122,20 @@ CONFIG += x86 ppc
bdir = os.path.abspath(os.path.join(self.build_temp, fullname)) bdir = os.path.abspath(os.path.join(self.build_temp, fullname))
if not os.path.exists(bdir): if not os.path.exists(bdir):
os.makedirs(bdir) os.makedirs(bdir)
ext.sources = map(os.path.abspath, ext.sources) ext.sources2 = map(os.path.abspath, ext.sources)
qt_dir = 'qt\\release' if iswindows else 'qt' qt_dir = 'qt\\release' if iswindows else 'qt'
objects = set(map(lambda x: os.path.join(bdir, qt_dir, replace_suffix(os.path.basename(x), '.o')), objects = set(map(lambda x: os.path.join(bdir, qt_dir, replace_suffix(os.path.basename(x), '.o')),
[s for s in ext.sources if not s.endswith('.h')])) [s for s in ext.sources2 if not s.endswith('.h')]))
newer = False newer = False
for object in objects: for object in objects:
if newer_group(ext.sources, object, missing='newer'): if newer_group(ext.sources2, object, missing='newer'):
newer = True newer = True
break break
headers = [f for f in ext.sources if f.endswith('.h')] headers = [f for f in ext.sources2 if f.endswith('.h')]
if self.force or newer: if self.force or newer:
log.info('building \'%s\' extension', ext.name) log.info('building \'%s\' extension', ext.name)
objects = self.build_qt_objects(ext, bdir) objects = self.build_qt_objects(ext, bdir)
self.sipcfg = sipconfig.Configuration() self.sipcfg = sipconfig.Configuration()
self.pyqtcfg = pyqtconfig.Configuration() self.pyqtcfg = pyqtconfig.Configuration()
sbf_sources = [] sbf_sources = []
@ -148,19 +148,19 @@ CONFIG += x86 ppc
generated_sources = [] generated_sources = []
for sbf in sbf_sources: for sbf in sbf_sources:
generated_sources += self.get_sip_output_list(sbf, bdir) generated_sources += self.get_sip_output_list(sbf, bdir)
depends = generated_sources + list(objects) depends = generated_sources + list(objects)
mod = os.path.join(bdir, os.path.basename(ext_filename)) mod = os.path.join(bdir, os.path.basename(ext_filename))
if self.force or newer_group(depends, mod, 'newer'): if self.force or newer_group(depends, mod, 'newer'):
self.build_pyqt(bdir, sbf_sources[0], ext, list(objects), headers) self.build_pyqt(bdir, sbf_sources[0], ext, list(objects), headers)
if self.force or newer_group([mod], ext_filename, 'newer'): if self.force or newer_group([mod], ext_filename, 'newer'):
if os.path.exists(ext_filename): if os.path.exists(ext_filename):
os.unlink(ext_filename) os.unlink(ext_filename)
shutil.copyfile(mod, ext_filename) shutil.copyfile(mod, ext_filename)
shutil.copymode(mod, ext_filename) shutil.copymode(mod, ext_filename)
def get_sip_output_list(self, sbf, bdir): def get_sip_output_list(self, sbf, bdir):
""" """
Parse the sbf file specified to extract the name of the generated source Parse the sbf file specified to extract the name of the generated source
@ -175,7 +175,7 @@ CONFIG += x86 ppc
return out return out
raise RuntimeError, "cannot parse SIP-generated '%s'" % sbf raise RuntimeError, "cannot parse SIP-generated '%s'" % sbf
def run_sip(self, sip_files): def run_sip(self, sip_files):
sip_bin = self.sipcfg.sip_bin sip_bin = self.sipcfg.sip_bin
sip_sources = [i[0] for i in sip_files] sip_sources = [i[0] for i in sip_files]
@ -191,6 +191,5 @@ CONFIG += x86 ppc
] + self.pyqtcfg.pyqt_sip_flags.split()+ ] + self.pyqtcfg.pyqt_sip_flags.split()+
[sip]) [sip])
generated_sources += self.get_sip_output_list(sbf) generated_sources += self.get_sip_output_list(sbf)
return generated_sources return generated_sources

View File

@ -1,45 +0,0 @@
#!/usr/bin/env python
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
'''
Compile resource files.
'''
import os, sys, glob
sys.path.insert(1, os.path.join(os.getcwd(), 'src'))
RESOURCES = dict(
opf_template = '%p/ebooks/metadata/opf.xml',
ncx_template = '%p/ebooks/metadata/ncx.xml',
fb2_xsl = '%p/ebooks/lrf/fb2/fb2.xsl',
metadata_sqlite = '%p/library/metadata_sqlite.sql',
)
def main(args=sys.argv):
data = ''
for key, value in RESOURCES.items():
path = value.replace('%p', 'src'+os.sep+'calibre')
bytes = repr(open(path, 'rb').read())
data += key + ' = ' + bytes + '\n\n'
translations_found = False
for TPATH in ('/usr/share/qt4/translations', '/usr/lib/qt4/translations'):
if os.path.exists(TPATH):
files = glob.glob(TPATH + '/qt_??.qm')
for f in files:
key = os.path.basename(f).partition('.')[0]
bytes = repr(open(f, 'rb').read())
data += key + ' = ' + bytes + '\n\n'
translations_found = True
break
if not translations_found:
print 'WARNING: Could not find Qt transations'
dest = os.path.abspath(os.path.join('src', 'calibre', 'resources.py'))
print 'Writing resources to', dest
open(dest, 'wb').write(data)
return 0
if __name__ == '__main__':
sys.exit(main())

369
setup.py
View File

@ -1,8 +1,8 @@
#!/usr/bin/env python from __future__ import with_statement
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import sys, re, os, shutil import sys, re, os, shutil, cStringIO, tempfile, subprocess
sys.path.append('src') sys.path.append('src')
iswindows = re.search('win(32|64)', sys.platform) iswindows = re.search('win(32|64)', sys.platform)
isosx = 'darwin' in sys.platform isosx = 'darwin' in sys.platform
@ -48,66 +48,291 @@ main_functions = {
if __name__ == '__main__': if __name__ == '__main__':
from setuptools import setup, find_packages, Extension from setuptools import setup, find_packages, Extension
from distutils.command.build import build as _build from distutils.command.build import build as _build
from distutils.core import Command from distutils.core import Command as _Command
from pyqtdistutils import PyQtExtension, build_ext from pyqtdistutils import PyQtExtension, build_ext
import subprocess, glob import subprocess, glob
class pot(Command): def newer(targets, sources):
user_options = [] '''
def initialize_options(self): pass Return True is sources is newer that targets or if targets
def finalize_options(self): pass does not exist.
'''
def run(self): for f in targets:
from calibre.translations import create_pot if not os.path.exists(f):
create_pot() return True
ttimes = map(lambda x: os.stat(x).st_mtime, targets)
def build_manual(): stimes = map(lambda x: os.stat(x).st_mtime, sources)
cwd = os.path.abspath(os.getcwd()) newest_source, oldest_target = max(stimes), min(ttimes)
os.chdir(os.path.join('src', 'calibre', 'manual')) return newest_source > oldest_target
try:
for d in ('.build', 'cli'):
if os.path.exists(d):
shutil.rmtree(d)
os.makedirs(d)
if not os.path.exists('.build'+os.sep+'html'):
os.makedirs('.build'+os.sep+'html')
subprocess.check_call(['sphinx-build', '-b', 'custom', '-d',
'.build/doctrees', '.', '.build/html'])
finally:
os.chdir(cwd)
class manual(Command): class Command(_Command):
user_options = [] user_options = []
def initialize_options(self): pass def initialize_options(self): pass
def finalize_options(self): pass def finalize_options(self): pass
class pot(Command):
''' Create the .pot template for all translatable strings '''
PATH = os.path.join('src', APPNAME, 'translations')
def source_files(self):
ans = []
for root, dirs, files in os.walk(os.path.dirname(self.PATH)):
for name in files:
if name.endswith('.py'):
ans.append(os.path.abspath(os.path.join(root, name)))
return ans
def run(self): def run(self):
build_manual() sys.path.insert(0, os.path.abspath(self.PATH))
try:
from pygettext import main as pygettext
files = self.source_files()
buf = cStringIO.StringIO()
print 'Creating translations template'
tempdir = tempfile.mkdtemp()
pygettext(buf, ['-p', tempdir]+files)
src = buf.getvalue()
pot = os.path.join(tempdir, 'calibre.pot')
f = open(pot, 'wb')
f.write(src)
f.close()
print 'Translations template:', pot
return pot
finally:
sys.path.remove(os.path.abspath(self.PATH))
class manual(Command):
''' Build the User Manual '''
def run(self):
cwd = os.path.abspath(os.getcwd())
os.chdir(os.path.join('src', 'calibre', 'manual'))
try:
for d in ('.build', 'cli'):
if os.path.exists(d):
shutil.rmtree(d)
os.makedirs(d)
if not os.path.exists('.build'+os.sep+'html'):
os.makedirs('.build'+os.sep+'html')
subprocess.check_call(['sphinx-build', '-b', 'custom', '-d',
'.build/doctrees', '.', '.build/html'])
finally:
os.chdir(cwd)
@classmethod
def clean(cls):
path = os.path.join('src', 'calibre', 'manual', '.build')
if os.path.exists(path):
shutil.rmtree(path)
class resources(Command):
'''
Compile various resource files used in calibre.
'''
RESOURCES = dict(
opf_template = 'ebooks/metadata/opf.xml',
ncx_template = 'ebooks/metadata/ncx.xml',
fb2_xsl = 'ebooks/lrf/fb2/fb2.xsl',
metadata_sqlite = 'library/metadata_sqlite.sql',
)
DEST = os.path.join('src', APPNAME, 'resources.py')
def get_qt_translations(self):
data = {}
translations_found = False
for TPATH in ('/usr/share/qt4/translations', '/usr/lib/qt4/translations'):
if os.path.exists(TPATH):
files = glob.glob(TPATH + '/qt_??.qm')
for f in files:
key = os.path.basename(f).partition('.')[0]
data[key] = f
translations_found = True
break
if not translations_found:
print 'WARNING: Could not find Qt transations'
return data
def run(self):
data, dest, RESOURCES = {}, self.DEST, self.RESOURCES
for key in RESOURCES:
path = RESOURCES[key]
if not os.path.isabs(path):
RESOURCES[key] = os.path.join('src', APPNAME, path)
translations = self.get_qt_translations()
RESOURCES.update(translations)
if newer([dest], RESOURCES.values()):
print 'Compiling resources...'
with open(dest, 'wb') as f:
for key in RESOURCES:
data = open(RESOURCES[key], 'rb').read()
f.write(key + ' = ' + repr(data)+'\n\n')
else:
print 'Resources are up to date'
@classmethod
def clean(cls):
path = cls.DEST
for path in glob.glob(path+'*'):
if os.path.exists(path):
os.remove(path)
class translations(Command):
'''
Compile the translations
'''
PATH = os.path.join('src', APPNAME, 'translations')
DEST = os.path.join(PATH, 'compiled.py')
def run(self):
sys.path.insert(0, os.path.abspath(self.PATH))
try:
files = glob.glob(os.path.join(self.PATH, '*.po'))
if newer([self.DEST], files):
from msgfmt import main as msgfmt
translations = {}
print 'Compiling translations...'
for po in files:
lang = os.path.basename(po).partition('.')[0]
buf = cStringIO.StringIO()
print 'Compiling', lang
msgfmt(buf, [po])
translations[lang] = buf.getvalue()
open(self.DEST, 'wb').write('translations = '+repr(translations))
else:
print 'Translations up to date'
finally:
sys.path.remove(os.path.abspath(self.PATH))
@classmethod
def clean(cls):
path = cls.DEST
if os.path.exists(path):
os.remove(path)
class gui(Command):
'''
Compile all GUI forms and image related resources.
'''
PATH = os.path.join('src', APPNAME, 'gui2')
IMAGES_DEST = os.path.join(PATH, 'images_rc.py')
@classmethod
def find_forms(cls):
forms = []
for root, dirs, files in os.walk(cls.PATH):
for name in files:
if name.endswith('.ui'):
forms.append(os.path.abspath(os.path.join(root, name)))
return forms
@classmethod
def form_to_compiled_form(cls, form):
return form.rpartition('.')[0]+'_ui.py'
def run(self):
self.build_forms()
self.build_images()
def build_images(self):
cwd, images = os.getcwd(), os.path.basename(self.IMAGES_DEST)
try:
os.chdir(self.PATH)
sources, files = [], []
for root, dirs, files in os.walk('images'):
for name in files:
sources.append(os.path.join(root, name))
if newer([images], sources):
print 'Compiling images...'
for s in sources:
alias = ' alias="library"' if s.endswith('images'+os.sep+'library.png') else ''
files.append('<file%s>%s</file>'%(alias, s))
manifest = '<RCC>\n<qresource prefix="/">\n%s\n</qresource>\n</RCC>'%'\n'.join(files)
with open('images.qrc', 'wb') as f:
f.write(manifest)
subprocess.check_call(['pyrcc4', '-o', images, 'images.qrc'])
os.remove('images.qrc')
else:
print 'Images are up to date'
finally:
os.chdir(cwd)
def build_forms(self):
from PyQt4.uic import compileUi
forms = self.find_forms()
for form in forms:
compiled_form = self.form_to_compiled_form(form)
if not os.path.exists(compiled_form) or os.stat(form).st_mtime > os.stat(compiled_form).st_mtime:
print 'Compiling form', form
buf = cStringIO.StringIO()
compileUi(form, buf)
dat = buf.getvalue()
dat = dat.replace('__appname__', APPNAME)
dat = dat.replace('import images_rc', 'from calibre.gui2 import images_rc')
dat = dat.replace('from library import', 'from calibre.gui2.library import')
dat = dat.replace('from widgets import', 'from calibre.gui2.widgets import')
dat = re.compile(r'QtGui.QApplication.translate\(.+?,\s+"(.+?)(?<!\\)",.+?\)', re.DOTALL).sub(r'_("\1")', dat)
# Workaround bug in Qt 4.4 on Windows
if form.endswith('dialogs%sconfig.ui'%os.sep) or form.endswith('dialogs%slrf_single.ui'%os.sep):
print 'Implementing Workaround for buggy pyuic in form', form
dat = re.sub(r'= QtGui\.QTextEdit\(self\..*?\)', '= QtGui.QTextEdit()', dat)
dat = re.sub(r'= QtGui\.QListWidget\(self\..*?\)', '= QtGui.QListWidget()', dat)
open(compiled_form, 'wb').write(dat)
@classmethod
def clean(cls):
forms = cls.find_forms()
for form in forms:
c = cls.form_to_compiled_form(form)
if os.path.exists(c):
os.remove(c)
images = cls.IMAGES_DEST
if os.path.exists(images):
os.remove(images)
class clean(Command):
''' Delete all computer generated files in the source tree'''
def run(self):
print 'Cleaning...'
manual.clean()
gui.clean()
translations.clean()
resources.clean()
for f in glob.glob(os.path.join('src', 'calibre', 'plugins', '*')):
os.remove(f)
for root, dirs, files in os.walk('.'):
for name in files:
if name.endswith('~') or \
name.endswith('.pyc') or \
name.endswith('.pyo'):
os.remove(os.path.join(root, name))
for dir in 'build', 'dist':
for f in os.listdir(dir):
if os.path.isdir(dir + os.sep + f):
shutil.rmtree(dir + os.sep + f)
else:
os.remove(dir + os.sep + f)
class build(_build): class build(_build):
def run(self): sub_commands = \
# Build resources [
resources = __import__('resources') ('resources', lambda self : 'CALIBRE_BUILDBOT' not in os.environ.keys()),
resources.main([sys.executable, 'resources.py']) ('translations', lambda self : 'CALIBRE_BUILDBOT' not in os.environ.keys()),
from calibre.translations import main as translations ('gui', lambda self : 'CALIBRE_BUILDBOT' not in os.environ.keys()),
cwd = os.path.abspath(os.getcwd()) ] + _build.sub_commands
# Build translations
try:
os.chdir(os.path.join('src', 'calibre', 'translations'))
translations([sys.executable])
finally:
os.chdir(cwd)
# Build GUI
from calibre.gui2.make import main as gui2
try:
os.chdir(os.path.join('src', 'calibre', 'gui2'))
print 'Compiling GUI resources...'
gui2([sys.executable])
finally:
os.chdir(cwd)
_build.run(self)
entry_points['console_scripts'].append('calibre_postinstall = calibre.linux:post_install') entry_points['console_scripts'].append('calibre_postinstall = calibre.linux:post_install')
ext_modules = [ ext_modules = [
@ -115,10 +340,12 @@ if __name__ == '__main__':
sources=['src/calibre/utils/lzx/lzxmodule.c', sources=['src/calibre/utils/lzx/lzxmodule.c',
'src/calibre/utils/lzx/lzxd.c'], 'src/calibre/utils/lzx/lzxd.c'],
include_dirs=['src/calibre/utils/lzx']), include_dirs=['src/calibre/utils/lzx']),
Extension('calibre.plugins.msdes', Extension('calibre.plugins.msdes',
sources=['src/calibre/utils/msdes/msdesmodule.c', sources=['src/calibre/utils/msdes/msdesmodule.c',
'src/calibre/utils/msdes/des.c'], 'src/calibre/utils/msdes/des.c'],
include_dirs=['src/calibre/utils/msdes']), include_dirs=['src/calibre/utils/msdes']),
PyQtExtension('calibre.plugins.pictureflow', PyQtExtension('calibre.plugins.pictureflow',
['src/calibre/gui2/pictureflow/pictureflow.cpp', ['src/calibre/gui2/pictureflow/pictureflow.cpp',
'src/calibre/gui2/pictureflow/pictureflow.h'], 'src/calibre/gui2/pictureflow/pictureflow.h'],
@ -137,20 +364,20 @@ if __name__ == '__main__':
) )
setup( setup(
name=APPNAME, name = APPNAME,
packages = find_packages('src'), packages = find_packages('src'),
package_dir = { '' : 'src' }, package_dir = { '' : 'src' },
version=VERSION, version = VERSION,
author='Kovid Goyal', author = 'Kovid Goyal',
author_email='kovid@kovidgoyal.net', author_email = 'kovid@kovidgoyal.net',
url = 'http://%s.kovidgoyal.net'%APPNAME, url = 'http://%s.kovidgoyal.net'%APPNAME,
package_data = {'calibre':['plugins/*']}, package_data = {'calibre':['plugins/*']},
include_package_data=True, include_package_data = True,
entry_points = entry_points, entry_points = entry_points,
zip_safe = False, zip_safe = False,
options = { 'bdist_egg' : {'exclude_source_files': True,}, }, options = { 'bdist_egg' : {'exclude_source_files': True,}, },
ext_modules=ext_modules, ext_modules = ext_modules,
description = description =
''' '''
E-book management application. E-book management application.
''', ''',
@ -171,7 +398,7 @@ if __name__ == '__main__':
'''%(APPNAME, APPNAME, APPNAME, APPNAME, APPNAME), '''%(APPNAME, APPNAME, APPNAME, APPNAME, APPNAME),
license = 'GPL', license = 'GPL',
classifiers = [ classifiers = [
'Development Status :: 4 - Beta', 'Development Status :: 4 - Beta',
'Environment :: Console', 'Environment :: Console',
'Environment :: X11 Applications :: Qt', 'Environment :: X11 Applications :: Qt',
@ -184,9 +411,17 @@ if __name__ == '__main__':
'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: System :: Hardware :: Hardware Drivers' 'Topic :: System :: Hardware :: Hardware Drivers'
], ],
cmdclass = {'build_ext': build_ext, 'build' : build, 'pot' : pot, cmdclass = {
'manual' : manual}, 'build_ext' : build_ext,
'build' : build,
'pot' : pot,
'manual' : manual,
'resources' : resources,
'translations' : translations,
'gui' : gui,
'clean' : clean,
},
) )
if 'develop' in ' '.join(sys.argv) and islinux: if 'develop' in ' '.join(sys.argv) and islinux:
subprocess.check_call('calibre_postinstall', shell=True) subprocess.check_call('calibre_postinstall --do-not-reload-udev-hal', shell=True)

View File

@ -2,7 +2,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
__appname__ = 'calibre' __appname__ = 'calibre'
__version__ = '0.4.84b2' __version__ = '0.4.84b3'
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>" __author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
''' '''
Various run time constants. Various run time constants.

View File

@ -603,7 +603,7 @@ class Book(Delegator):
def renderLrs(self, lrsFile, encoding="UTF-8"): def renderLrs(self, lrsFile, encoding="UTF-8"):
if isinstance(lrsFile, basestring): if isinstance(lrsFile, basestring):
lrsFile = codecs.open(lrsFile, "wb", encoding=encoding) lrsFile = codecs.open(lrsFile, "wb", encoding=encoding)
self.render(lrsFile, outputEncodingName=encoding) self.render(lrsFile, outputEncodingName=encoding)
lrsFile.close() lrsFile.close()

View File

@ -1,116 +0,0 @@
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
'''
Manage the PyQt build system pyrcc4, pylupdate4, lrelease and friends.
'''
import sys, os, subprocess, cStringIO, compiler, re
from functools import partial
from PyQt4.uic import compileUi
check_call = partial(subprocess.check_call, shell=True)
sys.path.insert(1, os.path.abspath('..%s..'%os.sep))
from calibre import __appname__
from calibre.path import path
def find_forms():
forms = []
for root, dirs, files in os.walk('.'):
for name in files:
if name.endswith('.ui'):
forms.append(os.path.abspath(os.path.join(root, name)))
return forms
def form_to_compiled_form(form):
return form.rpartition('.')[0]+'_ui.py'
def build_forms(forms):
for form in forms:
compiled_form = form_to_compiled_form(form)
if not os.path.exists(compiled_form) or os.stat(form).st_mtime > os.stat(compiled_form).st_mtime:
print 'Compiling form', form
buf = cStringIO.StringIO()
compileUi(form, buf)
dat = buf.getvalue()
dat = dat.replace('__appname__', __appname__)
dat = dat.replace('import images_rc', 'from calibre.gui2 import images_rc')
dat = dat.replace('from library import', 'from calibre.gui2.library import')
dat = dat.replace('from widgets import', 'from calibre.gui2.widgets import')
dat = re.compile(r'QtGui.QApplication.translate\(.+?,\s+"(.+?)(?<!\\)",.+?\)', re.DOTALL).sub(r'_("\1")', dat)
# Workaround bug in Qt 4.4 on Windows
if form.endswith('dialogs%sconfig.ui'%os.sep) or form.endswith('dialogs%slrf_single.ui'%os.sep):
print 'Implementing Workaround for buggy pyuic in form', form
dat = re.sub(r'= QtGui\.QTextEdit\(self\..*?\)', '= QtGui.QTextEdit()', dat)
dat = re.sub(r'= QtGui\.QListWidget\(self\..*?\)', '= QtGui.QListWidget()', dat)
open(compiled_form, 'wb').write(dat)
def build_images():
p = path('images')
mtime = p.mtime
for x in p.walk():
mtime = max(x.mtime, mtime)
images = path('images_rc.py')
if not images.exists() or mtime > images.mtime:
print 'Compiling images...'
files = []
for x in p.walk():
if '.svn' in x or '.bzr' in x or x.isdir():
continue
alias = ' alias="library"' if x == p/'library.png' else ''
files.append('<file%s>%s</file>'%(alias, x))
qrc = '<RCC>\n<qresource prefix="/">\n%s\n</qresource>\n</RCC>'%'\n'.join(files)
f = open('images.qrc', 'wb')
f.write(qrc)
f.close()
check_call(' '.join(['pyrcc4', '-o', images, 'images.qrc']))
compiler.compileFile(images)
os.utime(images, None)
os.utime(images, None)
print 'Size of images:', '%.2f MB'%(path(images+'c').size/(1024*1024.))
def build(forms):
build_forms(forms)
build_images()
def clean(forms):
for form in forms:
compiled_form = form_to_compiled_form(form)
if os.path.exists(compiled_form):
print 'Removing compiled form', compiled_form
os.unlink(compiled_form)
print 'Removing compiled images'
os.unlink('images_rc.py')
os.unlink('images_rc.pyc')
def main(args=sys.argv):
if not os.getcwd().endswith('gui2'):
raise Exception('Must be run from the gui2 directory')
forms = find_forms()
if len(args) == 1:
args.append('all')
if args[1] == 'all':
build(forms)
elif args[1] == 'clean':
clean(forms)
elif args[1] == 'test':
build(forms)
print 'Running main.py'
subprocess.call('python main.py', shell=True)
else:
print 'Usage: %s [all|clean|test]'%(args[0])
return 1
return 0
if __name__ == '__main__':
sys.exit(main())

View File

@ -246,11 +246,20 @@ class LibraryDatabase2(LibraryDatabase):
spath = os.path.join(self.library_path, *current_path.split('/')) spath = os.path.join(self.library_path, *current_path.split('/'))
if current_path and os.path.exists(spath): if current_path and os.path.exists(spath):
for f in os.listdir(spath): for f in os.listdir(spath):
copyfile(os.path.join(spath, f), os.path.join(tpath, f)) try:
copyfile(os.path.join(spath, f), os.path.join(tpath, f))
except OSError, err:
if err.errno == 78: # Happens if database is mounted via sshfs
shutil.copyfile(os.path.join(spath, f), os.path.join(tpath, f))
else:
raise
self.conn.execute('UPDATE books SET path=? WHERE id=?', (path, id)) self.conn.execute('UPDATE books SET path=? WHERE id=?', (path, id))
self.conn.commit() self.conn.commit()
if current_path and os.path.exists(spath): if current_path and os.path.exists(spath):
shutil.rmtree(spath) shutil.rmtree(spath)
parent = os.path.dirname(spath)
if len(os.listdir(parent)) == 0:
shutil.rmtree(parent)
def cover(self, index, index_is_id=False, as_file=False, as_image=False): def cover(self, index, index_is_id=False, as_file=False, as_image=False):
''' '''
@ -495,6 +504,7 @@ class LibraryDatabase2(LibraryDatabase):
QCoreApplication.processEvents() QCoreApplication.processEvents()
db.conn.row_factory = lambda cursor, row : tuple(row) db.conn.row_factory = lambda cursor, row : tuple(row)
books = db.conn.execute('SELECT id, title, sort, timestamp, uri, series_index, author_sort, isbn FROM books ORDER BY id ASC').fetchall() books = db.conn.execute('SELECT id, title, sort, timestamp, uri, series_index, author_sort, isbn FROM books ORDER BY id ASC').fetchall()
progress.setAutoReset(False)
progress.setRange(0, len(books)) progress.setRange(0, len(books))
for book in books: for book in books:
@ -533,4 +543,5 @@ books_series_link feeds
self.conn.commit() self.conn.commit()
progress.setLabelText(_('Compacting database')) progress.setLabelText(_('Compacting database'))
self.vacuum() self.vacuum()
progress.reset()

View File

@ -12,7 +12,7 @@ from gettext import GNUTranslations
# Default translation is NOOP # Default translation is NOOP
import __builtin__ import __builtin__
__builtin__.__dict__['_'] = lambda s: s __builtin__.__dict__['_'] = lambda s: s
from calibre.constants import iswindows, isosx, islinux, isfrozen,\ from calibre.constants import iswindows, isosx, islinux, isfrozen,\
preferred_encoding preferred_encoding
from calibre.translations.msgfmt import make from calibre.translations.msgfmt import make
@ -22,7 +22,7 @@ if not _run_once:
_run_once = True _run_once = True
################################################################################ ################################################################################
# Setup translations # Setup translations
def get_lang(): def get_lang():
lang = locale.getdefaultlocale()[0] lang = locale.getdefaultlocale()[0]
if lang is None and os.environ.has_key('LANG'): # Needed for OS X if lang is None and os.environ.has_key('LANG'): # Needed for OS X
@ -35,7 +35,7 @@ if not _run_once:
if match: if match:
lang = match.group() lang = match.group()
return lang return lang
def set_translator(): def set_translator():
# To test different translations invoke as # To test different translations invoke as
# LC_ALL=de_DE.utf8 program # LC_ALL=de_DE.utf8 program
@ -43,7 +43,7 @@ if not _run_once:
from calibre.translations.compiled import translations from calibre.translations.compiled import translations
except: except:
return return
lang = get_lang() lang = get_lang()
if lang: if lang:
buf = None buf = None
if os.access(lang+'.po', os.R_OK): if os.access(lang+'.po', os.R_OK):
@ -55,9 +55,9 @@ if not _run_once:
if buf is not None: if buf is not None:
t = GNUTranslations(buf) t = GNUTranslations(buf)
t.install(unicode=True) t.install(unicode=True)
set_translator() set_translator()
################################################################################ ################################################################################
# Initialize locale # Initialize locale
try: try:
@ -69,37 +69,42 @@ if not _run_once:
locale.setlocale(dl[0]) locale.setlocale(dl[0])
except: except:
pass pass
################################################################################ ################################################################################
# Load plugins # Load plugins
if isfrozen: def load_plugins():
if iswindows: plugins = {}
plugin_path = os.path.join(os.path.dirname(sys.executable), 'plugins') if isfrozen:
sys.path.insert(1, os.path.dirname(sys.executable)) if iswindows:
elif isosx: plugin_path = os.path.join(os.path.dirname(sys.executable), 'plugins')
plugin_path = os.path.join(getattr(sys, 'frameworks_dir'), 'plugins') sys.path.insert(1, os.path.dirname(sys.executable))
elif islinux: elif isosx:
plugin_path = os.path.join(getattr(sys, 'frozen_path'), 'plugins') plugin_path = os.path.join(getattr(sys, 'frameworks_dir'), 'plugins')
sys.path.insert(0, plugin_path) elif islinux:
else: plugin_path = os.path.join(getattr(sys, 'frozen_path'), 'plugins')
import pkg_resources sys.path.insert(0, plugin_path)
plugins = getattr(pkg_resources, 'resource_filename')('calibre', 'plugins') else:
sys.path.insert(0, plugins) import pkg_resources
plugin_path = getattr(pkg_resources, 'resource_filename')('calibre', 'plugins')
plugins = {} sys.path.insert(0, plugin_path)
for plugin in ['pictureflow', 'lzx', 'msdes'] + \
(['winutil'] if iswindows else []) + \ for plugin in ['pictureflow', 'lzx', 'msdes'] + \
(['usbobserver'] if isosx else []): (['winutil'] if iswindows else []) + \
try: (['usbobserver'] if isosx else []):
p, err = __import__(plugin), '' try:
except Exception, err: p, err = __import__(plugin), ''
p = None except Exception, err:
err = str(err) p = None
plugins[plugin] = (p, err) err = str(err)
plugins[plugin] = (p, err)
return plugins
plugins = load_plugins()
################################################################################ ################################################################################
# Improve builtin path functions to handle unicode sensibly # Improve builtin path functions to handle unicode sensibly
_abspath = os.path.abspath _abspath = os.path.abspath
def my_abspath(path, encoding=sys.getfilesystemencoding()): def my_abspath(path, encoding=sys.getfilesystemencoding()):
''' '''
@ -115,7 +120,7 @@ if not _run_once:
if to_unicode: if to_unicode:
res = res.decode(encoding) res = res.decode(encoding)
return res return res
os.path.abspath = my_abspath os.path.abspath = my_abspath
_join = os.path.join _join = os.path.join
def my_join(a, *p): def my_join(a, *p):
@ -127,15 +132,15 @@ if not _run_once:
_unicode = True _unicode = True
break break
p = [i.encode(encoding) if isinstance(i, unicode) else i for i in p] p = [i.encode(encoding) if isinstance(i, unicode) else i for i in p]
res = _join(*p) res = _join(*p)
if _unicode: if _unicode:
res = res.decode(encoding) res = res.decode(encoding)
return res return res
os.path.join = my_join os.path.join = my_join
################################################################################ ################################################################################
# Platform specific modules # Platform specific modules
winutil = winutilerror = None winutil = winutilerror = None
@ -145,10 +150,9 @@ if not _run_once:
raise RuntimeError('Failed to load the winutil plugin: %s'%winutilerror) raise RuntimeError('Failed to load the winutil plugin: %s'%winutilerror)
if len(sys.argv) > 1: if len(sys.argv) > 1:
sys.argv[1:] = winutil.argv()[1-len(sys.argv):] sys.argv[1:] = winutil.argv()[1-len(sys.argv):]
################################################################################ ################################################################################
# Convert command line arguments to unicode # Convert command line arguments to unicode
for i in range(1, len(sys.argv)): for i in range(1, len(sys.argv)):
if not isinstance(sys.argv[i], unicode): if not isinstance(sys.argv[i], unicode):
sys.argv[i] = sys.argv[i].decode(preferred_encoding, 'replace') sys.argv[i] = sys.argv[i].decode(preferred_encoding, 'replace')

View File

@ -3,102 +3,3 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
''' '''
Manage translation of user visible strings. Manage translation of user visible strings.
''' '''
import sys, os, cStringIO, tempfile, subprocess, functools, tarfile, re, time, \
glob, urllib2, shutil
check_call = functools.partial(subprocess.check_call, shell=True)
try:
from calibre.translations.pygettext import main as pygettext
from calibre.translations.msgfmt import main as msgfmt
except ImportError:
cwd = os.getcwd()
sys.path.insert(0, os.path.dirname(os.path.dirname(cwd)))
from calibre.translations.pygettext import main as pygettext
from calibre.translations.msgfmt import main as msgfmt
def source_files():
ans = []
for root, dirs, files in os.walk(os.path.dirname(os.getcwdu())):
for name in files:
if name.endswith('.py'):
ans.append(os.path.abspath(os.path.join(root, name)))
return ans
def create_pot():
files = source_files()
buf = cStringIO.StringIO()
print 'Creating translations template'
tempdir = tempfile.mkdtemp()
pygettext(buf, ['-p', tempdir]+files)
src = buf.getvalue()
pot = os.path.join(tempdir, 'calibre.pot')
f = open(pot, 'wb')
f.write(src)
f.close()
print 'Translations template:', pot
return pot
def compile_translations():
translations = {}
print 'Compiling translations...'
for po in glob.glob('*.po'):
lang = os.path.basename(po).partition('.')[0]
buf = cStringIO.StringIO()
print 'Compiling', lang
msgfmt(buf, [po])
translations[lang] = buf.getvalue()
open('compiled.py', 'wb').write('translations = '+repr(translations))
def import_from_launchpad(url):
f = open('/tmp/launchpad_export.tar.gz', 'wb')
shutil.copyfileobj(urllib2.urlopen(url), f)
f.close()
tf = tarfile.open('/tmp/launchpad_export.tar.gz', 'r:gz')
next = tf.next()
while next is not None:
if next.isfile() and next.name.endswith('.po'):
try:
po = re.search(r'-([a-z]{2,3}\.po)', next.name).group(1)
except:
next = tf.next()
continue
out = os.path.abspath(os.path.join('.', os.path.basename(po)))
print 'Updating', '%6s'%po, '-->', out
open(out, 'wb').write(tf.extractfile(next).read())
next = tf.next()
check_for_critical_bugs()
return 0
def check_for_critical_bugs():
if os.path.exists('.errors'):
shutil.rmtree('.errors')
pofilter = ('pofilter', '-i', '.', '-o', '.errors',
'-t', 'accelerators', '-t', 'escapes', '-t', 'variables',
'-t', 'xmltags')
subprocess.check_call(pofilter)
errs = os.listdir('.errors')
if errs:
print 'WARNING: Translation errors detected'
print 'See the .errors directory and http://translate.sourceforge.net/wiki/toolkit/using_pofilter'
def main(args=sys.argv):
if len(args) > 1:
if args[1] == 'pot':
create_pot()
else:
import_from_launchpad(args[1])
else:
compile_translations()
return 0
if __name__ == '__main__':
cwd = os.getcwd()
sys.path.insert(0, os.path.dirname(os.path.dirname(cwd)))
sys.exit(main())

View File

@ -284,6 +284,8 @@ class OptionSet(object):
def parse_string(self, src): def parse_string(self, src):
options = {'cPickle':cPickle} options = {'cPickle':cPickle}
if not isinstance(src, unicode):
src = src.decode('utf-8')
if src is not None: if src is not None:
exec src in options exec src in options
opts = OptionValues() opts = OptionValues()
@ -352,7 +354,7 @@ class Config(ConfigInterface):
if os.path.exists(self.config_file_path): if os.path.exists(self.config_file_path):
try: try:
with ExclusiveFile(self.config_file_path) as f: with ExclusiveFile(self.config_file_path) as f:
src = f.read() src = f.read().decode('utf-8')
except LockError: except LockError:
raise IOError('Could not lock config file: %s'%self.config_file_path) raise IOError('Could not lock config file: %s'%self.config_file_path)
return self.option_set.parse_string(src) return self.option_set.parse_string(src)
@ -362,7 +364,7 @@ class Config(ConfigInterface):
return '' return ''
try: try:
with ExclusiveFile(self.config_file_path) as f: with ExclusiveFile(self.config_file_path) as f:
return f.read() return f.read().decode('utf-8')
except LockError: except LockError:
raise IOError('Could not lock config file: %s'%self.config_file_path) raise IOError('Could not lock config file: %s'%self.config_file_path)
@ -380,6 +382,8 @@ class Config(ConfigInterface):
src = self.option_set.serialize(opts)+ '\n\n' + footer + '\n' src = self.option_set.serialize(opts)+ '\n\n' + footer + '\n'
f.seek(0) f.seek(0)
f.truncate() f.truncate()
if isinstance(src, unicode):
src = src.encode('utf-8')
f.write(src) f.write(src)
except LockError: except LockError:
raise IOError('Could not lock config file: %s'%self.config_file_path) raise IOError('Could not lock config file: %s'%self.config_file_path)

View File

@ -118,7 +118,7 @@ winutil_argv(PyObject *self, PyObject *args) {
LPSTR buf; LPSTR buf;
int argc, i, bytes; int argc, i, bytes;
if (!PyArg_ParseTuple(args, "")) return NULL; if (!PyArg_ParseTuple(args, "")) return NULL;
_argv = CommandLineToArgvW(GetCommandLine(), &argc); _argv = CommandLineToArgvW(GetCommandLineW(), &argc);
if (_argv == NULL) { PyErr_NoMemory(); return NULL; } if (_argv == NULL) { PyErr_NoMemory(); return NULL; }
argv = PyList_New(argc); argv = PyList_New(argc);
if (argv != NULL) { if (argv != NULL) {

View File

@ -27,6 +27,7 @@ TXT2LRF = "src/calibre/ebooks/lrf/txt/demo"
MOBILEREAD = 'ftp://dev.mobileread.com/calibre/' MOBILEREAD = 'ftp://dev.mobileread.com/calibre/'
BUILD_SCRIPT ='''\ BUILD_SCRIPT ='''\
#!/bin/bash #!/bin/bash
export CALIBRE_BUILDBOT=1
cd ~/build && \ cd ~/build && \
rsync -avz --exclude src/calibre/plugins --exclude calibre/src/calibre.egg-info --exclude docs --exclude .bzr --exclude .build --exclude build --exclude dist --exclude "*.pyc" --exclude "*.pyo" rsync://%(host)s/work/%(project)s . && \ rsync -avz --exclude src/calibre/plugins --exclude calibre/src/calibre.egg-info --exclude docs --exclude .bzr --exclude .build --exclude build --exclude dist --exclude "*.pyc" --exclude "*.pyo" rsync://%(host)s/work/%(project)s . && \
cd %(project)s && \ cd %(project)s && \