Use json file to define versions to be bundled.
[bitmask_bundler.git] / bundler / actions.py
1 import datetime
2 import hashlib
3 import json
4 import os
5 import stat
6 import subprocess
7 import sys
8 import textwrap
9 import urllib
10 import zipfile
11
12 from abc import ABCMeta, abstractmethod
13 from contextlib import contextmanager
14 from distutils import file_util, dir_util
15
16 from utils import IS_MAC, IS_WIN
17
18 if IS_MAC:
19     from sh import SetFile, hdiutil, codesign
20     from darwin_dyliber import fix_all_dylibs
21 if IS_WIN:
22     import pbs
23     from pbs import cd, glob
24     git = pbs.Command("C:\\Program Files\\Git\\bin\\git.exe")
25     python = pbs.Command("C:\\Python27\\python.exe")
26     pip = pbs.Command("C:\\Python27\\scripts\\pip.exe")
27     mkdir = pbs.Command("C:\\Program Files\\Git\\bin\\mkdir.exe")
28     make = pbs.Command("C:\\MinGW\\bin\\mingw32-make.exe")
29     cp = pbs.Command("C:\\Program Files\\Git\\bin\\cp.exe")
30     rm = pbs.Command("C:\\Program Files\\Git\\bin\\rm.exe")
31     find = pbs.Command("C:\\Program Files\\Git\\bin\\find.exe")
32     ln = pbs.Command("C:\\Program Files\\Git\\bin\\ln.exe")
33     tar = pbs.Command("C:\\Program Files\\Git\\bin\\tar.exe")
34     mv = pbs.Command("C:\\Program Files\\Git\\bin\\mv.exe")
35 else:
36     from sh import git, cd, python, mkdir, make, cp, glob, pip, rm
37     from sh import find, ln, tar, mv, strip
38
39 from depcollector import collect_deps
40
41
42 class Action(object):
43     __metaclass__ = ABCMeta
44
45     def __init__(self, name, basedir, skip=[], do=[]):
46         self._name = name
47         self._basedir = basedir
48         self._skip = skip
49         self._do = do
50
51     @property
52     def name(self):
53         return self._name
54
55     @property
56     def skip(self):
57         return self._name in self._skip
58
59     @property
60     def do(self):
61         if len(self._do) > 0:
62             return self._name in self._do
63         return True
64
65     @abstractmethod
66     def run(self, *args, **kwargs):
67         pass
68
69     def log(self, msg):
70         print "{0}: {1}".format(self._name.upper(), msg)
71
72
73 def skippable(func):
74     def skip_func(self, *args, **kwargs):
75         if self.skip:
76             print "SKIPPING: {0}...".format(self.name)
77             return
78         if not self.do:
79             print "SKIPPING: {0}...".format(self.name)
80             return
81         return func(self, *args, **kwargs)
82     return skip_func
83
84
85 def platform_dir(basedir, *args):
86     dir_ = os.path.join(basedir, "Bitmask", *args)
87
88     if IS_MAC:
89         dir_ = os.path.join(basedir, "Bitmask", "Bitmask.app",
90                             "Contents", "MacOS", *args)
91     return dir_
92
93
94 @contextmanager
95 def push_pop(*directories):
96     cd(os.path.join(*directories))
97     yield
98     cd(os.path.join(*(("..",)*len(directories))))
99
100
101 def get_version(repos, version):
102     if version is not None and version != 'nightly':
103         return version
104
105     m = hashlib.sha256()
106     for repo in repos:
107         version = "unknown"
108         with push_pop(repo):
109             try:
110                 version = git("describe").strip()
111             except:
112                 pass
113         m.update(version)
114
115     return "{0}-{1}".format(str(datetime.date.today()),
116                             m.hexdigest()[:8])
117
118
119 class GitCloneAll(Action):
120     def __init__(self, basedir, skip, do):
121         Action.__init__(self, "gitclone", basedir, skip, do)
122
123     def _repo_url(self, repo_name):
124         if repo_name == "leap_assets":
125             return "git://leap.se/leap_assets"
126         return "git://github.com/leapcode/{0}".format(repo_name)
127
128     @skippable
129     def run(self, sorted_repos):
130         self.log("cloning repositories...")
131         cd(self._basedir)
132
133         for repo in sorted_repos:
134             self.log("cloning {0}".format(repo))
135             rm("-rf", repo)
136             git.clone(self._repo_url(repo), repo)
137
138         self.log("done cloning repos.")
139
140
141 class GitCheckout(Action):
142     def __init__(self, basedir, skip, do):
143         Action.__init__(self, "gitcheckout", basedir, skip, do)
144
145     def _repo_url(self, repo_name):
146         if repo_name == "leap_assets":
147             return "git://leap.se/leap_assets"
148         return "git://github.com/leapcode/{0}".format(repo_name)
149
150     @skippable
151     def run(self, sorted_repos, versions_file):
152         self.log("`git checkout` repositories...")
153
154         versions = None
155         with open(versions_file, 'r') as f:
156             versions = json.load(f)
157
158         cd(self._basedir)
159
160         for repo in sorted_repos:
161             if repo not in versions:
162                 self.log("skipping {0}, no version specified.".format(repo))
163                 continue
164
165             where = versions[repo]  # where to checkout
166             self.log("Checkout {0} -> {1}".format(repo, where))
167
168             with push_pop(repo):
169                 git.fetch()
170                 git.checkout("--quiet", where)
171
172                 # just in case that we didn't just cloned but updated:
173                 git.reset("--hard", where)
174
175         self.log("done checking out repos.")
176
177
178 class PythonSetupAll(Action):
179     def __init__(self, basedir, skip, do):
180         Action.__init__(self, "pythonsetup", basedir, skip, do)
181
182     def _build_client(self, repo, binaries_path):
183         self.log("running make on the client...")
184         make()
185         self.log("running build to get correct version...")
186         python("setup.py", "build")
187         self.log("updating hashes")
188         os.environ["OPENVPN_BIN"] = os.path.join(
189             binaries_path, "openvpn.files", "leap-openvpn")
190         os.environ["BITMASK_ROOT"] = os.path.join(
191             self._basedir, repo, "pkg", "linux", "bitmask-root")
192         python("setup.py", "hash_binaries")
193
194     @skippable
195     def run(self, sorted_repos, binaries_path):
196         cd(self._basedir)
197         for repo in sorted_repos:
198
199             if repo in ["bitmask_launcher", "leap_assets"]:
200                 self.log("skipping repo: {0}...".format(repo))
201                 continue
202
203             self.log("setting up {0}".format(repo))
204
205             if repo == "soledad":
206                 for subrepo in ["common", "client"]:
207                     with push_pop(repo, subrepo):
208                         pip("install", "-r", "pkg/requirements.pip")
209                         python("setup.py", "develop")
210                         sys.path.append(os.path.join(self._basedir,
211                                                      repo, subrepo, "src"))
212             else:
213                 with push_pop(repo):
214                     pip("install", "-r", "pkg/requirements.pip")
215
216                     if repo == "bitmask_client":
217                         self._build_client(repo, binaries_path)
218
219                     python("setup.py", "develop")
220                     sys.path.append(os.path.join(self._basedir, repo, "src"))
221
222
223 def _convert_path_for_win(path):
224     npath = path
225     if IS_WIN:
226         npath = path.replace("\\", "/")
227     return npath
228
229
230 class CreateDirStructure(Action):
231     def __init__(self, basedir, skip, do):
232         Action.__init__(self, "createdirs", basedir, skip, do)
233
234     @skippable
235     def run(self):
236         self.log("creating directory structure...")
237         if IS_MAC:
238             self._darwin_create_dir_structure()
239             self._create_dir_structure(os.path.join(self._basedir,
240                                                     "Bitmask.app",
241                                                     "Contents", "MacOS"))
242         else:
243             self._create_dir_structure(self._basedir)
244         self.log("done.")
245
246     def _create_dir_structure(self, basedir):
247         mkdirp = mkdir.bake("-p")
248         apps = os.path.join(basedir, "apps")
249         mkdirp(_convert_path_for_win(apps))
250         if IS_WIN:
251             mkdirp(_convert_path_for_win(os.path.join(apps, "eip")))
252         else:
253             mkdirp(_convert_path_for_win(os.path.join(apps, "eip", "files")))
254         mkdirp(_convert_path_for_win(os.path.join(apps, "mail")))
255         mkdirp(_convert_path_for_win(os.path.join(basedir, "lib")))
256
257     def _darwin_create_dir_structure(self):
258         mkdirp = mkdir.bake("-p")
259         app_path = os.path.join(self._basedir, "Bitmask.app")
260         mkdirp(app_path)
261         mkdirp(os.path.join(app_path, "Contents", "MacOS"))
262         mkdirp(os.path.join(app_path, "Contents", "Resources"))
263         mkdirp(os.path.join(app_path, "Contents", "PlugIns"))
264         mkdirp(os.path.join(app_path, "Contents", "StartupItems"))
265         ln("-s", "/Applications", os.path.join(self._basedir, "Applications"))
266
267
268 class CollectAllDeps(Action):
269     def __init__(self, basedir, skip, do):
270         Action.__init__(self, "collectdeps", basedir, skip, do)
271
272     def _remove_unneeded(self, lib_dir):
273         self.log("removing unneeded files...")
274         files = find(lib_dir).strip().splitlines()
275         keep = ["QtCore.so",
276                 "QtGui.so",
277                 "__init__.py",
278                 "_utils.py",
279                 "PySide",
280                 ""]  # empty means the whole pyside dir
281         if IS_WIN:
282             keep = ["QtCore4.dll",
283                     "QtGui4.dll",
284                     "__init__.py",
285                     "_utils.py",
286                     "PySide",
287                     "QtGui.pyd",
288                     "QtCore.pyd",
289                     ""]  # empty means the whole pyside dir
290         for f in files:
291             if f.find("PySide") > 0:
292                 if os.path.split(f)[1] not in keep:
293                     rm("-rf", f)
294                     pass
295         self.log("done.")
296
297     @skippable
298     def run(self, path_file):
299         self.log("collecting dependencies...")
300         app_py = os.path.join(self._basedir,
301                               "bitmask_client",
302                               "src",
303                               "leap",
304                               "bitmask",
305                               "app.py")
306         dest_lib_dir = platform_dir(self._basedir, "lib")
307         collect_deps(app_py, dest_lib_dir, path_file)
308
309         self._remove_unneeded(dest_lib_dir)
310         self.log("done.")
311
312
313 class CopyBinaries(Action):
314     def __init__(self, basedir, skip, do):
315         Action.__init__(self, "copybinaries", basedir, skip, do)
316
317     @skippable
318     def run(self, binaries_path):
319         self.log("copying binaries...")
320         dest_lib_dir = platform_dir(self._basedir, "lib")
321
322         if IS_MAC:
323             cp(glob(os.path.join(binaries_path, "Qt*")), dest_lib_dir)
324             cp(glob(os.path.join(binaries_path, "*.dylib")), dest_lib_dir)
325             cp(glob(os.path.join(binaries_path, "Python")), dest_lib_dir)
326             resources_dir = os.path.join(self._basedir,
327                                          "Bitmask",
328                                          "Bitmask.app",
329                                          "Contents",
330                                          "Resources")
331             cp(glob(os.path.join(binaries_path, "openvpn.leap*")),
332                resources_dir)
333
334             mkdir("-p", os.path.join(resources_dir, "openvpn"))
335             cp("-r", glob(os.path.join(binaries_path, "openvpn.files", "*")),
336                os.path.join(resources_dir, "openvpn"))
337
338             cp(os.path.join(binaries_path, "cocoasudo"), resources_dir)
339
340             cp("-r", os.path.join(binaries_path, "qt_menu.nib"), resources_dir)
341             cp("-r", os.path.join(binaries_path, "tuntap-installer.app"),
342                resources_dir)
343             cp(os.path.join(binaries_path, "Bitmask"),
344                platform_dir(self._basedir))
345         elif IS_WIN:
346             root = _convert_path_for_win(
347                 os.path.join(self._basedir, "Bitmask"))
348             for i in glob(os.path.join(binaries_path, "*.dll")):
349                 cp(_convert_path_for_win(i),
350                    root)
351             import win32com
352             win32comext_path = os.path.split(win32com.__file__)[0] + "ext"
353             shell_path = os.path.join(win32comext_path, "shell")
354             cp("-r",
355                _convert_path_for_win(shell_path),
356                _convert_path_for_win(os.path.join(dest_lib_dir, "win32com")))
357             cp(_convert_path_for_win(
358                 os.path.join(binaries_path, "bitmask.exe")),
359                root)
360             cp(_convert_path_for_win(
361                 os.path.join(binaries_path, "Microsoft.VC90.CRT.manifest")),
362                root)
363             cp(_convert_path_for_win(
364                 os.path.join(binaries_path, "openvpn_leap.exe")),
365                _convert_path_for_win(
366                    os.path.join(root, "apps", "eip")))
367             cp(_convert_path_for_win(
368                 os.path.join(binaries_path, "openvpn_leap.exe.manifest")),
369                _convert_path_for_win(
370                    os.path.join(root, "apps", "eip")))
371             cp("-r",
372                _convert_path_for_win(
373                    os.path.join(binaries_path, "tap_driver")),
374                _convert_path_for_win(
375                    os.path.join(root, "apps", "eip")))
376         else:
377             cp(glob(os.path.join(binaries_path, "*.so*")), dest_lib_dir)
378
379             eip_dir = platform_dir(self._basedir, "apps", "eip")
380             # cp(os.path.join(binaries_path, "openvpn"), eip_dir)
381
382             cp("-r", glob(os.path.join(binaries_path, "openvpn.files", "*")),
383                os.path.join(eip_dir, "files"))
384             cp(os.path.join(binaries_path, "bitmask"),
385                platform_dir(self._basedir))
386
387         mail_dir = platform_dir(self._basedir, "apps", "mail")
388         cp(_convert_path_for_win(os.path.join(binaries_path, "gpg")),
389            _convert_path_for_win(mail_dir))
390         self.log("done.")
391
392
393 class PLister(Action):
394     plist = textwrap.dedent("""\
395         <?xml version="1.0" encoding="UTF-8"?>
396         <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
397         <plist version="1.0">
398         <dict>
399             <key>CFBundleDisplayName</key>
400             <string>Bitmask</string>
401             <key>CFBundleExecutable</key>
402             <string>MacOS/bitmask-launcher</string>
403             <key>CFBundleIconFile</key>
404             <string>bitmask.icns</string>
405             <key>CFBundleInfoDictionaryVersion</key>
406             <string>6.0</string>
407             <key>CFBundleName</key>
408             <string>Bitmask</string>
409             <key>CFBundlePackageType</key>
410             <string>APPL</string>
411             <key>CFBundleShortVersionString</key>
412             <string>1</string>
413             <key>LSBackgroundOnly</key>
414             <false/>
415             <key>CFBundleIdentifier</key>
416             <string>se.leap.bitmask</string>
417         </dict>
418         </plist>""").split("\n")
419
420     qtconf = textwrap.dedent(
421         """\
422         [Paths]
423         Plugins = PlugIns""")
424
425     def __init__(self, basedir, skip, do):
426         Action.__init__(self, "plister", basedir, skip, do)
427
428     @skippable
429     def run(self):
430         self.log("generating Info.plist file...")
431         file_util.write_file(os.path.join(self._basedir,
432                                           "Bitmask",
433                                           "Bitmask.app",
434                                           "Contents",
435                                           "Info.plist"),
436                              self.plist)
437         self.log("generating qt.conf file...")
438         file_util.write_file(os.path.join(self._basedir,
439                                           "Bitmask",
440                                           "Bitmask.app",
441                                           "Contents",
442                                           "Resources",
443                                           "qt.conf"),
444                              self.qtconf)
445         self.log("done.")
446
447
448 class SeededConfig(Action):
449     def __init__(self, basedir, skip, do):
450         Action.__init__(self, "seededconfig", basedir, skip, do)
451
452     @skippable
453     def run(self, seeded_config):
454         self.log("copying seeded config...")
455         dir_util.copy_tree(seeded_config,
456                            platform_dir(self._basedir, "config"))
457         self.log("done.")
458
459
460 class DarwinLauncher(Action):
461     launcher = textwrap.dedent(
462         """\
463         #!/bin/bash
464         #
465         # Launcher for the LEAP Client under OSX
466         #
467         DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)"
468         export DYLD_LIBRARY_PATH=$DIR/lib
469         export PATH=$DIR/../Resources/:$PATH
470         # ---------------------------
471         # DEBUG Info -- enable this if you
472         # are having problems with dynamic libraries loading
473
474         cd "${DIR}" && ./Bitmask $1 $2 $3 $4 $5""").split("\n")
475
476     def __init__(self, basedir, skip, do):
477         Action.__init__(self, "darwinlauncher", basedir, skip, do)
478
479     @skippable
480     def run(self):
481         self.log("generating launcher script for OSX...")
482         launcher_path = os.path.join(self._basedir,
483                                      "Bitmask",
484                                      "Bitmask.app",
485                                      "Contents",
486                                      "MacOS",
487                                      "bitmask-launcher")
488         file_util.write_file(launcher_path, self.launcher)
489         os.chmod(launcher_path, stat.S_IRGRP | stat.S_IROTH | stat.S_IRUSR
490                  | stat.S_IWGRP | stat.S_IWOTH | stat.S_IWUSR
491                  | stat.S_IXGRP | stat.S_IXOTH | stat.S_IXUSR)
492         self.log("done.")
493
494
495 class CopyAssets(Action):
496     def __init__(self, basedir, skip, do):
497         Action.__init__(self, "copyassets", basedir, skip, do)
498
499     @skippable
500     def run(self):
501         self.log("copying assets...")
502         resources_dir = os.path.join(self._basedir,
503                                      "Bitmask",
504                                      "Bitmask.app",
505                                      "Contents",
506                                      "Resources")
507         cp(os.path.join(self._basedir, "leap_assets", "mac", "bitmask.icns"),
508            resources_dir)
509         cp(os.path.join(self._basedir, "leap_assets", "mac", "bitmask.tiff"),
510            resources_dir)
511         self.log("done.")
512
513
514 class CopyMisc(Action):
515     TUF_CONFIG = textwrap.dedent("""\
516         [General]
517         updater_delay = 60
518
519         [Mirror.localhost]
520         url_prefix = http://dl.bitmask.net/tuf""")
521
522     def __init__(self, basedir, skip, do):
523         Action.__init__(self, "copymisc", basedir, skip, do)
524
525     @skippable
526     def run(self, binary_path):
527         self.log("downloading thunderbird extension...")
528         ext_path = platform_dir(self._basedir, "apps",
529                                 "bitmask-thunderbird-latest.xpi")
530         urllib.urlretrieve(
531             "https://downloads.leap.se/thunderbird_extension/"
532             "bitmask-thunderbird-latest.xpi",
533             ext_path)
534         self.log("done")
535         self.log("copying misc files...")
536         apps_dir = _convert_path_for_win(platform_dir(self._basedir, "apps"))
537         cp(_convert_path_for_win(
538             os.path.join(self._basedir, "bitmask_launcher", "src",
539                          "launcher.py")),
540            apps_dir)
541         cp("-r",
542            _convert_path_for_win(os.path.join(self._basedir, "bitmask_client",
543                                               "src", "leap")),
544            apps_dir)
545         lib_dir = _convert_path_for_win(platform_dir(self._basedir, "lib"))
546         cp(_convert_path_for_win(
547             os.path.join(self._basedir,
548                          "leap_pycommon",
549                          "src", "leap", "common", "cacert.pem")),
550            _convert_path_for_win(os.path.join(lib_dir, "leap", "common")))
551         cp(_convert_path_for_win(glob(os.path.join(self._basedir,
552                                                    "bitmask_client", "build",
553                                                    "lib*", "leap", "bitmask",
554                                                    "_version.py"))[0]),
555            os.path.join(apps_dir, "leap", "bitmask"))
556
557         cp(_convert_path_for_win(
558             os.path.join(self._basedir,
559                          "bitmask_client", "relnotes.txt")),
560            _convert_path_for_win(os.path.join(self._basedir, "Bitmask")))
561
562         launcher_path = os.path.join(self._basedir, "Bitmask", "launcher.conf")
563         with open(launcher_path, "w") as f:
564             f.write(self.TUF_CONFIG)
565
566         metadata = os.path.join(self._basedir, "Bitmask", "repo", "metadata")
567         mkdir("-p", os.path.join(metadata, "current"))
568         mkdir("-p", os.path.join(metadata, "previous"))
569         cp(os.path.join(binary_path, "root.json"),
570            os.path.join(metadata, "current"))
571         self.log("done")
572
573
574 class FixDylibs(Action):
575     def __init__(self, basedir, skip, do):
576         Action.__init__(self, "fixdylibs", basedir, skip, do)
577
578     @skippable
579     def run(self):
580         fix_all_dylibs(platform_dir(self._basedir))
581
582
583 class DmgIt(Action):
584     def __init__(self, basedir, skip, do):
585         Action.__init__(self, "dmgit", basedir, skip, do)
586
587     @skippable
588     def run(self, repos, nightly):
589         self.log("Dmg'ing it...")
590         cd(self._basedir)
591         version = get_version(repos, nightly)
592         dmg_dir = os.path.join(self._basedir, "dmg")
593         template_dir = os.path.join(self._basedir, "Bitmask")
594         mkdir("-p", dmg_dir)
595         cp("-R", os.path.join(template_dir, "Applications"), dmg_dir)
596         cp("-R", os.path.join(template_dir, "relnotes.txt"), dmg_dir)
597         cp("-R", os.path.join(template_dir, "Bitmask.app"), dmg_dir)
598         cp(os.path.join(self._basedir,
599                         "leap_assets",
600                         "mac", "bitmask.icns"),
601            os.path.join(dmg_dir, ".VolumeIcon.icns"))
602         SetFile("-c", "icnC", os.path.join(dmg_dir, ".VolumeIcon.icns"))
603
604         vol_name = "Bitmask"
605         dmg_name = "Bitmask-OSX-{0}.dmg".format(version)
606         raw_dmg_path = os.path.join(self._basedir, "raw-{0}".format(dmg_name))
607         dmg_path = os.path.join(self._basedir, dmg_name)
608
609         hdiutil("create", "-srcfolder", dmg_dir, "-volname", vol_name,
610                 "-fsargs", "-c c=64,a=16,e=16", "-fs", "HFS+",
611                 "-format", "UDRW", "-ov", "-size", "500000k",
612                 raw_dmg_path)
613         rm("-rf", dmg_dir)
614         mkdir(dmg_dir)
615         hdiutil("attach", raw_dmg_path, "-mountpoint", dmg_dir)
616         SetFile("-a", "C", dmg_dir)
617         hdiutil("detach", dmg_dir)
618
619         rm("-rf", dmg_dir)
620         hdiutil("convert", raw_dmg_path, "-format", "UDZO",
621                 "-imagekey", "zlib-level=9", "-o",
622                 dmg_path)
623         rm("-f", raw_dmg_path)
624         self.log("Done")
625
626
627 class TarballIt(Action):
628     def __init__(self, basedir, skip, do):
629         Action.__init__(self, "tarballit", basedir, skip, do)
630
631     @skippable
632     def run(self, repos, nightly):
633         self.log("Tarballing it...")
634         cd(self._basedir)
635         version = get_version(repos, nightly)
636         import platform
637         bits = platform.architecture()[0][:2]
638         bundle_name = "Bitmask-linux%s-%s" % (bits, version)
639         mv("Bitmask", bundle_name)
640         tar("cjf", bundle_name+".tar.bz2", bundle_name)
641         self.log("Done")
642
643
644 class PycRemover(Action):
645     def __init__(self, basedir, skip, do):
646         Action.__init__(self, "removepyc", basedir, skip, do)
647
648     @skippable
649     def run(self):
650         self.log("Removing .pyc files...")
651         files = find(self._basedir, "-name", "*.pyc").strip().splitlines()
652         for f in files:
653             rm(f)
654         files = find(self._basedir, "-name", "*\\.so*").strip().splitlines()
655         for f in files:
656             self.log("Stripping {0}".format(f))
657             try:
658                 strip(f)
659             except:
660                 pass
661         self.log("Done")
662
663
664 class MtEmAll(Action):
665     def __init__(self, basedir, skip, do):
666         Action.__init__(self, "mtemall", basedir, skip, do)
667
668     @skippable
669     def run(self):
670         self.log("Mt'ing all the files...")
671         cd(os.path.join(self._basedir, "Bitmask"))
672         subprocess.check_call(
673             ["C:\\Program Files\\Windows Kits\\8.0\\bin\\x86\\mt.exe",
674              "-nologo", "-manifest", "Microsoft.VC90.CRT.manifest",
675              "-outputresource:bitmask.exe;#1"])
676         cd(os.path.join("apps", "eip"))
677         subprocess.check_call(
678             ["C:\\Program Files\\Windows Kits\\8.0\\bin\\x86\\mt.exe",
679              "-nologo", "-manifest", "openvpn_leap.exe.manifest",
680              "-outputresource:openvpn_leap.exe;#1"])
681         self.log("Done")
682
683
684 class ZipIt(Action):
685     def __init__(self, basedir, skip, do):
686         Action.__init__(self, "zipit", basedir, skip, do)
687
688     def _zipdir(self, path, zf):
689         for root, dirs, files in os.walk(path):
690             for f in files:
691                 zf.write(os.path.join(root, f))
692
693     @skippable
694     def run(self, repos, nightly):
695         self.log("Ziping it...")
696         cd(self._basedir)
697         version = get_version(repos, nightly)
698         name = "Bitmask-win32-{0}".format(version)
699         mv(_convert_path_for_win(os.path.join(self._basedir, "Bitmask")),
700            _convert_path_for_win(os.path.join(self._basedir, name)))
701         zf = zipfile.ZipFile("{0}.zip".format(name), "w", zipfile.ZIP_DEFLATED)
702         self._zipdir(name, zf)
703         zf.close()
704         self.log("Done")
705
706
707 class SignIt(Action):
708     def __init__(self, basedir, skip, do):
709         Action.__init__(self, "signit", basedir, skip, do)
710
711     @skippable
712     def run(self, identity):
713         self.log("Signing tuntap kext...")
714         kext = os.path.join(self._basedir,
715                             "Bitmask",
716                             "Bitmask.app",
717                             "Contents",
718                             "Resources",
719                             "tuntap-installer.app",
720                             "Contents",
721                             "Extensions",
722                             "tun.kext")
723         codesign("-s", identity, "--deep", kext)
724         self.log("Done")
725         self.log("Signing tuntap installer...")
726         tuntap_app = os.path.join(self._basedir,
727                                   "Bitmask",
728                                   "Bitmask.app",
729                                   "Contents",
730                                   "Resources",
731                                   "tuntap-installer.app")
732         codesign("-s", identity, "--deep", tuntap_app)
733         self.log("Done")
734         self.log("Signing main structure, this will take a while...")
735         main_app = os.path.join(self._basedir,
736                                 "Bitmask",
737                                 "Bitmask.app")
738         self.log(codesign("-s", identity, "--force",
739                           "--deep", "--verbose", main_app))
740         self.log("Done")
741
742
743 class RemoveUnused(Action):
744     def __init__(self, basedir, skip, do):
745         Action.__init__(self, "rmunused", basedir, skip, do)
746
747     @skippable
748     def run(self):
749         self.log("Removing unused python code...")
750         test_dirs = find(self._basedir, "-name", "*test*").strip().splitlines()
751         for td in test_dirs:
752             rm("-rf", os.path.join(self._basedir, td))
753
754         # twisted_used = ["aplication", "conch", "cred",
755         #                 "version", "internet", "mail"]
756         # twisted_files = find(self._basedir, "-name", "t
757         self.log("Done")