#!/usr/bin/env python # License: GPLv3 Copyright: 2009, Kovid Goyal import atexit import glob import os import shutil import subprocess import sys import tempfile import textwrap import time from setup import Command, __appname__, __version__, basenames, functions, isbsd, ishaiku, islinux, modules HEADER = '''\ #!/usr/bin/env python{py_major_version} """ This is the standard runscript for all of calibre's tools. Do not modify it unless you know what you are doing. """ import sys, os path = os.environ.get('CALIBRE_PYTHON_PATH', {path!r}) if path not in sys.path: 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}) sys.executables_location = os.environ.get('CALIBRE_EXECUTABLES_PATH', {executables!r}) sys.system_plugins_location = {system_plugins_loc!r} ''' TEMPLATE = HEADER+''' from {module} import {func!s} sys.exit({func!s}()) ''' COMPLETE_TEMPLATE = HEADER+''' sys.path.insert(0, os.path.join(path, 'calibre', 'utils')) import complete sys.path = sys.path[1:] sys.exit(complete.main()) ''' class Develop(Command): description = textwrap.dedent('''\ Setup a development environment for calibre. This allows you to run calibre directly from the source tree. Binaries will be installed in /bin where is the prefix of your python installation. This can be controlled via the --prefix option. ''') short_description = 'Setup a development environment for calibre' MODE = 0o755 sub_commands = ['build', 'resources', 'iso639', 'iso3166', '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') parser.add_option('--system-plugins-location', help='Path to a directory from which the installed calibre will load plugins') self.add_postinstall_options(parser) def consolidate_paths(self): opts = self.opts if not opts.prefix: opts.prefix = sys.prefix for x in ('prefix', 'libdir', 'bindir', 'sharedir', 'staging_root', 'staging_libdir', 'staging_bindir', 'staging_sharedir'): o = getattr(opts, x, None) if o: setattr(opts, x, os.path.abspath(o)) 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') self.staging_libdir = opts.staging_libdir = self.j(self.staging_libdir, 'calibre') self.staging_sharedir = opts.staging_sharedir = self.j(self.staging_sharedir, 'calibre') self.system_plugins_loc = opts.system_plugins_location if self.__class__.__name__ == 'Develop': self.libdir = self.SRC self.sharedir = self.RESOURCES else: self.libdir = self.j(self.libdir, 'calibre') self.sharedir = self.j(self.sharedir, 'calibre') self.info('INSTALL paths:') self.info('\tLIB:', self.staging_libdir) self.info('\tSHARE:', self.staging_sharedir) def pre_sub_commands(self, opts): if not (islinux or isbsd or ishaiku): 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 os.geteuid() == 0: # We drop privileges for security, regaining them when installing # files. Also ensures that any config files created as a side # effect of the build process are not owned by root. self.drop_privileges() # Ensure any config files created as a side effect of importing calibre # during the build process are in /tmp os.environ['CALIBRE_CONFIG_DIRECTORY'] = os.environ.get('CALIBRE_CONFIG_DIRECTORY', '/tmp/calibre-install-config') def run(self, opts): self.manifest = [] self.opts = opts self.regain_privileges() self.consolidate_paths() self.install_files() self.write_templates() self.install_env_module() self.run_postinstall() self.success() def install_env_module(self): import sysconfig libdir = os.path.join( self.opts.staging_root, sysconfig.get_config_var('PLATLIBDIR') or 'lib', os.path.basename(sysconfig.get_config_var('DESTLIB') or sysconfig.get_config_var('LIBDEST') or f'python{sysconfig.get_python_version()}'), 'site-packages') try: if not os.path.exists(libdir): os.makedirs(libdir) except OSError: self.warn('Cannot install calibre environment module to: '+libdir) else: path = os.path.join(libdir, 'init_calibre.py') self.info('Installing calibre environment module: '+path) with open(path, 'wb') as f: f.write(HEADER.format(**self.template_args()).encode('utf-8')) self.manifest.append(path) def install_files(self): pass def run_postinstall(self): if self.opts.postinstall: from calibre.linux import PostInstall PostInstall(self.opts, info=self.info, warn=self.warn, manifest=self.manifest) def success(self): self.info('\nDevelopment environment successfully setup') def write_templates(self): for typ in ('console', 'gui'): for name, mod, func in zip(basenames[typ], modules[typ], functions[typ]): self.write_template(name, mod, func) def template_args(self): return { 'py_major_version': sys.version_info.major, 'path':self.libdir, 'resources':self.sharedir, 'executables':self.bindir, 'extensions':self.j(self.libdir, 'calibre', 'plugins'), 'system_plugins_loc': self.system_plugins_loc, } def write_template(self, name, mod, func): template = COMPLETE_TEMPLATE if name == 'calibre-complete' else TEMPLATE args = self.template_args() args['module'] = mod args['func'] = func script = template.format(**args) 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) if os.path.lexists(path) and not os.path.exists(path): os.remove(path) with open(path, 'wb') as f: f.write(script.encode('utf-8')) os.chmod(path, self.MODE) self.manifest.append(path) class Install(Develop): description = textwrap.dedent('''\ Install calibre to your system. By default, calibre is installed to /bin, /lib/calibre, /share/calibre. These can all be controlled via options. The default is the prefix of your python installation. The .desktop, .mime and icon files are installed using XDG. The location they are installed to can be controlled by setting the environment variables: XDG_DATA_DIRS=/usr/share equivalent XDG_UTILS_INSTALL_MODE=system For staged installs this will be automatically set to: /share ''') 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. 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') parser.add_option('--system-plugins-location', help='Path to a directory from which the installed calibre will load plugins') self.add_postinstall_options(parser) def install_files(self): dest = self.staging_libdir if os.path.exists(dest): shutil.rmtree(dest) self.info('Installing code to', dest) self.manifest.append(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 ('.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) self.info('Installing resources to', dest) shutil.copytree(self.RESOURCES, dest, symlinks=True) self.manifest.append(dest) def success(self): self.info('\n\ncalibre successfully installed. You can start' ' it by running the command calibre') class Sdist(Command): description = 'Create a source distribution' DEST = os.path.join('dist', f'{__appname__}-{__version__}.tar.xz') def run(self, opts): if not self.e(self.d(self.DEST)): os.makedirs(self.d(self.DEST)) tdir = tempfile.mkdtemp() atexit.register(shutil.rmtree, tdir) tdir = self.j(tdir, f'calibre-{__version__}') self.info('\tRunning git export...') os.mkdir(tdir) subprocess.check_call('git archive HEAD | tar -x -C ' + tdir, shell=True) for x in open('.gitignore').readlines(): if not x.startswith('/resources/'): continue x = x[1:] p = x.strip().replace('/', os.sep) for p in glob.glob(p): d = self.j(tdir, os.path.dirname(p)) if not self.e(d): os.makedirs(d) if os.path.isdir(p): shutil.copytree(p, self.j(tdir, p)) else: shutil.copy2(p, d) for x in os.walk(os.path.join(self.SRC, 'calibre')): for f in x[-1]: if not f.endswith('_ui.py'): continue f = os.path.join(x[0], f) f = os.path.relpath(f) dest = os.path.join(tdir, self.d(f)) shutil.copy2(f, dest) tbase = self.j(self.d(self.SRC), 'translations') for x in ('iso_639', 'calibre'): destdir = self.j(tdir, 'translations', x) if not os.path.exists(destdir): os.makedirs(destdir) for y in glob.glob(self.j(tbase, x, '*.po')) + glob.glob(self.j(tbase, x, '*.pot')): dest = self.j(destdir, self.b(y)) if not os.path.exists(dest): shutil.copy2(y, dest) shutil.copytree(self.j(tbase, 'manual'), self.j(tdir, 'translations', 'manual')) self.add_man_pages(self.j(tdir, 'man-pages')) self.info('\tCreating tarfile...') dest = self.DEST.rpartition('.')[0] shutil.rmtree(os.path.join(tdir, '.github')) subprocess.check_call(['tar', '--mtime=now', '-cf', self.a(dest), f'calibre-{__version__}'], cwd=self.d(tdir)) self.info('\tCompressing tarfile...') if os.path.exists(self.a(self.DEST)): os.remove(self.a(self.DEST)) subprocess.check_call(['xz', '-9', self.a(dest)]) def add_man_pages(self, dest): from setup.commands import man_pages man_pages.build_man_pages(dest) def clean(self): if os.path.exists(self.DEST): os.remove(self.DEST) class Bootstrap(Command): description = 'Bootstrap a fresh checkout of calibre from git to a state where it can be installed. Requires various development tools/libraries/headers' TRANSLATIONS_REPO = 'kovidgoyal/calibre-translations' sub_commands = 'build iso639 iso3166 translations gui resources cacerts recent_uas'.split() def add_options(self, parser): parser.add_option('--ephemeral', default=False, action='store_true', help='Do not download all history for the translations. Speeds up first time download but subsequent downloads will be slower.') parser.add_option('--path-to-translations', help='Path to existing out-of-tree translations checkout. Use this to avoid downloading translations at all.') def pre_sub_commands(self, opts): tdir = self.j(self.d(self.SRC), 'translations') clone_cmd = [ 'git', 'clone', f'https://github.com/{self.TRANSLATIONS_REPO}.git', 'translations'] if opts.path_to_translations: if os.path.exists(tdir): shutil.rmtree(tdir) shutil.copytree(opts.path_to_translations, tdir) # Change permissions for the top-level folder os.chmod(tdir, 0o755) for root, dirs, files in os.walk(tdir): # set perms on sub-directories for momo in dirs: os.chmod(os.path.join(root, momo), 0o755) # set perms on files for momo in files: os.chmod(os.path.join(root, momo), 0o644) elif opts.ephemeral: if os.path.exists(tdir): shutil.rmtree(tdir) st = time.time() clone_cmd.insert(2, '--depth=1') subprocess.check_call(clone_cmd, cwd=self.d(self.SRC)) print(f'Downloaded translations in {int(time.time() - st)} seconds') else: if os.path.exists(tdir): subprocess.check_call(['git', 'pull'], cwd=tdir) else: subprocess.check_call(clone_cmd, cwd=self.d(self.SRC)) def run(self, opts): self.info(f'\n\nAll done! You should now be able to run "{sys.executable} setup.py install" to install calibre')