mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-06-23 15:30:45 -04:00
IGN:Improve UI for build system
This commit is contained in:
parent
d8b443fd26
commit
b096b292bf
372
setup.py
372
setup.py
@ -47,350 +47,15 @@ main_functions = {
|
||||
|
||||
if __name__ == '__main__':
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools.command.build_py import build_py as _build_py, convert_path
|
||||
from distutils.command.build import build as _build
|
||||
from distutils.core import Command as _Command
|
||||
from pyqtdistutils import PyQtExtension, build_ext, Extension
|
||||
import subprocess, glob
|
||||
from upload2 import sdist, pot, build, build_py, manual, \
|
||||
resources, clean, gui, translations, update, \
|
||||
tag_release, upload_demo, build_linux, build_windows, \
|
||||
build_osx, upload_installers, upload_user_manual, \
|
||||
upload_to_pypi, stage3, stage2, stage1, upload
|
||||
|
||||
def newer(targets, sources):
|
||||
'''
|
||||
Return True is sources is newer that targets or if targets
|
||||
does not exist.
|
||||
'''
|
||||
for f in targets:
|
||||
if not os.path.exists(f):
|
||||
return True
|
||||
ttimes = map(lambda x: os.stat(x).st_mtime, targets)
|
||||
stimes = map(lambda x: os.stat(x).st_mtime, sources)
|
||||
newest_source, oldest_target = max(stimes), min(ttimes)
|
||||
return newest_source > oldest_target
|
||||
|
||||
class build_py(_build_py):
|
||||
|
||||
def find_data_files(self, package, src_dir):
|
||||
"""
|
||||
Return filenames for package's data files in 'src_dir'
|
||||
Modified to treat data file specs as paths not globs
|
||||
"""
|
||||
globs = (self.package_data.get('', [])
|
||||
+ self.package_data.get(package, []))
|
||||
files = self.manifest_files.get(package, [])[:]
|
||||
for pattern in globs:
|
||||
# Each pattern has to be converted to a platform-specific path
|
||||
pattern = os.path.join(src_dir, convert_path(pattern))
|
||||
next = glob.glob(pattern)
|
||||
files.extend(next if next else [pattern])
|
||||
|
||||
return self.exclude_data_files(package, src_dir, files)
|
||||
|
||||
|
||||
class Command(_Command):
|
||||
user_options = []
|
||||
def initialize_options(self): pass
|
||||
def finalize_options(self): pass
|
||||
|
||||
class sdist(Command):
|
||||
|
||||
description = "create a source distribution using bzr"
|
||||
|
||||
def run(self):
|
||||
name = 'dist/calibre-%s.tar.gz'%VERSION
|
||||
subprocess.check_call(('bzr export '+name).split())
|
||||
self.distribution.dist_files.append(('sdist', '', name))
|
||||
|
||||
class pot(Command):
|
||||
description = '''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):
|
||||
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, ['-k', '__', '-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):
|
||||
description='''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):
|
||||
description='''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',
|
||||
jquery = 'gui2/viewer/jquery.js',
|
||||
jquery_scrollTo = 'gui2/viewer/jquery_scrollTo.js',
|
||||
html_css = 'ebooks/oeb/html.css',
|
||||
)
|
||||
|
||||
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 get_static_resources(self):
|
||||
sdir = os.path.join('src', 'calibre', 'library', 'static')
|
||||
resources, max = {}, 0
|
||||
for f in os.listdir(sdir):
|
||||
resources[f] = open(os.path.join(sdir, f), 'rb').read()
|
||||
mtime = os.stat(os.path.join(sdir, f)).st_mtime
|
||||
max = mtime if mtime > max else max
|
||||
return resources, max
|
||||
|
||||
def get_recipes(self):
|
||||
sdir = os.path.join('src', 'calibre', 'web', 'feeds', 'recipes')
|
||||
resources, max = {}, 0
|
||||
for f in os.listdir(sdir):
|
||||
if f.endswith('.py') and f != '__init__.py':
|
||||
resources[f.replace('.py', '')] = open(os.path.join(sdir, f), 'rb').read()
|
||||
mtime = os.stat(os.path.join(sdir, f)).st_mtime
|
||||
max = mtime if mtime > max else max
|
||||
return resources, max
|
||||
|
||||
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)
|
||||
static, smax = self.get_static_resources()
|
||||
recipes, rmax = self.get_recipes()
|
||||
amax = max(rmax, smax)
|
||||
if newer([dest], RESOURCES.values()) or os.stat(dest).st_mtime < amax:
|
||||
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')
|
||||
f.write('server_resources = %s\n\n'%repr(static))
|
||||
f.write('recipes = %s\n\n'%repr(recipes))
|
||||
f.write('build_time = "%s"\n\n'%time.strftime('%d %m %Y %H%M%S'))
|
||||
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):
|
||||
description='''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):
|
||||
description='''Compile all GUI forms and images'''
|
||||
PATH = os.path.join('src', APPNAME, 'gui2')
|
||||
IMAGES_DEST = os.path.join(PATH, 'images_rc.py')
|
||||
QRC = os.path.join(PATH, 'images.qrc')
|
||||
|
||||
@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'])
|
||||
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)
|
||||
|
||||
if form.endswith('viewer%smain.ui'%os.sep):
|
||||
print 'Promoting WebView'
|
||||
dat = dat.replace('self.view = QtWebKit.QWebView(', 'self.view = DocumentView(')
|
||||
dat += '\n\nfrom calibre.gui2.viewer.documentview import DocumentView'
|
||||
|
||||
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)
|
||||
for x in (cls.IMAGES_DEST, cls.QRC):
|
||||
if os.path.exists(x):
|
||||
os.remove(x)
|
||||
|
||||
class clean(Command):
|
||||
description='''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:
|
||||
for t in ('.pyc', '.pyo', '~'):
|
||||
if name.endswith(t):
|
||||
os.remove(os.path.join(root, name))
|
||||
break
|
||||
|
||||
for dir in ('build', 'dist', os.path.join('src', 'calibre.egg-info')):
|
||||
shutil.rmtree(dir, ignore_errors=True)
|
||||
|
||||
class build(_build):
|
||||
|
||||
sub_commands = [
|
||||
('resources', lambda self : 'CALIBRE_BUILDBOT' not in os.environ.keys()),
|
||||
('translations', lambda self : 'CALIBRE_BUILDBOT' not in os.environ.keys()),
|
||||
('gui', lambda self : 'CALIBRE_BUILDBOT' not in os.environ.keys()),
|
||||
('build_ext', lambda self: True),
|
||||
('build_py', lambda self: True),
|
||||
('build_clib', _build.has_c_libraries),
|
||||
('build_scripts', _build.has_scripts),
|
||||
]
|
||||
|
||||
entry_points['console_scripts'].append('calibre_postinstall = calibre.linux:post_install')
|
||||
entry_points['console_scripts'].append(
|
||||
'calibre_postinstall = calibre.linux:post_install')
|
||||
ext_modules = [
|
||||
Extension('calibre.plugins.lzx',
|
||||
sources=['src/calibre/utils/lzx/lzxmodule.c',
|
||||
@ -430,8 +95,10 @@ if __name__ == '__main__':
|
||||
plugins = ['plugins/%s.so'%(x.name.rpartition('.')[-1]) for x in ext_modules]
|
||||
else:
|
||||
plugins = ['plugins/%s.pyd'%(x.name.rpartition('.')[-1]) for x in ext_modules] + \
|
||||
['plugins/%s.pyd.manifest'%(x.name.rpartition('.')[-1]) for x in ext_modules if 'pictureflow' not in x.name]
|
||||
['plugins/%s.pyd.manifest'%(x.name.rpartition('.')[-1]) \
|
||||
for x in ext_modules if 'pictureflow' not in x.name]
|
||||
|
||||
|
||||
setup(
|
||||
name = APPNAME,
|
||||
packages = find_packages('src'),
|
||||
@ -451,7 +118,11 @@ if __name__ == '__main__':
|
||||
''',
|
||||
long_description =
|
||||
'''
|
||||
%s is an e-book library manager. It can view, convert and catalog e-books in most of the major e-book formats. It can also talk to a few e-book reader devices. It can go out to the internet and fetch metadata for your books. It can download newspapers and convert them into e-books for convenient reading. It is cross platform, running on Linux, Windows and OS X.
|
||||
%s is an e-book library manager. It can view, convert and catalog e-books \
|
||||
in most of the major e-book formats. It can also talk to e-book reader \
|
||||
devices. It can go out to the internet and fetch metadata for your books. \
|
||||
It can download newspapers and convert them into e-books for convenient \
|
||||
reading. It is cross platform, running on Linux, Windows and OS X.
|
||||
|
||||
For screenshots: https://%s.kovidgoyal.net/wiki/Screenshots
|
||||
|
||||
@ -490,6 +161,19 @@ if __name__ == '__main__':
|
||||
'gui' : gui,
|
||||
'clean' : clean,
|
||||
'sdist' : sdist,
|
||||
'update' : update,
|
||||
'tag_release' : tag_release,
|
||||
'upload_demo' : upload_demo,
|
||||
'build_linux' : build_linux,
|
||||
'build_windows' : build_windows,
|
||||
'build_osx' : build_osx,
|
||||
'upload_installers': upload_installers,
|
||||
'upload_user_manual': upload_user_manual,
|
||||
'upload_to_pypi': upload_to_pypi,
|
||||
'stage3' : stage3,
|
||||
'stage2' : stage2,
|
||||
'stage1' : stage1,
|
||||
'upload' : upload,
|
||||
},
|
||||
)
|
||||
|
||||
|
899
upload.py
899
upload.py
@ -1,11 +1,30 @@
|
||||
#!/usr/bin/python
|
||||
import sys, os, shutil, time, tempfile, socket, fcntl, struct, cStringIO, pycurl, re
|
||||
sys.path.append('src')
|
||||
import subprocess
|
||||
from subprocess import check_call as _check_call
|
||||
from functools import partial
|
||||
from __future__ import with_statement
|
||||
__license__ = 'GPL 3'
|
||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import shutil, os, glob, re, cStringIO, sys, tempfile, time, textwrap, socket, \
|
||||
struct
|
||||
from setuptools.command.build_py import build_py as _build_py, convert_path
|
||||
from distutils.core import Command
|
||||
from subprocess import check_call, call, Popen
|
||||
from distutils.command.build import build as _build
|
||||
|
||||
raw = open(os.path.join('src', 'calibre', 'constants.py'), 'rb').read()
|
||||
__version__ = re.search(r'__version__\s+=\s+[\'"]([^\'"]+)[\'"]', raw).group(1)
|
||||
__appname__ = re.search(r'__appname__\s+=\s+[\'"]([^\'"]+)[\'"]', raw).group(1)
|
||||
|
||||
PREFIX = "/var/www/calibre.kovidgoyal.net"
|
||||
DOWNLOADS = PREFIX+"/htdocs/downloads"
|
||||
DOCS = PREFIX+"/htdocs/apidocs"
|
||||
USER_MANUAL = PREFIX+'/htdocs/user_manual'
|
||||
HTML2LRF = "src/calibre/ebooks/lrf/html/demo"
|
||||
TXT2LRF = "src/calibre/ebooks/lrf/txt/demo"
|
||||
MOBILEREAD = 'ftp://dev.mobileread.com/calibre/'
|
||||
|
||||
|
||||
def get_ip_address(ifname):
|
||||
import fcntl
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
return socket.inet_ntoa(fcntl.ioctl(
|
||||
s.fileno(),
|
||||
@ -16,252 +35,664 @@ def get_ip_address(ifname):
|
||||
try:
|
||||
HOST=get_ip_address('eth0')
|
||||
except:
|
||||
HOST=get_ip_address('wlan0')
|
||||
PROJECT=os.path.basename(os.getcwd())
|
||||
try:
|
||||
HOST=get_ip_address('wlan0')
|
||||
except:
|
||||
HOST='unknown'
|
||||
|
||||
def newer(targets, sources):
|
||||
'''
|
||||
Return True is sources is newer that targets or if targets
|
||||
does not exist.
|
||||
'''
|
||||
for f in targets:
|
||||
if not os.path.exists(f):
|
||||
return True
|
||||
ttimes = map(lambda x: os.stat(x).st_mtime, targets)
|
||||
stimes = map(lambda x: os.stat(x).st_mtime, sources)
|
||||
newest_source, oldest_target = max(stimes), min(ttimes)
|
||||
return newest_source > oldest_target
|
||||
|
||||
|
||||
from calibre import __version__, __appname__
|
||||
|
||||
PREFIX = "/var/www/calibre.kovidgoyal.net"
|
||||
DOWNLOADS = PREFIX+"/htdocs/downloads"
|
||||
DOCS = PREFIX+"/htdocs/apidocs"
|
||||
USER_MANUAL = PREFIX+'/htdocs/user_manual'
|
||||
HTML2LRF = "src/calibre/ebooks/lrf/html/demo"
|
||||
TXT2LRF = "src/calibre/ebooks/lrf/txt/demo"
|
||||
MOBILEREAD = 'ftp://dev.mobileread.com/calibre/'
|
||||
BUILD_SCRIPT ='''\
|
||||
#!/bin/bash
|
||||
export CALIBRE_BUILDBOT=1
|
||||
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 . && \
|
||||
cd %(project)s && \
|
||||
%%s && \
|
||||
rm -rf build/* dist/* && \
|
||||
%%s %%s
|
||||
'''%dict(host=HOST, project=PROJECT)
|
||||
check_call = partial(_check_call, shell=True)
|
||||
#h = Host(hostType=VIX_SERVICEPROVIDER_VMWARE_WORKSTATION)
|
||||
class OptionlessCommand(Command):
|
||||
user_options = []
|
||||
def initialize_options(self): pass
|
||||
def finalize_options(self): pass
|
||||
|
||||
def run(self):
|
||||
for cmd_name in self.get_sub_commands():
|
||||
self.run_command(cmd_name)
|
||||
|
||||
|
||||
def tag_release():
|
||||
print 'Tagging release'
|
||||
check_call('bzr tag '+__version__)
|
||||
check_call('bzr commit --unchanged -m "IGN:Tag release"')
|
||||
class sdist(OptionlessCommand):
|
||||
|
||||
description = 'Create a source distribution using bzr'
|
||||
|
||||
def run(self):
|
||||
name = os.path.join('dist', '%s-%s.tar.gz'%(__appname__, __version__))
|
||||
check_call(('bzr export '+name).split())
|
||||
self.distribution.dist_files.append(('sdist', '', name))
|
||||
print 'Source distribution created in', os.path.abspath(name)
|
||||
|
||||
class pot(OptionlessCommand):
|
||||
description = '''Create the .pot template for all translatable strings'''
|
||||
|
||||
PATH = os.path.join('src', __appname__, 'translations')
|
||||
|
||||
def source_files(self):
|
||||
ans = []
|
||||
for root, _, 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):
|
||||
sys.path.insert(0, os.path.abspath(self.PATH))
|
||||
try:
|
||||
pygettext = __import__('pygettext', fromlist=['main']).main
|
||||
files = self.source_files()
|
||||
buf = cStringIO.StringIO()
|
||||
print 'Creating translations template'
|
||||
tempdir = tempfile.mkdtemp()
|
||||
pygettext(buf, ['-k', '__', '-p', tempdir]+files)
|
||||
src = buf.getvalue()
|
||||
pot = os.path.join(tempdir, __appname__+'.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(OptionlessCommand):
|
||||
|
||||
description='''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')
|
||||
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(OptionlessCommand):
|
||||
description='''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',
|
||||
jquery = 'gui2/viewer/jquery.js',
|
||||
jquery_scrollTo = 'gui2/viewer/jquery_scrollTo.js',
|
||||
html_css = 'ebooks/oeb/html.css',
|
||||
)
|
||||
|
||||
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 get_static_resources(self):
|
||||
sdir = os.path.join('src', 'calibre', 'library', 'static')
|
||||
resources, max = {}, 0
|
||||
for f in os.listdir(sdir):
|
||||
resources[f] = open(os.path.join(sdir, f), 'rb').read()
|
||||
mtime = os.stat(os.path.join(sdir, f)).st_mtime
|
||||
max = mtime if mtime > max else max
|
||||
return resources, max
|
||||
|
||||
def get_recipes(self):
|
||||
sdir = os.path.join('src', 'calibre', 'web', 'feeds', 'recipes')
|
||||
resources, max = {}, 0
|
||||
for f in os.listdir(sdir):
|
||||
if f.endswith('.py') and f != '__init__.py':
|
||||
resources[f.replace('.py', '')] = open(os.path.join(sdir, f), 'rb').read()
|
||||
mtime = os.stat(os.path.join(sdir, f)).st_mtime
|
||||
max = mtime if mtime > max else max
|
||||
return resources, max
|
||||
|
||||
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)
|
||||
static, smax = self.get_static_resources()
|
||||
recipes, rmax = self.get_recipes()
|
||||
amax = max(rmax, smax)
|
||||
if newer([dest], RESOURCES.values()) or os.stat(dest).st_mtime < amax:
|
||||
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')
|
||||
f.write('server_resources = %s\n\n'%repr(static))
|
||||
f.write('recipes = %s\n\n'%repr(recipes))
|
||||
f.write('build_time = "%s"\n\n'%time.strftime('%d %m %Y %H%M%S'))
|
||||
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(OptionlessCommand):
|
||||
description='''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):
|
||||
msgfmt = __import__('msgfmt', fromlist=['main']).main
|
||||
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(OptionlessCommand):
|
||||
description='''Compile all GUI forms and images'''
|
||||
PATH = os.path.join('src', __appname__, 'gui2')
|
||||
IMAGES_DEST = os.path.join(PATH, 'images_rc.py')
|
||||
QRC = os.path.join(PATH, 'images.qrc')
|
||||
|
||||
@classmethod
|
||||
def find_forms(cls):
|
||||
forms = []
|
||||
for root, _, 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, _, 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)
|
||||
check_call(['pyrcc4', '-o', images, '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)
|
||||
|
||||
if form.endswith('viewer%smain.ui'%os.sep):
|
||||
print 'Promoting WebView'
|
||||
dat = dat.replace('self.view = QtWebKit.QWebView(', 'self.view = DocumentView(')
|
||||
dat += '\n\nfrom calibre.gui2.viewer.documentview import DocumentView'
|
||||
|
||||
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)
|
||||
for x in (cls.IMAGES_DEST, cls.QRC):
|
||||
if os.path.exists(x):
|
||||
os.remove(x)
|
||||
|
||||
class clean(OptionlessCommand):
|
||||
|
||||
description='''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, _, files in os.walk('.'):
|
||||
for name in files:
|
||||
for t in ('.pyc', '.pyo', '~'):
|
||||
if name.endswith(t):
|
||||
os.remove(os.path.join(root, name))
|
||||
break
|
||||
|
||||
for dir in ('build', 'dist', os.path.join('src', 'calibre.egg-info')):
|
||||
shutil.rmtree(dir, ignore_errors=True)
|
||||
|
||||
class build_py(_build_py):
|
||||
|
||||
def find_data_files(self, package, src_dir):
|
||||
"""
|
||||
Return filenames for package's data files in 'src_dir'
|
||||
Modified to treat data file specs as paths not globs
|
||||
"""
|
||||
globs = (self.package_data.get('', [])
|
||||
+ self.package_data.get(package, []))
|
||||
files = self.manifest_files.get(package, [])[:]
|
||||
for pattern in globs:
|
||||
# Each pattern has to be converted to a platform-specific path
|
||||
pattern = os.path.join(src_dir, convert_path(pattern))
|
||||
next = glob.glob(pattern)
|
||||
files.extend(next if next else [pattern])
|
||||
|
||||
return self.exclude_data_files(package, src_dir, files)
|
||||
|
||||
class build(_build):
|
||||
|
||||
sub_commands = [
|
||||
('resources', lambda self : 'CALIBRE_BUILDBOT' not in os.environ.keys()),
|
||||
('translations', lambda self : 'CALIBRE_BUILDBOT' not in os.environ.keys()),
|
||||
('gui', lambda self : 'CALIBRE_BUILDBOT' not in os.environ.keys()),
|
||||
('build_ext', lambda self: True),
|
||||
('build_py', lambda self: True),
|
||||
('build_clib', _build.has_c_libraries),
|
||||
('build_scripts', _build.has_scripts),
|
||||
]
|
||||
|
||||
|
||||
class update(OptionlessCommand):
|
||||
|
||||
description = 'Rebuild plugins and run develop. Should be called after ' +\
|
||||
' a version update.'
|
||||
|
||||
def run(self):
|
||||
for x in ['build', 'dist', 'docs'] + \
|
||||
glob.glob(os.path.join('src', 'calibre', 'plugins', '*')):
|
||||
if os.path.exists(x):
|
||||
if os.path.isdir(x):
|
||||
check_call('sudo rm -rf '+x, shell=True)
|
||||
os.mkdir(x)
|
||||
else:
|
||||
os.remove(x)
|
||||
|
||||
check_call('python setup.py build_ext build'.split())
|
||||
check_call('sudo python setup.py develop'.split())
|
||||
|
||||
class tag_release(OptionlessCommand):
|
||||
|
||||
description = 'Tag a new release in bzr'
|
||||
|
||||
def run(self):
|
||||
print 'Tagging release'
|
||||
check_call(('bzr tag '+__version__).split())
|
||||
check_call('bzr commit --unchanged -m'.split() + ['IGN:Tag release'])
|
||||
|
||||
|
||||
class upload_demo(OptionlessCommand):
|
||||
|
||||
description = 'Rebuild and upload various demos'
|
||||
|
||||
def run(self):
|
||||
check_call(
|
||||
'''html2lrf --title='Demonstration of html2lrf' --author='Kovid Goyal' '''
|
||||
'''--header --output=/tmp/html2lrf.lrf %s/demo.html '''
|
||||
'''--serif-family "/usr/share/fonts/corefonts, Times New Roman" '''
|
||||
'''--mono-family "/usr/share/fonts/corefonts, Andale Mono" '''
|
||||
''''''%(HTML2LRF,), shell=True)
|
||||
|
||||
check_call(
|
||||
'cd src/calibre/ebooks/lrf/html/demo/ && '
|
||||
'zip -j /tmp/html-demo.zip * /tmp/html2lrf.lrf', shell=True)
|
||||
|
||||
check_call('scp /tmp/html-demo.zip divok:%s/'%(DOWNLOADS,), shell=True)
|
||||
|
||||
check_call(
|
||||
'''txt2lrf -t 'Demonstration of txt2lrf' -a 'Kovid Goyal' '''
|
||||
'''--header -o /tmp/txt2lrf.lrf %s/demo.txt'''%(TXT2LRF,), shell=True)
|
||||
|
||||
check_call('cd src/calibre/ebooks/lrf/txt/demo/ && '
|
||||
'zip -j /tmp/txt-demo.zip * /tmp/txt2lrf.lrf', shell=True)
|
||||
|
||||
check_call('''scp /tmp/txt-demo.zip divok:%s/'''%(DOWNLOADS,), shell=True)
|
||||
|
||||
|
||||
def installer_name(ext):
|
||||
if ext in ('exe', 'dmg'):
|
||||
return 'dist/%s-%s.%s'%(__appname__, __version__, ext)
|
||||
return 'dist/%s-%s-i686.%s'%(__appname__, __version__, ext)
|
||||
|
||||
def start_vm(vm, ssh_host, build_script, sleep=75):
|
||||
vmware = ('vmware', '-q', '-x', '-n', vm)
|
||||
subprocess.Popen(vmware)
|
||||
t = tempfile.NamedTemporaryFile(suffix='.sh')
|
||||
t.write(build_script)
|
||||
t.flush()
|
||||
print 'Waiting for VM to startup'
|
||||
while subprocess.call('ping -q -c1 '+ssh_host, shell=True, stdout=open('/dev/null', 'w')) != 0:
|
||||
time.sleep(5)
|
||||
time.sleep(20)
|
||||
print 'Trying to SSH into VM'
|
||||
subprocess.check_call(('scp', t.name, ssh_host+':build-'+PROJECT))
|
||||
subprocess.check_call('ssh -t %s bash build-%s'%(ssh_host, PROJECT), shell=True)
|
||||
|
||||
def run_windows_install_jammer(installer):
|
||||
ibp = os.path.abspath('installer/windows')
|
||||
sys.path.insert(0, ibp)
|
||||
import build_installer
|
||||
sys.path.remove(ibp)
|
||||
build_installer.run_install_jammer(installer_name=os.path.basename(installer))
|
||||
if not os.path.exists(installer):
|
||||
raise Exception('Failed to run installjammer')
|
||||
class build_linux(OptionlessCommand):
|
||||
|
||||
def run(self):
|
||||
installer = installer_name('tar.bz2')
|
||||
locals = {}
|
||||
exec open('installer/linux/freeze.py') in locals
|
||||
locals['freeze']()
|
||||
if not os.path.exists(installer):
|
||||
raise Exception('Failed to build installer '+installer)
|
||||
return os.path.basename(installer)
|
||||
|
||||
def build_windows(shutdown=True):
|
||||
installer = installer_name('exe')
|
||||
vm = '/mnt/backup/calibre_windows_xp_home/calibre_windows_xp_home.vmx'
|
||||
start_vm(vm, 'windows', BUILD_SCRIPT%('python setup.py develop', 'python','installer\\\\windows\\\\freeze.py'))
|
||||
if os.path.exists('build/py2exe'):
|
||||
shutil.rmtree('build/py2exe')
|
||||
subprocess.check_call(('scp', '-rp', 'windows:build/%s/build/py2exe'%PROJECT, 'build'))
|
||||
if not os.path.exists('build/py2exe'):
|
||||
raise Exception('Failed to run py2exe')
|
||||
if shutdown:
|
||||
subprocess.Popen(('ssh', 'windows', 'shutdown', '-s', '-t', '0'))
|
||||
run_windows_install_jammer(installer)
|
||||
return os.path.basename(installer)
|
||||
class VMInstaller(OptionlessCommand):
|
||||
|
||||
user_options = [('dont-shutdown', 'd', 'Dont shutdown Vm after build')]
|
||||
boolean_options = ['dont-shutdown']
|
||||
|
||||
def initialize_options(self):
|
||||
self.dont_shutdown = False
|
||||
|
||||
BUILD_SCRIPT = textwrap.dedent('''\
|
||||
#!/bin/bash
|
||||
export CALIBRE_BUILDBOT=1
|
||||
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 . && \
|
||||
cd %(project)s && \
|
||||
%%s && \
|
||||
rm -rf build/* dist/* && \
|
||||
%%s %%s
|
||||
'''%dict(host=HOST, project=__appname__))
|
||||
|
||||
def get_build_script(self, subs):
|
||||
return self.BUILD_SCRIPT%subs
|
||||
|
||||
def start_vm(self, ssh_host, build_script, sleep=75):
|
||||
build_script = self.get_build_script(build_script)
|
||||
vmware = ('vmware', '-q', '-x', '-n', self.VM)
|
||||
Popen(vmware)
|
||||
t = tempfile.NamedTemporaryFile(suffix='.sh')
|
||||
t.write(build_script)
|
||||
t.flush()
|
||||
print 'Waiting for VM to startup'
|
||||
while call('ping -q -c1 '+ssh_host, shell=True,
|
||||
stdout=open('/dev/null', 'w')) != 0:
|
||||
time.sleep(5)
|
||||
time.sleep(20)
|
||||
print 'Trying to SSH into VM'
|
||||
check_call(('scp', t.name, ssh_host+':build-calibre'))
|
||||
check_call('ssh -t %s bash build-calibre'%ssh_host, shell=True)
|
||||
|
||||
def build_osx(shutdown=True):
|
||||
installer = installer_name('dmg')
|
||||
vm = '/vmware/Mac OSX/Mac OSX.vmx'
|
||||
python = '/Library/Frameworks/Python.framework/Versions/Current/bin/python'
|
||||
start_vm(vm, 'osx', (BUILD_SCRIPT%('sudo %s setup.py develop'%python, python, 'installer/osx/freeze.py')).replace('rm ', 'sudo rm '))
|
||||
subprocess.check_call(('scp', 'osx:build/%s/dist/*.dmg'%PROJECT, 'dist'))
|
||||
if not os.path.exists(installer):
|
||||
raise Exception('Failed to build installer '+installer)
|
||||
if shutdown:
|
||||
subprocess.Popen(('ssh', 'osx', 'sudo', '/sbin/shutdown', '-h', 'now'))
|
||||
return os.path.basename(installer)
|
||||
class build_windows(VMInstaller):
|
||||
|
||||
VM = '/mnt/backup/calibre_windows_xp_home/calibre_windows_xp_home.vmx'
|
||||
if not os.path.exists(VM):
|
||||
VM = '/home/kovid/calibre_windows_xp_home/calibre_windows_xp_home.vmx'
|
||||
|
||||
def run(self):
|
||||
installer = installer_name('exe')
|
||||
self.start_vm('windows', ('python setup.py develop',
|
||||
'python',
|
||||
r'installer\\windows\\freeze.py'))
|
||||
if os.path.exists('build/py2exe'):
|
||||
shutil.rmtree('build/py2exe')
|
||||
check_call(('scp', '-rp', 'windows:build/%s/build/py2exe'%__appname__,
|
||||
'build'))
|
||||
if not os.path.exists('build/py2exe'):
|
||||
raise Exception('Failed to run py2exe')
|
||||
if not self.dont_shutdown:
|
||||
Popen(('ssh', 'windows', 'shutdown', '-s', '-t', '0'))
|
||||
self.run_windows_install_jammer(installer)
|
||||
return os.path.basename(installer)
|
||||
|
||||
def run_windows_install_jammer(self, installer):
|
||||
ibp = os.path.abspath('installer/windows')
|
||||
sys.path.insert(0, ibp)
|
||||
build_installer = __import__('build_installer')
|
||||
sys.path.remove(ibp)
|
||||
build_installer.run_install_jammer(
|
||||
installer_name=os.path.basename(installer))
|
||||
if not os.path.exists(installer):
|
||||
raise Exception('Failed to run installjammer')
|
||||
|
||||
|
||||
def build_linux(*args, **kwargs):
|
||||
installer = installer_name('tar.bz2')
|
||||
exec open('installer/linux/freeze.py')
|
||||
freeze()
|
||||
if not os.path.exists(installer):
|
||||
raise Exception('Failed to build installer '+installer)
|
||||
return os.path.basename(installer)
|
||||
|
||||
def build_installers():
|
||||
return build_linux(), build_windows(), build_osx()
|
||||
|
||||
def upload_demo():
|
||||
check_call('''html2lrf --title='Demonstration of html2lrf' --author='Kovid Goyal' '''
|
||||
'''--header --output=/tmp/html2lrf.lrf %s/demo.html '''
|
||||
'''--serif-family "/usr/share/fonts/corefonts, Times New Roman" '''
|
||||
'''--mono-family "/usr/share/fonts/corefonts, Andale Mono" '''
|
||||
''''''%(HTML2LRF,))
|
||||
check_call('cd src/calibre/ebooks/lrf/html/demo/ && zip -j /tmp/html-demo.zip * /tmp/html2lrf.lrf')
|
||||
check_call('''scp /tmp/html-demo.zip divok:%s/'''%(DOWNLOADS,))
|
||||
check_call('''txt2lrf -t 'Demonstration of txt2lrf' -a 'Kovid Goyal' '''
|
||||
'''--header -o /tmp/txt2lrf.lrf %s/demo.txt'''%(TXT2LRF,) )
|
||||
check_call('cd src/calibre/ebooks/lrf/txt/demo/ && zip -j /tmp/txt-demo.zip * /tmp/txt2lrf.lrf')
|
||||
check_call('''scp /tmp/txt-demo.zip divok:%s/'''%(DOWNLOADS,))
|
||||
|
||||
def curl_list_dir(url=MOBILEREAD, listonly=1):
|
||||
c = pycurl.Curl()
|
||||
c.setopt(pycurl.URL, url)
|
||||
c.setopt(c.FTP_USE_EPSV, 1)
|
||||
c.setopt(c.NETRC, c.NETRC_REQUIRED)
|
||||
c.setopt(c.FTPLISTONLY, listonly)
|
||||
c.setopt(c.FTP_CREATE_MISSING_DIRS, 1)
|
||||
b = cStringIO.StringIO()
|
||||
c.setopt(c.WRITEFUNCTION, b.write)
|
||||
c.perform()
|
||||
c.close()
|
||||
return b.getvalue().split() if listonly else b.getvalue().splitlines()
|
||||
|
||||
def curl_delete_file(path, url=MOBILEREAD):
|
||||
c = pycurl.Curl()
|
||||
c.setopt(pycurl.URL, url)
|
||||
c.setopt(c.FTP_USE_EPSV, 1)
|
||||
c.setopt(c.NETRC, c.NETRC_REQUIRED)
|
||||
print 'Deleting file %s on %s'%(path, url)
|
||||
c.setopt(c.QUOTE, ['dele '+ path])
|
||||
c.perform()
|
||||
c.close()
|
||||
class build_osx(VMInstaller):
|
||||
|
||||
VM = '/vmware/Mac OSX/Mac OSX.vmx'
|
||||
if not os.path.exists(VM):
|
||||
VM = '/home/kovid/calibre_os_x/Mac OSX.vmx'
|
||||
|
||||
def get_build_script(self, subs):
|
||||
return (self.BUILD_SCRIPT%subs).replace('rm ', 'sudo rm ')
|
||||
|
||||
def run(self):
|
||||
installer = installer_name('dmg')
|
||||
python = '/Library/Frameworks/Python.framework/Versions/Current/bin/python'
|
||||
self.start_vm('osx', ('sudo %s setup.py develop'%python, python,
|
||||
'installer/osx/freeze.py'))
|
||||
check_call(('scp', 'osx:build/calibre/dist/*.dmg', 'dist'), shell=True)
|
||||
if not os.path.exists(installer):
|
||||
raise Exception('Failed to build installer '+installer)
|
||||
if not self.dont_shutdown:
|
||||
Popen(('ssh', 'osx', 'sudo', '/sbin/shutdown', '-h', 'now'))
|
||||
return os.path.basename(installer)
|
||||
|
||||
|
||||
def curl_upload_file(stream, url):
|
||||
c = pycurl.Curl()
|
||||
c.setopt(pycurl.URL, url)
|
||||
c.setopt(pycurl.UPLOAD, 1)
|
||||
c.setopt(c.NETRC, c.NETRC_REQUIRED)
|
||||
c.setopt(pycurl.READFUNCTION, stream.read)
|
||||
stream.seek(0, 2)
|
||||
c.setopt(pycurl.INFILESIZE_LARGE, stream.tell())
|
||||
stream.seek(0)
|
||||
c.setopt(c.NOPROGRESS, 0)
|
||||
c.setopt(c.FTP_CREATE_MISSING_DIRS, 1)
|
||||
print 'Uploading file %s to url %s' % (getattr(stream, 'name', ''), url)
|
||||
try:
|
||||
|
||||
class upload_installers(OptionlessCommand):
|
||||
|
||||
def curl_list_dir(self, url=MOBILEREAD, listonly=1):
|
||||
import pycurl
|
||||
c = pycurl.Curl()
|
||||
c.setopt(pycurl.URL, url)
|
||||
c.setopt(c.FTP_USE_EPSV, 1)
|
||||
c.setopt(c.NETRC, c.NETRC_REQUIRED)
|
||||
c.setopt(c.FTPLISTONLY, listonly)
|
||||
c.setopt(c.FTP_CREATE_MISSING_DIRS, 1)
|
||||
b = cStringIO.StringIO()
|
||||
c.setopt(c.WRITEFUNCTION, b.write)
|
||||
c.perform()
|
||||
c.close()
|
||||
except:
|
||||
pass
|
||||
files = curl_list_dir(listonly=0)
|
||||
for line in files:
|
||||
line = line.split()
|
||||
if url.endswith(line[-1]):
|
||||
size = long(line[4])
|
||||
stream.seek(0,2)
|
||||
if size != stream.tell():
|
||||
raise RuntimeError('curl failed to upload %s correctly'%getattr(stream, 'name', ''))
|
||||
|
||||
|
||||
|
||||
def upload_installer(name):
|
||||
if not os.path.exists(name):
|
||||
return
|
||||
bname = os.path.basename(name)
|
||||
pat = re.compile(bname.replace(__version__, r'\d+\.\d+\.\d+'))
|
||||
for f in curl_list_dir():
|
||||
if pat.search(f):
|
||||
curl_delete_file('/calibre/'+f)
|
||||
curl_upload_file(open(name, 'rb'), MOBILEREAD+os.path.basename(name))
|
||||
|
||||
def upload_installers():
|
||||
for i in ('dmg', 'exe', 'tar.bz2'):
|
||||
upload_installer(installer_name(i))
|
||||
|
||||
check_call('''ssh divok echo %s \\> %s/latest_version'''%(__version__, DOWNLOADS))
|
||||
|
||||
|
||||
def upload_docs():
|
||||
check_call('''epydoc --config epydoc.conf''')
|
||||
check_call('''scp -r docs/html divok:%s/'''%(DOCS,))
|
||||
check_call('''epydoc -v --config epydoc-pdf.conf''')
|
||||
check_call('''scp docs/pdf/api.pdf divok:%s/'''%(DOCS,))
|
||||
|
||||
def upload_user_manual():
|
||||
check_call('python setup.py manual')
|
||||
check_call('scp -r src/calibre/manual/.build/html/* divok:%s'%USER_MANUAL)
|
||||
return b.getvalue().split() if listonly else b.getvalue().splitlines()
|
||||
|
||||
def build_src_tarball():
|
||||
check_call('bzr export dist/calibre-%s.tar.gz'%__version__)
|
||||
def curl_delete_file(self, path, url=MOBILEREAD):
|
||||
import pycurl
|
||||
c = pycurl.Curl()
|
||||
c.setopt(pycurl.URL, url)
|
||||
c.setopt(c.FTP_USE_EPSV, 1)
|
||||
c.setopt(c.NETRC, c.NETRC_REQUIRED)
|
||||
print 'Deleting file %s on %s'%(path, url)
|
||||
c.setopt(c.QUOTE, ['dele '+ path])
|
||||
c.perform()
|
||||
c.close()
|
||||
|
||||
|
||||
def curl_upload_file(self, stream, url):
|
||||
import pycurl
|
||||
c = pycurl.Curl()
|
||||
c.setopt(pycurl.URL, url)
|
||||
c.setopt(pycurl.UPLOAD, 1)
|
||||
c.setopt(c.NETRC, c.NETRC_REQUIRED)
|
||||
c.setopt(pycurl.READFUNCTION, stream.read)
|
||||
stream.seek(0, 2)
|
||||
c.setopt(pycurl.INFILESIZE_LARGE, stream.tell())
|
||||
stream.seek(0)
|
||||
c.setopt(c.NOPROGRESS, 0)
|
||||
c.setopt(c.FTP_CREATE_MISSING_DIRS, 1)
|
||||
print 'Uploading file %s to url %s' % (getattr(stream, 'name', ''), url)
|
||||
try:
|
||||
c.perform()
|
||||
c.close()
|
||||
except:
|
||||
pass
|
||||
files = self.curl_list_dir(listonly=0)
|
||||
for line in files:
|
||||
line = line.split()
|
||||
if url.endswith(line[-1]):
|
||||
size = long(line[4])
|
||||
stream.seek(0,2)
|
||||
if size != stream.tell():
|
||||
raise RuntimeError('curl failed to upload %s correctly'%getattr(stream, 'name', ''))
|
||||
|
||||
def upload_installer(self, name):
|
||||
if not os.path.exists(name):
|
||||
return
|
||||
bname = os.path.basename(name)
|
||||
pat = re.compile(bname.replace(__version__, r'\d+\.\d+\.\d+'))
|
||||
for f in self.curl_list_dir():
|
||||
if pat.search(f):
|
||||
self.curl_delete_file('/calibre/'+f)
|
||||
self.curl_upload_file(open(name, 'rb'), MOBILEREAD+os.path.basename(name))
|
||||
|
||||
def upload_src_tarball():
|
||||
check_call('ssh divok rm -f %s/calibre-\*.tar.gz'%DOWNLOADS)
|
||||
check_call('scp dist/calibre-*.tar.gz divok:%s/'%DOWNLOADS)
|
||||
def run(self):
|
||||
print 'Uploading installers...'
|
||||
for i in ('dmg', 'exe', 'tar.bz2'):
|
||||
self.upload_installer(installer_name(i))
|
||||
|
||||
check_call('''ssh divok echo %s \\> %s/latest_version'''\
|
||||
%(__version__, DOWNLOADS), shell=True)
|
||||
|
||||
def stage_one():
|
||||
check_call('sudo rm -rf build src/calibre/plugins/*', shell=True)
|
||||
os.mkdir('build')
|
||||
shutil.rmtree('docs')
|
||||
os.mkdir('docs')
|
||||
check_call('python setup.py build_ext build', shell=True)
|
||||
check_call('sudo python setup.py develop', shell=True)
|
||||
tag_release()
|
||||
upload_demo()
|
||||
|
||||
def stage_two():
|
||||
subprocess.check_call('rm -rf dist/*', shell=True)
|
||||
build_installers()
|
||||
|
||||
def stage_three():
|
||||
print 'Uploading installers...'
|
||||
upload_installers()
|
||||
print 'Uploading documentation...'
|
||||
#upload_docs()
|
||||
upload_user_manual()
|
||||
print 'Uploading to PyPI...'
|
||||
check_call('rm -f dist/*')
|
||||
check_call('python setup.py register')
|
||||
check_call('sudo rm -rf build src/calibre/plugins/*')
|
||||
os.mkdir('build')
|
||||
check_call('python2.5 setup.py build_ext bdist_egg --exclude-source-files upload')
|
||||
check_call('sudo rm -rf build src/calibre/plugins/*')
|
||||
os.mkdir('build')
|
||||
check_call('python setup.py build_ext bdist_egg --exclude-source-files upload')
|
||||
check_call('python setup.py sdist upload')
|
||||
upload_src_tarball()
|
||||
check_call('''rm -rf dist/* build/*''')
|
||||
check_call('''ssh divok bzr update /var/www/calibre.kovidgoyal.net/calibre/''')
|
||||
|
||||
def betas():
|
||||
subprocess.check_call('rm -f dist/*', shell=True)
|
||||
build_installers()
|
||||
check_call('ssh divok rm -f /var/www/calibre.kovidgoyal.net/htdocs/downloads/betas/*')
|
||||
check_call('scp dist/* divok:/var/www/calibre.kovidgoyal.net/htdocs/downloads/betas/')
|
||||
|
||||
def main(args=sys.argv):
|
||||
print 'Starting stage one...'
|
||||
stage_one()
|
||||
print 'Starting stage two...'
|
||||
stage_two()
|
||||
print 'Starting stage three...'
|
||||
stage_three()
|
||||
print 'Finished'
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
class upload_user_manual(OptionlessCommand):
|
||||
|
||||
sub_commands = [('manual', None)]
|
||||
|
||||
def run(self):
|
||||
OptionlessCommand.run(self)
|
||||
check_call(' '.join(['scp', '-r', 'src/calibre/manual/.build/html/*',
|
||||
'divok:%s'%USER_MANUAL]), shell=True)
|
||||
|
||||
class upload_to_pypi(OptionlessCommand):
|
||||
|
||||
def run(self):
|
||||
check_call('python setup.py register')
|
||||
check_call('rm -f dist/*', shell=True)
|
||||
check_call('sudo rm -rf build src/calibre/plugins/*', shell=True)
|
||||
os.mkdir('build')
|
||||
check_call('python2.5 setup.py build_ext bdist_egg --exclude-source-files upload'.split())
|
||||
check_call('sudo rm -rf build src/calibre/plugins/*', shell=True)
|
||||
os.mkdir('build')
|
||||
check_call('python setup.py build_ext bdist_egg --exclude-source-files upload'.split())
|
||||
check_call('python setup.py sdist upload'.split())
|
||||
|
||||
class stage3(OptionlessCommand):
|
||||
|
||||
sub_commands = [
|
||||
('upload_installers', None),
|
||||
('upload_user_manual', None),
|
||||
('upload_to_pypi', None),
|
||||
]
|
||||
|
||||
def run(self):
|
||||
OptionlessCommand.run(self)
|
||||
check_call('ssh divok rm -f %s/calibre-\*.tar.gz'%DOWNLOADS, shell=True)
|
||||
check_call('scp dist/calibre-*.tar.gz divok:%s/'%DOWNLOADS, shell=True)
|
||||
check_call('''rm -rf dist/* build/*''', shell=True)
|
||||
check_call('ssh divok bzr update /var/www/calibre.kovidgoyal.net/calibre/',
|
||||
shell=True)
|
||||
|
||||
class stage2(OptionlessCommand):
|
||||
|
||||
sub_commands = [
|
||||
('build_linux', None),
|
||||
('build_windows', None),
|
||||
('build_osx', None),
|
||||
('upload_installers', None),
|
||||
]
|
||||
|
||||
def run(self):
|
||||
check_call('rm -rf dist/*', shell=True)
|
||||
OptionlessCommand.run(self)
|
||||
|
||||
class stage1(OptionlessCommand):
|
||||
|
||||
sub_commands = [
|
||||
('update', None),
|
||||
('tag_release', None),
|
||||
('upload_demo', None),
|
||||
]
|
||||
|
||||
class upload(OptionlessCommand):
|
||||
|
||||
sub_commands = [
|
||||
('stage1', None),
|
||||
('stage2', None),
|
||||
('stage3', None)
|
||||
]
|
Loading…
x
Reference in New Issue
Block a user