symbian-qemu-0.9.1-12/python-2.6.1/Mac/BuildScript/build-installer.py
changeset 1 2fb8b9db1c86
equal deleted inserted replaced
0:ffa851df0825 1:2fb8b9db1c86
       
     1 #!/usr/bin/python2.3
       
     2 """
       
     3 This script is used to build the "official unofficial" universal build on
       
     4 Mac OS X. It requires Mac OS X 10.4, Xcode 2.2 and the 10.4u SDK to do its
       
     5 work.
       
     6 
       
     7 Please ensure that this script keeps working with Python 2.3, to avoid
       
     8 bootstrap issues (/usr/bin/python is Python 2.3 on OSX 10.4)
       
     9 
       
    10 Usage: see USAGE variable in the script.
       
    11 """
       
    12 import platform, os, sys, getopt, textwrap, shutil, urllib2, stat, time, pwd
       
    13 import grp
       
    14 
       
    15 INCLUDE_TIMESTAMP = 1
       
    16 VERBOSE = 1
       
    17 
       
    18 from plistlib import Plist
       
    19 
       
    20 import MacOS
       
    21 import Carbon.File
       
    22 import Carbon.Icn
       
    23 import Carbon.Res
       
    24 from Carbon.Files import kCustomIconResource, fsRdWrPerm, kHasCustomIcon
       
    25 from Carbon.Files import kFSCatInfoFinderInfo
       
    26 
       
    27 try:
       
    28     from plistlib import writePlist
       
    29 except ImportError:
       
    30     # We're run using python2.3
       
    31     def writePlist(plist, path):
       
    32         plist.write(path)
       
    33 
       
    34 def shellQuote(value):
       
    35     """
       
    36     Return the string value in a form that can safely be inserted into
       
    37     a shell command.
       
    38     """
       
    39     return "'%s'"%(value.replace("'", "'\"'\"'"))
       
    40 
       
    41 def 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 
       
    48 def getVersion():
       
    49     return grepValue(os.path.join(SRCDIR, 'configure'), 'PACKAGE_VERSION')
       
    50 
       
    51 def 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 
       
    59 # The directory we'll use to create the build (will be erased and recreated)
       
    60 WORKDIR = "/tmp/_py"
       
    61 
       
    62 # The directory we'll use to store third-party sources. Set this to something
       
    63 # else if you don't want to re-fetch required libraries every time.
       
    64 DEPSRC = os.path.join(WORKDIR, 'third-party')
       
    65 DEPSRC = os.path.expanduser('~/Universal/other-sources')
       
    66 
       
    67 # Location of the preferred SDK
       
    68 SDKPATH = "/Developer/SDKs/MacOSX10.4u.sdk"
       
    69 #SDKPATH = "/"
       
    70 
       
    71 ARCHLIST = ('i386', 'ppc',)
       
    72 
       
    73 # Source directory (asume we're in Mac/BuildScript)
       
    74 SRCDIR = os.path.dirname(
       
    75         os.path.dirname(
       
    76             os.path.dirname(
       
    77                 os.path.abspath(__file__
       
    78         ))))
       
    79 
       
    80 USAGE = textwrap.dedent("""\
       
    81     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.
       
    95 LIBRARY_RECIPES = [
       
    96     dict(
       
    97         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(
       
   117         # 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(
       
   132         name="SQLite 3.6.3",
       
   133         url="http://www.sqlite.org/sqlite-3.6.3.tar.gz",
       
   134         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(
       
   173         name="Sleepycat DB 4.7.25",
       
   174         url="http://download.oracle.com/berkeley-db/db-4.7.25.tar.gz",
       
   175         #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.
       
   187 PKG_RECIPES = [
       
   188     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",
       
   202         source="/Applications/Python %(VER)s",
       
   203         readme="""\
       
   204             This package installs IDLE (an interactive Python IDE),
       
   205             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="""\
       
   258             This package updates the system python installation on
       
   259             Mac OS X 10.3 to ensure that you can build new python extensions
       
   260             using that copy of python after installing this version.
       
   261             """,
       
   262         postflight="../Tools/fixapplepython23.py",
       
   263         topdir="/Library/Frameworks/Python.framework",
       
   264         source="/empty-dir",
       
   265         required=False,
       
   266     )
       
   267 ]
       
   268 
       
   269 def 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 
       
   278 def fileContents(fn):
       
   279     """
       
   280     Return the contents of the named file
       
   281     """
       
   282     return open(fn, 'rb').read()
       
   283 
       
   284 def 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()
       
   292     if xit is not None:
       
   293         sys.stdout.write(data)
       
   294         raise RuntimeError, "command failed: %s"%(commandline,)
       
   295 
       
   296     if VERBOSE:
       
   297         sys.stdout.write(data); sys.stdout.flush()
       
   298 
       
   299 def captureCommand(commandline):
       
   300     fd = os.popen(commandline, 'r')
       
   301     data = fd.read()
       
   302     xit = fd.close()
       
   303     if xit is not None:
       
   304         sys.stdout.write(data)
       
   305         raise RuntimeError, "command failed: %s"%(commandline,)
       
   306 
       
   307     return data
       
   308 
       
   309 def 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 
       
   326 def parseOptions(args=None):
       
   327     """
       
   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 
       
   381 def 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 
       
   428 KNOWNSIZES = {
       
   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 
       
   433 def 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 
       
   460 def 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 
       
   511     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         ]
       
   518 
       
   519         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)
       
   526 
       
   527         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"%(
       
   534                     shellQuote(SDKPATH)[1:-1],
       
   535                     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             ])
       
   545 
       
   546         if 'configure_post' in recipe:
       
   547             configure_args = configure_args = list(recipe['configure_post'])
       
   548 
       
   549         configure_args.insert(0, configure)
       
   550         configure_args = [ shellQuote(a) for a in configure_args ]
       
   551 
       
   552         print "Running configure for %s"%(name,)
       
   553         runCommand(' '.join(configure_args) + ' 2>&1')
       
   554 
       
   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 
       
   563 def 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:
       
   576         buildRecipe(recipe, universal, ARCHLIST)
       
   577 
       
   578 
       
   579 
       
   580 def 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 
       
   588     novername = 'python-docs-html.tar.bz2'
       
   589     name = 'html-%s.tar.bz2'%(getFullVersion(),)
       
   590     sourceArchive = os.path.join(DEPSRC, name)
       
   591     if os.path.exists(sourceArchive):
       
   592         print "Using local copy of %s"%(name,)
       
   593 
       
   594     else:
       
   595         print "Downloading %s"%(novername,)
       
   596         downloadURL('http://www.python.org/ftp/python/doc/%s/%s'%(
       
   597             getFullVersion(), novername), sourceArchive)
       
   598         print "Archive for %s stored as %s"%(name, sourceArchive)
       
   599 
       
   600     extractArchive(os.path.dirname(docdir), sourceArchive)
       
   601 
       
   602     os.rename(
       
   603             os.path.join(
       
   604                 os.path.dirname(docdir), 'python-docs-html'),
       
   605             docdir)
       
   606 
       
   607 
       
   608 def 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 
       
   641     print "Running make frameworkinstall"
       
   642     runCommand("make frameworkinstall DESTDIR=%s"%(
       
   643         shellQuote(rootDir)))
       
   644 
       
   645     print "Running make frameworkinstallextras"
       
   646     runCommand("make frameworkinstallextras DESTDIR=%s"%(
       
   647         shellQuote(rootDir)))
       
   648 
       
   649     print "Copying required shared libraries"
       
   650     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')
       
   662     gid = grp.getgrnam('admin').gr_gid
       
   663 
       
   664     for dirpath, dirnames, filenames in os.walk(frmDir):
       
   665         for dn in dirnames:
       
   666             os.chmod(os.path.join(dirpath, dn), 0775)
       
   667             os.chown(os.path.join(dirpath, dn), -1, gid)
       
   668 
       
   669 
       
   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)
       
   677             os.chmod(p, stat.S_IMODE(st.st_mode) | stat.S_IWGRP)
       
   678             os.chown(p, -1, gid)
       
   679 
       
   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 
       
   712 def 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())
       
   719 
       
   720     # This one is not handy as a template variable
       
   721     data = data.replace('$PYTHONFRAMEWORKINSTALLDIR', '/Library/Frameworks/Python.framework')
       
   722     fp = open(outPath, 'wb')
       
   723     fp.write(data)
       
   724     fp.close()
       
   725 
       
   726 def 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 
       
   736 def packageFromRecipe(targetDir, recipe):
       
   737     curdir = os.getcwd()
       
   738     try:
       
   739         # 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
       
   741         # common.
       
   742         pkgname = '%s-%s'%(recipe['name'], getVersion())
       
   743         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 
       
   828 def 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(
       
   843                     IFPkgFlagPackageLocation='%s-%s.pkg'%(item['name'], getVersion()),
       
   844                     IFPkgFlagPackageSelection='selected'
       
   845                 )
       
   846                 for item in PKG_RECIPES
       
   847             ],
       
   848             IFPkgFormatVersion=0.10000000149011612,
       
   849             IFPkgFlagBackgroundScaling="proportional",
       
   850             IFPkgFlagBackgroundAlignment="left",
       
   851             IFPkgFlagAuthorizationAction="RootAuthorization",
       
   852         )
       
   853 
       
   854     writePlist(pl, path)
       
   855 
       
   856 
       
   857 def 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 
       
   899     shutil.copy("../../LICENSE", os.path.join(rsrcDir, 'License.txt'))
       
   900 
       
   901 
       
   902 def 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 
       
   912 def buildDMG():
       
   913     """
       
   914     Create DMG containing the rootDir.
       
   915     """
       
   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)
       
   927     runCommand("hdiutil create -volname 'Universal MacPython %s' -srcfolder %s %s"%(
       
   928             getFullVersion(),
       
   929             shellQuote(os.path.join(WORKDIR, 'installer')),
       
   930             shellQuote(imagepath)))
       
   931 
       
   932     return imagepath
       
   933 
       
   934 
       
   935 def 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:
       
   955         # There is a problem with getting this into the pax(1) archive,
       
   956         # just ignore directory icons for now.
       
   957         return
       
   958 
       
   959         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 
       
  1015 def 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",
       
  1033                 "Python %s"%(getVersion(),), "Update Shell Profile.command")
       
  1034     patchFile("scripts/postflight.patch-profile",  fn)
       
  1035     os.chmod(fn, 0755)
       
  1036 
       
  1037     folder = os.path.join(WORKDIR, "_root", "Applications", "Python %s"%(
       
  1038         getVersion(),))
       
  1039     os.chmod(folder, 0755)
       
  1040     setIcon(folder, "../Icons/Python Folder.icns")
       
  1041 
       
  1042     # Create the installer
       
  1043     buildInstaller()
       
  1044 
       
  1045     # And copy the readme into the directory containing the installer
       
  1046     patchFile('resources/ReadMe.txt', os.path.join(WORKDIR, 'installer', 'ReadMe.txt'))
       
  1047 
       
  1048     # Ditto for the license file.
       
  1049     shutil.copy('../../LICENSE', os.path.join(WORKDIR, 'installer', 'License.txt'))
       
  1050 
       
  1051     fp = open(os.path.join(WORKDIR, 'installer', 'Build.txt'), 'w')
       
  1052     print >> fp, "# BUILD INFO"
       
  1053     print >> fp, "# Date:", time.ctime()
       
  1054     print >> fp, "# By:", pwd.getpwuid(os.getuid()).pw_gecos
       
  1055     fp.close()
       
  1056 
       
  1057     # Custom icon for the DMG, shown when the DMG is mounted.
       
  1058     shutil.copy("../Icons/Disk Image.icns",
       
  1059             os.path.join(WORKDIR, "installer", ".VolumeIcon.icns"))
       
  1060     os.system("/Developer/Tools/SetFile -a C %s"%(
       
  1061             os.path.join(WORKDIR, "installer", ".VolumeIcon.icns")))
       
  1062 
       
  1063 
       
  1064     # And copy it to a DMG
       
  1065     buildDMG()
       
  1066 
       
  1067 
       
  1068 if __name__ == "__main__":
       
  1069     main()