From ef06fbee81e520f636082d4e7ccc39f8af26e073 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 27 Sep 2009 10:20:36 -0600 Subject: [PATCH] Fix #3523 (Please re-add ./setup.py install --root option) and clean up install/post-install code. Post install now no longer runs in a separate process. --- setup.py | 11 +- setup/__init__.py | 11 +- setup/check.py | 2 + setup/extensions.py | 2 + setup/install.py | 200 ++++---- setup/installer/linux/__init__.py | 6 + setup/installer/osx/__init__.py | 4 + setup/installer/osx/freeze.py | 2 + setup/installer/windows/__init__.py | 4 + setup/installer/windows/freeze.py | 2 + setup/resources.py | 2 + setup/translations.py | 1 + setup/upload.py | 2 + src/calibre/debug.py | 4 +- src/calibre/devices/linux_mount_helper.c | 12 +- src/calibre/linux.py | 603 +++++++++++++---------- 16 files changed, 501 insertions(+), 367 deletions(-) diff --git a/setup.py b/setup.py index b0acff3963..d8bd0267ee 100644 --- a/setup.py +++ b/setup.py @@ -41,7 +41,14 @@ def clean_backups(): def main(args=sys.argv): if len(args) == 1 or args[1] in ('-h', '--help'): print 'Usage: python', args[0], 'command', '[options]' - print '\nWhere command is one of:', ', '.join(commands.__all__) + print '\nWhere command is one of:' + print + for x in sorted(commands.__all__): + print '%-20s -'%x, + c = getattr(commands, x) + desc = getattr(c, 'short_description', c.description) + print desc + print '\nTo get help on a particular command, run:' print '\tpython', args[0], 'command -h' return 1 @@ -83,7 +90,7 @@ def main(args=sys.argv): prints('There were', len(warnings), 'warning(s):') print for args, kwargs in warnings: - prints(*args, **kwargs) + prints('*', *args, **kwargs) print return 0 diff --git a/setup/__init__.py b/setup/__init__.py index 714a3bcb85..d947042fc4 100644 --- a/setup/__init__.py +++ b/setup/__init__.py @@ -111,6 +111,7 @@ class Command(object): self.b = os.path.basename self.s = os.path.splitext self.e = os.path.exists + self.orig_euid = os.geteuid() self.real_uid = os.environ.get('SUDO_UID', None) self.real_gid = os.environ.get('SUDO_GID', None) self.real_user = os.environ.get('SUDO_USER', None) @@ -121,19 +122,19 @@ class Command(object): if self.real_user is not None: self.info('Dropping privileges to those of', self.real_user+':', self.real_uid) + if self.real_gid is not None: + os.setegid(int(self.real_gid)) if self.real_uid is not None: os.seteuid(int(self.real_uid)) - #if self.real_gid is not None: - # os.setegid(int(self.real_gid)) def regain_privileges(self): if not islinux or isosx: return - if os.geteuid() != 0: + if os.geteuid() != 0 and self.orig_euid == 0: self.info('Trying to get root privileges') os.seteuid(0) - #if os.getegid() != 0: - # os.setegid(0) + if os.getegid() != 0: + os.setegid(0) def pre_sub_commands(self, opts): pass diff --git a/setup/check.py b/setup/check.py index 75a6d82530..14992b1628 100644 --- a/setup/check.py +++ b/setup/check.py @@ -37,6 +37,8 @@ def check_for_python_errors(filename, builtins): class Check(Command): + description = 'Check for errors in the calibre source code' + BUILTINS = ['_', '__', 'dynamic_property', 'I', 'P'] CACHE = '.check-cache.pickle' diff --git a/setup/extensions.py b/setup/extensions.py index 6e993e84a0..7228cf3d03 100644 --- a/setup/extensions.py +++ b/setup/extensions.py @@ -175,6 +175,8 @@ if iswindows: 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 diff --git a/setup/install.py b/setup/install.py index 62ad9bd7b5..abd008df68 100644 --- a/setup/install.py +++ b/setup/install.py @@ -6,7 +6,7 @@ __license__ = 'GPL v3' __copyright__ = '2009, Kovid Goyal ' __docformat__ = 'restructuredtext en' -import sys, os, textwrap, subprocess, shutil, tempfile, atexit +import sys, os, textwrap, subprocess, shutil, tempfile, atexit, stat from setup import Command, islinux, basenames, modules, functions, \ __appname__, __version__ @@ -19,28 +19,26 @@ This is the standard runscript for all of calibre's tools. Do not modify it unless you know what you are doing. """ -import sys +import sys, os + +path = os.environ.get('CALIBRE_PYTHON_PATH', {path!r}) +sys.path.insert(0, path) + +sys.resources_location = os.environ.get('CALIBRE_RESOURCES_PATH', {resources!r}) +sys.extensions_location = os.environ.get('CALIBRE_EXTENSIONS_PATH', {extensions!r}) + ''' TEMPLATE = HEADER+''' -sys.path.insert(0, {path!r}) - -sys.resources_location = {resources!r} -sys.extensions_location = {extensions!r} - from {module} import {func!s} sys.exit({func!s}()) ''' COMPLETE_TEMPLATE = HEADER+''' -import os -sys.path.insert(0, {path!r}) -sys.path.insert(0, os.path.join({path!r}, 'calibre', 'utils')) +sys.path.insert(0, os.path.join(path, 'calibre', 'utils')) import complete sys.path = sys.path[1:] -sys.resources_location = {resources!r} -sys.extensions_location = {extensions!r} sys.exit(complete.main()) ''' @@ -53,93 +51,123 @@ class Develop(Command): the prefix of your python installation. This can be controlled via the --prefix option. ''') + short_description = 'Setup a development environment for calibre' MODE = 0755 sub_commands = ['build', 'resources', 'gui'] + def add_postinstall_options(self, parser): + parser.add_option('--make-errors-fatal', action='store_true', default=False, + dest='fatal_errors', help='If set die on post install errors.') + parser.add_option('--no-postinstall', action='store_false', + dest='postinstall', default=True, + help='Don\'t run post install actions like creating MAN pages, setting'+ + ' up desktop integration and so on') + + def add_options(self, parser): parser.add_option('--prefix', help='Binaries will be installed in /bin') - self.root = '' + self.add_postinstall_options(parser) + + def consolidate_paths(self): + opts = self.opts + if not opts.prefix: + opts.prefix = sys.prefix + self.libdir = getattr(opts, 'libdir', None) + if self.libdir is None: + self.libdir = self.j(opts.prefix, 'lib') + self.bindir = getattr(opts, 'bindir', None) + if self.bindir is None: + self.bindir = self.j(opts.prefix, 'bin') + self.sharedir = getattr(opts, 'sharedir', None) + if self.sharedir is None: + self.sharedir = self.j(opts.prefix, 'share') + if not getattr(opts, 'staging_root', None): + opts.staging_root = opts.prefix + self.staging_libdir = getattr(opts, 'staging_libdir', None) + if self.staging_libdir is None: + self.staging_libdir = opts.staging_libdir = self.j(opts.staging_root, 'lib') + self.staging_bindir = getattr(opts, 'staging_bindir', None) + if self.staging_bindir is None: + self.staging_bindir = opts.staging_bindir = self.j(opts.staging_root, 'bin') + self.staging_sharedir = getattr(opts, 'staging_sharedir', None) + if self.staging_sharedir is None: + self.staging_sharedir = opts.staging_sharedir = self.j(opts.staging_root, 'share') + + if self.__class__.__name__ == 'Develop': + self.libdir = self.SRC + self.sharedir = self.RESOURCES def pre_sub_commands(self, opts): if not islinux: - self.info('\nSetting up a development environment is only ' - 'supported on linux. On other platforms, install the calibre ' - 'binary and use the calibre-debug command.') + self.info('\nSetting up a source based development environment is only ' + 'supported on linux. On other platforms, see the User Manual' + ' for help with setting up a development environment.') raise SystemExit(1) - if not os.geteuid() == 0: - self.info('\nError: This command must be run as root.') - raise SystemExit(1) - self.drop_privileges() + if os.geteuid() == 0: + self.drop_privileges() + # Ensure any calibre config files are created as correct user + import calibre.utils.config as c + c def run(self, opts): + self.opts = opts self.regain_privileges() - self.find_locations(opts) - self.write_templates(opts) + self.consolidate_paths() + self.write_templates() self.setup_mount_helper() - self.install_files(opts) + self.install_files() self.run_postinstall() self.success() def setup_mount_helper(self): def warn(): self.warn('Failed to compile mount helper. Auto mounting of', - 'devices will not work') + ' devices will not work') if os.geteuid() != 0: - return warn() - import stat + return self.warn('Must be run as root to compile mount helper. Auto ' + 'mounting of devices will not work.') src = os.path.join(self.SRC, 'calibre', 'devices', 'linux_mount_helper.c') - dest = self.root + os.path.join(self.bindir, 'calibre-mount-helper') + dest = os.path.join(self.staging_bindir, 'calibre-mount-helper') self.info('Installing mount helper to '+ dest) - p = subprocess.Popen(['gcc', '-Wall', src, '-o', dest]) + p = subprocess.Popen(['gcc', '-Wall', '-pedantic', src, '-o', dest]) ret = p.wait() if ret != 0: return warn() os.chown(dest, 0, 0) - os.chmod(dest, - stat.S_ISUID|stat.S_ISGID|stat.S_IRUSR|stat.S_IWUSR|stat.S_IXUSR|stat.S_IXGRP|stat.S_IXOTH) + os.chmod(dest, stat.S_ISUID|stat.S_ISGID|stat.S_IRUSR|stat.S_IWUSR|\ + stat.S_IXUSR|stat.S_IXGRP|stat.S_IXOTH) return dest - def install_files(self, opts): + def install_files(self): pass def run_postinstall(self): - env = dict(**os.environ) - env['DESTDIR'] = self.prefix - subprocess.check_call(['calibre_postinstall', '--use-destdir'], env=env) + if self.opts.postinstall: + from calibre.linux import PostInstall + PostInstall(self.opts, info=self.info, warn=self.warn) def success(self): self.info('\nDevelopment environment successfully setup') - def find_locations(self, opts): - self.prefix = opts.prefix - if self.prefix is None: - self.prefix = sys.prefix - self.path = self.SRC - self.resources = self.j(self.d(self.SRC), 'resources') - self.extensions = self.j(self.SRC, 'calibre', 'plugins') - self.bindir = self.j(self.prefix, 'bin') - - def write_templates(self, opts): + def write_templates(self): for typ in ('console', 'gui'): for name, mod, func in zip(basenames[typ], modules[typ], functions[typ]): - self.write_template(opts, name, mod, func) - if islinux: - self.write_template(opts, 'calibre_postinstall', 'calibre.linux', 'main') + self.write_template(name, mod, func) - def write_template(self, opts, name, mod, func): + def write_template(self, name, mod, func): template = COMPLETE_TEMPLATE if name == 'calibre-complete' else TEMPLATE script = template.format( module=mod, func=func, - path=self.path, resources=self.resources, - extensions=self.extensions) - path = self.root + self.j(self.bindir, name) - if not os.path.exists(self.bindir): - os.makedirs(self.bindir) + path=self.libdir, resources=self.sharedir, + extensions=self.j(self.libdir, 'calibre', 'plugins')) + path = self.j(self.staging_bindir, name) + if not os.path.exists(self.staging_bindir): + os.makedirs(self.staging_bindir) self.info('Installing binary:', path) open(path, 'wb').write(script) os.chmod(path, self.MODE) @@ -154,49 +182,45 @@ class Install(Develop): The default is the prefix of your python installation. ''') + short_description = 'Install calibre from source' sub_commands = ['build', 'gui'] def add_options(self, parser): - parser.add_option('--prefix', help='Installation prefix') - parser.add_option('--libdir', help='Where to put calibre library files') - parser.add_option('--bindir', help='Where to install calibre binaries') - parser.add_option('--sharedir', help='Where to install calibre data files') - parser.add_option('--root', default='', - help='Use a different installation root (mainly for packaging)') - self.root = '' + parser.add_option('--prefix', help='Installation prefix.') + parser.add_option('--libdir', + help='Where to put calibre library files. Default is /lib') + parser.add_option('--bindir', + help='Where to put the calibre binaries. Default is /bin') + parser.add_option('--sharedir', + help='Where to put the calibre data files. Default is /share') + parser.add_option('--staging-root', '--root', default=None, + help=('Use a different installation root (mainly for packaging).' + ' The prefix option controls the paths written into ' + 'the launcher scripts. This option controls the prefix ' + 'to which the install will actually copy files. By default ' + 'it is set to the value of --prefix.')) + parser.add_option('--staging-libdir', + help='Where to put calibre library files. Default is /lib') + parser.add_option('--staging-bindir', + help='Where to put the calibre binaries. Default is /bin') + parser.add_option('--staging-sharedir', + help='Where to put the calibre data files. Default is /share') + self.add_postinstall_options(parser) - def find_locations(self, opts): - if opts.prefix is None: - opts.prefix = sys.prefix - if opts.libdir is None: - opts.libdir = self.j(opts.prefix, 'lib', 'calibre') - if opts.bindir is None: - opts.bindir = self.j(opts.prefix, 'bin') - if opts.sharedir is None: - opts.sharedir = self.j(opts.prefix, 'share', 'calibre') - self.prefix = opts.prefix - self.bindir = opts.bindir - self.path = opts.libdir - self.resources = opts.sharedir - self.extensions = self.j(self.path, 'calibre', 'plugins') - self.root = opts.root - - def install_files(self, opts): - dest = self.root + self.path + def install_files(self): + dest = self.staging_libdir if os.path.exists(dest): shutil.rmtree(dest) - shutil.copytree(self.SRC, dest) - for x in ('calibre/manual', 'calibre/trac', - 'calibre/ebooks/lrf/html/demo'): - x = self.j(dest, x) - if os.path.exists(dest): - shutil.rmtree(x) - for x in os.walk(dest): + for x in os.walk(self.SRC): + reldir = os.path.relpath(x[0], self.SRC) + destdir = os.path.join(dest, reldir) for f in x[-1]: - if os.path.splitext(f)[1] in ('.c', '.cpp', '.h'): - os.remove(self.j(x[0], f)) - dest = self.root + self.resources + if os.path.splitext(f)[1] in ('.py', '.so'): + if not os.path.exists(destdir): + os.makedirs(destdir) + shutil.copy2(self.j(x[0], f), destdir) + dest = self.staging_sharedir if os.path.exists(dest): shutil.rmtree(dest) shutil.copytree(self.RESOURCES, dest) diff --git a/setup/installer/linux/__init__.py b/setup/installer/linux/__init__.py index f3819af913..46014dfac6 100644 --- a/setup/installer/linux/__init__.py +++ b/setup/installer/linux/__init__.py @@ -13,6 +13,8 @@ from setup import Command, installer_name class Linux32(VMInstaller): + description = 'Build 32bit linux binary installer' + INSTALLER_EXT = 'tar.bz2' VM_NAME = 'gentoo32_build' VM = '/vmware/bin/gentoo32_build' @@ -21,6 +23,8 @@ class Linux32(VMInstaller): class Linux64(Command): + description = 'Build 64bit linux binary installer' + sub_commands = ['linux_freeze'] def run(self, opts): @@ -31,4 +35,6 @@ class Linux64(Command): class Linux(Command): + description = 'Build linux binary installers' + sub_commands = ['linux64', 'linux32'] diff --git a/setup/installer/osx/__init__.py b/setup/installer/osx/__init__.py index 5089859978..e5e689244a 100644 --- a/setup/installer/osx/__init__.py +++ b/setup/installer/osx/__init__.py @@ -12,6 +12,8 @@ from setup.installer import VMInstaller class OSX(Command): + description = 'Build OS X binary installers' + sub_commands = ['osx32'] def run(self, opts): @@ -20,6 +22,8 @@ class OSX(Command): class OSX32(VMInstaller): + description = 'Build 32 bit OS X binary installer' + INSTALLER_EXT = 'dmg' VM_NAME = 'tiger_build' VM = '/vmware/bin/%s'%VM_NAME diff --git a/setup/installer/osx/freeze.py b/setup/installer/osx/freeze.py index 8358a61a81..8164cd22f8 100644 --- a/setup/installer/osx/freeze.py +++ b/setup/installer/osx/freeze.py @@ -21,6 +21,8 @@ info = warn = None class OSX32_Freeze(Command): + description = 'Freeze OSX calibre installation' + def run(self, opts): global info, warn info, warn = self.info, self.warn diff --git a/setup/installer/windows/__init__.py b/setup/installer/windows/__init__.py index 31973194d1..17667368c7 100644 --- a/setup/installer/windows/__init__.py +++ b/setup/installer/windows/__init__.py @@ -14,6 +14,8 @@ from setup.installer.windows import build_installer class Win(Command): + description = 'Build windows binary installers' + sub_commands = ['win32'] def run(self, opts): @@ -22,6 +24,8 @@ class Win(Command): class Win32(VMInstaller): + description = 'Build 32bit windows binary installer' + INSTALLER_EXT = 'exe' VM_NAME = 'xp_build' VM = '/vmware/bin/%s'%VM_NAME diff --git a/setup/installer/windows/freeze.py b/setup/installer/windows/freeze.py index 286f7092b3..03b0d09096 100644 --- a/setup/installer/windows/freeze.py +++ b/setup/installer/windows/freeze.py @@ -52,6 +52,8 @@ info = warn = None class Win32Freeze(Command): + description = 'Freeze windows calibre installation' + def run(self, opts): global info, warn info, warn = self.info, self.warn diff --git a/setup/resources.py b/setup/resources.py index 39416d88c6..253876989e 100644 --- a/setup/resources.py +++ b/setup/resources.py @@ -24,6 +24,8 @@ def get_opts_from_parser(parser): class Resources(Command): + description = 'Compile various needed calibre resources' + def get_recipes(self): sdir = os.path.join('src', 'calibre', 'web', 'feeds', 'recipes') resources= {} diff --git a/setup/translations.py b/setup/translations.py index 5ba84c4a5e..658ad0f4f5 100644 --- a/setup/translations.py +++ b/setup/translations.py @@ -196,6 +196,7 @@ class GetTranslations(Translations): class ISO639(Command): + description = 'Compile translations for ISO 639 codes' XML = '/usr/lib/python2.6/site-packages/pycountry/databases/iso639.xml' def run(self, opts): diff --git a/setup/upload.py b/setup/upload.py index 6580544ad6..b99e80ca18 100644 --- a/setup/upload.py +++ b/setup/upload.py @@ -127,6 +127,8 @@ class UploadDemo(Command): class UploadToServer(Command): + description = 'Upload miscellaneous data to calibre server' + def run(self, opts): 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) diff --git a/src/calibre/debug.py b/src/calibre/debug.py index d695891ae9..575308fe14 100644 --- a/src/calibre/debug.py +++ b/src/calibre/debug.py @@ -179,8 +179,8 @@ def main(args=sys.argv): elif opts.add_simple_plugin is not None: add_simple_plugin(opts.add_simple_plugin) elif opts.paths: - prints('CALIBRE_RESOURCES_LOCATION='+sys.resources_location) - prints('CALIBRE_EXTENSIONS_LOCATION='+sys.extensions_location) + prints('CALIBRE_RESOURCES_PATH='+sys.resources_location) + prints('CALIBRE_EXTENSIONS_PATH='+sys.extensions_location) prints('CALIBRE_PYTHON_PATH='+os.pathsep.join(sys.path)) elif opts.pdfreflow: from calibre.ebooks.pdf.reflow import option_parser as px, run diff --git a/src/calibre/devices/linux_mount_helper.c b/src/calibre/devices/linux_mount_helper.c index 41dab3fada..23bab36389 100644 --- a/src/calibre/devices/linux_mount_helper.c +++ b/src/calibre/devices/linux_mount_helper.c @@ -27,6 +27,7 @@ int get_root() { int do_mount(char *dev, char *mp) { char options[1000]; char marker[2000]; + int errsv; if (exists(dev) == 0) { fprintf(stderr, "Specified device node does not exist\n"); return EXIT_FAILURE; @@ -55,19 +56,19 @@ int do_mount(char *dev, char *mp) { return EXIT_FAILURE; } execlp("mount", "mount", "-t", "vfat", "-o", options, dev, mp, NULL); - int errsv = errno; + errsv = errno; fprintf(stderr, "Failed to mount with error: %s\n", strerror(errsv)); return EXIT_FAILURE; } int do_eject(char *dev, char*mp) { char marker[2000]; - int status = EXIT_FAILURE, ret; + int status = EXIT_FAILURE, ret, pid, errsv, i, rmd; if (get_root() != 0) { fprintf(stderr, "Failed to elevate to root privileges\n"); return EXIT_FAILURE; } - int pid = fork(); + pid = fork(); if (pid == -1) { fprintf(stderr, "Failed to fork\n"); return EXIT_FAILURE; @@ -78,11 +79,10 @@ int do_eject(char *dev, char*mp) { return EXIT_FAILURE; } execlp("eject", "eject", "-s", dev, NULL); - int errsv = errno; + errsv = errno; fprintf(stderr, "Failed to eject with error: %s\n", strerror(errsv)); return EXIT_FAILURE; } else { - int i; for (i =0; i < 7; i++) { sleep(1); ret = waitpid(pid, &status, WNOHANG); @@ -99,7 +99,7 @@ int do_eject(char *dev, char*mp) { fprintf(stderr, "Failed to unlink marker: %s\n", strerror(errno)); return EXIT_FAILURE; } - int rmd = rmdir(mp); + rmd = rmdir(mp); if (rmd == -1) { fprintf(stderr, "Failed to remove mount point: %s\n", strerror(errno)); return EXIT_FAILURE; diff --git a/src/calibre/linux.py b/src/calibre/linux.py index fd2903c94b..4a63b54a68 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -1,17 +1,13 @@ __license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal ' + ''' Post installation script for linux ''' -import sys, os, shutil + +import sys, os, shutil, cPickle, textwrap from subprocess import check_call -from calibre import __version__, __appname__ -from calibre.customize.ui import device_plugins +from calibre import __version__, __appname__, prints -DEVICES = device_plugins() - -DESTDIR = '' -if os.environ.has_key('DESTDIR'): - DESTDIR = os.environ['DESTDIR'] entry_points = { 'console_scripts': [ \ @@ -40,6 +36,335 @@ entry_points = { ], } +UNINSTALL = '''\ +#!{python} +euid = {euid} + +import os + +if os.geteuid() != euid: + print 'WARNING: uninstaller must be run as', euid, 'to remove all files' + +for x in {manifest!r}: + if not os.path.exists(x): continue + try: + if os.path.isdir(x): + shutil.rmtree(x) + else: + os.unlink(x) + except Exception, e: + print 'Failed to delete', x + print '\t', e +''' + +class PostInstall: + + def task_failed(self, msg): + self.warn(msg, 'with error:') + import traceback + tb = '\n\t'.join(traceback.format_exc().splitlines()) + self.info('\t'+tb) + print + + def warning(self, *args, **kwargs): + print '\n'+'_'*20, 'WARNING','_'*20 + prints(*args, **kwargs) + print '_'*50 + self.warnings.append((args, kwargs)) + sys.stdout.flush() + + + def __init__(self, opts, info=prints, warn=None, manifest=None): + self.opts = opts + self.info = info + self.warn = warn + self.warnings = [] + if self.warn is None: + self.warn = self.warning + + if not self.opts.staging_bindir: + self.opts.staging_bindir = os.path.join(self.opts.staging_root, + 'bin') + if not self.opts.staging_sharedir: + self.opts.staging_sharedir = os.path.join(self.opts.staging_root, + 'etc') + self.opts.staging_etc = '/etc' if self.opts.staging_root == '/usr' else \ + os.path.join(self.opts.staging_root, 'etc') + + scripts = cPickle.loads(P('scripts.pickle', data=True)) + if getattr(sys, 'frozen_path', False): + self.info('Creating symlinks...') + for exe in scripts.keys(): + dest = os.path.join(self.opts.staging_bindir, exe) + if os.path.exists(dest): + os.unlink(dest) + tgt = os.path.join(getattr(sys, 'frozen_path'), exe) + self.info('\tSymlinking %s to %s'%(tgt, dest)) + os.symlink(tgt, dest) + + if manifest is None: + manifest = [os.path.abspath(os.path.join(opts.staging_bindir, x)) for x in + scripts.keys()] + self.manifest = manifest + self.icon_resources = [] + self.menu_resources = [] + self.mime_resources = [] + self.setup_completion() + self.setup_udev_rules() + self.install_man_pages() + self.setup_desktop_integration() + + from calibre.utils.config import config_dir + if os.path.exists(config_dir): + os.chdir(config_dir) + for f in os.listdir('.'): + if os.stat(f).st_uid == 0: + os.rmdir(f) if os.path.isdir(f) else os.unlink(f) + if os.stat(config_dir).st_uid == 0: + os.rmdir(config_dir) + + if warn is None and self.warnings: + self.info('There were %d warnings'%len(self.warnings)) + for args, kwargs in self.warnings: + self.info('*', *args, **kwargs) + print + + + def setup_completion(self): + try: + self.info('Setting up bash completion...') + from calibre.ebooks.metadata.cli import option_parser as metaop, filetypes as meta_filetypes + from calibre.ebooks.lrf.lrfparser import option_parser as lrf2lrsop + from calibre.gui2.lrf_renderer.main import option_parser as lrfviewerop + from calibre.web.fetch.simple import option_parser as web2disk + from calibre.web.feeds.recipes import titles as feed_titles + from calibre.ebooks.metadata.fetch import option_parser as fem_op + from calibre.gui2.main import option_parser as guiop + from calibre.utils.smtp import option_parser as smtp_op + any_formats = ['epub', 'htm', 'html', 'xhtml', 'xhtm', 'rar', 'zip', + 'txt', 'lit', 'rtf', 'pdf', 'prc', 'mobi', 'fb2', 'odt'] + if os.path.exists(os.path.join(self.opts.staging_sharedir, + 'bash-completion')): + f = os.path.join(self.opts.staging_sharedir, + 'bash-completion', 'calibre') + else: + f = os.path.join(self.opts.staging_etc, + 'bash_completion.d/calibre') + if not os.path.exists(os.path.dirname(f)): + os.makedirs(os.path.dirname(f)) + self.manifest.append(f) + with open(f, 'wb') as f: + f.write('# calibre Bash Shell Completion\n') + f.write(opts_and_exts('calibre', guiop, any_formats)) + f.write(opts_and_exts('lrf2lrs', lrf2lrsop, ['lrf'])) + f.write(opts_and_exts('ebook-meta', metaop, list(meta_filetypes()))) + f.write(opts_and_exts('lrfviewer', lrfviewerop, ['lrf'])) + f.write(opts_and_words('web2disk', web2disk, feed_titles)) + f.write(opts_and_words('fetch-ebook-metadata', fem_op, [])) + f.write(opts_and_words('calibre-smtp', smtp_op, [])) + f.write(textwrap.dedent(''' + _ebook_device_ls() + { + local pattern search listing prefix + pattern="$1" + search="$1" + if [[ -n "{$pattern}" ]]; then + if [[ "${pattern:(-1)}" == "/" ]]; then + pattern="" + else + pattern="$(basename ${pattern} 2> /dev/null)" + search="$(dirname ${search} 2> /dev/null)" + fi + fi + + if [[ "x${search}" == "x" || "x${search}" == "x." ]]; then + search="/" + fi + + listing="$(ebook-device ls ${search} 2>/dev/null)" + + prefix="${search}" + if [[ "x${prefix:(-1)}" != "x/" ]]; then + prefix="${prefix}/" + fi + + echo $(compgen -P "${prefix}" -W "${listing}" "${pattern}") + } + + _ebook_device() + { + local cur prev + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + COMPREPLY=() + case "${prev}" in + ls|rm|mkdir|touch|cat ) + COMPREPLY=( $(_ebook_device_ls "${cur}") ) + return 0 + ;; + cp ) + if [[ ${cur} == prs500:* ]]; then + COMPREPLY=( $(_ebook_device_ls "${cur:7}") ) + return 0 + else + _filedir + return 0 + fi + ;; + prs500 ) + COMPREPLY=( $(compgen -W "cp ls rm mkdir touch cat info books df" "${cur}") ) + return 0 + ;; + * ) + if [[ ${cur} == prs500:* ]]; then + COMPREPLY=( $(_ebook_device_ls "${cur:7}") ) + return 0 + else + if [[ ${prev} == prs500:* ]]; then + _filedir + return 0 + else + COMPREPLY=( $(compgen -W "prs500:" "${cur}") ) + return 0 + fi + return 0 + fi + ;; + esac + } + complete -o nospace -F _ebook_device ebook-device + + complete -o nospace -C calibre-complete ebook-convert + ''')) + except TypeError, err: + if 'resolve_entities' in str(err): + print 'You need python-lxml >= 2.0.5 for calibre' + sys.exit(1) + raise + except: + if self.opts.fatal_errors: + raise + self.task_failed('Setting up completion failed') + + def setup_udev_rules(self): + self.info('Trying to setup udev rules...') + try: + group_file = os.path.join(self.opts.staging_etc, 'group') + groups = open(group_file, 'rb').read() + group = 'plugdev' if 'plugdev' in groups else 'usb' + old_udev = '/etc/udev/rules.d/95-calibre.rules' + if os.path.exists(old_udev): + os.remove(old_udev) + if self.opts.staging_root == '/usr': + base = '/lib' + else: + base = os.path.join(self.opts.staging_root, 'lib') + base = os.path.join(base, 'udev', 'rules.d') + if not os.path.exists(base): + os.makedirs(base) + with open(os.path.join(base, '95-calibre.rules'), 'wb') as udev: + self.manifest.append(udev.name) + udev.write('''# Sony Reader PRS-500\n''' + '''BUS=="usb", SYSFS{idProduct}=="029b", SYSFS{idVendor}=="054c", MODE="660", GROUP="%s"\n'''%(group,) + ) + except: + if self.opts.fatal_errors: + raise + self.task_failed('Setting up udev rules failed') + + def install_man_pages(self): + try: + from calibre.utils.help2man import create_man_page + manpath = os.path.join(self.opts.staging_sharedir, 'man/man1') + if not os.path.exists(manpath): + os.makedirs(manpath) + self.info('Installing MAN pages...') + for src in entry_points['console_scripts']: + prog, right = src.split('=') + prog = prog.strip() + module = __import__(right.split(':')[0].strip(), fromlist=['a']) + parser = getattr(module, 'option_parser', None) + if parser is None: + continue + parser = parser() + raw = create_man_page(prog, parser) + manfile = os.path.join(manpath, prog+'.1'+__appname__+'.bz2') + self.info('\tInstalling MAN page for', prog) + open(manfile, 'wb').write(raw) + self.manifest.append(manfile) + except: + if self.opts.fatal_errors: + raise + self.task_failed('Installing MAN pages failed') + + def setup_desktop_integration(self): + try: + from PyQt4.QtCore import QFile + from tempfile import mkdtemp + + self.info('Setting up desktop integration...') + + + tdir = mkdtemp() + cwd = os.getcwdu() + try: + os.chdir(tdir) + render_svg(QFile(I('mimetypes/lrf.svg')), os.path.join(tdir, 'calibre-lrf.png')) + check_call('xdg-icon-resource install --noupdate --context mimetypes --size 128 calibre-lrf.png application-lrf', shell=True) + self.icon_resources.append(('mimetypes', 'application-lrf')) + check_call('xdg-icon-resource install --noupdate --context mimetypes --size 128 calibre-lrf.png text-lrs', shell=True) + self.icon_resources.append(('mimetypes', 'application-lrs')) + QFile(I('library.png')).copy(os.path.join(tdir, 'calibre-gui.png')) + check_call('xdg-icon-resource install --noupdate --size 128 calibre-gui.png calibre-gui', shell=True) + self.icon_resources.append(('apps', 'calibre-gui')) + render_svg(QFile(I('viewer.svg')), os.path.join(tdir, 'calibre-viewer.png')) + check_call('xdg-icon-resource install --size 128 calibre-viewer.png calibre-viewer', shell=True) + self.icon_resources.append(('apps', 'calibre-viewer')) + + f = open('calibre-lrfviewer.desktop', 'wb') + f.write(VIEWER) + f.close() + f = open('calibre-ebook-viewer.desktop', 'wb') + f.write(EVIEWER) + f.close() + f = open('calibre-gui.desktop', 'wb') + f.write(GUI) + f.close() + des = ('calibre-gui.desktop', 'calibre-lrfviewer.desktop', + 'calibre-ebook-viewer.desktop') + for x in des: + cmd = ['xdg-desktop-menu', 'install', './'+x] + if x != des[-1]: + cmd.insert(2, '--noupdate') + check_call(' '.join(cmd), shell=True) + self.menu_resources.append(x) + f = open('calibre-mimetypes', 'wb') + f.write(MIME) + f.close() + self.mime_resources.append('calibre-mimetypes') + check_call('xdg-mime install ./calibre-mimetypes', shell=True) + finally: + os.chdir(cwd) + shutil.rmtree(tdir) + except Exception, err: + if self.opts.fatal_errors: + raise + self.task_failed('Setting up desktop integration failed') + +def option_parser(): + from calibre.utils.config import OptionParser + parser = OptionParser() + parser.add_option('--make-errors-fatal', action='store_true', default=False, + dest='fatal_errors', help='If set die on errors.') + parser.add_option('--root', dest='staging_root', default='/usr', + help='Prefix under which to install files') + parser.add_option('--bindir', default=None, dest='staging_bindir', + help='Location where calibre launcher scripts were installed') + parser.add_option('--sharedir', default=None, dest='staging_sharedir', + help='Location where calibre resources were installed') + + return parser + def options(option_parser): parser = option_parser() @@ -121,194 +446,6 @@ def opts_and_exts(name, op, exts): } complete -o filenames -F _'''%(opts,exts) + name + ' ' + name +"\n\n" -use_destdir = False - -def open_file(path, mode='wb'): - if use_destdir: - if os.path.isabs(path): - path = path[1:] - path = os.path.join(DESTDIR, path) - if not os.path.exists(os.path.dirname(path)): - os.makedirs(os.path.dirname(path)) - return open(path, mode) - -def setup_completion(fatal_errors): - manifest = [] - try: - print 'Setting up bash completion...', - sys.stdout.flush() - from calibre.ebooks.metadata.cli import option_parser as metaop, filetypes as meta_filetypes - from calibre.ebooks.lrf.lrfparser import option_parser as lrf2lrsop - from calibre.gui2.lrf_renderer.main import option_parser as lrfviewerop - from calibre.web.fetch.simple import option_parser as web2disk - from calibre.web.feeds.recipes import titles as feed_titles - from calibre.ebooks.metadata.fetch import option_parser as fem_op - from calibre.gui2.main import option_parser as guiop - from calibre.utils.smtp import option_parser as smtp_op - any_formats = ['epub', 'htm', 'html', 'xhtml', 'xhtm', 'rar', 'zip', - 'txt', 'lit', 'rtf', 'pdf', 'prc', 'mobi', 'fb2', 'odt'] - if os.path.exists('/usr/share/bash-completion'): - f = open_file('/usr/share/bash-completion/calibre') - else: - f = open_file('/etc/bash_completion.d/calibre') - manifest.append(f.name) - - f.write('# calibre Bash Shell Completion\n') - f.write(opts_and_exts('calibre', guiop, any_formats)) - f.write(opts_and_exts('lrf2lrs', lrf2lrsop, ['lrf'])) - f.write(opts_and_exts('ebook-meta', metaop, list(meta_filetypes()))) - f.write(opts_and_exts('lrfviewer', lrfviewerop, ['lrf'])) - f.write(opts_and_words('web2disk', web2disk, feed_titles)) - f.write(opts_and_words('fetch-ebook-metadata', fem_op, [])) - f.write(opts_and_words('calibre-smtp', smtp_op, [])) - f.write(''' -_prs500_ls() -{ - local pattern search listing prefix - pattern="$1" - search="$1" - if [[ -n "{$pattern}" ]]; then - if [[ "${pattern:(-1)}" == "/" ]]; then - pattern="" - else - pattern="$(basename ${pattern} 2> /dev/null)" - search="$(dirname ${search} 2> /dev/null)" - fi - fi - - if [[ "x${search}" == "x" || "x${search}" == "x." ]]; then - search="/" - fi - - listing="$(prs500 ls ${search} 2>/dev/null)" - - prefix="${search}" - if [[ "x${prefix:(-1)}" != "x/" ]]; then - prefix="${prefix}/" - fi - - echo $(compgen -P "${prefix}" -W "${listing}" "${pattern}") -} - -_prs500() -{ - local cur prev - cur="${COMP_WORDS[COMP_CWORD]}" - prev="${COMP_WORDS[COMP_CWORD-1]}" - COMPREPLY=() - case "${prev}" in - ls|rm|mkdir|touch|cat ) - COMPREPLY=( $(_prs500_ls "${cur}") ) - return 0 - ;; - cp ) - if [[ ${cur} == prs500:* ]]; then - COMPREPLY=( $(_prs500_ls "${cur:7}") ) - return 0 - else - _filedir - return 0 - fi - ;; - prs500 ) - COMPREPLY=( $(compgen -W "cp ls rm mkdir touch cat info books df" "${cur}") ) - return 0 - ;; - * ) - if [[ ${cur} == prs500:* ]]; then - COMPREPLY=( $(_prs500_ls "${cur:7}") ) - return 0 - else - if [[ ${prev} == prs500:* ]]; then - _filedir - return 0 - else - COMPREPLY=( $(compgen -W "prs500:" "${cur}") ) - return 0 - fi - return 0 - fi - ;; - esac -} -complete -o nospace -F _prs500 ebook-device - -complete -o nospace -C calibre-complete ebook-convert -''') - f.close() - print 'done' - except TypeError, err: - if 'resolve_entities' in str(err): - print 'You need python-lxml >= 2.0.5 for calibre' - sys.exit(1) - raise - except: - if fatal_errors: - raise - print 'failed' - import traceback - traceback.print_exc() - return manifest - -def setup_udev_rules(group_file, reload, fatal_errors): - print 'Trying to setup udev rules...' - manifest = [] - sys.stdout.flush() - groups = open(group_file, 'rb').read() - group = 'plugdev' if 'plugdev' in groups else 'usb' - old_udev = '/etc/udev/rules.d/95-calibre.rules' - if os.path.exists(old_udev): - os.remove(old_udev) - if os.path.exists('/lib/udev/rules.d'): - udev = open_file('/lib/udev/rules.d/95-calibre.rules') - else: - udev = open_file(old_udev) - manifest.append(udev.name) - udev.write('''# Sony Reader PRS-500\n''' - '''BUS=="usb", SYSFS{idProduct}=="029b", SYSFS{idVendor}=="054c", MODE="660", GROUP="%s"\n'''%(group,) - ) - udev.close() - return manifest - -def option_parser(): - from optparse import OptionParser - parser = OptionParser() - parser.add_option('--use-destdir', action='store_true', default=False, dest='destdir', - help='If set, respect the environment variable DESTDIR when installing files') - parser.add_option('--do-not-reload-udev-hal', action='store_true', dest='dont_reload', default=False, - help='Does nothing. Present for legacy reasons.') - parser.add_option('--group-file', default='/etc/group', dest='group_file', - help='File from which to read group information. Default: %default') - parser.add_option('--dont-check-root', action='store_true', default=False, dest='no_root', - help='If set, do not check if we are root.') - parser.add_option('--make-errors-fatal', action='store_true', default=False, - dest='fatal_errors', help='If set die on errors.') - parser.add_option('--save-manifest-to', default=None, - help='Save a manifest of all installed files to the specified location') - return parser - -def install_man_pages(fatal_errors, use_destdir=False): - from calibre.utils.help2man import create_man_page - prefix = os.environ.get('DESTDIR', '/') if use_destdir else '/' - manpath = os.path.join(prefix, 'usr/share/man/man1') - if not os.path.exists(manpath): - os.makedirs(manpath) - print 'Installing MAN pages...' - manifest = [] - for src in entry_points['console_scripts']: - prog, right = src.split('=') - prog = prog.strip() - module = __import__(right.split(':')[0].strip(), fromlist=['a']) - parser = getattr(module, 'option_parser', None) - if parser is None: - continue - parser = parser() - raw = create_man_page(prog, parser) - manfile = os.path.join(manpath, prog+'.1'+__appname__+'.bz2') - print '\tInstalling MAN page for', prog - open(manfile, 'wb').write(raw) - manifest.append(manfile) - return manifest def post_install(): parser = option_parser() @@ -317,13 +454,6 @@ def post_install(): global use_destdir use_destdir = opts.destdir manifest = [] - setup_desktop_integration(opts.fatal_errors) - if opts.no_root or os.geteuid() == 0: - manifest += install_man_pages(opts.fatal_errors, use_destdir) - manifest += setup_udev_rules(opts.group_file, not opts.dont_reload, opts.fatal_errors) - manifest += setup_completion(opts.fatal_errors) - else: - print "Skipping udev, completion, and man-page install for non-root user." try: from PyQt4 import Qt @@ -335,26 +465,9 @@ def post_install(): if opts.save_manifest_to: open(opts.save_manifest_to, 'wb').write('\n'.join(manifest)+'\n') - from calibre.utils.config import config_dir - if os.path.exists(config_dir): - os.chdir(config_dir) - for f in os.listdir('.'): - if os.stat(f).st_uid == 0: - os.rmdir(f) if os.path.isdir(f) else os.unlink(f) - if os.stat(config_dir).st_uid == 0: - os.rmdir(config_dir) def binary_install(): manifest = os.path.join(getattr(sys, 'frozen_path'), 'manifest') - exes = [x.strip() for x in open(manifest).readlines()] - print 'Creating symlinks...' - for exe in exes: - dest = os.path.join('/usr', 'bin', exe) - if os.path.exists(dest): - os.unlink(dest) - tgt = os.path.join(getattr(sys, 'frozen_path'), exe) - print '\tSymlinking %s to %s'%(tgt, dest) - os.symlink(tgt, dest) post_install() return 0 @@ -431,50 +544,12 @@ def render_svg(image, dest): painter.end() image.save(dest) -def setup_desktop_integration(fatal_errors): - try: - from PyQt4.QtCore import QFile - from tempfile import mkdtemp - - print 'Setting up desktop integration...' +def main(): + p = option_parser() + opts, args = p.parse_args() + PostInstall(opts) + return 0 - tdir = mkdtemp() - cwd = os.getcwdu() - try: - os.chdir(tdir) - render_svg(QFile(I('mimetypes/lrf.svg')), os.path.join(tdir, 'calibre-lrf.png')) - check_call('xdg-icon-resource install --context mimetypes --size 128 calibre-lrf.png application-lrf', shell=True) - check_call('xdg-icon-resource install --context mimetypes --size 128 calibre-lrf.png text-lrs', shell=True) - QFile(I('library.png')).copy(os.path.join(tdir, 'calibre-gui.png')) - check_call('xdg-icon-resource install --size 128 calibre-gui.png calibre-gui', shell=True) - render_svg(QFile(I('viewer.svg')), os.path.join(tdir, 'calibre-viewer.png')) - check_call('xdg-icon-resource install --size 128 calibre-viewer.png calibre-viewer', shell=True) - - f = open('calibre-lrfviewer.desktop', 'wb') - f.write(VIEWER) - f.close() - f = open('calibre-ebook-viewer.desktop', 'wb') - f.write(EVIEWER) - f.close() - f = open('calibre-gui.desktop', 'wb') - f.write(GUI) - f.close() - check_call('xdg-desktop-menu install ./calibre-gui.desktop ./calibre-lrfviewer.desktop', shell=True) - f = open('calibre-mimetypes', 'wb') - f.write(MIME) - f.close() - check_call('xdg-mime install calibre-mimetypes', shell=True) - finally: - os.chdir(cwd) - shutil.rmtree(tdir) - except Exception, err: - if fatal_errors: - raise - print >>sys.stderr, 'Could not setup desktop integration. Error:' - print err - -main = post_install if __name__ == '__main__': - post_install() - + sys.exit(main())