Index: scripts/slave/recipe_modules/conda/resources/butcher_conda.py |
diff --git a/scripts/slave/recipe_modules/conda/resources/butcher_conda.py b/scripts/slave/recipe_modules/conda/resources/butcher_conda.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..53031c75a2310e42af05051789d224740a9820d9 |
--- /dev/null |
+++ b/scripts/slave/recipe_modules/conda/resources/butcher_conda.py |
@@ -0,0 +1,200 @@ |
+# Copyright 2015 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+"""Attempts to remove references to Conda installation directory to make the |
+environment more relocatable in exchange for its modifiability. |
+ |
+No new package can be installed into a butchered environment. |
+ |
+Works great on Mac and Win, but doesn't really makes the environment prefix |
+independent on Linux, since Linux binaries like to have prefix hardcoded. |
+ |
+Everything seems to work even on Linux though, but watch out for weirdness. |
+ |
+Helpful links: |
+ https://github.com/conda/conda-recipes |
+ http://conda.pydata.org/docs/building/meta-yaml.html#relocatable |
+""" |
+ |
+import ast |
+import glob |
+import os |
+import shutil |
+import sys |
+ |
+ |
+IS_WIN = sys.platform in ('win32', 'cygwin') |
+ |
+ |
+def main(conda_dir): |
+ butcher_bin(conda_dir) |
+ butcher_conda_meta(conda_dir) |
+ butcher_include(conda_dir) |
+ butcher_lib(conda_dir) |
+ butcher_pkgs(conda_dir) |
+ butcher_pyqt(conda_dir) |
+ |
+ |
+def butcher_bin(conda_dir): |
+ if IS_WIN: |
+ bin_dir = os.path.join(conda_dir, 'Library', 'bin') |
+ else: |
+ bin_dir = os.path.join(conda_dir, 'bin') |
+ |
+ whitelist = [ |
+ 'python', # relative symlink to python2.7, keep it, it's cute |
+ ] |
+ blacklist = [ |
+ 'c_rehash', # perl script with references to local prefix |
+ 'cairo-trace', # not needed |
+ 'freetype-config', # we won't be linking to freetype |
+ 'libpng16-config', # same |
+ 'xml2-config', # same |
+ 'pyuic4', # just use PyQt4/uic/pyuic.py directly |
+ 'pyuic4.bat', # same |
+ 'qt.conf', # Qt manages to work without it |
+ ] |
+ |
+ def is_naughty_shell_script(path): |
+ """True if script's shebang points to local prefix.""" |
+ if IS_WIN: |
+ return False |
+ with open(path, 'r') as f: |
+ return f.read(1024).startswith('#!' + bin_dir) |
+ |
+ # Remove meaningless symlinks and shell scripts that'll broke when relocated. |
+ # 'python' stays, it is all that matters. |
+ for p in os.listdir(bin_dir): |
+ full = os.path.join(bin_dir, p) |
+ kill_it = not (p in whitelist or os.path.isdir(full)) and ( |
+ p in blacklist or |
+ os.path.islink(full) or |
+ is_naughty_shell_script(full)) |
+ if kill_it: |
+ kill_file(full) |
+ |
+ |
+def butcher_conda_meta(conda_dir): |
+ # 'conda-meta' contains history of local commands with full paths, as well |
+ # as ton of *.json files referencing local paths. We aren't going to install |
+ # any more conda packages, no need for meta files. |
+ kill_dir(os.path.join(conda_dir, 'conda-meta')) |
+ |
+ |
+def butcher_include(conda_dir): |
+ # Header files? Where we're going we don't need header files. |
+ # But in case we do, a special care must be given to openssl/opensslconf.h, |
+ # it references local prefix. |
+ kill_dir(os.path.join(conda_dir, 'include')) |
+ |
+ |
+def butcher_lib(conda_dir): |
+ if IS_WIN: |
+ lib = os.path.join(conda_dir, 'Library', 'lib') |
+ else: |
+ lib = os.path.join(conda_dir, 'lib') |
+ |
+ # We aren't going to build Tk\Tcl extensions, it's not 80s. |
+ kill_file(os.path.join(lib, 'tclConfig.sh')) |
+ kill_file(os.path.join(lib, 'tkConfig.sh')) |
+ |
+ # That's all for Win. |
+ if IS_WIN: |
+ return |
+ |
+ # We won't be using cmake. |
+ kill_dir(os.path.join(lib, 'cmake')) |
+ |
+ # We aren't going to build C stuff, kill libtool and pkg-config files. |
+ kill_glob(os.path.join(lib, '*.la')) |
+ kill_glob(os.path.join(lib, 'cairo', '*.la')) |
+ kill_dir(os.path.join(lib, 'pkgconfig')) |
+ |
+ # We aren't be building python extensions at all, in fact. |
+ kill_file(os.path.join(lib, 'python2.7', 'config', 'Makefile')) |
+ |
+ # This file looks important, let's patch it instead of removing. |
+ sysconf = os.path.join(lib, 'python2.7', '_sysconfigdata.py') |
+ if patch_file(sysconf, conda_dir): |
+ kill_file(os.path.join(lib, 'python2.7', '_sysconfigdata.pyc')) |
+ kill_file(os.path.join(lib, 'python2.7', '_sysconfigdata.pyo')) |
+ |
+ |
+def butcher_pkgs(conda_dir): |
+ # pkgs contains unpacked Conda packages that act as source of hardlinks that |
+ # gets installed into actual prefix. Since its hard links, it's OK to remove |
+ # originals (thus "converting" hardlinks into regular files). |
+ kill_dir(os.path.join(conda_dir, 'pkgs')) # TODO: Windows? |
+ |
+ |
+def butcher_pyqt(conda_dir): |
+ if IS_WIN: |
+ prefix = os.path.join(conda_dir, 'Library') |
+ else: |
+ prefix = conda_dir |
+ |
+ # We won't be using qmake. |
+ kill_glob(os.path.join(prefix, 'lib', '*.prl')) |
+ kill_dir(os.path.join(prefix, 'mkspecs')) |
+ |
+ # We don't care about Qt4 tests. |
+ kill_dir(os.path.join(prefix, 'tests', 'qt4')) |
+ |
+ if not IS_WIN: |
+ # We won't by using PyQt build system. |
+ kill_file( |
+ os.path.join( |
+ conda_dir, 'lib', 'python2.7', 'site-packages', 'sipconfig.py')) |
+ |
+ # This file looks important, let's patch it instead. |
+ patch_file( |
+ os.path.join( |
+ conda_dir, 'lib', 'python2.7', 'site-packages', |
+ 'PyQt4', 'pyqtconfig.py'), |
+ conda_dir) |
+ else: |
+ kill_file(os.path.join(conda_dir, 'qt.conf')) |
+ |
+ |
+### |
+ |
+ |
+def patch_file(path, prefix): |
+ """Replaces references to 'prefix' with '/opt/fake-python-prefix'.""" |
+ with open(path, 'rb') as f: |
+ blob = f.read() |
+ if prefix.endswith('/') or prefix.endswith('\\'): |
+ prefix = prefix[:-1] |
+ if IS_WIN: |
+ fake_prefix = 'C:\\fake-python-prefix' |
+ else: |
+ fake_prefix = '/opt/fake-python-prefix' |
+ modified = blob.replace(prefix, fake_prefix) |
+ if modified != blob: |
+ print 'Patching %s' % os.path.basename(path) |
+ with open(path, 'wb') as f: |
+ f.write(modified) |
+ return True |
+ return False |
+ |
+ |
+def kill_file(path): |
+ if os.path.exists(path) or os.path.lexists(path): |
+ print 'Removing %s' % os.path.basename(path) |
+ os.remove(path) |
+ |
+ |
+def kill_dir(path): |
+ if os.path.exists(path): |
+ print 'Removing %s directory' % os.path.basename(path) |
+ shutil.rmtree(path) |
+ |
+ |
+def kill_glob(path): |
+ for p in glob.glob(path): |
+ kill_file(p) |
+ |
+ |
+if __name__ == '__main__': |
+ sys.exit(main(os.path.abspath(sys.argv[1]))) |