| """tools for BuildApplet and BuildApplication""" | |
| import warnings | |
| warnings.warnpy3k("the buildtools module is deprecated and is removed in 3.0", | |
| stacklevel=2) | |
| import sys | |
| import os | |
| import string | |
| import imp | |
| import marshal | |
| from Carbon import Res | |
| import Carbon.Files | |
| import Carbon.File | |
| import MacOS | |
| import macostools | |
| import macresource | |
| try: | |
| import EasyDialogs | |
| except ImportError: | |
| EasyDialogs = None | |
| 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 | |
| # Parameter for FSOpenResourceFile | |
| RESOURCE_FORK_NAME=Carbon.File.FSGetResourceForkName() | |
| def findtemplate(template=None): | |
| """Locate the applet template along sys.path""" | |
| if MacOS.runtimemodel == 'macho': | |
| return None | |
| if not template: | |
| template=TEMPLATE | |
| for p in sys.path: | |
| file = os.path.join(p, template) | |
| try: | |
| file, d1, d2 = Carbon.File.FSResolveAliasFile(file, 1) | |
| break | |
| except (Carbon.File.Error, ValueError): | |
| continue | |
| else: | |
| raise BuildError, "Template %r not found on sys.path" % (template,) | |
| file = file.as_pathname() | |
| return file | |
| def process(template, filename, destname, copy_codefragment=0, | |
| rsrcname=None, others=[], raw=0, progress="default", destroot=""): | |
| if progress == "default": | |
| if EasyDialogs is None: | |
| print "Compiling %s"%(os.path.split(filename)[1],) | |
| process = None | |
| else: | |
| 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 + '\n', 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, filename, destroot) | |
| def update(template, filename, output): | |
| if MacOS.runtimemodel == 'macho': | |
| raise BuildError, "No updating yet for MachO applets" | |
| if progress: | |
| if EasyDialogs is None: | |
| print "Updating %s"%(os.path.split(filename)[1],) | |
| progress = None | |
| else: | |
| 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=[], filename=None, destroot=""): | |
| if MacOS.runtimemodel == 'macho': | |
| return process_common_macho(template, progress, code, rsrcname, destname, | |
| is_update, raw, others, filename, destroot) | |
| if others: | |
| raise BuildError, "Extra files only allowed for MachoPython applets" | |
| # Create FSSpecs for the various files | |
| template_fsr, d1, d2 = Carbon.File.FSResolveAliasFile(template, 1) | |
| template = template_fsr.as_pathname() | |
| # 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.FSOpenResourceFile(destname, RESOURCE_FORK_NAME, WRITE) | |
| except MacOS.Error: | |
| destdir, destfile = os.path.split(destname) | |
| Res.FSCreateResourceFile(destdir, unicode(destfile), RESOURCE_FORK_NAME) | |
| output = Res.FSOpenResourceFile(destname, RESOURCE_FORK_NAME, WRITE) | |
| # Copy the resources from the target specific resource template, if any | |
| typesfound, ownertype = [], None | |
| try: | |
| input = Res.FSOpenResourceFile(rsrcname, RESOURCE_FORK_NAME, 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.FSOpenResourceFile(template, RESOURCE_FORK_NAME, READ) | |
| dummy, tmplowner = copyres(input, output, skiptypes, 1, progress) | |
| Res.CloseResFile(input) | |
| ## if ownertype is 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 is 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. | |
| # Done with FSSpec's, FSRef FInfo isn't good enough yet (2.3a1+) | |
| dest_fss = Carbon.File.FSSpec(destname) | |
| dest_finfo = dest_fss.FSpGetFInfo() | |
| dest_finfo.Creator = ownertype | |
| dest_finfo.Type = 'APPL' | |
| dest_finfo.Flags = dest_finfo.Flags | Carbon.Files.kHasBundle | Carbon.Files.kIsShared | |
| dest_finfo.Flags = dest_finfo.Flags & ~Carbon.Files.kHasBeenInited | |
| dest_fss.FSpSetFInfo(dest_finfo) | |
| macostools.touched(destname) | |
| if progress: | |
| progress.label("Done.") | |
| progress.inc(0) | |
| def process_common_macho(template, progress, code, rsrcname, destname, is_update, | |
| raw=0, others=[], filename=None, destroot=""): | |
| # Check that we have a filename | |
| if filename is None: | |
| raise BuildError, "Need source filename on MacOSX" | |
| # First make sure the name ends in ".app" | |
| if destname[-4:] != '.app': | |
| destname = destname + '.app' | |
| # Now deduce the short name | |
| destdir, shortname = os.path.split(destname) | |
| 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 | |
| if not icnsname: | |
| dft_icnsname = os.path.join(sys.prefix, 'Resources/Python.app/Contents/Resources/PythonApplet.icns') | |
| if os.path.exists(dft_icnsname): | |
| icnsname = dft_icnsname | |
| if not os.path.exists(rsrcname): | |
| rsrcname = None | |
| if progress: | |
| progress.label('Creating bundle...') | |
| import bundlebuilder | |
| builder = bundlebuilder.AppBuilder(verbosity=0) | |
| builder.mainprogram = filename | |
| builder.builddir = destdir | |
| builder.name = shortname | |
| builder.destroot = destroot | |
| if rsrcname: | |
| realrsrcname = macresource.resource_pathname(rsrcname) | |
| builder.files.append((realrsrcname, | |
| os.path.join('Contents/Resources', os.path.basename(rsrcname)))) | |
| for o in others: | |
| if type(o) == str: | |
| builder.resources.append(o) | |
| else: | |
| builder.files.append(o) | |
| if plistname: | |
| import plistlib | |
| builder.plist = plistlib.Plist.fromFile(plistname) | |
| if icnsname: | |
| builder.iconfile = icnsname | |
| if not raw: | |
| builder.argv_emulation = 1 | |
| builder.setup() | |
| builder.build() | |
| 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() |