blob: 91a578b969e2d75b085f9942e8c396f8eb080a2a [file] [log] [blame]
Alexander Afanasyevcf18c802016-03-20 22:48:15 -07001#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3#
4#
5# Loosely based on original bash-version by Sebastian Schlingmann (based, again, on a OSX application bundler
6# by Thomas Keller).
7#
8
9import sys, os, string, re, shutil, plistlib, tempfile, exceptions, datetime, tarfile
10from subprocess import Popen, PIPE
11from optparse import OptionParser
12
13import platform
14
15if platform.system () != 'Darwin':
16 print "This script is indended to be run only on OSX platform"
17 exit (1)
18
19MIN_SUPPORTED_VERSION="10.10"
20
21current_version = tuple(int(i) for i in platform.mac_ver()[0].split('.')[0:2])
22min_supported_version = tuple(int(i) for i in MIN_SUPPORTED_VERSION.split('.')[0:2])
23
24if current_version < min_supported_version:
25 print "This script is indended to be run only on OSX >= %s platform" % MIN_SUPPORTED_VERSION
26 exit (1)
27
28options = None
29
30def gitrev():
31 return os.popen('git describe').read()[:-1]
32
33def codesign(path):
34 '''Call the codesign executable.'''
35
36 if hasattr(path, 'isalpha'):
37 path = (path,)
38
39 for p in path:
40 p = Popen(('codesign', '-vvvv', '--deep', '--force', '--sign', options.codesign, p))
41 retval = p.wait()
42 if retval != 0:
43 return retval
44 return 0
45
46class AppBundle(object):
47
48 def __init__(self, bundle, version, binary):
49 shutil.copytree (src = binary, dst = bundle, symlinks = True)
50
51 self.framework_path = ''
52 self.handled_libs = {}
53 self.bundle = bundle
54 self.version = version
55 self.infopath = os.path.join(os.path.abspath(bundle), 'Contents', 'Info.plist')
56 self.infoplist = plistlib.readPlist(self.infopath)
57 self.binary = os.path.join(os.path.abspath(bundle), 'Contents', 'MacOS', self.infoplist['CFBundleExecutable'])
58 print ' * Preparing AppBundle'
59
60 def is_system_lib(self, lib):
61 '''
62 Is the library a system library, meaning that we should not include it in our bundle?
63 '''
64 if lib.startswith('/System/Library/'):
65 return True
66 if lib.startswith('/usr/lib/'):
67 return True
68
69 return False
70
71 def is_dylib(self, lib):
72 '''
73 Is the library a dylib?
74 '''
75 return lib.endswith('.dylib')
76
77 def get_framework_base(self, fw):
78 '''
79 Extracts the base .framework bundle path from a library in an abitrary place in a framework.
80 '''
81 paths = fw.split('/')
82 for i, str in enumerate(paths):
83 if str.endswith('.framework'):
84 return '/'.join(paths[:i+1])
85 return None
86
87 def is_framework(self, lib):
88 '''
89 Is the library a framework?
90 '''
91 return bool(self.get_framework_base(lib))
92
93 def get_binary_libs(self, path):
94 '''
95 Get a list of libraries that we depend on.
96 '''
97 m = re.compile('^\t(.*)\ \(.*$')
98 libs = Popen(['otool', '-L', path], stdout=PIPE).communicate()[0]
99 libs = string.split(libs, '\n')
100 ret = []
101 bn = os.path.basename(path)
102 for line in libs:
103 g = m.match(line)
104 if g is not None:
105 lib = g.groups()[0]
106 if lib != bn:
107 ret.append(lib)
108 return ret
109
110 def handle_libs(self):
111 '''
112 Copy non-system libraries that we depend on into our bundle, and fix linker
113 paths so they are relative to our bundle.
114 '''
115 print ' * Taking care of libraries'
116
117 # Does our fwpath exist?
118 fwpath = os.path.join(os.path.abspath(self.bundle), 'Contents', 'Frameworks')
119 if not os.path.exists(fwpath):
120 os.mkdir(fwpath)
121
122 self.handle_binary_libs()
123
124 def handle_binary_libs(self, macho=None, loader_path=None):
125 '''
126 Fix up dylib depends for a specific binary.
127 '''
128 # Does our fwpath exist already? If not, create it.
129 if not self.framework_path:
130 self.framework_path = self.bundle + '/Contents/Frameworks'
131 if not os.path.exists(self.framework_path):
132 os.mkdir(self.framework_path)
133 else:
134 shutil.rmtree(self.framework_path)
135 os.mkdir(self.framework_path)
136
137 # If we weren't explicitly told which binary to operate on, pick the
138 # bundle's default executable from its property list.
139 if macho is None:
140 macho = os.path.abspath(self.binary)
141 else:
142 macho = os.path.abspath(macho)
143
144 print "Processing [%s]" % macho
145
146 libs = self.get_binary_libs(macho)
147
148 for lib in libs:
149
150 # Skip system libraries
151 if self.is_system_lib(lib):
152 continue
153
154 # Frameworks are 'special'.
155 if self.is_framework(lib):
156 fw_path = self.get_framework_base(lib)
157 basename = os.path.basename(fw_path)
158 name = basename.split('.framework')[0]
159 rel = basename + '/' + name
160
161 abs = self.framework_path + '/' + rel
162
163 if not basename in self.handled_libs:
164 dst = self.framework_path + '/' + basename
165 print "COPY ", fw_path, dst
166 shutil.copytree(fw_path, dst, symlinks=True)
167 if name.startswith('Qt'):
168 os.remove(dst + '/' + name + '.prl')
169 os.remove(dst + '/Headers')
170 shutil.rmtree(dst + '/Versions/Current/Headers')
171
172 os.chmod(abs, 0755)
173 os.system('install_name_tool -id "@executable_path/../Frameworks/%s" "%s"' % (rel, abs))
174 self.handled_libs[basename] = True
175 self.handle_binary_libs(abs)
176
177 os.chmod(macho, 0755)
Alexander Afanasyev2fda1262016-03-20 23:26:23 -0700178 # print 'install_name_tool -change "%s" "@executable_path/../Frameworks/%s" "%s"' % (lib, rel, macho)
Alexander Afanasyevcf18c802016-03-20 22:48:15 -0700179 os.system('install_name_tool -change "%s" "@executable_path/../Frameworks/%s" "%s"' % (lib, rel, macho))
180
181 # Regular dylibs
182 else:
183 basename = os.path.basename(lib)
184 rel = basename
185
186 if not basename in self.handled_libs:
187 if lib.startswith('@loader_path'):
188 copypath = lib.replace('@loader_path', loader_path)
189 else:
190 copypath = lib
191
192 print "COPY ", copypath
193 shutil.copy(copypath, self.framework_path + '/' + basename)
194
195 abs = self.framework_path + '/' + rel
196 os.chmod(abs, 0755)
197 os.system('install_name_tool -id "@executable_path/../Frameworks/%s" "%s"' % (rel, abs))
198 self.handled_libs[basename] = True
199 self.handle_binary_libs(abs, loader_path=os.path.dirname(lib) if loader_path is None else loader_path)
200
Alexander Afanasyev2fda1262016-03-20 23:26:23 -0700201 # print 'install_name_tool -change "%s" "@executable_path/../Frameworks/%s" "%s"' % (lib, rel, macho)
Alexander Afanasyevcf18c802016-03-20 22:48:15 -0700202 os.chmod(macho, 0755)
203 os.system('install_name_tool -change "%s" "@executable_path/../Frameworks/%s" "%s"' % (lib, rel, macho))
204
205 def copy_resources(self, rsrcs):
206 '''
207 Copy needed resources into our bundle.
208 '''
209 print ' * Copying needed resources'
210 rsrcpath = os.path.join(self.bundle, 'Contents', 'Resources')
211 if not os.path.exists(rsrcpath):
212 os.mkdir(rsrcpath)
213
214 # Copy resources already in the bundle
215 for rsrc in rsrcs:
216 b = os.path.basename(rsrc)
217 if os.path.isdir(rsrc):
218 shutil.copytree(rsrc, os.path.join(rsrcpath, b), symlinks=True)
219 elif os.path.isfile(rsrc):
220 shutil.copy(rsrc, os.path.join(rsrcpath, b))
221
222 return
223
Alexander Afanasyev8e986f82016-03-21 14:19:15 -0700224 def copy_etc(self, rsrcs):
Alexander Afanasyevcf18c802016-03-20 22:48:15 -0700225 '''
Alexander Afanasyev8e986f82016-03-21 14:19:15 -0700226 Copy needed config files into our bundle.
Alexander Afanasyevcf18c802016-03-20 22:48:15 -0700227 '''
Alexander Afanasyev8e986f82016-03-21 14:19:15 -0700228 print ' * Copying needed config files'
Alexander Afanasyev964feb92016-03-22 13:03:11 -0700229 rsrcpath = os.path.join(self.bundle, 'Contents', 'etc', 'ndn')
Alexander Afanasyev8e986f82016-03-21 14:19:15 -0700230 if not os.path.exists(rsrcpath):
Alexander Afanasyev964feb92016-03-22 13:03:11 -0700231 os.makedirs(rsrcpath)
Alexander Afanasyevcf18c802016-03-20 22:48:15 -0700232
Alexander Afanasyev8e986f82016-03-21 14:19:15 -0700233 # Copy resources already in the bundle
234 for rsrc in rsrcs:
235 b = os.path.basename(rsrc)
236 if os.path.isdir(rsrc):
237 shutil.copytree(rsrc, os.path.join(rsrcpath, b), symlinks=True)
238 elif os.path.isfile(rsrc):
239 shutil.copy(rsrc, os.path.join(rsrcpath, b))
Alexander Afanasyevcf18c802016-03-20 22:48:15 -0700240
Alexander Afanasyeve1236c72016-03-21 15:52:09 -0700241 return
242
243 def copy_framework(self, framework):
244 '''
245 Copy frameworks
246 '''
247 print ' * Copying framework'
248 rsrcpath = os.path.join(self.bundle, 'Contents', 'Frameworks', os.path.basename(framework))
249
250 shutil.copytree(framework, rsrcpath, symlinks = True)
251
Alexander Afanasyev8e986f82016-03-21 14:19:15 -0700252 # def copy_qt_plugins(self):
253 # '''
254 # Copy over any needed Qt plugins.
255 # '''
Alexander Afanasyevcf18c802016-03-20 22:48:15 -0700256
Alexander Afanasyev8e986f82016-03-21 14:19:15 -0700257 # print ' * Copying Qt and preparing plugins'
Alexander Afanasyevcf18c802016-03-20 22:48:15 -0700258
Alexander Afanasyev8e986f82016-03-21 14:19:15 -0700259 # src = os.popen('qmake -query QT_INSTALL_PLUGINS').read().strip()
260 # dst = os.path.join(self.bundle, 'Contents', 'QtPlugins')
261 # shutil.copytree(src, dst, symlinks=False)
Alexander Afanasyevcf18c802016-03-20 22:48:15 -0700262
Alexander Afanasyev8e986f82016-03-21 14:19:15 -0700263 # top = dst
264 # files = {}
Alexander Afanasyevcf18c802016-03-20 22:48:15 -0700265
Alexander Afanasyev8e986f82016-03-21 14:19:15 -0700266 # def cb(arg, dirname, fnames):
267 # if dirname == top:
268 # return
269 # files[os.path.basename(dirname)] = fnames
Alexander Afanasyevcf18c802016-03-20 22:48:15 -0700270
Alexander Afanasyev8e986f82016-03-21 14:19:15 -0700271 # os.path.walk(top, cb, None)
Alexander Afanasyevcf18c802016-03-20 22:48:15 -0700272
Alexander Afanasyev8e986f82016-03-21 14:19:15 -0700273 # exclude = ( 'phonon_backend', 'designer', 'script' )
274
275 # for dir, files in files.items():
276 # absdir = dst + '/' + dir
277 # if dir in exclude:
278 # shutil.rmtree(absdir)
279 # continue
280 # for file in files:
281 # abs = absdir + '/' + file
282 # if file.endswith('_debug.dylib'):
283 # os.remove(abs)
284 # else:
285 # os.system('install_name_tool -id "%s" "%s"' % (file, abs))
286 # self.handle_binary_libs(abs)
287
288 def macdeployqt(self):
289 Popen(['macdeployqt', self.bundle, '-qmldir=src', '-executable=%s' % self.binary]).communicate()
290
Alexander Afanasyevb2cf5c02016-03-21 11:04:28 -0700291 def copy_ndn_deps(self, path):
292 '''
293 Copy over NDN dependencies (NFD and related apps)
294 '''
295 print ' * Copying NDN dependencies'
296
Alexander Afanasyev8e986f82016-03-21 14:19:15 -0700297 src = os.path.join(path, 'bin')
298 dst = os.path.join(self.bundle, 'Contents', 'Platform')
Alexander Afanasyevb2cf5c02016-03-21 11:04:28 -0700299 shutil.copytree(src, dst, symlinks=False)
300
Alexander Afanasyev8e986f82016-03-21 14:19:15 -0700301 for subdir, dirs, files in os.walk(dst):
Alexander Afanasyevb2cf5c02016-03-21 11:04:28 -0700302 for file in files:
Alexander Afanasyev8e986f82016-03-21 14:19:15 -0700303 abs = subdir + "/" + file
304 self.handle_binary_libs(abs)
Alexander Afanasyevb2cf5c02016-03-21 11:04:28 -0700305
Alexander Afanasyev8e986f82016-03-21 14:19:15 -0700306 # top = dst
307 # files = {}
308
309 # def cb(arg, dirname, fnames):
310 # if dirname == top:
311 # return
312 # files[dirname] = fnames
313
314 # os.path.walk(top, cb, None)
315
316 # # Cleanup debug folders stuff
317 # excludeDirs = ['include', 'pkgconfig', 'lib'] # lib already processed
318 # excludeFiles = ['libndn-cxx.dylib', 'nfd-start', 'nfd-stop']
319
320 # for dir, files in files.items():
321 # basename = os.path.basename(dir)
322 # if basename in excludeDirs:
323 # shutil.rmtree(dir)
324 # continue
325 # for file in files:
326 # if file in excludeFiles:
327 # abs = dir + '/' + file
328 # os.remove(abs)
329
330 # top = dst
331 # files = {}
332
333 # os.path.walk(top, cb, None)
334
335 # for dir, files in files.items():
336 # for file in files:
337 # abs = dir + '/' + file
338 # type = Popen(['file', '-b', abs], stdout=PIPE).communicate()[0].strip()
339 # if type.startswith('Mach-O'):
340 # self.handle_binary_libs(abs)
Alexander Afanasyevb2cf5c02016-03-21 11:04:28 -0700341
342
Alexander Afanasyevcf18c802016-03-20 22:48:15 -0700343 def set_min_macosx_version(self, version):
344 '''
345 Set the minimum version of Mac OS X version that this App will run on.
346 '''
347 print ' * Setting minimum Mac OS X version to: %s' % (version)
348 self.infoplist['LSMinimumSystemVersion'] = version
349
350 def done(self):
351 plistlib.writePlist(self.infoplist, self.infopath)
352 print ' * Done!'
353 print ''
354
355class FolderObject(object):
356 class Exception(exceptions.Exception):
357 pass
358
359 def __init__(self):
360 self.tmp = tempfile.mkdtemp()
361
362 def copy(self, src, dst='/'):
363 '''
364 Copy a file or directory into foler
365 '''
366 asrc = os.path.abspath(src)
367
368 if dst[0] != '/':
369 raise self.Exception
370
371 # Determine destination
372 if dst[-1] == '/':
373 adst = os.path.abspath(self.tmp + '/' + dst + os.path.basename(src))
374 else:
375 adst = os.path.abspath(self.tmp + '/' + dst)
376
377 if os.path.isdir(asrc):
378 print ' * Copying directory: %s' % os.path.basename(asrc)
379 shutil.copytree(asrc, adst, symlinks=True)
380 elif os.path.isfile(asrc):
381 print ' * Copying file: %s' % os.path.basename(asrc)
382 shutil.copy(asrc, adst)
383
384 def symlink(self, src, dst):
385 '''
386 Create a symlink inside the folder
387 '''
388 asrc = os.path.abspath(src)
389 adst = self.tmp + '/' + dst
390 print " * Creating symlink %s" % os.path.basename(asrc)
391 os.symlink(asrc, adst)
392
393 def mkdir(self, name):
394 '''
395 Create a directory inside the folder.
396 '''
397 print ' * Creating directory %s' % os.path.basename(name)
398 adst = self.tmp + '/' + name
399 os.makedirs(adst)
400
401class DiskImage(FolderObject):
402
403 def __init__(self, filename, volname):
404 FolderObject.__init__(self)
405 print ' * Preparing to create diskimage'
406 self.filename = filename
407 self.volname = volname
408
409 def create(self):
410 '''
411 Create the disk image
412 '''
413 print ' * Creating disk image. Please wait...'
414 if os.path.exists(self.filename):
Alexander Afanasyev2fda1262016-03-20 23:26:23 -0700415 os.remove(self.filename)
416 shutil.rmtree(self.filename, ignore_errors=True)
Alexander Afanasyevcf18c802016-03-20 22:48:15 -0700417 p = Popen(['hdiutil', 'create',
418 '-srcfolder', self.tmp,
419 '-format', 'UDBZ',
420 '-volname', self.volname,
421 self.filename])
422
423 retval = p.wait()
424 print ' * Removing temporary directory.'
425 shutil.rmtree(self.tmp)
426 print ' * Done!'
427
428
429if __name__ == '__main__':
430 parser = OptionParser()
431 parser.add_option('-r', '--release', dest='release', help='Build a release. This determines the version number of the release.')
432 parser.add_option('-s', '--snapshot', dest='snapshot', help='Build a snapshot release. This determines the \'snapshot version\'.')
433 parser.add_option('-g', '--git', dest='git', help='Build a snapshot release. Use the git revision number as the \'snapshot version\'.', action='store_true', default=False)
434 parser.add_option('--codesign', dest='codesign', help='Identity to use for code signing. (If not set, no code signing will occur)')
435
436 options, args = parser.parse_args()
437
438 # Release
439 if options.release:
440 ver = options.release
441 # Snapshot
442 elif options.snapshot or options.git:
443 if not options.git:
444 ver = options.snapshot
445 else:
446 ver = gitrev()
447 else:
448 print 'ERROR: Neither snapshot or release selected. Bailing.'
449 parser.print_help ()
450 sys.exit(1)
451
452 # Do the finishing touches to our Application bundle before release
453 shutil.rmtree('build/%s/NDN.app' % (MIN_SUPPORTED_VERSION), ignore_errors=True)
454 a = AppBundle('build/%s/NDN.app' % (MIN_SUPPORTED_VERSION), ver, 'build/NFD Control Center.app')
Alexander Afanasyev8e986f82016-03-21 14:19:15 -0700455 # a.copy_qt_plugins()
456 # a.handle_libs()
Alexander Afanasyevb2cf5c02016-03-21 11:04:28 -0700457 a.copy_ndn_deps("build/deps")
Alexander Afanasyev8e986f82016-03-21 14:19:15 -0700458 # a.copy_resources(['qt.conf'])
459 a.copy_etc(['nfd.conf'])
Alexander Afanasyev11ae34d2016-03-21 11:55:16 -0700460 a.set_min_macosx_version('%s.0' % MIN_SUPPORTED_VERSION)
Alexander Afanasyev8e986f82016-03-21 14:19:15 -0700461 a.macdeployqt()
Alexander Afanasyeve1236c72016-03-21 15:52:09 -0700462 a.copy_framework("build/Sparkle.framework")
Alexander Afanasyev11ae34d2016-03-21 11:55:16 -0700463 a.done()
Alexander Afanasyevcf18c802016-03-20 22:48:15 -0700464
Alexander Afanasyev11ae34d2016-03-21 11:55:16 -0700465 # Sign our binaries, etc.
466 if options.codesign:
467 print ' * Signing binaries with identity `%s\'' % options.codesign
Alexander Afanasyeve1236c72016-03-21 15:52:09 -0700468 binaries = (a.bundle)
Alexander Afanasyevcf18c802016-03-20 22:48:15 -0700469
Alexander Afanasyev11ae34d2016-03-21 11:55:16 -0700470 codesign(binaries)
471 print ''
Alexander Afanasyevcf18c802016-03-20 22:48:15 -0700472
Alexander Afanasyev11ae34d2016-03-21 11:55:16 -0700473 # Create diskimage
474 title = "NDN-%s-%s" % (ver, MIN_SUPPORTED_VERSION)
475 fn = "build/%s.dmg" % title
476 d = DiskImage(fn, title)
477 d.symlink('/Applications', '/Applications')
478 d.copy('build/%s/NDN.app' % MIN_SUPPORTED_VERSION, '/NDN.app')
479 d.create()