Fixes for linux
[bitmask_bundler.git] / bundler / actions.py
1 import os
2 import stat
3 import sys
4
5 from abc import ABCMeta, abstractmethod
6 from contextlib import contextmanager
7 from distutils import file_util, dir_util
8
9 from sh import git, cd, python, mkdir, make, cp, glob, pip, rm
10 from sh import find, ln, tar, mv
11
12 from utils import IS_MAC
13
14 if IS_MAC:
15     from sh import SetFile, hdiutil
16     from darwin_dyliber import fix_all_dylibs
17
18 from depcollector import collect_deps
19
20 class Action(object):
21     __metaclass__ = ABCMeta
22
23     def __init__(self, name, basedir, skip=[], do=[]):
24         self._name = name
25         self._basedir = basedir
26         self._skip = skip
27         self._do = do
28
29     @property
30     def name(self):
31         return self._name
32
33     @property
34     def skip(self):
35         return self._name in self._skip
36
37     @property
38     def do(self):
39         if len(self._do) > 0:
40             return self._name in self._do
41         return True
42
43     @abstractmethod
44     def run(self, *args, **kwargs):
45         pass
46
47 def skippable(func):
48     def skip_func(self, *args, **kwargs):
49         if self.skip:
50             print "Skipping...", self.name
51             return
52         if not self.do:
53             print "Skipping...", self.name
54             return
55         return func(self, *args, **kwargs)
56     return skip_func
57
58 def platform_dir(basedir, *args):
59     dir = os.path.join(basedir,
60                        "Bitmask",
61                        *args)
62     if IS_MAC:
63         dir = os.path.join(basedir,
64                            "Bitmask",
65                            "Bitmask.app",
66                            "Contents",
67                            "MacOS",
68                            *args)
69     return dir
70
71 @contextmanager
72 def push_pop(*directories):
73     cd(os.path.join(*directories))
74     yield
75     cd(os.path.join(*(("..",)*len(directories))))
76
77 class GitCloneAll(Action):
78     def __init__(self, basedir, skip, do):
79         Action.__init__(self, "gitclone", basedir, skip, do)
80
81     def _repo_url(self, repo_name):
82         if repo_name == "leap_assets":
83             return "git://leap.se/leap_assets"
84         return "https://github.com/leapcode/{0}".format(repo_name)
85
86     @skippable
87     def run(self, sorted_repos, nightly):
88         print "Cloning repositories..."
89         cd(self._basedir)
90         for repo in sorted_repos:
91             print "Cloning", repo
92             rm("-rf", repo)
93             git.clone(self._repo_url(repo), repo)
94             with push_pop(repo):
95                 # Thandy is a special case regarding branches, we'll just use
96                 # develop
97                 if repo in ["thandy", "leap_assets"]:
98                     continue
99                 if not nightly:
100                     git.checkout("master")
101                     git.pull("--ff-only", "origin", "master")
102                     git.fetch()
103                     git.reset("--hard", "origin/master")
104                     latest_tag = git.describe("--abbrev=0").strip()
105                     git.checkout("--quiet", latest_tag)
106                 else:
107                     git.checkout("develop")
108
109         print "Done cloning repos..."
110
111 class PythonSetupAll(Action):
112     def __init__(self, basedir, skip, do):
113         Action.__init__(self, "pythonsetup", basedir, skip, do)
114
115     @skippable
116     def run(self, sorted_repos):
117         cd(self._basedir)
118         for repo in sorted_repos:
119             print "Setting up", repo
120             if repo == "soledad":
121                 for subrepo in ["common", "client"]:
122                     with push_pop(repo, subrepo):
123                         pip("install", "-r", "pkg/requirements.pip")
124                         python("setup.py", "develop")
125                         sys.path.append(os.path.join(self._basedir, repo, subrepo, "src"))
126             elif repo in ["bitmask_launcher", "leap_assets"]:
127                 print "Skipping launcher..."
128                 continue
129             else:
130                 with push_pop(repo):
131                     if repo != "thandy":
132                         pip("install", "-r", "pkg/requirements.pip")
133                     else:
134                         # Thandy is a special kid at this point in
135                         # terms of packaging. So we install
136                         # dependencies ourselves for the time being
137                         pip("install", "pycrypto")
138                     if repo == "bitmask_client":
139                         print "Running make on the client..."
140                         make()
141                         print "Running build to get correct version..."
142                         python("setup.py", "build")
143                     python("setup.py", "develop")
144                     sys.path.append(os.path.join(self._basedir, repo, "src"))
145
146 class CreateDirStructure(Action):
147     def __init__(self, basedir, skip, do):
148         Action.__init__(self, "createdirs", basedir, skip, do)
149
150     @skippable
151     def run(self):
152         print "Creating directory structure..."
153         if IS_MAC:
154             self._darwin_create_dir_structure()
155             self._create_dir_structure(os.path.join(self._basedir, "Bitmask.app", "Contents", "MacOS"))
156         else:
157             self._create_dir_structure(self._basedir)
158         print "Done"
159
160     def _create_dir_structure(self, basedir):
161         mkdirp = mkdir.bake("-p")
162         apps = os.path.join(basedir, "apps")
163         mkdirp(apps)
164         if not IS_MAC:
165             mkdirp(os.path.join(apps, "eip", "files"))
166         mkdirp(os.path.join(apps, "mail"))
167         mkdirp(os.path.join(basedir, "lib"))
168
169     def _darwin_create_dir_structure(self):
170         mkdirp = mkdir.bake("-p")
171         app_path = os.path.join(self._basedir, "Bitmask.app")
172         mkdirp(app_path)
173         mkdirp(os.path.join(app_path, "Contents", "MacOS"))
174         mkdirp(os.path.join(app_path, "Contents", "Resources"))
175         mkdirp(os.path.join(app_path, "Contents", "PlugIns"))
176         mkdirp(os.path.join(app_path, "Contents", "StartupItems"))
177         ln("-s", "/Applications", os.path.join(self._basedir, "Applications"))
178
179 class CollectAllDeps(Action):
180     def __init__(self, basedir, skip, do):
181         Action.__init__(self, "collectdeps", basedir, skip, do)
182
183     def _remove_unneeded(self, lib_dir):
184         print "Removing unneeded files..."
185         files = find(lib_dir).strip().splitlines()
186         for f in files:
187             if f.find("PySide") > 0:
188                 if os.path.split(f)[1] not in ["QtCore.so",
189                                                "QtGui.so",
190                                                "__init__.py",
191                                                "_utils.py",
192                                                "PySide",
193                                                ""]:  # empty means the whole pyside dir
194                     rm("-rf", f)
195         print "Done"
196
197     @skippable
198     def run(self, path_file):
199         print "Collecting dependencies..."
200         app_py = os.path.join(self._basedir,
201                               "bitmask_client",
202                               "src",
203                               "leap",
204                               "bitmask",
205                               "app.py")
206         dest_lib_dir = platform_dir(self._basedir, "lib")
207         collect_deps(app_py, dest_lib_dir, path_file)
208
209         self._remove_unneeded(dest_lib_dir)
210         print "Done"
211
212 class CopyBinaries(Action):
213     def __init__(self, basedir, skip, do):
214         Action.__init__(self, "copybinaries", basedir, skip, do)
215
216     @skippable
217     def run(self, binaries_path):
218         print "Copying binaries..."
219         dest_lib_dir = platform_dir(self._basedir, "lib")
220
221         if IS_MAC:
222             cp(glob(os.path.join(binaries_path, "Qt*")), dest_lib_dir)
223             cp(glob(os.path.join(binaries_path, "*.dylib")), dest_lib_dir)
224             cp(glob(os.path.join(binaries_path, "Python")), dest_lib_dir)
225             resources_dir = os.path.join(self._basedir,
226                                          "Bitmask",
227                                          "Bitmask.app",
228                                          "Contents",
229                                          "Resources")
230             cp(glob(os.path.join(binaries_path, "openvpn.leap*")), resources_dir)
231
232             mkdir("-p", os.path.join(resources_dir, "openvpn"))
233             cp("-r", glob(os.path.join(binaries_path, "openvpn.files", "*")), os.path.join(resources_dir, "openvpn"))
234
235             cp(os.path.join(binaries_path, "cocoasudo"), resources_dir)
236
237             cp("-r", os.path.join(binaries_path, "qt_menu.nib"), resources_dir)
238             cp("-r", os.path.join(binaries_path, "tuntap-installer.app"), resources_dir)
239             cp(os.path.join(binaries_path, "Bitmask"), platform_dir(self._basedir))
240         else:
241             cp(glob(os.path.join(binaries_path, "*.so*")), dest_lib_dir)
242
243             eip_dir = platform_dir(self._basedir, "apps", "eip")
244             cp(os.path.join(binaries_path, "openvpn"), eip_dir)
245
246             cp("-r", glob(os.path.join(binaries_path, "openvpn.files", "*")), os.path.join(eip_dir, "files"))
247             cp(os.path.join(binaries_path, "bitmask"), platform_dir(self._basedir))
248
249         mail_dir = platform_dir(self._basedir, "apps", "mail")
250         cp(os.path.join(binaries_path, "gpg"), mail_dir)
251         print "Done"
252
253 class PLister(Action):
254     plist = """<?xml version="1.0" encoding="UTF-8"?>
255 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
256 <plist version="1.0">
257 <dict>
258         <key>CFBundleDisplayName</key>
259         <string>Bitmask</string>
260         <key>CFBundleExecutable</key>
261         <string>MacOS/bitmask-launcher</string>
262         <key>CFBundleIconFile</key>
263         <string>bitmask.icns</string>
264         <key>CFBundleInfoDictionaryVersion</key>
265         <string>6.0</string>
266         <key>CFBundleName</key>
267   <string>Bitmask</string>
268         <key>CFBundlePackageType</key>
269         <string>APPL</string>
270         <key>CFBundleShortVersionString</key>
271         <string>1</string>
272         <key>LSBackgroundOnly</key>
273         <false/>
274 </dict>
275 </plist>""".split("\n")
276
277     qtconf = """[Paths]
278 Plugins = PlugIns"""
279
280     def __init__(self, basedir, skip, do):
281         Action.__init__(self, "plister", basedir, skip, do)
282
283     @skippable
284     def run(self):
285         print "Generating Info.plist file..."
286         file_util.write_file(os.path.join(self._basedir,
287                                           "Bitmask",
288                                           "Bitmask.app",
289                                           "Contents",
290                                           "Info.plist"),
291                              self.plist)
292         print "Generating qt.conf file..."
293         file_util.write_file(os.path.join(self._basedir,
294                                           "Bitmask",
295                                           "Bitmask.app",
296                                           "Contents",
297                                           "Resources",
298                                           "qt.conf"),
299                              self.qtconf)
300         print "Done"
301
302 class SeededConfig(Action):
303     def __init__(self, basedir, skip, do):
304         Action.__init__(self, "seededconfig", basedir, skip, do)
305
306     @skippable
307     def run(self, seeded_config):
308         print "Copying seeded config..."
309         dir_util.copy_tree(seeded_config,
310                            platform_dir(self._basedir, "config"))
311         print "Done"
312
313 class DarwinLauncher(Action):
314     launcher = """#!/bin/bash
315 #
316 # Launcher for the LEAP Client under OSX
317 #
318 DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)"
319 export DYLD_LIBRARY_PATH=$DIR/lib
320 export PATH=$DIR/../Resources/:$PATH
321 # ---------------------------
322 # DEBUG Info -- enable this if you
323 # are having problems with dynamic libraries loading
324
325 cd "${DIR}" && ./Bitmask $1 $2 $3 $4 $5""".split("\n")
326
327     def __init__(self, basedir, skip, do):
328         Action.__init__(self, "darwinlauncher", basedir, skip, do)
329
330     @skippable
331     def run(self):
332         print "Generating launcher script for OSX..."
333         launcher_path = os.path.join(self._basedir,
334                                      "Bitmask",
335                                      "Bitmask.app",
336                                      "Contents",
337                                      "MacOS",
338                                      "bitmask-launcher")
339         file_util.write_file(launcher_path, self.launcher)
340         os.chmod(launcher_path, stat.S_IRGRP | stat.S_IROTH | stat.S_IRUSR \
341                  | stat.S_IWGRP | stat.S_IWOTH | stat.S_IWUSR \
342                  | stat.S_IXGRP | stat.S_IXOTH | stat.S_IXUSR)
343         print "Done"
344
345 class CopyAssets(Action):
346     def __init__(self, basedir, skip, do):
347         Action.__init__(self, "copyassets", basedir, skip, do)
348
349     @skippable
350     def run(self):
351         print "Copying assets..."
352         resources_dir = os.path.join(self._basedir,
353                                      "Bitmask",
354                                      "Bitmask.app",
355                                      "Contents",
356                                      "Resources")
357         cp(os.path.join(self._basedir, "leap_assets", "mac", "bitmask.icns"),
358            resources_dir)
359         cp(os.path.join(self._basedir, "leap_assets", "mac", "leap-client.tiff"),
360            resources_dir)
361         print "Done"
362
363 class CopyMisc(Action):
364     def __init__(self, basedir, skip, do):
365         Action.__init__(self, "copymisc", basedir, skip, do)
366
367     @skippable
368     def run(self):
369         print "Copying misc files..."
370         apps_dir = platform_dir(self._basedir, "apps")
371         cp(os.path.join(self._basedir, "bitmask_launcher", "src", "launcher.py"),
372            apps_dir)
373         cp("-r", os.path.join(self._basedir, "thandy", "lib", "thandy"),
374            apps_dir)
375         cp("-r", os.path.join(self._basedir, "bitmask_client", "src", "leap"),
376            apps_dir)
377         lib_dir = platform_dir(self._basedir, "lib")
378         cp(os.path.join(self._basedir,
379                         "leap_pycommon",
380                         "src", "leap", "common", "cacert.pem"),
381            os.path.join(lib_dir, "leap", "common"))
382         cp(glob(os.path.join(self._basedir,
383                              "bitmask_client", "build",
384                              "lib*", "leap", "bitmask", "_version.py")),
385            os.path.join(apps_dir, "leap", "bitmask"))
386
387         cp(os.path.join(self._basedir,
388                         "bitmask_client", "relnotes.txt"),
389            os.path.join(self._basedir, "Bitmask"))
390         print "Done"
391
392 class FixDylibs(Action):
393     def __init__(self, basedir, skip, do):
394         Action.__init__(self, "fixdylibs", basedir, skip, do)
395
396     @skippable
397     def run(self):
398         fix_all_dylibs(platform_dir(self._basedir))
399
400 class DmgIt(Action):
401     def __init__(self, basedir, skip, do):
402         Action.__init__(self, "dmgit", basedir, skip, do)
403
404     @skippable
405     def run(self):
406         cd(self._basedir)
407         version = "unknown"
408         with push_pop("bitmask_client"):
409             version = git("describe").strip()
410         dmg_dir = os.path.join(self._basedir, "dmg")
411         template_dir = os.path.join(self._basedir, "Bitmask")
412         mkdir("-p", dmg_dir)
413         cp("-R", os.path.join(template_dir, "Applications"), dmg_dir)
414         cp("-R", os.path.join(template_dir, "relnotes.txt"), dmg_dir)
415         cp("-R", os.path.join(template_dir, "Bitmask.app"), dmg_dir)
416         cp(os.path.join(self._basedir,
417                         "leap_assets",
418                         "mac", "bitmask.icns"),
419            os.path.join(dmg_dir, ".VolumeIcon.icns"))
420         SetFile("-c", "icnC", os.path.join(dmg_dir, ".VolumeIcon.icns"))
421
422         vol_name = "Bitmask"
423         dmg_name = "Bitmask-OSX-{0}.dmg".format(version)
424         raw_dmg_path = os.path.join(self._basedir, "raw-{0}".format(dmg_name))
425         dmg_path = os.path.join(self._basedir, dmg_name)
426
427         hdiutil("create", "-srcfolder", dmg_dir, "-volname", vol_name,
428                 "-format", "UDRW", "-ov",
429                 raw_dmg_path)
430         rm("-rf", dmg_dir)
431         mkdir(dmg_dir)
432         hdiutil("attach", raw_dmg_path, "-mountpoint", dmg_dir)
433         SetFile("-a", "C", dmg_dir)
434         hdiutil("detach", dmg_dir)
435
436         rm("-rf", dmg_dir)
437         hdiutil("convert", raw_dmg_path, "-format", "UDZO", "-o",
438                 dmg_path)
439         rm("-f", raw_dmg_path)
440
441 class TarballIt(Action):
442     def __init__(self, basedir, skip, do):
443         Action.__init__(self, "tarballit", basedir, skip, do)
444
445     @skippable
446     def run(self):
447         cd(self._basedir)
448         version = "unknown"
449         with push_pop("bitmask_client"):
450             version = git("describe").strip()
451         import platform
452         bits = platform.architecture()[0][:2]
453         bundle_name = "Bitmask-linux%s-%s" % (bits, version)
454         mv("Bitmask", bundle_name)
455         tar("cjf", bundle_name+".tar.bz2", bundle_name)
456
457 class PycRemover(Action):
458     def __init__(self, basedir, skip, do):
459         Action.__init__(self, "removepyc", basedir, skip, do)
460
461     @skippable
462     def run(self):
463         print "Removing .pyc files..."
464         find(self._basedir, "-name", "\"*.pyc\"", "-delete")
465         print "Done"