diff options
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 0000000000..01b5392e6c --- /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() +  | 
