blob: 0559b4519e651d06316fe4b9f93985b4eb504115 [file] [log] [blame]
Thomas Wouters477c8d52006-05-27 19:21:47 +00001#!/usr/bin/python2.3
2"""
3This script is used to build the "official unofficial" universal build on
4Mac OS X. It requires Mac OS X 10.4, Xcode 2.2 and the 10.4u SDK to do its
5work.
6
7Please ensure that this script keeps working with Python 2.3, to avoid
8bootstrap issues (/usr/bin/python is Python 2.3 on OSX 10.4)
9
10Usage: see USAGE variable in the script.
11"""
Georg Brandl029986a2008-06-23 11:44:14 +000012import platform, os, sys, getopt, textwrap, shutil, stat, time, pwd
13import urllib.request
Thomas Wouters89f507f2006-12-13 04:49:30 +000014import grp
Thomas Wouters477c8d52006-05-27 19:21:47 +000015
Thomas Wouters89f507f2006-12-13 04:49:30 +000016INCLUDE_TIMESTAMP = 1
17VERBOSE = 1
Thomas Wouters477c8d52006-05-27 19:21:47 +000018
19from plistlib import Plist
20
21import MacOS
22import Carbon.File
23import Carbon.Icn
24import Carbon.Res
25from Carbon.Files import kCustomIconResource, fsRdWrPerm, kHasCustomIcon
26from Carbon.Files import kFSCatInfoFinderInfo
27
28try:
29 from plistlib import writePlist
30except ImportError:
31 # We're run using python2.3
32 def writePlist(plist, path):
33 plist.write(path)
34
35def shellQuote(value):
36 """
Thomas Wouters89f507f2006-12-13 04:49:30 +000037 Return the string value in a form that can safely be inserted into
Thomas Wouters477c8d52006-05-27 19:21:47 +000038 a shell command.
39 """
40 return "'%s'"%(value.replace("'", "'\"'\"'"))
41
42def grepValue(fn, variable):
43 variable = variable + '='
44 for ln in open(fn, 'r'):
45 if ln.startswith(variable):
46 value = ln[len(variable):].strip()
47 return value[1:-1]
48
49def getVersion():
50 return grepValue(os.path.join(SRCDIR, 'configure'), 'PACKAGE_VERSION')
51
52def getFullVersion():
53 fn = os.path.join(SRCDIR, 'Include', 'patchlevel.h')
54 for ln in open(fn):
55 if 'PY_VERSION' in ln:
56 return ln.split()[-1][1:-1]
57
Collin Winter828f04a2007-08-31 00:04:24 +000058 raise RuntimeError("Cannot find full version??")
Thomas Wouters477c8d52006-05-27 19:21:47 +000059
Thomas Wouters89f507f2006-12-13 04:49:30 +000060# The directory we'll use to create the build (will be erased and recreated)
61WORKDIR = "/tmp/_py"
Thomas Wouters477c8d52006-05-27 19:21:47 +000062
Thomas Wouters89f507f2006-12-13 04:49:30 +000063# The directory we'll use to store third-party sources. Set this to something
Thomas Wouters477c8d52006-05-27 19:21:47 +000064# else if you don't want to re-fetch required libraries every time.
Thomas Wouters89f507f2006-12-13 04:49:30 +000065DEPSRC = os.path.join(WORKDIR, 'third-party')
66DEPSRC = os.path.expanduser('~/Universal/other-sources')
Thomas Wouters477c8d52006-05-27 19:21:47 +000067
68# Location of the preferred SDK
Thomas Wouters89f507f2006-12-13 04:49:30 +000069SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
70#SDKPATH = "/"
Thomas Wouters477c8d52006-05-27 19:21:47 +000071
Thomas Wouters89f507f2006-12-13 04:49:30 +000072ARCHLIST = ('i386', 'ppc',)
Thomas Wouters0e3f5912006-08-11 14:57:12 +000073
Thomas Wouters73e5a5b2006-06-08 15:35:45 +000074# Source directory (asume we're in Mac/BuildScript)
Thomas Wouters89f507f2006-12-13 04:49:30 +000075SRCDIR = os.path.dirname(
Thomas Wouters477c8d52006-05-27 19:21:47 +000076 os.path.dirname(
77 os.path.dirname(
Thomas Wouters73e5a5b2006-06-08 15:35:45 +000078 os.path.abspath(__file__
79 ))))
Thomas Wouters477c8d52006-05-27 19:21:47 +000080
Thomas Wouters89f507f2006-12-13 04:49:30 +000081USAGE = textwrap.dedent("""\
Thomas Wouters477c8d52006-05-27 19:21:47 +000082 Usage: build_python [options]
83
84 Options:
85 -? or -h: Show this message
86 -b DIR
87 --build-dir=DIR: Create build here (default: %(WORKDIR)r)
88 --third-party=DIR: Store third-party sources here (default: %(DEPSRC)r)
89 --sdk-path=DIR: Location of the SDK (default: %(SDKPATH)r)
90 --src-dir=DIR: Location of the Python sources (default: %(SRCDIR)r)
91""")% globals()
92
93
94# Instructions for building libraries that are necessary for building a
95# batteries included python.
Thomas Wouters89f507f2006-12-13 04:49:30 +000096LIBRARY_RECIPES = [
Thomas Wouters477c8d52006-05-27 19:21:47 +000097 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +000098 name="Bzip2 1.0.3",
99 url="http://www.bzip.org/1.0.3/bzip2-1.0.3.tar.gz",
100 configure=None,
101 install='make install PREFIX=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
102 shellQuote(os.path.join(WORKDIR, 'libraries')),
103 ' -arch '.join(ARCHLIST),
104 SDKPATH,
105 ),
106 ),
107 dict(
108 name="ZLib 1.2.3",
109 url="http://www.gzip.org/zlib/zlib-1.2.3.tar.gz",
110 configure=None,
111 install='make install prefix=%s/usr/local/ CFLAGS="-arch %s -isysroot %s"'%(
112 shellQuote(os.path.join(WORKDIR, 'libraries')),
113 ' -arch '.join(ARCHLIST),
114 SDKPATH,
115 ),
116 ),
117 dict(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000118 # Note that GNU readline is GPL'd software
119 name="GNU Readline 5.1.4",
120 url="http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz" ,
121 patchlevel='0',
122 patches=[
123 # The readline maintainers don't do actual micro releases, but
124 # just ship a set of patches.
125 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-001',
126 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-002',
127 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-003',
128 'http://ftp.gnu.org/pub/gnu/readline/readline-5.1-patches/readline51-004',
129 ]
130 ),
131
132 dict(
Benjamin Petersonf10a79a2008-10-11 00:49:57 +0000133 name="SQLite 3.6.3",
134 url="http://www.sqlite.org/sqlite-3.6.3.tar.gz",
Thomas Wouters477c8d52006-05-27 19:21:47 +0000135 checksum='93f742986e8bc2dfa34792e16df017a6feccf3a2',
136 configure_pre=[
137 '--enable-threadsafe',
138 '--enable-tempstore',
139 '--enable-shared=no',
140 '--enable-static=yes',
141 '--disable-tcl',
142 ]
143 ),
144
145 dict(
146 name="NCurses 5.5",
147 url="http://ftp.gnu.org/pub/gnu/ncurses/ncurses-5.5.tar.gz",
148 configure_pre=[
149 "--without-cxx",
150 "--without-ada",
151 "--without-progs",
152 "--without-curses-h",
153 "--enable-shared",
154 "--with-shared",
155 "--datadir=/usr/share",
156 "--sysconfdir=/etc",
157 "--sharedstatedir=/usr/com",
158 "--with-terminfo-dirs=/usr/share/terminfo",
159 "--with-default-terminfo-dir=/usr/share/terminfo",
160 "--libdir=/Library/Frameworks/Python.framework/Versions/%s/lib"%(getVersion(),),
161 "--enable-termcap",
162 ],
163 patches=[
164 "ncurses-5.5.patch",
165 ],
166 useLDFlags=False,
167 install='make && make install DESTDIR=%s && cd %s/usr/local/lib && ln -fs ../../../Library/Frameworks/Python.framework/Versions/%s/lib/lib* .'%(
168 shellQuote(os.path.join(WORKDIR, 'libraries')),
169 shellQuote(os.path.join(WORKDIR, 'libraries')),
170 getVersion(),
171 ),
172 ),
173 dict(
Benjamin Petersonf10a79a2008-10-11 00:49:57 +0000174 name="Sleepycat DB 4.7.25",
175 url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
Thomas Wouters477c8d52006-05-27 19:21:47 +0000176 #name="Sleepycat DB 4.3.29",
177 #url="http://downloads.sleepycat.com/db-4.3.29.tar.gz",
178 buildDir="build_unix",
179 configure="../dist/configure",
180 configure_pre=[
181 '--includedir=/usr/local/include/db4',
182 ]
183 ),
184]
185
186
187# Instructions for building packages inside the .mpkg.
Thomas Wouters89f507f2006-12-13 04:49:30 +0000188PKG_RECIPES = [
Thomas Wouters477c8d52006-05-27 19:21:47 +0000189 dict(
190 name="PythonFramework",
191 long_name="Python Framework",
192 source="/Library/Frameworks/Python.framework",
193 readme="""\
194 This package installs Python.framework, that is the python
195 interpreter and the standard library. This also includes Python
196 wrappers for lots of Mac OS X API's.
197 """,
198 postflight="scripts/postflight.framework",
199 ),
200 dict(
201 name="PythonApplications",
202 long_name="GUI Applications",
Christian Heimes81ee3ef2008-05-04 22:42:01 +0000203 source="/Applications/Python %(VER)s",
Thomas Wouters477c8d52006-05-27 19:21:47 +0000204 readme="""\
Thomas Wouters89f507f2006-12-13 04:49:30 +0000205 This package installs IDLE (an interactive Python IDE),
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000206 Python Launcher and Build Applet (create application bundles
207 from python scripts).
208
209 It also installs a number of examples and demos.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000210 """,
211 required=False,
212 ),
213 dict(
214 name="PythonUnixTools",
215 long_name="UNIX command-line tools",
216 source="/usr/local/bin",
217 readme="""\
218 This package installs the unix tools in /usr/local/bin for
219 compatibility with older releases of MacPython. This package
220 is not necessary to use MacPython.
221 """,
222 required=False,
223 ),
224 dict(
225 name="PythonDocumentation",
226 long_name="Python Documentation",
227 topdir="/Library/Frameworks/Python.framework/Versions/%(VER)s/Resources/English.lproj/Documentation",
228 source="/pydocs",
229 readme="""\
230 This package installs the python documentation at a location
231 that is useable for pydoc and IDLE. If you have installed Xcode
232 it will also install a link to the documentation in
233 /Developer/Documentation/Python
234 """,
235 postflight="scripts/postflight.documentation",
236 required=False,
237 ),
238 dict(
239 name="PythonProfileChanges",
240 long_name="Shell profile updater",
241 readme="""\
242 This packages updates your shell profile to make sure that
243 the MacPython tools are found by your shell in preference of
244 the system provided Python tools.
245
246 If you don't install this package you'll have to add
247 "/Library/Frameworks/Python.framework/Versions/%(VER)s/bin"
248 to your PATH by hand.
249 """,
250 postflight="scripts/postflight.patch-profile",
251 topdir="/Library/Frameworks/Python.framework",
252 source="/empty-dir",
253 required=False,
254 ),
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000255 dict(
256 name="PythonSystemFixes",
257 long_name="Fix system Python",
258 readme="""\
259 This package updates the system python installation on
260 Mac OS X 10.3 to ensure that you can build new python extensions
Thomas Wouters89f507f2006-12-13 04:49:30 +0000261 using that copy of python after installing this version.
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000262 """,
Thomas Wouters73e5a5b2006-06-08 15:35:45 +0000263 postflight="../Tools/fixapplepython23.py",
264 topdir="/Library/Frameworks/Python.framework",
265 source="/empty-dir",
266 required=False,
267 )
Thomas Wouters477c8d52006-05-27 19:21:47 +0000268]
269
Thomas Wouters477c8d52006-05-27 19:21:47 +0000270def fatal(msg):
271 """
272 A fatal error, bail out.
273 """
274 sys.stderr.write('FATAL: ')
275 sys.stderr.write(msg)
276 sys.stderr.write('\n')
277 sys.exit(1)
278
279def fileContents(fn):
280 """
281 Return the contents of the named file
282 """
283 return open(fn, 'rb').read()
284
285def runCommand(commandline):
286 """
287 Run a command and raise RuntimeError if it fails. Output is surpressed
288 unless the command fails.
289 """
290 fd = os.popen(commandline, 'r')
291 data = fd.read()
292 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000293 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000294 sys.stdout.write(data)
Collin Winter828f04a2007-08-31 00:04:24 +0000295 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000296
297 if VERBOSE:
298 sys.stdout.write(data); sys.stdout.flush()
299
300def captureCommand(commandline):
301 fd = os.popen(commandline, 'r')
302 data = fd.read()
303 xit = fd.close()
Benjamin Peterson2a691a82008-03-31 01:51:45 +0000304 if xit is not None:
Thomas Wouters477c8d52006-05-27 19:21:47 +0000305 sys.stdout.write(data)
Collin Winter828f04a2007-08-31 00:04:24 +0000306 raise RuntimeError("command failed: %s"%(commandline,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000307
308 return data
309
310def checkEnvironment():
311 """
312 Check that we're running on a supported system.
313 """
314
315 if platform.system() != 'Darwin':
316 fatal("This script should be run on a Mac OS X 10.4 system")
317
318 if platform.release() <= '8.':
319 fatal("This script should be run on a Mac OS X 10.4 system")
320
321 if not os.path.exists(SDKPATH):
322 fatal("Please install the latest version of Xcode and the %s SDK"%(
323 os.path.basename(SDKPATH[:-4])))
324
325
326
Thomas Wouters89f507f2006-12-13 04:49:30 +0000327def parseOptions(args=None):
Thomas Wouters477c8d52006-05-27 19:21:47 +0000328 """
329 Parse arguments and update global settings.
330 """
331 global WORKDIR, DEPSRC, SDKPATH, SRCDIR
332
333 if args is None:
334 args = sys.argv[1:]
335
336 try:
337 options, args = getopt.getopt(args, '?hb',
338 [ 'build-dir=', 'third-party=', 'sdk-path=' , 'src-dir='])
Guido van Rossumb940e112007-01-10 16:19:56 +0000339 except getopt.error as msg:
Collin Wintere7bf59f2007-08-30 18:39:28 +0000340 print(msg)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000341 sys.exit(1)
342
343 if args:
Collin Wintere7bf59f2007-08-30 18:39:28 +0000344 print("Additional arguments")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000345 sys.exit(1)
346
347 for k, v in options:
348 if k in ('-h', '-?'):
Collin Wintere7bf59f2007-08-30 18:39:28 +0000349 print(USAGE)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000350 sys.exit(0)
351
352 elif k in ('-d', '--build-dir'):
353 WORKDIR=v
354
355 elif k in ('--third-party',):
356 DEPSRC=v
357
358 elif k in ('--sdk-path',):
359 SDKPATH=v
360
361 elif k in ('--src-dir',):
362 SRCDIR=v
363
364 else:
Collin Winter828f04a2007-08-31 00:04:24 +0000365 raise NotImplementedError(k)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000366
367 SRCDIR=os.path.abspath(SRCDIR)
368 WORKDIR=os.path.abspath(WORKDIR)
369 SDKPATH=os.path.abspath(SDKPATH)
370 DEPSRC=os.path.abspath(DEPSRC)
371
Collin Wintere7bf59f2007-08-30 18:39:28 +0000372 print("Settings:")
373 print(" * Source directory:", SRCDIR)
374 print(" * Build directory: ", WORKDIR)
375 print(" * SDK location: ", SDKPATH)
376 print(" * third-party source:", DEPSRC)
377 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000378
379
380
381
382def extractArchive(builddir, archiveName):
383 """
384 Extract a source archive into 'builddir'. Returns the path of the
385 extracted archive.
386
387 XXX: This function assumes that archives contain a toplevel directory
388 that is has the same name as the basename of the archive. This is
389 save enough for anything we use.
390 """
391 curdir = os.getcwd()
392 try:
393 os.chdir(builddir)
394 if archiveName.endswith('.tar.gz'):
395 retval = os.path.basename(archiveName[:-7])
396 if os.path.exists(retval):
397 shutil.rmtree(retval)
398 fp = os.popen("tar zxf %s 2>&1"%(shellQuote(archiveName),), 'r')
399
400 elif archiveName.endswith('.tar.bz2'):
401 retval = os.path.basename(archiveName[:-8])
402 if os.path.exists(retval):
403 shutil.rmtree(retval)
404 fp = os.popen("tar jxf %s 2>&1"%(shellQuote(archiveName),), 'r')
405
406 elif archiveName.endswith('.tar'):
407 retval = os.path.basename(archiveName[:-4])
408 if os.path.exists(retval):
409 shutil.rmtree(retval)
410 fp = os.popen("tar xf %s 2>&1"%(shellQuote(archiveName),), 'r')
411
412 elif archiveName.endswith('.zip'):
413 retval = os.path.basename(archiveName[:-4])
414 if os.path.exists(retval):
415 shutil.rmtree(retval)
416 fp = os.popen("unzip %s 2>&1"%(shellQuote(archiveName),), 'r')
417
418 data = fp.read()
419 xit = fp.close()
420 if xit is not None:
421 sys.stdout.write(data)
Collin Winter828f04a2007-08-31 00:04:24 +0000422 raise RuntimeError("Cannot extract %s"%(archiveName,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000423
424 return os.path.join(builddir, retval)
425
426 finally:
427 os.chdir(curdir)
428
429KNOWNSIZES = {
430 "http://ftp.gnu.org/pub/gnu/readline/readline-5.1.tar.gz": 7952742,
431 "http://downloads.sleepycat.com/db-4.4.20.tar.gz": 2030276,
432}
433
434def downloadURL(url, fname):
435 """
436 Download the contents of the url into the file.
437 """
438 try:
439 size = os.path.getsize(fname)
440 except OSError:
441 pass
442 else:
443 if KNOWNSIZES.get(url) == size:
Collin Wintere7bf59f2007-08-30 18:39:28 +0000444 print("Using existing file for", url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000445 return
Georg Brandl029986a2008-06-23 11:44:14 +0000446 fpIn = urllib.request.urlopen(url)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000447 fpOut = open(fname, 'wb')
448 block = fpIn.read(10240)
449 try:
450 while block:
451 fpOut.write(block)
452 block = fpIn.read(10240)
453 fpIn.close()
454 fpOut.close()
455 except:
456 try:
457 os.unlink(fname)
458 except:
459 pass
460
461def buildRecipe(recipe, basedir, archList):
462 """
463 Build software using a recipe. This function does the
464 'configure;make;make install' dance for C software, with a possibility
465 to customize this process, basically a poor-mans DarwinPorts.
466 """
467 curdir = os.getcwd()
468
469 name = recipe['name']
470 url = recipe['url']
471 configure = recipe.get('configure', './configure')
472 install = recipe.get('install', 'make && make install DESTDIR=%s'%(
473 shellQuote(basedir)))
474
475 archiveName = os.path.split(url)[-1]
476 sourceArchive = os.path.join(DEPSRC, archiveName)
477
478 if not os.path.exists(DEPSRC):
479 os.mkdir(DEPSRC)
480
481
482 if os.path.exists(sourceArchive):
Collin Wintere7bf59f2007-08-30 18:39:28 +0000483 print("Using local copy of %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000484
485 else:
Collin Wintere7bf59f2007-08-30 18:39:28 +0000486 print("Downloading %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000487 downloadURL(url, sourceArchive)
Collin Wintere7bf59f2007-08-30 18:39:28 +0000488 print("Archive for %s stored as %s"%(name, sourceArchive))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000489
Collin Wintere7bf59f2007-08-30 18:39:28 +0000490 print("Extracting archive for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000491 buildDir=os.path.join(WORKDIR, '_bld')
492 if not os.path.exists(buildDir):
493 os.mkdir(buildDir)
494
495 workDir = extractArchive(buildDir, sourceArchive)
496 os.chdir(workDir)
497 if 'buildDir' in recipe:
498 os.chdir(recipe['buildDir'])
499
500
501 for fn in recipe.get('patches', ()):
502 if fn.startswith('http://'):
503 # Download the patch before applying it.
504 path = os.path.join(DEPSRC, os.path.basename(fn))
505 downloadURL(fn, path)
506 fn = path
507
508 fn = os.path.join(curdir, fn)
509 runCommand('patch -p%s < %s'%(recipe.get('patchlevel', 1),
510 shellQuote(fn),))
511
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000512 if configure is not None:
513 configure_args = [
514 "--prefix=/usr/local",
515 "--enable-static",
516 "--disable-shared",
517 #"CPP=gcc -arch %s -E"%(' -arch '.join(archList,),),
518 ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000519
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000520 if 'configure_pre' in recipe:
521 args = list(recipe['configure_pre'])
522 if '--disable-static' in args:
523 configure_args.remove('--enable-static')
524 if '--enable-shared' in args:
525 configure_args.remove('--disable-shared')
526 configure_args.extend(args)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000527
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000528 if recipe.get('useLDFlags', 1):
529 configure_args.extend([
530 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
531 ' -arch '.join(archList),
532 shellQuote(SDKPATH)[1:-1],
533 shellQuote(basedir)[1:-1],),
534 "LDFLAGS=-syslibroot,%s -L%s/usr/local/lib -arch %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000535 shellQuote(SDKPATH)[1:-1],
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000536 shellQuote(basedir)[1:-1],
537 ' -arch '.join(archList)),
538 ])
539 else:
540 configure_args.extend([
541 "CFLAGS=-arch %s -isysroot %s -I%s/usr/local/include"%(
542 ' -arch '.join(archList),
543 shellQuote(SDKPATH)[1:-1],
544 shellQuote(basedir)[1:-1],),
545 ])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000546
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000547 if 'configure_post' in recipe:
548 configure_args = configure_args = list(recipe['configure_post'])
Thomas Wouters477c8d52006-05-27 19:21:47 +0000549
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000550 configure_args.insert(0, configure)
551 configure_args = [ shellQuote(a) for a in configure_args ]
Thomas Wouters477c8d52006-05-27 19:21:47 +0000552
Collin Wintere7bf59f2007-08-30 18:39:28 +0000553 print("Running configure for %s"%(name,))
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000554 runCommand(' '.join(configure_args) + ' 2>&1')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000555
Collin Wintere7bf59f2007-08-30 18:39:28 +0000556 print("Running install for %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000557 runCommand('{ ' + install + ' ;} 2>&1')
558
Collin Wintere7bf59f2007-08-30 18:39:28 +0000559 print("Done %s"%(name,))
560 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000561
562 os.chdir(curdir)
563
564def buildLibraries():
565 """
566 Build our dependencies into $WORKDIR/libraries/usr/local
567 """
Collin Wintere7bf59f2007-08-30 18:39:28 +0000568 print("")
569 print("Building required libraries")
570 print("")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000571 universal = os.path.join(WORKDIR, 'libraries')
572 os.mkdir(universal)
573 os.makedirs(os.path.join(universal, 'usr', 'local', 'lib'))
574 os.makedirs(os.path.join(universal, 'usr', 'local', 'include'))
575
576 for recipe in LIBRARY_RECIPES:
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000577 buildRecipe(recipe, universal, ARCHLIST)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000578
579
580
581def buildPythonDocs():
582 # This stores the documentation as Resources/English.lproj/Docuentation
583 # inside the framwork. pydoc and IDLE will pick it up there.
Collin Wintere7bf59f2007-08-30 18:39:28 +0000584 print("Install python documentation")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000585 rootDir = os.path.join(WORKDIR, '_root')
586 version = getVersion()
587 docdir = os.path.join(rootDir, 'pydocs')
588
Benjamin Petersonf10a79a2008-10-11 00:49:57 +0000589 novername = 'python-docs-html.tar.bz2'
Thomas Wouters477c8d52006-05-27 19:21:47 +0000590 name = 'html-%s.tar.bz2'%(getFullVersion(),)
591 sourceArchive = os.path.join(DEPSRC, name)
592 if os.path.exists(sourceArchive):
Collin Wintere7bf59f2007-08-30 18:39:28 +0000593 print("Using local copy of %s"%(name,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000594
595 else:
Benjamin Petersonf10a79a2008-10-11 00:49:57 +0000596 print "Downloading %s"%(novername,)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000597 downloadURL('http://www.python.org/ftp/python/doc/%s/%s'%(
Benjamin Petersonf10a79a2008-10-11 00:49:57 +0000598 getFullVersion(), novername), sourceArchive)
Collin Wintere7bf59f2007-08-30 18:39:28 +0000599 print("Archive for %s stored as %s"%(name, sourceArchive))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000600
601 extractArchive(os.path.dirname(docdir), sourceArchive)
Benjamin Petersonf10a79a2008-10-11 00:49:57 +0000602
Thomas Wouters477c8d52006-05-27 19:21:47 +0000603 os.rename(
604 os.path.join(
Benjamin Petersonf10a79a2008-10-11 00:49:57 +0000605 os.path.dirname(docdir), 'python-docs-html'),
Thomas Wouters477c8d52006-05-27 19:21:47 +0000606 docdir)
607
608
609def buildPython():
Collin Wintere7bf59f2007-08-30 18:39:28 +0000610 print("Building a universal python")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000611
612 buildDir = os.path.join(WORKDIR, '_bld', 'python')
613 rootDir = os.path.join(WORKDIR, '_root')
614
615 if os.path.exists(buildDir):
616 shutil.rmtree(buildDir)
617 if os.path.exists(rootDir):
618 shutil.rmtree(rootDir)
619 os.mkdir(buildDir)
620 os.mkdir(rootDir)
621 os.mkdir(os.path.join(rootDir, 'empty-dir'))
622 curdir = os.getcwd()
623 os.chdir(buildDir)
624
625 # Not sure if this is still needed, the original build script
626 # claims that parts of the install assume python.exe exists.
627 os.symlink('python', os.path.join(buildDir, 'python.exe'))
628
629 # Extract the version from the configure file, needed to calculate
630 # several paths.
631 version = getVersion()
632
Collin Wintere7bf59f2007-08-30 18:39:28 +0000633 print("Running configure...")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000634 runCommand("%s -C --enable-framework --enable-universalsdk=%s LDFLAGS='-g -L%s/libraries/usr/local/lib' OPT='-g -O3 -I%s/libraries/usr/local/include' 2>&1"%(
635 shellQuote(os.path.join(SRCDIR, 'configure')),
636 shellQuote(SDKPATH), shellQuote(WORKDIR)[1:-1],
637 shellQuote(WORKDIR)[1:-1]))
638
Collin Wintere7bf59f2007-08-30 18:39:28 +0000639 print("Running make")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000640 runCommand("make")
641
Collin Wintere7bf59f2007-08-30 18:39:28 +0000642 print("Running make frameworkinstall")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000643 runCommand("make frameworkinstall DESTDIR=%s"%(
644 shellQuote(rootDir)))
645
Collin Wintere7bf59f2007-08-30 18:39:28 +0000646 print("Running make frameworkinstallextras")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000647 runCommand("make frameworkinstallextras DESTDIR=%s"%(
648 shellQuote(rootDir)))
649
Collin Wintere7bf59f2007-08-30 18:39:28 +0000650 print("Copying required shared libraries")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000651 if os.path.exists(os.path.join(WORKDIR, 'libraries', 'Library')):
652 runCommand("mv %s/* %s"%(
653 shellQuote(os.path.join(
654 WORKDIR, 'libraries', 'Library', 'Frameworks',
655 'Python.framework', 'Versions', getVersion(),
656 'lib')),
657 shellQuote(os.path.join(WORKDIR, '_root', 'Library', 'Frameworks',
658 'Python.framework', 'Versions', getVersion(),
659 'lib'))))
660
Collin Wintere7bf59f2007-08-30 18:39:28 +0000661 print("Fix file modes")
Thomas Wouters477c8d52006-05-27 19:21:47 +0000662 frmDir = os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework')
Thomas Wouters89f507f2006-12-13 04:49:30 +0000663 gid = grp.getgrnam('admin').gr_gid
664
Thomas Wouters477c8d52006-05-27 19:21:47 +0000665 for dirpath, dirnames, filenames in os.walk(frmDir):
666 for dn in dirnames:
667 os.chmod(os.path.join(dirpath, dn), 0775)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000668 os.chown(os.path.join(dirpath, dn), -1, gid)
669
Thomas Wouters477c8d52006-05-27 19:21:47 +0000670
671 for fn in filenames:
672 if os.path.islink(fn):
673 continue
674
675 # "chmod g+w $fn"
676 p = os.path.join(dirpath, fn)
677 st = os.stat(p)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000678 os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
679 os.chown(p, -1, gid)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000680
681 # We added some directories to the search path during the configure
682 # phase. Remove those because those directories won't be there on
683 # the end-users system.
684 path =os.path.join(rootDir, 'Library', 'Frameworks', 'Python.framework',
685 'Versions', version, 'lib', 'python%s'%(version,),
686 'config', 'Makefile')
687 fp = open(path, 'r')
688 data = fp.read()
689 fp.close()
690
691 data = data.replace('-L%s/libraries/usr/local/lib'%(WORKDIR,), '')
692 data = data.replace('-I%s/libraries/usr/local/include'%(WORKDIR,), '')
693 fp = open(path, 'w')
694 fp.write(data)
695 fp.close()
696
697 # Add symlinks in /usr/local/bin, using relative links
698 usr_local_bin = os.path.join(rootDir, 'usr', 'local', 'bin')
699 to_framework = os.path.join('..', '..', '..', 'Library', 'Frameworks',
700 'Python.framework', 'Versions', version, 'bin')
701 if os.path.exists(usr_local_bin):
702 shutil.rmtree(usr_local_bin)
703 os.makedirs(usr_local_bin)
704 for fn in os.listdir(
705 os.path.join(frmDir, 'Versions', version, 'bin')):
706 os.symlink(os.path.join(to_framework, fn),
707 os.path.join(usr_local_bin, fn))
708
709 os.chdir(curdir)
710
711
712
713def patchFile(inPath, outPath):
714 data = fileContents(inPath)
715 data = data.replace('$FULL_VERSION', getFullVersion())
716 data = data.replace('$VERSION', getVersion())
717 data = data.replace('$MACOSX_DEPLOYMENT_TARGET', '10.3 or later')
718 data = data.replace('$ARCHITECTURES', "i386, ppc")
719 data = data.replace('$INSTALL_SIZE', installSize())
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000720
721 # This one is not handy as a template variable
722 data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
Thomas Wouters477c8d52006-05-27 19:21:47 +0000723 fp = open(outPath, 'wb')
724 fp.write(data)
725 fp.close()
726
727def patchScript(inPath, outPath):
728 data = fileContents(inPath)
729 data = data.replace('@PYVER@', getVersion())
730 fp = open(outPath, 'wb')
731 fp.write(data)
732 fp.close()
733 os.chmod(outPath, 0755)
734
735
736
737def packageFromRecipe(targetDir, recipe):
738 curdir = os.getcwd()
739 try:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000740 # The major version (such as 2.5) is included in the package name
741 # because having two version of python installed at the same time is
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000742 # common.
743 pkgname = '%s-%s'%(recipe['name'], getVersion())
Thomas Wouters477c8d52006-05-27 19:21:47 +0000744 srcdir = recipe.get('source')
745 pkgroot = recipe.get('topdir', srcdir)
746 postflight = recipe.get('postflight')
747 readme = textwrap.dedent(recipe['readme'])
748 isRequired = recipe.get('required', True)
749
Collin Wintere7bf59f2007-08-30 18:39:28 +0000750 print("- building package %s"%(pkgname,))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000751
752 # Substitute some variables
753 textvars = dict(
754 VER=getVersion(),
755 FULLVER=getFullVersion(),
756 )
757 readme = readme % textvars
758
759 if pkgroot is not None:
760 pkgroot = pkgroot % textvars
761 else:
762 pkgroot = '/'
763
764 if srcdir is not None:
765 srcdir = os.path.join(WORKDIR, '_root', srcdir[1:])
766 srcdir = srcdir % textvars
767
768 if postflight is not None:
769 postflight = os.path.abspath(postflight)
770
771 packageContents = os.path.join(targetDir, pkgname + '.pkg', 'Contents')
772 os.makedirs(packageContents)
773
774 if srcdir is not None:
775 os.chdir(srcdir)
776 runCommand("pax -wf %s . 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
777 runCommand("gzip -9 %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.pax')),))
778 runCommand("mkbom . %s 2>&1"%(shellQuote(os.path.join(packageContents, 'Archive.bom')),))
779
780 fn = os.path.join(packageContents, 'PkgInfo')
781 fp = open(fn, 'w')
782 fp.write('pmkrpkg1')
783 fp.close()
784
785 rsrcDir = os.path.join(packageContents, "Resources")
786 os.mkdir(rsrcDir)
787 fp = open(os.path.join(rsrcDir, 'ReadMe.txt'), 'w')
788 fp.write(readme)
789 fp.close()
790
791 if postflight is not None:
792 patchScript(postflight, os.path.join(rsrcDir, 'postflight'))
793
794 vers = getFullVersion()
795 major, minor = map(int, getVersion().split('.', 2))
796 pl = Plist(
797 CFBundleGetInfoString="MacPython.%s %s"%(pkgname, vers,),
798 CFBundleIdentifier='org.python.MacPython.%s'%(pkgname,),
799 CFBundleName='MacPython.%s'%(pkgname,),
800 CFBundleShortVersionString=vers,
801 IFMajorVersion=major,
802 IFMinorVersion=minor,
803 IFPkgFormatVersion=0.10000000149011612,
804 IFPkgFlagAllowBackRev=False,
805 IFPkgFlagAuthorizationAction="RootAuthorization",
806 IFPkgFlagDefaultLocation=pkgroot,
807 IFPkgFlagFollowLinks=True,
808 IFPkgFlagInstallFat=True,
809 IFPkgFlagIsRequired=isRequired,
810 IFPkgFlagOverwritePermissions=False,
811 IFPkgFlagRelocatable=False,
812 IFPkgFlagRestartAction="NoRestart",
813 IFPkgFlagRootVolumeOnly=True,
814 IFPkgFlagUpdateInstalledLangauges=False,
815 )
816 writePlist(pl, os.path.join(packageContents, 'Info.plist'))
817
818 pl = Plist(
819 IFPkgDescriptionDescription=readme,
820 IFPkgDescriptionTitle=recipe.get('long_name', "MacPython.%s"%(pkgname,)),
821 IFPkgDescriptionVersion=vers,
822 )
823 writePlist(pl, os.path.join(packageContents, 'Resources', 'Description.plist'))
824
825 finally:
826 os.chdir(curdir)
827
828
829def makeMpkgPlist(path):
830
831 vers = getFullVersion()
832 major, minor = map(int, getVersion().split('.', 2))
833
834 pl = Plist(
835 CFBundleGetInfoString="MacPython %s"%(vers,),
836 CFBundleIdentifier='org.python.MacPython',
837 CFBundleName='MacPython',
838 CFBundleShortVersionString=vers,
839 IFMajorVersion=major,
840 IFMinorVersion=minor,
841 IFPkgFlagComponentDirectory="Contents/Packages",
842 IFPkgFlagPackageList=[
843 dict(
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000844 IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
Thomas Wouters477c8d52006-05-27 19:21:47 +0000845 IFPkgFlagPackageSelection='selected'
846 )
847 for item in PKG_RECIPES
848 ],
849 IFPkgFormatVersion=0.10000000149011612,
850 IFPkgFlagBackgroundScaling="proportional",
851 IFPkgFlagBackgroundAlignment="left",
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000852 IFPkgFlagAuthorizationAction="RootAuthorization",
Thomas Wouters477c8d52006-05-27 19:21:47 +0000853 )
854
855 writePlist(pl, path)
856
857
858def buildInstaller():
859
860 # Zap all compiled files
861 for dirpath, _, filenames in os.walk(os.path.join(WORKDIR, '_root')):
862 for fn in filenames:
863 if fn.endswith('.pyc') or fn.endswith('.pyo'):
864 os.unlink(os.path.join(dirpath, fn))
865
866 outdir = os.path.join(WORKDIR, 'installer')
867 if os.path.exists(outdir):
868 shutil.rmtree(outdir)
869 os.mkdir(outdir)
870
871 pkgroot = os.path.join(outdir, 'MacPython.mpkg', 'Contents')
872 pkgcontents = os.path.join(pkgroot, 'Packages')
873 os.makedirs(pkgcontents)
874 for recipe in PKG_RECIPES:
875 packageFromRecipe(pkgcontents, recipe)
876
877 rsrcDir = os.path.join(pkgroot, 'Resources')
878
879 fn = os.path.join(pkgroot, 'PkgInfo')
880 fp = open(fn, 'w')
881 fp.write('pmkrpkg1')
882 fp.close()
883
884 os.mkdir(rsrcDir)
885
886 makeMpkgPlist(os.path.join(pkgroot, 'Info.plist'))
887 pl = Plist(
888 IFPkgDescriptionTitle="Universal MacPython",
889 IFPkgDescriptionVersion=getVersion(),
890 )
891
892 writePlist(pl, os.path.join(pkgroot, 'Resources', 'Description.plist'))
893 for fn in os.listdir('resources'):
894 if fn == '.svn': continue
895 if fn.endswith('.jpg'):
896 shutil.copy(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
897 else:
898 patchFile(os.path.join('resources', fn), os.path.join(rsrcDir, fn))
899
Thomas Wouters0e3f5912006-08-11 14:57:12 +0000900 shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +0000901
902
903def installSize(clear=False, _saved=[]):
904 if clear:
905 del _saved[:]
906 if not _saved:
907 data = captureCommand("du -ks %s"%(
908 shellQuote(os.path.join(WORKDIR, '_root'))))
909 _saved.append("%d"%((0.5 + (int(data.split()[0]) / 1024.0)),))
910 return _saved[0]
911
912
913def buildDMG():
914 """
Thomas Wouters89f507f2006-12-13 04:49:30 +0000915 Create DMG containing the rootDir.
Thomas Wouters477c8d52006-05-27 19:21:47 +0000916 """
917 outdir = os.path.join(WORKDIR, 'diskimage')
918 if os.path.exists(outdir):
919 shutil.rmtree(outdir)
920
921 imagepath = os.path.join(outdir,
922 'python-%s-macosx'%(getFullVersion(),))
923 if INCLUDE_TIMESTAMP:
924 imagepath = imagepath + '%04d-%02d-%02d'%(time.localtime()[:3])
925 imagepath = imagepath + '.dmg'
926
927 os.mkdir(outdir)
Thomas Wouters89f507f2006-12-13 04:49:30 +0000928 runCommand("hdiutil create -volname 'Universal MacPython %s' -srcfolder %s %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +0000929 getFullVersion(),
930 shellQuote(os.path.join(WORKDIR, 'installer')),
931 shellQuote(imagepath)))
932
933 return imagepath
934
935
936def setIcon(filePath, icnsPath):
937 """
938 Set the custom icon for the specified file or directory.
939
940 For a directory the icon data is written in a file named 'Icon\r' inside
941 the directory. For both files and directories write the icon as an 'icns'
942 resource. Furthermore set kHasCustomIcon in the finder flags for filePath.
943 """
944 ref, isDirectory = Carbon.File.FSPathMakeRef(icnsPath)
945 icon = Carbon.Icn.ReadIconFile(ref)
946 del ref
947
948 #
949 # Open the resource fork of the target, to add the icon later on.
950 # For directories we use the file 'Icon\r' inside the directory.
951 #
952
953 ref, isDirectory = Carbon.File.FSPathMakeRef(filePath)
954
955 if isDirectory:
Thomas Wouters89f507f2006-12-13 04:49:30 +0000956 # There is a problem with getting this into the pax(1) archive,
957 # just ignore directory icons for now.
958 return
959
Thomas Wouters477c8d52006-05-27 19:21:47 +0000960 tmpPath = os.path.join(filePath, "Icon\r")
961 if not os.path.exists(tmpPath):
962 fp = open(tmpPath, 'w')
963 fp.close()
964
965 tmpRef, _ = Carbon.File.FSPathMakeRef(tmpPath)
966 spec = Carbon.File.FSSpec(tmpRef)
967
968 else:
969 spec = Carbon.File.FSSpec(ref)
970
971 try:
972 Carbon.Res.HCreateResFile(*spec.as_tuple())
973 except MacOS.Error:
974 pass
975
976 # Try to create the resource fork again, this will avoid problems
977 # when adding an icon to a directory. I have no idea why this helps,
978 # but without this adding the icon to a directory will fail sometimes.
979 try:
980 Carbon.Res.HCreateResFile(*spec.as_tuple())
981 except MacOS.Error:
982 pass
983
984 refNum = Carbon.Res.FSpOpenResFile(spec, fsRdWrPerm)
985
986 Carbon.Res.UseResFile(refNum)
987
988 # Check if there already is an icon, remove it if there is.
989 try:
990 h = Carbon.Res.Get1Resource('icns', kCustomIconResource)
991 except MacOS.Error:
992 pass
993
994 else:
995 h.RemoveResource()
996 del h
997
998 # Add the icon to the resource for of the target
999 res = Carbon.Res.Resource(icon)
1000 res.AddResource('icns', kCustomIconResource, '')
1001 res.WriteResource()
1002 res.DetachResource()
1003 Carbon.Res.CloseResFile(refNum)
1004
1005 # And now set the kHasCustomIcon property for the target. Annoyingly,
1006 # python doesn't seem to have bindings for the API that is needed for
1007 # this. Cop out and call SetFile
1008 os.system("/Developer/Tools/SetFile -a C %s"%(
1009 shellQuote(filePath),))
1010
1011 if isDirectory:
1012 os.system('/Developer/Tools/SetFile -a V %s'%(
1013 shellQuote(tmpPath),
1014 ))
1015
1016def main():
1017 # First parse options and check if we can perform our work
1018 parseOptions()
1019 checkEnvironment()
1020
1021 os.environ['MACOSX_DEPLOYMENT_TARGET'] = '10.3'
1022
1023 if os.path.exists(WORKDIR):
1024 shutil.rmtree(WORKDIR)
1025 os.mkdir(WORKDIR)
1026
1027 # Then build third-party libraries such as sleepycat DB4.
1028 buildLibraries()
1029
1030 # Now build python itself
1031 buildPython()
1032 buildPythonDocs()
1033 fn = os.path.join(WORKDIR, "_root", "Applications",
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001034 "Python %s"%(getVersion(),), "Update Shell Profile.command")
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001035 patchFile("scripts/postflight.patch-profile", fn)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001036 os.chmod(fn, 0755)
1037
Benjamin Petersonf10a79a2008-10-11 00:49:57 +00001038 folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
Thomas Wouters477c8d52006-05-27 19:21:47 +00001039 getVersion(),))
1040 os.chmod(folder, 0755)
1041 setIcon(folder, "../Icons/Python Folder.icns")
1042
1043 # Create the installer
1044 buildInstaller()
1045
1046 # And copy the readme into the directory containing the installer
1047 patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
1048
1049 # Ditto for the license file.
Thomas Wouters0e3f5912006-08-11 14:57:12 +00001050 shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
Thomas Wouters477c8d52006-05-27 19:21:47 +00001051
1052 fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
Collin Wintere7bf59f2007-08-30 18:39:28 +00001053 print("# BUILD INFO", file=fp)
1054 print("# Date:", time.ctime(), file=fp)
1055 print("# By:", pwd.getpwuid(os.getuid()).pw_gecos, file=fp)
Thomas Wouters477c8d52006-05-27 19:21:47 +00001056 fp.close()
1057
1058 # Custom icon for the DMG, shown when the DMG is mounted.
1059 shutil.copy("../Icons/Disk Image.icns",
1060 os.path.join(WORKDIR, "installer", ".VolumeIcon.icns"))
1061 os.system("/Developer/Tools/SetFile -a C %s"%(
1062 os.path.join(WORKDIR, "installer", ".VolumeIcon.icns")))
1063
1064
1065 # And copy it to a DMG
1066 buildDMG()
1067
1068
1069if __name__ == "__main__":
1070 main()