summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTomás Touceda <chiiph@leap.se>2013-11-21 12:31:23 -0300
committerTomás Touceda <chiiph@leap.se>2013-11-22 11:08:48 -0300
commit870cc8d8d51c600d1b9faa0a2d6a5a9dba8b5354 (patch)
treec1ceeb3aefab8a13491401a2dd9f6708af498b61
parent9dcd4133733e09f487919e2b55d751040ac6d811 (diff)
Create bundler for OSXfeature/osx_bundle
-rw-r--r--.gitattributes19
-rw-r--r--.gitignore37
-rw-r--r--README0
-rw-r--r--README.rst102
-rw-r--r--bundler/actions.py443
-rw-r--r--bundler/create_paths.py18
-rw-r--r--bundler/darwin_dyliber.py59
-rw-r--r--bundler/depcollector.py112
-rw-r--r--bundler/main.py116
-rw-r--r--bundler/utils.py3
-rw-r--r--pkg/requirements.pip3
11 files changed, 912 insertions, 0 deletions
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..eb8672e
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,19 @@
+*.swp export-ignore
+*.swo export-ignore
+*.pyc export-ignore
+.* export-ignore
+bin/ export-ignore
+build/ export-ignore
+core export-ignore
+debian/python-leap-client/ export-ignore
+dist/ export-ignore
+docs/_build export-ignore
+docs/covhtml export-ignore
+include/ export-ignore
+lib/ export-ignore
+local/ export-ignore
+man/ export-ignore
+share/ export-ignore
+src/leap.egg-info/ export-ignore
+src/leap_client.egg-info export-ignore
+src/leap/_version.py export-subst
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b7c287d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,37 @@
+*.swp
+*.swo
+*.pyc
+*.log
+*.*~
+.*
+*_rc.py
+ui_*.py
+!.coveragerc
+!.tx
+!.gitattributes
+bin/
+build/
+core
+dist/
+docs/_build
+docs/covhtml
+include/
+lib/
+local/
+share/
+pkg/osx/dist
+pkg/osx/build
+
+src/*.egg-info
+src/pysqlcipher
+src/leap/bitmask/util/reqs.txt
+MANIFEST
+_trial_temp*
+config/*
+CHANGELOG~
+
+data/bitmask.pro
+
+binaries
+bundler.paths
+seeded_config
diff --git a/README b/README
deleted file mode 100644
index e69de29..0000000
--- a/README
+++ /dev/null
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..d7eaaf3
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,102 @@
+Bundler
++++++++
+
+This application is intended to create bundles for the platform in which its being run. This should eventually become the main tool in order to create reproducible builds.
+
+How to use
+++++++++++
+
+NOTE: Most of this will be done automatically in a while, but this is how it's done now.
+
+- Install Xcode and command line tools (OSX only)
+- Create a new virtualenv
+
+::
+ mkvirtualenv bundle
+
+- Install bundler deps
+
+::
+ pip install -r pkg/requirements.pip
+
+- psutils is a dependency for another dependency, it might get installed in a zip form, which we don't want, so we install it by hand for now
+
+::
+ pip install psutil
+
+- We need a slightly different python-gnupg, so clone from a different repo
+
+::
+ git clone https://github.com/chiiph/python-gnupg
+ cd python-gnupg/
+ git checkout develop
+ git pull origin develop
+ python setup.py develop
+
+- Same thing with protobuf.socketrpc
+
+::
+ git clone https://github.com/chiiph/protobuf-socket-rpc
+ cd protobuf-socket-rpc
+ python setup.py easy_install -Z .
+
+- Install Qt 4.8 in whatever way you prefer.
+
+- Build PySide:
+
+::
+ git clone git://gitorious.org/pyside/apiextractor.git
+ git clone git://gitorious.org/pyside/generatorrunner.git
+ git clone git://gitorious.org/pyside/shiboken.git
+ git clone git://gitorious.org/pyside/pyside.git
+ git clone git://gitorious.org/pyside/pyside-tools.git
+ export PYSIDESANDBOXPATH=$HOME/Code/pyside/sandbox
+ export PATH=$PYSIDESANDBOXPATH/bin:$PATH
+ export PYTHONPATH=$PYSIDESANDBOXPATH/lib/python2.6/site-packages:$PYTHONPATH
+ export DYLD_LIBRARY_PATH=$PYSIDESANDBOXPATH/lib:$DYLD_LIBRARY_PATH
+ export PKG_CONFIG_PATH=$PYSIDESANDBOXPATH/lib/pkgconfig:$PKG_CONFIG_PATH
+
+ # In OSX, the paths may vary depending on the Qt installation
+ runcmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.7 -DCMAKE_OSX_SYSROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.8.sdk .. -DQT_QMAKE_EXECUTABLE=/usr/local/bin/qmake -DQT_INCLUDE_DIR=/usr/local/include/ -DQT_INCLUDES=/usr/local/include/ -DALTERNATIVE_QT_INCLUDE_DIR=/usr/local/include/
+ # In Linux
+ runcmake ..
+
+ make install
+
+ # Make them available from the virtualenv
+ ln -s $PYSIDESANDBOXPATH/lib/python2.7/site-packages/PySide $VIRTUAL_ENV/lib/python2.7/site-packages/PySide
+ ln -s $PYSIDESANDBOXPATH/lib/python2.7/site-packages/pysideuic $VIRTUAL_ENV/lib/python2.7/site-packages/pysideuic
+
+- Create a paths file: The problem is that inside a virtualenv we don't have access to the real distutils, so we'll need to look for it on the "original" (i.e. non-virtualenv) paths for python.
+
+::
+ python bundler/create_paths.py <paths file>
+
+- Collect the binaries. We aren't building everything yet, so you'll need to collect the following files:
+
+::
+ # OSX
+ Bitmask <-- this is the bitmask_launcher
+ Python
+ QtCore
+ QtGui
+ cocoasudo
+ gpg
+ libboost_filesystem.dylib
+ libboost_python.dylib
+ libboost_system.dylib
+ libpng15.15.dylib
+ libpyside-python2.7.1.2.dylib
+ libshiboken-python2.7.1.2.dylib
+ openvpn.files
+ openvpn.leap
+ qt_menu.nib
+ tuntap-installer.app
+
+- (Optional) Seed a configuration: You might want to create a bundle with a specific configuration pinned providers.
+
+- Create the bundle:
+
+::
+ python bundler/main.py --workon <path/to/bundle/temp> --paths-file <paths file> --binaries <binaries dir> --seeded-config <seeded config> [--nightly] --do gitclone pythonsetup
+ python bundler/main.py --workon <path/to/bundle/temp> --paths-file <paths file> --binaries <binaries dir> --seeded-config <seeded config> [--nightly] --skip gitclone pythonsetup
diff --git a/bundler/actions.py b/bundler/actions.py
new file mode 100644
index 0000000..6b977d4
--- /dev/null
+++ b/bundler/actions.py
@@ -0,0 +1,443 @@
+import os
+import stat
+import sys
+
+from abc import ABCMeta, abstractmethod
+from contextlib import contextmanager
+from distutils import file_util, dir_util
+
+from sh import git, cd, python, mkdir, make, cp, glob, pip, rm
+from sh import find, SetFile, hdiutil, ln
+
+from utils import IS_MAC
+from depcollector import collect_deps
+from darwin_dyliber import fix_all_dylibs
+
+class Action(object):
+ __metaclass__ = ABCMeta
+
+ def __init__(self, name, basedir, skip=[], do=[]):
+ self._name = name
+ self._basedir = basedir
+ self._skip = skip
+ self._do = do
+
+ @property
+ def name(self):
+ return self._name
+
+ @property
+ def skip(self):
+ return self._name in self._skip
+
+ @property
+ def do(self):
+ if len(self._do) > 0:
+ return self._name in self._do
+ return True
+
+ @abstractmethod
+ def run(self, *args, **kwargs):
+ pass
+
+def skippable(func):
+ def skip_func(self, *args, **kwargs):
+ if self.skip:
+ print "Skipping...", self.name
+ return
+ if not self.do:
+ print "Skipping...", self.name
+ return
+ return func(self, *args, **kwargs)
+ return skip_func
+
+def platform_dir(basedir, *args):
+ dir = os.path.join(basedir,
+ "Bitmask",
+ *args)
+ if IS_MAC:
+ dir = os.path.join(basedir,
+ "Bitmask",
+ "Bitmask.app",
+ "Contents",
+ "MacOS",
+ *args)
+ return dir
+
+@contextmanager
+def push_pop(*directories):
+ cd(os.path.join(*directories))
+ yield
+ cd(os.path.join(*(("..",)*len(directories))))
+
+class GitCloneAll(Action):
+ def __init__(self, basedir, skip, do):
+ Action.__init__(self, "gitclone", basedir, skip, do)
+
+ def _repo_url(self, repo_name):
+ if repo_name == "leap_assets":
+ return "git://leap.se/leap_assets"
+ return "https://github.com/leapcode/{0}".format(repo_name)
+
+ @skippable
+ def run(self, sorted_repos, nightly):
+ print "Cloning repositories..."
+ cd(self._basedir)
+ for repo in sorted_repos:
+ print "Cloning", repo
+ rm("-rf", repo)
+ git.clone(self._repo_url(repo), repo)
+ with push_pop(repo):
+ # Thandy is a special case regarding branches, we'll just use
+ # develop
+ if repo in ["thandy", "leap_assets"]:
+ continue
+ if not nightly:
+ git.checkout("master")
+ git.pull("--ff-only", "origin", "master")
+ git.fetch()
+ git.reset("--hard", "origin/master")
+ latest_tag = git.describe("--abbrev=0").strip()
+ git.checkout("--quiet", latest_tag)
+ else:
+ git.checkout("develop")
+
+ print "Done cloning repos..."
+
+class PythonSetupAll(Action):
+ def __init__(self, basedir, skip, do):
+ Action.__init__(self, "pythonsetup", basedir, skip, do)
+
+ @skippable
+ def run(self, sorted_repos):
+ cd(self._basedir)
+ for repo in sorted_repos:
+ print "Setting up", repo
+ if repo == "soledad":
+ for subrepo in ["common", "client"]:
+ with push_pop(repo, subrepo):
+ pip("install", "-r", "pkg/requirements.pip")
+ python("setup.py", "develop")
+ sys.path.append(os.path.join(self._basedir, repo, subrepo, "src"))
+ elif repo in ["bitmask_launcher", "leap_assets"]:
+ print "Skipping launcher..."
+ continue
+ else:
+ with push_pop(repo):
+ if repo != "thandy":
+ pip("install", "-r", "pkg/requirements.pip")
+ else:
+ # Thandy is a special kid at this point in
+ # terms of packaging. So we install
+ # dependencies ourselves for the time being
+ pip("install", "pycrypto")
+ if repo == "bitmask_client":
+ print "Running make on the client..."
+ make()
+ print "Running build to get correct version..."
+ python("setup.py", "build")
+ python("setup.py", "develop")
+ sys.path.append(os.path.join(self._basedir, repo, "src"))
+
+class CreateDirStructure(Action):
+ def __init__(self, basedir, skip, do):
+ Action.__init__(self, "createdirs", basedir, skip, do)
+
+ @skippable
+ def run(self):
+ print "Creating directory structure..."
+ if IS_MAC:
+ self._darwin_create_dir_structure()
+ self._create_dir_structure(os.path.join(self._basedir, "Bitmask.app", "Contents", "MacOS"))
+ else:
+ self._create_dir_structure(self._basedir)
+ print "Done"
+
+ def _create_dir_structure(self, basedir):
+ mkdirp = mkdir.bake("-p")
+ apps = os.path.join(basedir, "apps")
+ mkdirp(apps)
+ if not IS_MAC:
+ mkdirp(os.path.join(apps, "eip", "files"))
+ mkdirp(os.path.join(apps, "mail"))
+ mkdirp(os.path.join(basedir, "lib"))
+
+ def _darwin_create_dir_structure(self):
+ mkdirp = mkdir.bake("-p")
+ app_path = os.path.join(self._basedir, "Bitmask.app")
+ mkdirp(app_path)
+ mkdirp(os.path.join(app_path, "Contents", "MacOS"))
+ mkdirp(os.path.join(app_path, "Contents", "Resources"))
+ mkdirp(os.path.join(app_path, "Contents", "PlugIns"))
+ mkdirp(os.path.join(app_path, "Contents", "StartupItems"))
+ ln("-s", "/Applications", os.path.join(self._basedir, "Applications"))
+
+class CollectAllDeps(Action):
+ def __init__(self, basedir, skip, do):
+ Action.__init__(self, "collectdeps", basedir, skip, do)
+
+ def _remove_unneeded(self, lib_dir):
+ print "Removing unneeded files..."
+ files = find(lib_dir).strip().splitlines()
+ for f in files:
+ if f.find("PySide") > 0:
+ if os.path.split(f)[1] not in ["QtCore.so",
+ "QtGui.so",
+ "__init__.py",
+ "_utils.py",
+ "PySide",
+ ""]: # empty means the whole pyside dir
+ rm("-rf", f)
+ print "Done"
+
+ @skippable
+ def run(self, path_file):
+ print "Collecting dependencies..."
+ app_py = os.path.join(self._basedir,
+ "bitmask_client",
+ "src",
+ "leap",
+ "bitmask",
+ "app.py")
+ dest_lib_dir = platform_dir(self._basedir, "lib")
+ collect_deps(app_py, dest_lib_dir, path_file)
+
+ self._remove_unneeded(dest_lib_dir)
+ print "Done"
+
+class CopyBinaries(Action):
+ def __init__(self, basedir, skip, do):
+ Action.__init__(self, "copybinaries", basedir, skip, do)
+
+ @skippable
+ def run(self, binaries_path):
+ print "Copying binaries..."
+ dest_lib_dir = platform_dir(self._basedir, "lib")
+ cp(glob(os.path.join(binaries_path, "Qt*")), dest_lib_dir)
+ cp(glob(os.path.join(binaries_path, "*.dylib")), dest_lib_dir)
+ cp(glob(os.path.join(binaries_path, "Python")), dest_lib_dir)
+
+ if IS_MAC:
+ resources_dir = os.path.join(self._basedir,
+ "Bitmask",
+ "Bitmask.app",
+ "Contents",
+ "Resources")
+ cp(glob(os.path.join(binaries_path, "openvpn.leap*")), resources_dir)
+
+ mkdir("-p", os.path.join(resources_dir, "openvpn"))
+ cp("-r", glob(os.path.join(binaries_path, "openvpn.files", "*")), os.path.join(resources_dir, "openvpn"))
+
+ cp(os.path.join(binaries_path, "cocoasudo"), resources_dir)
+
+ cp("-r", os.path.join(binaries_path, "qt_menu.nib"), resources_dir)
+ cp("-r", os.path.join(binaries_path, "tuntap-installer.app"), resources_dir)
+ else:
+ eip_dir = platform_dir(self._basedir, "apps", "eip")
+ cp(glob(os.path.join(binaries_path, "openvpn.leap*")), eip_dir)
+
+ mkdir(os.path.join(resources_dir, "openvpn"))
+ cp("-r", glob(os.path.join(binaries_path, "openvpn.files", "*")), os.path.join(eip_dir, "files"))
+
+ mail_dir = platform_dir(self._basedir, "apps", "mail")
+ cp(os.path.join(binaries_path, "gpg"), mail_dir)
+ cp(os.path.join(binaries_path, "Bitmask"), platform_dir(self._basedir))
+ print "Done"
+
+class PLister(Action):
+ plist = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDisplayName</key>
+ <string>Bitmask</string>
+ <key>CFBundleExecutable</key>
+ <string>MacOS/bitmask-launcher</string>
+ <key>CFBundleIconFile</key>
+ <string>bitmask.icns</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>Bitmask</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1</string>
+ <key>LSBackgroundOnly</key>
+ <false/>
+</dict>
+</plist>""".split("\n")
+
+ qtconf = """[Paths]
+Plugins = PlugIns"""
+
+ def __init__(self, basedir, skip, do):
+ Action.__init__(self, "plister", basedir, skip, do)
+
+ @skippable
+ def run(self):
+ print "Generating Info.plist file..."
+ file_util.write_file(os.path.join(self._basedir,
+ "Bitmask",
+ "Bitmask.app",
+ "Contents",
+ "Info.plist"),
+ self.plist)
+ print "Generating qt.conf file..."
+ file_util.write_file(os.path.join(self._basedir,
+ "Bitmask",
+ "Bitmask.app",
+ "Contents",
+ "Resources",
+ "qt.conf"),
+ self.qtconf)
+ print "Done"
+
+class SeededConfig(Action):
+ def __init__(self, basedir, skip, do):
+ Action.__init__(self, "seededconfig", basedir, skip, do)
+
+ @skippable
+ def run(self, seeded_config):
+ print "Copying seeded config..."
+ dir_util.copy_tree(seeded_config,
+ platform_dir(self._basedir, "config"))
+ print "Done"
+
+class DarwinLauncher(Action):
+ launcher = """#!/bin/bash
+#
+# Launcher for the LEAP Client under OSX
+#
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)"
+export DYLD_LIBRARY_PATH=$DIR/lib
+export PATH=$DIR/../Resources/:$PATH
+# ---------------------------
+# DEBUG Info -- enable this if you
+# are having problems with dynamic libraries loading
+
+cd "${DIR}" && ./Bitmask $1 $2 $3 $4 $5""".split("\n")
+
+ def __init__(self, basedir, skip, do):
+ Action.__init__(self, "darwinlauncher", basedir, skip, do)
+
+ @skippable
+ def run(self):
+ print "Generating launcher script for OSX..."
+ launcher_path = os.path.join(self._basedir,
+ "Bitmask",
+ "Bitmask.app",
+ "Contents",
+ "MacOS",
+ "bitmask-launcher")
+ file_util.write_file(launcher_path, self.launcher)
+ os.chmod(launcher_path, stat.S_IRGRP | stat.S_IROTH | stat.S_IRUSR \
+ | stat.S_IWGRP | stat.S_IWOTH | stat.S_IWUSR \
+ | stat.S_IXGRP | stat.S_IXOTH | stat.S_IXUSR)
+ print "Done"
+
+class CopyAssets(Action):
+ def __init__(self, basedir, skip, do):
+ Action.__init__(self, "copyassets", basedir, skip, do)
+
+ @skippable
+ def run(self):
+ print "Copying assets..."
+ resources_dir = os.path.join(self._basedir,
+ "Bitmask",
+ "Bitmask.app",
+ "Contents",
+ "Resources")
+ cp(os.path.join(self._basedir, "leap_assets", "mac", "bitmask.icns"),
+ resources_dir)
+ cp(os.path.join(self._basedir, "leap_assets", "mac", "leap-client.tiff"),
+ resources_dir)
+ print "Done"
+
+class CopyMisc(Action):
+ def __init__(self, basedir, skip, do):
+ Action.__init__(self, "copymisc", basedir, skip, do)
+
+ @skippable
+ def run(self):
+ print "Copying misc files..."
+ apps_dir = platform_dir(self._basedir, "apps")
+ cp(os.path.join(self._basedir, "bitmask_launcher", "src", "launcher.py"),
+ apps_dir)
+ cp("-r", os.path.join(self._basedir, "thandy", "lib", "thandy"),
+ apps_dir)
+ cp("-r", os.path.join(self._basedir, "bitmask_client", "src", "leap"),
+ apps_dir)
+ lib_dir = platform_dir(self._basedir, "lib")
+ cp(os.path.join(self._basedir,
+ "leap_pycommon",
+ "src", "leap", "common", "cacert.pem"),
+ os.path.join(lib_dir, "leap", "common"))
+ cp(os.path.join(self._basedir,
+ "bitmask_client", "build",
+ "lib", "leap", "bitmask", "_version.py"),
+ os.path.join(apps_dir, "leap", "bitmask"))
+
+ cp(os.path.join(self._basedir,
+ "bitmask_client", "relnotes.txt"),
+ os.path.join(self._basedir, "Bitmask"))
+ print "Done"
+
+class FixDylibs(Action):
+ def __init__(self, basedir, skip, do):
+ Action.__init__(self, "fixdylibs", basedir, skip, do)
+
+ @skippable
+ def run(self):
+ fix_all_dylibs(platform_dir(self._basedir))
+
+class DmgIt(Action):
+ def __init__(self, basedir, skip, do):
+ Action.__init__(self, "dmgit", basedir, skip, do)
+
+ @skippable
+ def run(self):
+ cd(self._basedir)
+ version = "unknown"
+ with push_pop("bitmask_client"):
+ version = git("describe").strip()
+ dmg_dir = os.path.join(self._basedir, "dmg")
+ template_dir = os.path.join(self._basedir, "Bitmask")
+ mkdir("-p", dmg_dir)
+ cp("-R", os.path.join(template_dir, "Applications"), dmg_dir)
+ cp("-R", os.path.join(template_dir, "relnotes.txt"), dmg_dir)
+ cp("-R", os.path.join(template_dir, "Bitmask.app"), dmg_dir)
+ cp(os.path.join(self._basedir,
+ "leap_assets",
+ "mac", "bitmask.icns"),
+ os.path.join(dmg_dir, ".VolumeIcon.icns"))
+ SetFile("-c", "icnC", os.path.join(dmg_dir, ".VolumeIcon.icns"))
+
+ vol_name = "Bitmask"
+ dmg_name = "Bitmask-OSX-{0}.dmg".format(version)
+ raw_dmg_path = os.path.join(self._basedir, "raw-{0}".format(dmg_name))
+ dmg_path = os.path.join(self._basedir, dmg_name)
+
+ hdiutil("create", "-srcfolder", dmg_dir, "-volname", vol_name,
+ "-format", "UDRW", "-ov",
+ raw_dmg_path)
+ rm("-rf", dmg_dir)
+ mkdir(dmg_dir)
+ hdiutil("attach", raw_dmg_path, "-mountpoint", dmg_dir)
+ SetFile("-a", "C", dmg_dir)
+ hdiutil("detach", dmg_dir)
+
+ rm("-rf", dmg_dir)
+ hdiutil("convert", raw_dmg_path, "-format", "UDZO", "-o",
+ dmg_path)
+ rm("-f", raw_dmg_path)
+
+class PycRemover(Action):
+ def __init__(self, basedir, skip, do):
+ Action.__init__(self, "removepyc", basedir, skip, do)
+
+ @skippable
+ def run(self):
+ print "Removing .pyc files..."
+ find(self._basedir, "-name", "\"*.pyc\"", "-delete")
+ print "Done"
diff --git a/bundler/create_paths.py b/bundler/create_paths.py
new file mode 100644
index 0000000..137e6e6
--- /dev/null
+++ b/bundler/create_paths.py
@@ -0,0 +1,18 @@
+import sys
+from distutils import file_util
+
+def main():
+ if len(sys.argv) != 2:
+ print "ERROR: Wrong amount of parameters."
+ print
+ print "./create_paths.py <output file>"
+ print
+ quit()
+ filename = sys.argv[1]
+
+ print "Generating paths file in", filename
+ file_util.write_file(filename, sys.path)
+ print "Done"
+
+if __name__ == "__main__":
+ main()
diff --git a/bundler/darwin_dyliber.py b/bundler/darwin_dyliber.py
new file mode 100644
index 0000000..3a138d6
--- /dev/null
+++ b/bundler/darwin_dyliber.py
@@ -0,0 +1,59 @@
+import os
+
+from sh import otool, install_name_tool, find
+
+def parse_otool_output(output):
+ lines = output.splitlines()[1:]
+ libs = []
+ for line in lines:
+ line = line.strip()
+ if len(line) == 0:
+ continue
+ line = line.split("(")[0].strip()
+ lib = os.path.split(line)[-1]
+ libs.append((lib, line))
+
+ return libs
+
+def locate_lib(executable_path, lib):
+ return find(executable_path, "-name", lib, "-type", "f").strip()
+
+def install_name_tooler(executable_path, lib_path):
+ out = otool("-L", lib_path)
+ _, lib_name = os.path.split(lib_path)
+ libs = parse_otool_output(out)
+ updated_any = False
+ for lib, original in libs:
+ do_id = lib == lib_name
+
+ if original.find("Carbon") > 0:
+ continue
+ location = locate_lib(executable_path, lib)
+ if location is None or len(location) == 0:
+ continue
+ try:
+ if do_id:
+ install_name_tool("-id",
+ os.path.join("@executable_path",
+ os.path.relpath(location,
+ executable_path)),
+ lib_path)
+ else:
+ install_name_tool("-change", original,
+ os.path.join("@executable_path",
+ os.path.relpath(location,
+ executable_path)),
+ lib_path)
+ updated_any = True
+ except Exception as e:
+ print "ERROR Fixing", lib
+ print e
+ if updated_any:
+ print "Fixed", lib_path
+
+def fix_all_dylibs(executable_path):
+ print "Fixing all dylibs, this might take a while..."
+ files = find(executable_path, "-type", "f").strip().splitlines()
+ for f in files:
+ install_name_tooler(executable_path, f)
+ print "Done"
diff --git a/bundler/depcollector.py b/bundler/depcollector.py
new file mode 100644
index 0000000..de765a0
--- /dev/null
+++ b/bundler/depcollector.py
@@ -0,0 +1,112 @@
+import sys
+import os
+import errno
+
+from distutils import dir_util, file_util
+from modulegraph import modulegraph
+
+
+def mkdir_p(path):
+ try:
+ os.makedirs(path)
+ except OSError as exc:
+ if exc.errno == errno.EEXIST and os.path.isdir(path):
+ pass
+ else: raise
+
+def collect_deps(root, dest_lib_dir, path_file):
+ mg = modulegraph.ModuleGraph([sys.path[0]] + [x.strip() for x in open(path_file, 'r').readlines()] + sys.path[1:])#, debug=3)
+
+ mg.import_hook("distutils")
+ mg.import_hook("site")
+ mg.import_hook("jsonschema")
+ mg.import_hook("scrypt")
+ mg.import_hook("_scrypt")
+ mg.import_hook("ConfigParser")
+ mg.import_hook("Crypto")
+ mg.import_hook("encodings.idna")
+ mg.import_hook("leap.soledad.client")
+ mg.import_hook("leap.mail")
+ mg.import_hook("leap.keymanager")
+ mg.import_hook("argparse")
+ mg.import_hook("srp")
+ mg.import_hook("pkgutil")
+ mg.import_hook("pkg_resources")
+ mg.import_hook("_sre")
+ mg.import_hook("zope.proxy")
+ mg.run_script(root)
+
+ packages = [mg.findNode(i) for i in ["leap.common", "leap.keymanager", "leap.mail", "leap.soledad.client", "leap.soledad.common", "jsonschema"]]
+ other = []
+
+ sorted_pkg = [(os.path.basename(mod.identifier), mod) for mod in mg.flatten()]
+ sorted_pkg.sort()
+ for (name, pkg) in sorted_pkg:
+ # skip namespace packages
+ if name == "leap" or name == "leap.soledad" or name == "google" or name == "zope" or name.endswith("leap/bitmask/app.py"):
+ continue
+ # print pkg
+ if isinstance(pkg, modulegraph.MissingModule):
+ # print "ignoring", pkg.identifier
+ continue
+ elif isinstance(pkg, modulegraph.Package):
+ foundpackage = False
+ for i in packages:
+ if pkg.identifier.startswith(i.identifier):
+ # print "skipping", pkg.identifier, "member of", i.identifier
+ # print " found in", i.filename
+ foundpackage = True
+ break
+ if foundpackage:
+ continue
+ if pkg.filename is None:
+ continue
+ if pkg not in packages:
+ packages.append(pkg)
+ else: #if isinstance(pkg, modulegraph.Extension):
+ foundpackage = False
+ for i in packages:
+ if pkg.identifier.startswith(i.identifier):
+ # print "skipping", pkg.identifier, "member of", i.identifier
+ # print " found in", i.filename
+ foundpackage = True
+ break
+ if foundpackage:
+ continue
+ if pkg.filename is None:
+ continue
+ other.append(pkg)
+ # print pkg.identifier
+ #import pdb; pdb.set_trace()
+
+ print "Packages", len(packages)
+ for i in sorted(packages):
+ # if i.identifier == "distutils":
+ # i.filename = distutils.__file__
+ print i.identifier, i.filename
+ parts = i.identifier.split(".")
+ destdir = os.path.join(*([dest_lib_dir]+parts))
+ mkdir_p(destdir)
+ dir_util.copy_tree(os.path.dirname(i.filename), destdir)
+ before = []
+ for part in parts:
+ before.append(part)
+ current = before + ["__init__.py"]
+ try:
+ with open(os.path.join(dest_lib_dir, *current), 'a'):
+ pass
+ except Exception:
+ pass
+
+ print "Other", len(other)
+ for i in sorted(other):
+ # if i.identifier == "site":
+ # i.filename = site.__file__
+ print i.identifier, i.filename
+ file_util.copy_file(i.filename, dest_lib_dir)
+
+ # TODO: remove everything in dest_lib_dir/PySide that is not QtCore, QtGui and __init__
+
+ # ON OSX ONLY:
+ # TODO: remove dest_lib_dir/sre* and PYTHONHOME from the launcher script
+ # remove dest_lib_dir/_socket.so
diff --git a/bundler/main.py b/bundler/main.py
new file mode 100644
index 0000000..2c1207e
--- /dev/null
+++ b/bundler/main.py
@@ -0,0 +1,116 @@
+# TODO:
+# - Check if inside a virtualenv, and warn before doing anything
+# - Build everything that we are currently expecting as a binary
+# - Create complete bundle changelog
+
+import argparse
+import os
+import tempfile
+
+from contextlib import contextmanager
+from distutils import dir_util
+
+from actions import GitCloneAll, PythonSetupAll, CreateDirStructure
+from actions import CollectAllDeps, CopyBinaries, PLister, SeededConfig
+from actions import DarwinLauncher, CopyAssets, CopyMisc, FixDylibs
+from actions import DmgIt, PycRemover
+
+from utils import IS_MAC
+
+sorted_repos = [
+ "leap_assets",
+ "leap_pycommon",
+ "keymanager",
+ "soledad",
+ "leap_mail",
+ "bitmask_client",
+ "bitmask_launcher",
+ "thandy"
+]
+
+@contextmanager
+def new_build_dir(default=None):
+ bd = default
+ if bd is None:
+ bd = tempfile.mkdtemp(prefix="bundler-")
+ yield bd
+ # Only remove if created a temp dir
+ if default is None:
+ dir_util.remove_tree(bd)
+
+def main():
+ parser = argparse.ArgumentParser(description='Bundle creation tool.')
+ parser.add_argument('--workon', help="")
+ parser.add_argument('--skip', nargs="*", default=[], help="")
+ parser.add_argument('--do', nargs="*", default=[], help="")
+ parser.add_argument('--paths-file', help="")
+ parser.add_argument('--binaries', help="")
+ parser.add_argument('--seeded-config', help="")
+ parser.add_argument('--nightly', action="store_true", help="")
+
+ args = parser.parse_args()
+
+ assert args.paths_file is not None, \
+ "We need a paths file, otherwise you'll get " \
+ "problems with distutils and site"
+ paths_file = os.path.realpath(args.paths_file)
+
+ assert args.binaries is not None, \
+ "We don't support building from source, so you'll need to " \
+ "specify a binaries path"
+ binaries_path = os.path.realpath(args.binaries)
+
+ seeded_config = None
+ if args.seeded_config is not None:
+ seeded_config = os.path.realpath(args.seeded_config)
+
+ with new_build_dir(os.path.realpath(args.workon)) as bd:
+ print "Doing it all in", bd
+
+ def init(t, bd=bd):
+ return t(bd, args.skip, args.do)
+
+ gc = init(GitCloneAll)
+ gc.run(sorted_repos, args.nightly)
+
+ ps = init(PythonSetupAll)
+ ps.run(sorted_repos)
+
+ cd = init(CreateDirStructure, os.path.join(bd, "Bitmask"))
+ cd.run()
+
+ dp = init(CollectAllDeps)
+ dp.run(paths_file)
+
+ if binaries_path is not None:
+ cb = init(CopyBinaries)
+ cb.run(binaries_path)
+
+ if seeded_config is not None:
+ sc = init(SeededConfig)
+ sc.run(seeded_config)
+
+ if IS_MAC:
+ pl = init(PLister)
+ pl.run()
+ dl = init(DarwinLauncher)
+ dl.run()
+ ca = init(CopyAssets)
+ ca.run()
+ fd = init(FixDylibs)
+ fd.run()
+
+ cm = init(CopyMisc)
+ cm.run()
+
+ pyc = init(PycRemover)
+ pyc.run()
+
+ if IS_MAC:
+ dm = init(DmgIt)
+ dm.run()
+
+ # do manifest on windows
+
+if __name__ == "__main__":
+ main()
diff --git a/bundler/utils.py b/bundler/utils.py
new file mode 100644
index 0000000..3852ee7
--- /dev/null
+++ b/bundler/utils.py
@@ -0,0 +1,3 @@
+import sys
+
+IS_MAC = sys.platform == "darwin"
diff --git a/pkg/requirements.pip b/pkg/requirements.pip
new file mode 100644
index 0000000..60186b8
--- /dev/null
+++ b/pkg/requirements.pip
@@ -0,0 +1,3 @@
+sh
+modulegraph
+altgraph