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