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