diff options
author | Jack Jansen <jack.jansen@cwi.nl> | 2002-12-30 22:04:23 (GMT) |
---|---|---|
committer | Jack Jansen <jack.jansen@cwi.nl> | 2002-12-30 22:04:23 (GMT) |
commit | 60087fb45092d9c199cea162e58d9193c7c1558c (patch) | |
tree | 05f3343e7707c4a4179e409506b39601279f04c1 /Lib/plat-mac/buildtools.py | |
parent | c262a1f51ce89dbea4aeb072cf631686c47ed97f (diff) | |
download | cpython-60087fb45092d9c199cea162e58d9193c7c1558c.zip cpython-60087fb45092d9c199cea162e58d9193c7c1558c.tar.gz cpython-60087fb45092d9c199cea162e58d9193c7c1558c.tar.bz2 |
Moved most of Mac/Lib hierarchy to Lib/plat-mac: it can be used both
in MacPython-OS9 and MacPython-OSX (or the equivalent unix Python on
Mac OS X). The only items remaining in Mac/Lib are modules that are
meaningful only for MacPython-OS9 (CFM stuff, MacPython preferences
in resources, etc).
Diffstat (limited to 'Lib/plat-mac/buildtools.py')
-rw-r--r-- | Lib/plat-mac/buildtools.py | 521 |
1 files changed, 521 insertions, 0 deletions
diff --git a/Lib/plat-mac/buildtools.py b/Lib/plat-mac/buildtools.py new file mode 100644 index 0000000..01b5392 --- /dev/null +++ b/Lib/plat-mac/buildtools.py @@ -0,0 +1,521 @@ +"""tools for BuildApplet and BuildApplication""" + +import sys +import os +import string +import imp +import marshal +import macfs +from Carbon import Res +import MACFS +import MacOS +import macostools +import macresource +import EasyDialogs +import shutil + + +BuildError = "BuildError" + +# .pyc file (and 'PYC ' resource magic number) +MAGIC = imp.get_magic() + +# Template file (searched on sys.path) +TEMPLATE = "PythonInterpreter" + +# Specification of our resource +RESTYPE = 'PYC ' +RESNAME = '__main__' + +# A resource with this name sets the "owner" (creator) of the destination +# It should also have ID=0. Either of these alone is not enough. +OWNERNAME = "owner resource" + +# Default applet creator code +DEFAULT_APPLET_CREATOR="Pyta" + +# OpenResFile mode parameters +READ = 1 +WRITE = 2 + + +def findtemplate(template=None): + """Locate the applet template along sys.path""" + if MacOS.runtimemodel == 'macho': + if template: + return template + return findtemplate_macho() + if not template: + template=TEMPLATE + for p in sys.path: + file = os.path.join(p, template) + try: + file, d1, d2 = macfs.ResolveAliasFile(file) + break + except (macfs.error, ValueError): + continue + else: + raise BuildError, "Template %s not found on sys.path" % `template` + file = file.as_pathname() + return file + +def findtemplate_macho(): + execpath = sys.executable.split('/') + if not 'Contents' in execpath: + raise BuildError, "Not running from a .app bundle: %s" % sys.executable + i = execpath.index('Contents') + return '/'.join(execpath[:i]) + + +def process(template, filename, destname, copy_codefragment, + rsrcname=None, others=[], raw=0, progress="default"): + + if progress == "default": + progress = EasyDialogs.ProgressBar("Processing %s..."%os.path.split(filename)[1], 120) + progress.label("Compiling...") + progress.inc(0) + # check for the script name being longer than 32 chars. This may trigger a bug + # on OSX that can destroy your sourcefile. + if '#' in os.path.split(filename)[1]: + raise BuildError, "BuildApplet could destroy your sourcefile on OSX, please rename: %s" % filename + # Read the source and compile it + # (there's no point overwriting the destination if it has a syntax error) + + fp = open(filename, 'rU') + text = fp.read() + fp.close() + try: + code = compile(text, filename, "exec") + except SyntaxError, arg: + raise BuildError, "Syntax error in script %s: %s" % (filename, arg) + except EOFError: + raise BuildError, "End-of-file in script %s" % (filename,) + + # Set the destination file name. Note that basename + # does contain the whole filepath, only a .py is stripped. + + if string.lower(filename[-3:]) == ".py": + basename = filename[:-3] + if MacOS.runtimemodel != 'macho' and not destname: + destname = basename + else: + basename = filename + + if not destname: + if MacOS.runtimemodel == 'macho': + destname = basename + '.app' + else: + destname = basename + '.applet' + if not rsrcname: + rsrcname = basename + '.rsrc' + + # Try removing the output file. This fails in MachO, but it should + # do any harm. + try: + os.remove(destname) + except os.error: + pass + process_common(template, progress, code, rsrcname, destname, 0, + copy_codefragment, raw, others) + + +def update(template, filename, output): + if MacOS.runtimemodel == 'macho': + raise BuildError, "No updating yet for MachO applets" + if progress: + progress = EasyDialogs.ProgressBar("Updating %s..."%os.path.split(filename)[1], 120) + else: + progress = None + if not output: + output = filename + ' (updated)' + + # Try removing the output file + try: + os.remove(output) + except os.error: + pass + process_common(template, progress, None, filename, output, 1, 1) + + +def process_common(template, progress, code, rsrcname, destname, is_update, + copy_codefragment, raw=0, others=[]): + if MacOS.runtimemodel == 'macho': + return process_common_macho(template, progress, code, rsrcname, destname, + is_update, raw, others) + if others: + raise BuildError, "Extra files only allowed for MachoPython applets" + # Create FSSpecs for the various files + template_fss = macfs.FSSpec(template) + template_fss, d1, d2 = macfs.ResolveAliasFile(template_fss) + dest_fss = macfs.FSSpec(destname) + + # Copy data (not resources, yet) from the template + if progress: + progress.label("Copy data fork...") + progress.set(10) + + if copy_codefragment: + tmpl = open(template, "rb") + dest = open(destname, "wb") + data = tmpl.read() + if data: + dest.write(data) + dest.close() + tmpl.close() + del dest + del tmpl + + # Open the output resource fork + + if progress: + progress.label("Copy resources...") + progress.set(20) + try: + output = Res.FSpOpenResFile(dest_fss, WRITE) + except MacOS.Error: + Res.FSpCreateResFile(destname, '????', 'APPL', MACFS.smAllScripts) + output = Res.FSpOpenResFile(dest_fss, WRITE) + + # Copy the resources from the target specific resource template, if any + typesfound, ownertype = [], None + try: + input = Res.FSpOpenResFile(rsrcname, READ) + except (MacOS.Error, ValueError): + pass + if progress: + progress.inc(50) + else: + if is_update: + skip_oldfile = ['cfrg'] + else: + skip_oldfile = [] + typesfound, ownertype = copyres(input, output, skip_oldfile, 0, progress) + Res.CloseResFile(input) + + # Check which resource-types we should not copy from the template + skiptypes = [] + if 'vers' in typesfound: skiptypes.append('vers') + if 'SIZE' in typesfound: skiptypes.append('SIZE') + if 'BNDL' in typesfound: skiptypes = skiptypes + ['BNDL', 'FREF', 'icl4', + 'icl8', 'ics4', 'ics8', 'ICN#', 'ics#'] + if not copy_codefragment: + skiptypes.append('cfrg') +## skipowner = (ownertype <> None) + + # Copy the resources from the template + + input = Res.FSpOpenResFile(template_fss, READ) + dummy, tmplowner = copyres(input, output, skiptypes, 1, progress) + + Res.CloseResFile(input) +## if ownertype == None: +## raise BuildError, "No owner resource found in either resource file or template" + # Make sure we're manipulating the output resource file now + + Res.UseResFile(output) + + if ownertype == None: + # No owner resource in the template. We have skipped the + # Python owner resource, so we have to add our own. The relevant + # bundle stuff is already included in the interpret/applet template. + newres = Res.Resource('\0') + newres.AddResource(DEFAULT_APPLET_CREATOR, 0, "Owner resource") + ownertype = DEFAULT_APPLET_CREATOR + + if code: + # Delete any existing 'PYC ' resource named __main__ + + try: + res = Res.Get1NamedResource(RESTYPE, RESNAME) + res.RemoveResource() + except Res.Error: + pass + + # Create the raw data for the resource from the code object + if progress: + progress.label("Write PYC resource...") + progress.set(120) + + data = marshal.dumps(code) + del code + data = (MAGIC + '\0\0\0\0') + data + + # Create the resource and write it + + id = 0 + while id < 128: + id = Res.Unique1ID(RESTYPE) + res = Res.Resource(data) + res.AddResource(RESTYPE, id, RESNAME) + attrs = res.GetResAttrs() + attrs = attrs | 0x04 # set preload + res.SetResAttrs(attrs) + res.WriteResource() + res.ReleaseResource() + + # Close the output file + + Res.CloseResFile(output) + + # Now set the creator, type and bundle bit of the destination + dest_finfo = dest_fss.GetFInfo() + dest_finfo.Creator = ownertype + dest_finfo.Type = 'APPL' + dest_finfo.Flags = dest_finfo.Flags | MACFS.kHasBundle | MACFS.kIsShared + dest_finfo.Flags = dest_finfo.Flags & ~MACFS.kHasBeenInited + dest_fss.SetFInfo(dest_finfo) + + macostools.touched(dest_fss) + if progress: + progress.label("Done.") + progress.inc(0) + +def process_common_macho(template, progress, code, rsrcname, destname, is_update, raw=0, others=[]): + # First make sure the name ends in ".app" + if destname[-4:] != '.app': + destname = destname + '.app' + # Now deduce the short name + shortname = os.path.split(destname)[1] + if shortname[-4:] == '.app': + # Strip the .app suffix + shortname = shortname[:-4] + # And deduce the .plist and .icns names + plistname = None + icnsname = None + if rsrcname and rsrcname[-5:] == '.rsrc': + tmp = rsrcname[:-5] + plistname = tmp + '.plist' + if os.path.exists(plistname): + icnsname = tmp + '.icns' + if not os.path.exists(icnsname): + icnsname = None + else: + plistname = None + # Start with copying the .app framework + if not is_update: + exceptlist = ["Contents/Info.plist", + "Contents/Resources/English.lproj/InfoPlist.strings", + "Contents/Resources/English.lproj/Documentation", + "Contents/Resources/python.rsrc", + ] + copyapptree(template, destname, exceptlist, progress) + # SERIOUS HACK. If we've just copied a symlink as the + # executable we assume we're running from the MacPython addon + # to 10.2 python. We remove the symlink again and install + # the appletrunner script. + executable = os.path.join(destname, "Contents/MacOS/python") + if os.path.islink(executable): + os.remove(executable) + dummyfp, appletrunner, d2 = imp.find_module('appletrunner') + del dummyfp + shutil.copy2(appletrunner, executable) + os.chmod(executable, 0775) + # Now either use the .plist file or the default + if progress: + progress.label('Create info.plist') + progress.inc(0) + if plistname: + shutil.copy2(plistname, os.path.join(destname, 'Contents', 'Info.plist')) + if icnsname: + icnsdest = os.path.split(icnsname)[1] + icnsdest = os.path.join(destname, + os.path.join('Contents', 'Resources', icnsdest)) + shutil.copy2(icnsname, icnsdest) + # XXXX Wrong. This should be parsed from plist file. Also a big hack:-) + if shortname == 'PythonIDE': + ownertype = 'Pide' + else: + ownertype = 'PytA' + # XXXX Should copy .icns file + else: + cocoainfo = '' + for o in others: + if o[-4:] == '.nib': + nibname = os.path.split(o)[1][:-4] + cocoainfo = """ + <key>NSMainNibFile</key> + <string>%s</string> + <key>NSPrincipalClass</key> + <string>NSApplication</string>""" % nibname + elif o[-6:] == '.lproj': + files = os.listdir(o) + for f in files: + if f[-4:] == '.nib': + nibname = os.path.split(f)[1][:-4] + cocoainfo = """ + <key>NSMainNibFile</key> + <string>%s</string> + <key>NSPrincipalClass</key> + <string>NSApplication</string>""" % nibname + + plistname = os.path.join(template, 'Contents', 'Resources', 'Applet-Info.plist') + plistdata = open(plistname).read() + plistdata = plistdata % {'appletname':shortname, 'cocoainfo':cocoainfo} + ofp = open(os.path.join(destname, 'Contents', 'Info.plist'), 'w') + ofp.write(plistdata) + ofp.close() + ownertype = 'PytA' + # Create the PkgInfo file + if progress: + progress.label('Create PkgInfo') + progress.inc(0) + ofp = open(os.path.join(destname, 'Contents', 'PkgInfo'), 'wb') + ofp.write('APPL' + ownertype) + ofp.close() + + + # Copy the resources from the target specific resource template, if any + typesfound, ownertype = [], None + try: + input = macresource.open_pathname(rsrcname) + except (MacOS.Error, ValueError): + if progress: + progress.inc(50) + else: + if progress: + progress.label("Copy resources...") + progress.set(20) + resfilename = 'python.rsrc' # XXXX later: '%s.rsrc' % shortname + try: + output = Res.FSOpenResourceFile( + os.path.join(destname, 'Contents', 'Resources', resfilename), + u'', WRITE) + except MacOS.Error: + fsr, dummy = Res.FSCreateResourceFile( + os.path.join(destname, 'Contents', 'Resources'), + unicode(resfilename), '') + output = Res.FSOpenResourceFile(fsr, u'', WRITE) + + typesfound, ownertype = copyres(input, output, [], 0, progress) + Res.CloseResFile(input) + Res.CloseResFile(output) + + if code: + if raw: + pycname = '__rawmain__.pyc' + else: + pycname = '__main__.pyc' + # And we also create __rawmain__.pyc + outputfilename = os.path.join(destname, 'Contents', 'Resources', '__rawmain__.pyc') + if progress: + progress.label('Creating __rawmain__.pyc') + progress.inc(0) + rawsourcefp, rawsourcefile, d2 = imp.find_module('appletrawmain') + rawsource = rawsourcefp.read() + rawcode = compile(rawsource, rawsourcefile, 'exec') + writepycfile(rawcode, outputfilename) + + outputfilename = os.path.join(destname, 'Contents', 'Resources', pycname) + if progress: + progress.label('Creating '+pycname) + progress.inc(0) + writepycfile(code, outputfilename) + # Copy other files the user asked for + for osrc in others: + oname = os.path.split(osrc)[1] + odst = os.path.join(destname, 'Contents', 'Resources', oname) + if progress: + progress.label('Copy ' + oname) + progress.inc(0) + if os.path.isdir(osrc): + copyapptree(osrc, odst) + else: + shutil.copy2(osrc, odst) + if progress: + progress.label('Done.') + progress.inc(0) + +## macostools.touched(dest_fss) + +# Copy resources between two resource file descriptors. +# skip a resource named '__main__' or (if skipowner is set) with ID zero. +# Also skip resources with a type listed in skiptypes. +# +def copyres(input, output, skiptypes, skipowner, progress=None): + ctor = None + alltypes = [] + Res.UseResFile(input) + ntypes = Res.Count1Types() + progress_type_inc = 50/ntypes + for itype in range(1, 1+ntypes): + type = Res.Get1IndType(itype) + if type in skiptypes: + continue + alltypes.append(type) + nresources = Res.Count1Resources(type) + progress_cur_inc = progress_type_inc/nresources + for ires in range(1, 1+nresources): + res = Res.Get1IndResource(type, ires) + id, type, name = res.GetResInfo() + lcname = string.lower(name) + + if lcname == OWNERNAME and id == 0: + if skipowner: + continue # Skip this one + else: + ctor = type + size = res.size + attrs = res.GetResAttrs() + if progress: + progress.label("Copy %s %d %s"%(type, id, name)) + progress.inc(progress_cur_inc) + res.LoadResource() + res.DetachResource() + Res.UseResFile(output) + try: + res2 = Res.Get1Resource(type, id) + except MacOS.Error: + res2 = None + if res2: + if progress: + progress.label("Overwrite %s %d %s"%(type, id, name)) + progress.inc(0) + res2.RemoveResource() + res.AddResource(type, id, name) + res.WriteResource() + attrs = attrs | res.GetResAttrs() + res.SetResAttrs(attrs) + Res.UseResFile(input) + return alltypes, ctor + +def copyapptree(srctree, dsttree, exceptlist=[], progress=None): + names = [] + if os.path.exists(dsttree): + shutil.rmtree(dsttree) + os.mkdir(dsttree) + todo = os.listdir(srctree) + while todo: + this, todo = todo[0], todo[1:] + if this in exceptlist: + continue + thispath = os.path.join(srctree, this) + if os.path.isdir(thispath): + thiscontent = os.listdir(thispath) + for t in thiscontent: + todo.append(os.path.join(this, t)) + names.append(this) + for this in names: + srcpath = os.path.join(srctree, this) + dstpath = os.path.join(dsttree, this) + if os.path.isdir(srcpath): + os.mkdir(dstpath) + elif os.path.islink(srcpath): + endpoint = os.readlink(srcpath) + os.symlink(endpoint, dstpath) + else: + if progress: + progress.label('Copy '+this) + progress.inc(0) + shutil.copy2(srcpath, dstpath) + +def writepycfile(codeobject, cfile): + import marshal + fc = open(cfile, 'wb') + fc.write('\0\0\0\0') # MAGIC placeholder, written later + fc.write('\0\0\0\0') # Timestap placeholder, not needed + marshal.dump(codeobject, fc) + fc.flush() + fc.seek(0, 0) + fc.write(MAGIC) + fc.close() + |