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