#!/usr/bin/env python # vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai from __future__ import (unicode_literals, division, absolute_import, print_function) __license__ = 'GPL v3' __copyright__ = '2014, Kovid Goyal ' __docformat__ = 'restructuredtext en' ''' Setup instructions for linux build-system Edit /etc/network/interfaces and add iface eth1 inet static address 192.168.xxx.xxx Also add eth1 to the auto line (use sudo ifup eth1 to start eth1 without rebooting) sudo visudo (all no password actions for user) sudo apt-get install build-essential module-assistant vim zsh vim-scripts rsync \ htop nasm unzip libdbus-1-dev cmake libltdl-dev libudev-dev apt-file \ libdbus-glib-1-dev libcups2-dev "^libxcb.*" libx11-xcb-dev libglu1-mesa-dev \ libxrender-dev flex bison gperf libasound2-dev libgstreamer0.10-dev \ libgstreamer-plugins-base0.10-dev libpulse-dev libgtk2.0-dev libffi-dev apt-file update # For recent enough version of debian (>= sid) also install libxkbcommon-dev mkdir -p ~/bin ~/sw/sources ~/sw/build chsh -s /bin/zsh Edit /etc/default/grub and change the GRUB_TIMEOUT to 1, then run sudo update-grub Copy over authorized_keys, .vimrc and .zshrc Create ~/.zshenv as export SW=$HOME/sw export MAKEOPTS="-j2" export CFLAGS=-I$SW/include export LDFLAGS=-L$SW/lib export LD_LIBRARY_PATH=$SW/lib export PKG_CONFIG_PATH=$SW/lib/pkgconfig:$PKG_CONFIG_PATH typeset -U path path=($SW/bin "$path[@]") path=($SW/qt/bin "$path[@]") path=(~/bin "$path[@]") ''' import sys, os, shutil, platform, subprocess, stat, py_compile, glob, textwrap, tarfile, time from functools import partial from setup import Command, modules, basenames, functions, __version__, __appname__ from setup.build_environment import QT_DLLS, QT_PLUGINS, qt, PYQT_MODULES, sw as SW from setup.parallel_build import create_job, parallel_build j = os.path.join is64bit = platform.architecture()[0] == '64bit' py_ver = '.'.join(map(str, sys.version_info[:2])) arch = 'x86_64' if is64bit else 'i686' def binary_includes(): return [ j(SW, 'bin', x) for x in ('pdftohtml', 'pdfinfo', 'pdftoppm')] + [ j(SW, 'lib', 'lib' + x) for x in ( 'usb-1.0.so.0', 'mtp.so.9', 'expat.so.1', 'sqlite3.so.0', 'podofo.so.0.9.1', 'z.so.1', 'bz2.so.1.0', 'poppler.so.46', 'iconv.so.2', 'xml2.so.2', 'xslt.so.1', 'jpeg.so.8', 'png16.so.16', 'exslt.so.0', 'imobiledevice.so.4', 'usbmuxd.so.2', 'plist.so.2', 'MagickCore-6.Q16.so.2', 'MagickWand-6.Q16.so.2', 'ssl.so.1.0.0', 'crypto.so.1.0.0', 'readline.so.6', 'chm.so.0', 'icudata.so.53', 'icui18n.so.53', 'icuuc.so.53', 'icuio.so.53', 'python%s.so.1.0' % py_ver, 'gcrypt.so.20', 'gpg-error.so.0', 'gobject-2.0.so.0', 'glib-2.0.so.0', 'gthread-2.0.so.0', 'gmodule-2.0.so.0', 'gio-2.0.so.0', )] + [ glob.glob('/lib/*/lib' + x)[-1] for x in ( 'dbus-1.so.3', 'pcre.so.3' )] + [ glob.glob('/usr/lib/*/lib' + x)[-1] for x in ( 'gstreamer-0.10.so.0', 'gstbase-0.10.so.0', 'gstpbutils-0.10.so.0', 'gstapp-0.10.so.0', 'gstinterfaces-0.10.so.0', 'gstvideo-0.10.so.0', 'orc-0.4.so.0', 'ffi.so.5', # 'stdc++.so.6', # We dont include libstdc++.so as the OpenGL dlls on the target # computer fail to load in the QPA xcb plugin if they were compiled # with a newer version of gcc than the one on the build computer. # libstdc++, like glibc is forward compatible and I dont think any # distros do not have libstdc++.so.6, so it should be safe to leave it out. # https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html (The current # debian stable libstdc++ is libstdc++.so.6.0.17) )] + [ j(qt['libs'], 'lib%s.so.5' % x) for x in QT_DLLS] def ignore_in_lib(base, items, ignored_dirs=None): ans = [] if ignored_dirs is None: ignored_dirs = {'.svn', '.bzr', '.git', 'test', 'tests', 'testing'} for name in items: path = os.path.join(base, name) if os.path.isdir(path): if name in ignored_dirs or not os.path.exists(j(path, '__init__.py')): if name != 'plugins': ans.append(name) else: if name.rpartition('.')[-1] not in ('so', 'py'): ans.append(name) return ans def import_site_packages(srcdir, dest): if not os.path.exists(dest): os.mkdir(dest) for x in os.listdir(srcdir): ext = x.rpartition('.')[-1] f = j(srcdir, x) if ext in ('py', 'so'): shutil.copy2(f, dest) elif ext == 'pth' and x != 'setuptools.pth': for line in open(f, 'rb').read().splitlines(): src = os.path.abspath(j(srcdir, line)) if os.path.exists(src) and os.path.isdir(src): import_site_packages(src, dest) elif os.path.exists(j(f, '__init__.py')): shutil.copytree(f, j(dest, x), ignore=ignore_in_lib) def is_elf(path): with open(path, 'rb') as f: return f.read(4) == b'\x7fELF' STRIPCMD = ['strip'] def strip_files(files, argv_max=(256 * 1024)): """ Strip a list of files """ while files: cmd = list(STRIPCMD) pathlen = sum(len(s) + 1 for s in cmd) while pathlen < argv_max and files: f = files.pop() cmd.append(f) pathlen += len(f) + 1 if len(cmd) > len(STRIPCMD): all_files = cmd[len(STRIPCMD):] unwritable_files = tuple(filter(None, (None if os.access(x, os.W_OK) else (x, os.stat(x).st_mode) for x in all_files))) [os.chmod(x, stat.S_IWRITE | old_mode) for x, old_mode in unwritable_files] subprocess.check_call(cmd) [os.chmod(x, old_mode) for x, old_mode in unwritable_files] class LinuxFreeze(Command): def add_options(self, parser): if not parser.has_option('--dont-strip'): parser.add_option('-x', '--dont-strip', default=False, action='store_true', help='Dont strip the generated binaries') def run(self, opts): self.drop_privileges() self.opts = opts self.src_root = self.d(self.SRC) self.base = self.j(self.src_root, 'build', 'linfrozen') self.lib_dir = self.j(self.base, 'lib') self.bin_dir = self.j(self.base, 'bin') self.initbase() self.copy_libs() self.copy_python() self.build_launchers() if not self.opts.dont_strip: self.strip_files() self.create_tarfile() def initbase(self): if os.path.exists(self.base): shutil.rmtree(self.base) os.makedirs(self.base) def copy_libs(self): self.info('Copying libs...') os.mkdir(self.lib_dir) os.mkdir(self.bin_dir) for x in binary_includes(): dest = self.bin_dir if '/bin/' in x else self.lib_dir shutil.copy2(x, dest) base = qt['plugins'] dest = self.j(self.lib_dir, 'qt_plugins') os.mkdir(dest) for x in QT_PLUGINS: shutil.copytree(self.j(base, x), self.j(dest, x)) im = glob.glob(SW + '/lib/ImageMagick-*')[-1] self.magick_base = os.path.basename(im) dest = self.j(self.lib_dir, self.magick_base) shutil.copytree(im, dest, ignore=shutil.ignore_patterns('*.a')) def copy_python(self): self.info('Copying python...') srcdir = self.j(SW, 'lib/python'+py_ver) self.py_dir = self.j(self.lib_dir, self.b(srcdir)) if not os.path.exists(self.py_dir): os.mkdir(self.py_dir) for x in os.listdir(srcdir): y = self.j(srcdir, x) ext = os.path.splitext(x)[1] if os.path.isdir(y) and x not in ('test', 'hotshot', 'distutils', 'site-packages', 'idlelib', 'lib2to3', 'dist-packages'): shutil.copytree(y, self.j(self.py_dir, x), ignore=ignore_in_lib) if os.path.isfile(y) and ext in ('.py', '.so'): shutil.copy2(y, self.py_dir) srcdir = self.j(srcdir, 'site-packages') dest = self.j(self.py_dir, 'site-packages') import_site_packages(srcdir, dest) filter_pyqt = {x+'.so' for x in PYQT_MODULES} pyqt = self.j(dest, 'PyQt5') for x in os.listdir(pyqt): if x.endswith('.so') and x not in filter_pyqt: os.remove(self.j(pyqt, x)) for x in os.listdir(self.SRC): c = self.j(self.SRC, x) if os.path.exists(self.j(c, '__init__.py')): shutil.copytree(c, self.j(dest, x), ignore=partial(ignore_in_lib, ignored_dirs={})) elif os.path.isfile(c): shutil.copy2(c, self.j(dest, x)) shutil.copytree(self.j(self.src_root, 'resources'), self.j(self.base, 'resources')) self.create_site_py() for x in os.walk(self.py_dir): for f in x[-1]: if f.endswith('.py'): y = self.j(x[0], f) rel = os.path.relpath(y, self.py_dir) try: py_compile.compile(y, dfile=rel, doraise=True) os.remove(y) z = y+'c' if os.path.exists(z): os.remove(z) except: if '/uic/port_v3/' not in y: self.warn('Failed to byte-compile', y) def run_builder(self, cmd, verbose=True): p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if verbose: self.info(*cmd) x = p.stdout.read() + p.stderr.read() if x.strip(): self.info(x.strip()) if p.wait() != 0: self.info('Failed to run builder') sys.exit(1) def create_tarfile(self): self.info('Creating archive...') base = self.j(self.d(self.SRC), 'dist') if not os.path.exists(base): os.mkdir(base) dist = os.path.join(base, '%s-%s-%s.tar'%(__appname__, __version__, arch)) with tarfile.open(dist, mode='w', format=tarfile.PAX_FORMAT) as tf: cwd = os.getcwd() os.chdir(self.base) try: for x in os.listdir('.'): tf.add(x) finally: os.chdir(cwd) self.info('Compressing archive...') ans = dist.rpartition('.')[0] + '.txz' if False: os.rename(dist, ans) else: start_time = time.time() subprocess.check_call(['xz', '--threads=0', '-f', '-9', dist]) secs = time.time() - start_time self.info('Compressed in %d minutes %d seconds' % (secs // 60, secs % 60)) os.rename(dist + '.xz', ans) self.info('Archive %s created: %.2f MB'%( os.path.basename(ans), os.stat(ans).st_size/(1024.**2))) def build_launchers(self): self.obj_dir = self.j(self.src_root, 'build', 'launcher') if not os.path.exists(self.obj_dir): os.makedirs(self.obj_dir) base = self.j(self.src_root, 'setup', 'installer', 'linux') sources = [self.j(base, x) for x in ['util.c']] headers = [self.j(base, x) for x in ['util.h']] objects = [self.j(self.obj_dir, self.b(x)+'.o') for x in sources] cflags = '-fno-strict-aliasing -W -Wall -c -O2 -pipe -DPYTHON_VER="python%s"'%py_ver cflags = cflags.split() + ['-I%s/include/python%s' % (SW, py_ver)] for src, obj in zip(sources, objects): if not self.newer(obj, headers+[src, __file__]): continue cmd = ['gcc'] + cflags + ['-fPIC', '-o', obj, src] self.run_builder(cmd) dll = self.j(self.lib_dir, 'libcalibre-launcher.so') if self.newer(dll, objects): cmd = ['gcc', '-O2', '-Wl,--rpath=$ORIGIN/../lib', '-fPIC', '-o', dll, '-shared'] + objects + \ ['-L%s/lib'%SW, '-lpython'+py_ver] self.info('Linking libcalibre-launcher.so') self.run_builder(cmd) src = self.j(base, 'main.c') modules['console'].append('calibre.linux') basenames['console'].append('calibre_postinstall') functions['console'].append('main') c_launcher = '/tmp/calibre-c-launcher' lsrc = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'launcher.c') cmd = ['gcc', '-O2', '-DMAGICK_BASE="%s"' % self.magick_base, '-o', c_launcher, lsrc, ] self.info('Compiling launcher') self.run_builder(cmd, verbose=False) jobs = [] self.info('Processing launchers') for typ in ('console', 'gui', ): for mod, bname, func in zip(modules[typ], basenames[typ], functions[typ]): xflags = list(cflags) xflags.remove('-c') xflags += ['-DGUI_APP='+('1' if typ == 'gui' else '0')] xflags += ['-DMODULE="%s"'%mod, '-DBASENAME="%s"'%bname, '-DFUNCTION="%s"'%func] exe = self.j(self.bin_dir, bname) if self.newer(exe, [src, __file__]+headers): cmd = ['gcc'] + xflags + [src, '-o', exe, '-L' + self.lib_dir, '-lcalibre-launcher'] jobs.append(create_job(cmd)) sh = self.j(self.base, bname) shutil.copy2(c_launcher, sh) os.chmod(sh, stat.S_IREAD|stat.S_IEXEC|stat.S_IWRITE|stat.S_IRGRP|stat.S_IXGRP|stat.S_IROTH|stat.S_IXOTH) if jobs: if not parallel_build(jobs, self.info, verbose=False): raise SystemExit(1) def strip_files(self): from calibre import walk files = {self.j(self.bin_dir, x) for x in os.listdir(self.bin_dir)} | { x for x in { self.j(self.d(self.bin_dir), x) for x in os.listdir(self.bin_dir)} if os.path.exists(x)} for x in walk(self.lib_dir): x = os.path.realpath(x) if x not in files and is_elf(x): files.add(x) self.info('Stripping %d files...' % len(files)) before = sum(os.path.getsize(x) for x in files) strip_files(files) after = sum(os.path.getsize(x) for x in files) self.info('Stripped %.1f MB' % ((before - after)/(1024*1024.))) def create_site_py(self): # {{{ with open(self.j(self.py_dir, 'site.py'), 'wb') as f: f.write(textwrap.dedent('''\ import sys import encodings # noqa import __builtin__ import locale import os import codecs def set_default_encoding(): try: locale.setlocale(locale.LC_ALL, '') except: print ('WARNING: Failed to set default libc locale, using en_US.UTF-8') locale.setlocale(locale.LC_ALL, 'en_US.UTF-8') enc = locale.getdefaultlocale()[1] if not enc: enc = locale.nl_langinfo(locale.CODESET) if not enc or enc.lower() == 'ascii': enc = 'UTF-8' try: enc = codecs.lookup(enc).name except LookupError: enc = 'UTF-8' sys.setdefaultencoding(enc) del sys.setdefaultencoding class _Helper(object): """Define the builtin 'help'. This is a wrapper around pydoc.help (with a twist). """ def __repr__(self): return "Type help() for interactive help, " \ "or help(object) for help about object." def __call__(self, *args, **kwds): import pydoc return pydoc.help(*args, **kwds) def set_helper(): __builtin__.help = _Helper() def main(): try: sys.argv[0] = sys.calibre_basename dfv = os.environ.get('CALIBRE_DEVELOP_FROM', None) if dfv and os.path.exists(dfv): sys.path.insert(0, os.path.abspath(dfv)) set_default_encoding() set_helper() mod = __import__(sys.calibre_module, fromlist=[1]) func = getattr(mod, sys.calibre_function) return func() except SystemExit as err: if err.code is None: return 0 if isinstance(err.code, int): return err.code print (err.code) return 1 except: import traceback traceback.print_exc() return 1 ''')) # }}}