Commit 388fbf3d authored by Jack Jansen's avatar Jack Jansen

- Better commandline interface to BuildApplet, complete with options,

  verbose output to the console, etc.
- Allow Cocoa applets to be built with BuildApplet.

No full testing has been done yet to ensure OS9 operation hasn't suffered.
parent 2befa489
...@@ -17,9 +17,6 @@ import shutil ...@@ -17,9 +17,6 @@ import shutil
BuildError = "BuildError" BuildError = "BuildError"
DEBUG=1
# .pyc file (and 'PYC ' resource magic number) # .pyc file (and 'PYC ' resource magic number)
MAGIC = imp.get_magic() MAGIC = imp.get_magic()
...@@ -70,13 +67,13 @@ def findtemplate_macho(): ...@@ -70,13 +67,13 @@ def findtemplate_macho():
return '/'.join(execpath[:i]) return '/'.join(execpath[:i])
def process(template, filename, output, copy_codefragment): def process(template, filename, destname, copy_codefragment,
rsrcname=None, others=[], raw=0, progress="default"):
if DEBUG: if progress == "default":
progress = EasyDialogs.ProgressBar("Processing %s..."%os.path.split(filename)[1], 120) progress = EasyDialogs.ProgressBar("Processing %s..."%os.path.split(filename)[1], 120)
progress.label("Compiling...") progress.label("Compiling...")
else: progress.inc(0)
progress = None
# Read the source and compile it # Read the source and compile it
# (there's no point overwriting the destination if it has a syntax error) # (there's no point overwriting the destination if it has a syntax error)
...@@ -89,20 +86,23 @@ def process(template, filename, output, copy_codefragment): ...@@ -89,20 +86,23 @@ def process(template, filename, output, copy_codefragment):
except (SyntaxError, EOFError): except (SyntaxError, EOFError):
raise BuildError, "Syntax error in script %s" % `filename` raise BuildError, "Syntax error in script %s" % `filename`
# Set the destination file name # Set the destination file name. Note that basename
# does contain the whole filepath, only a .py is stripped.
if string.lower(filename[-3:]) == ".py": if string.lower(filename[-3:]) == ".py":
destname = filename[:-3] basename = filename[:-3]
rsrcname = destname + '.rsrc' if MacOS.runtimemodel != 'macho' and not destname:
destname = basename
else: else:
basename = filename
if not destname:
if MacOS.runtimemodel == 'macho': if MacOS.runtimemodel == 'macho':
destname = filename + '.app' destname = basename + '.app'
else: else:
destname = filename + ".applet" destname = basename + '.applet'
rsrcname = filename + '.rsrc' if not rsrcname:
rsrcname = basename + '.rsrc'
if output:
destname = output
# Try removing the output file. This fails in MachO, but it should # Try removing the output file. This fails in MachO, but it should
# do any harm. # do any harm.
...@@ -110,13 +110,14 @@ def process(template, filename, output, copy_codefragment): ...@@ -110,13 +110,14 @@ def process(template, filename, output, copy_codefragment):
os.remove(destname) os.remove(destname)
except os.error: except os.error:
pass pass
process_common(template, progress, code, rsrcname, destname, 0, copy_codefragment) process_common(template, progress, code, rsrcname, destname, 0,
copy_codefragment, raw, others)
def update(template, filename, output): def update(template, filename, output):
if MacOS.runtimemodel == 'macho': if MacOS.runtimemodel == 'macho':
raise BuildError, "No updating yet for MachO applets" raise BuildError, "No updating yet for MachO applets"
if DEBUG: if progress:
progress = EasyDialogs.ProgressBar("Updating %s..."%os.path.split(filename)[1], 120) progress = EasyDialogs.ProgressBar("Updating %s..."%os.path.split(filename)[1], 120)
else: else:
progress = None progress = None
...@@ -131,16 +132,20 @@ def update(template, filename, output): ...@@ -131,16 +132,20 @@ def update(template, filename, output):
process_common(template, progress, None, filename, output, 1, 1) process_common(template, progress, None, filename, output, 1, 1)
def process_common(template, progress, code, rsrcname, destname, is_update, copy_codefragment): def process_common(template, progress, code, rsrcname, destname, is_update,
copy_codefragment, raw=0, others=[]):
if MacOS.runtimemodel == 'macho': if MacOS.runtimemodel == 'macho':
return process_common_macho(template, progress, code, rsrcname, destname, is_update) 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 # Create FSSpecs for the various files
template_fss = macfs.FSSpec(template) template_fss = macfs.FSSpec(template)
template_fss, d1, d2 = macfs.ResolveAliasFile(template_fss) template_fss, d1, d2 = macfs.ResolveAliasFile(template_fss)
dest_fss = macfs.FSSpec(destname) dest_fss = macfs.FSSpec(destname)
# Copy data (not resources, yet) from the template # Copy data (not resources, yet) from the template
if DEBUG: if progress:
progress.label("Copy data fork...") progress.label("Copy data fork...")
progress.set(10) progress.set(10)
...@@ -157,7 +162,7 @@ def process_common(template, progress, code, rsrcname, destname, is_update, copy ...@@ -157,7 +162,7 @@ def process_common(template, progress, code, rsrcname, destname, is_update, copy
# Open the output resource fork # Open the output resource fork
if DEBUG: if progress:
progress.label("Copy resources...") progress.label("Copy resources...")
progress.set(20) progress.set(20)
try: try:
...@@ -172,7 +177,7 @@ def process_common(template, progress, code, rsrcname, destname, is_update, copy ...@@ -172,7 +177,7 @@ def process_common(template, progress, code, rsrcname, destname, is_update, copy
input = Res.FSpOpenResFile(rsrcname, READ) input = Res.FSpOpenResFile(rsrcname, READ)
except (MacOS.Error, ValueError): except (MacOS.Error, ValueError):
pass pass
if DEBUG: if progress:
progress.inc(50) progress.inc(50)
else: else:
if is_update: if is_update:
...@@ -222,7 +227,7 @@ def process_common(template, progress, code, rsrcname, destname, is_update, copy ...@@ -222,7 +227,7 @@ def process_common(template, progress, code, rsrcname, destname, is_update, copy
pass pass
# Create the raw data for the resource from the code object # Create the raw data for the resource from the code object
if DEBUG: if progress:
progress.label("Write PYC resource...") progress.label("Write PYC resource...")
progress.set(120) progress.set(120)
...@@ -256,10 +261,11 @@ def process_common(template, progress, code, rsrcname, destname, is_update, copy ...@@ -256,10 +261,11 @@ def process_common(template, progress, code, rsrcname, destname, is_update, copy
dest_fss.SetFInfo(dest_finfo) dest_fss.SetFInfo(dest_finfo)
macostools.touched(dest_fss) macostools.touched(dest_fss)
if DEBUG: if progress:
progress.label("Done.") progress.label("Done.")
progress.inc(0)
def process_common_macho(template, progress, code, rsrcname, destname, is_update): def process_common_macho(template, progress, code, rsrcname, destname, is_update, raw=0, others=[]):
# First make sure the name ends in ".app" # First make sure the name ends in ".app"
if destname[-4:] != '.app': if destname[-4:] != '.app':
destname = destname + '.app' destname = destname + '.app'
...@@ -286,14 +292,17 @@ def process_common_macho(template, progress, code, rsrcname, destname, is_update ...@@ -286,14 +292,17 @@ def process_common_macho(template, progress, code, rsrcname, destname, is_update
"Contents/Resources/English.lproj/InfoPlist.strings", "Contents/Resources/English.lproj/InfoPlist.strings",
"Contents/Resources/python.rsrc", "Contents/Resources/python.rsrc",
] ]
copyapptree(template, destname, exceptlist) copyapptree(template, destname, exceptlist, progress)
# Now either use the .plist file or the default # Now either use the .plist file or the default
if progress:
progress.label('Create info.plist')
progress.inc(0)
if plistname: if plistname:
shutil.copy2(plistname, os.path.join(destname, 'Contents/Info.plist')) shutil.copy2(plistname, os.path.join(destname, 'Contents', 'Info.plist'))
if icnsname: if icnsname:
icnsdest = os.path.split(icnsname)[1] icnsdest = os.path.split(icnsname)[1]
icnsdest = os.path.join(destname, icnsdest = os.path.join(destname,
os.path.join('Contents/Resources', icnsdest)) os.path.join('Contents', 'Resources', icnsdest))
shutil.copy2(icnsname, icnsdest) shutil.copy2(icnsname, icnsdest)
# XXXX Wrong. This should be parsed from plist file. Also a big hack:-) # XXXX Wrong. This should be parsed from plist file. Also a big hack:-)
if shortname == 'PythonIDE': if shortname == 'PythonIDE':
...@@ -302,31 +311,44 @@ def process_common_macho(template, progress, code, rsrcname, destname, is_update ...@@ -302,31 +311,44 @@ def process_common_macho(template, progress, code, rsrcname, destname, is_update
ownertype = 'PytA' ownertype = 'PytA'
# XXXX Should copy .icns file # XXXX Should copy .icns file
else: else:
plistname = os.path.join(template, 'Contents/Resources/Applet-Info.plist') 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
plistname = os.path.join(template, 'Contents', 'Resources', 'Applet-Info.plist')
plistdata = open(plistname).read() plistdata = open(plistname).read()
plistdata = plistdata % {'appletname':shortname} plistdata = plistdata % {'appletname':shortname, 'cocoainfo':cocoainfo}
ofp = open(os.path.join(destname, 'Contents/Info.plist'), 'w') ofp = open(os.path.join(destname, 'Contents', 'Info.plist'), 'w')
ofp.write(plistdata) ofp.write(plistdata)
ofp.close() ofp.close()
ownertype = 'PytA' ownertype = 'PytA'
# Create the PkgInfo file # Create the PkgInfo file
ofp = open(os.path.join(destname, 'Contents/PkgInfo'), 'wb') if progress:
progress.label('Create PkgInfo')
progress.inc(0)
ofp = open(os.path.join(destname, 'Contents', 'PkgInfo'), 'wb')
ofp.write('APPL' + ownertype) ofp.write('APPL' + ownertype)
ofp.close() ofp.close()
if DEBUG: if progress:
progress.label("Copy resources...") progress.label("Copy resources...")
progress.set(20) progress.set(20)
resfilename = '%s.rsrc' % shortname resfilename = '%s.rsrc' % shortname
respartialpathname = 'Contents/Resources/%s' % resfilename
try: try:
output = Res.FSOpenResourceFile( output = Res.FSOpenResourceFile(
os.path.join(destname, respartialpathname), os.path.join(destname, 'Contents', 'Resources', resfilename),
u'', WRITE) u'', WRITE)
except MacOS.Error: except MacOS.Error:
fsr, dummy = Res.FSCreateResourceFile( fsr, dummy = Res.FSCreateResourceFile(
os.path.join(destname, 'Contents/Resources'), os.path.join(destname, 'Contents', 'Resources'),
unicode(resfilename), '') unicode(resfilename), '')
output = Res.FSOpenResourceFile(fsr, u'', WRITE) output = Res.FSOpenResourceFile(fsr, u'', WRITE)
...@@ -336,7 +358,7 @@ def process_common_macho(template, progress, code, rsrcname, destname, is_update ...@@ -336,7 +358,7 @@ def process_common_macho(template, progress, code, rsrcname, destname, is_update
input = macresource.open_pathname(rsrcname) input = macresource.open_pathname(rsrcname)
except (MacOS.Error, ValueError): except (MacOS.Error, ValueError):
pass pass
if DEBUG: if progress:
progress.inc(50) progress.inc(50)
else: else:
typesfound, ownertype = copyres(input, output, [], 0, progress) typesfound, ownertype = copyres(input, output, [], 0, progress)
...@@ -355,8 +377,12 @@ def process_common_macho(template, progress, code, rsrcname, destname, is_update ...@@ -355,8 +377,12 @@ def process_common_macho(template, progress, code, rsrcname, destname, is_update
# Copy the resources from the template # Copy the resources from the template
input = Res.FSOpenResourceFile( input = Res.FSOpenResourceFile(
os.path.join(template, 'Contents/Resources/python.rsrc'), u'', READ) os.path.join(template, 'Contents', 'Resources', 'python.rsrc'), u'', READ)
dummy, tmplowner = copyres(input, output, skiptypes, 1, progress) if progress:
progress.label("Copy standard resources...")
progress.inc(0)
## dummy, tmplowner = copyres(input, output, skiptypes, 1, progress)
dummy, tmplowner = copyres(input, output, skiptypes, 1, None)
Res.CloseResFile(input) Res.CloseResFile(input)
## if ownertype == None: ## if ownertype == None:
...@@ -366,8 +392,29 @@ def process_common_macho(template, progress, code, rsrcname, destname, is_update ...@@ -366,8 +392,29 @@ def process_common_macho(template, progress, code, rsrcname, destname, is_update
Res.CloseResFile(output) Res.CloseResFile(output)
if code: if code:
outputfilename = os.path.join(destname, 'Contents/Resources/__main__.pyc') if raw:
pycname = '__rawmain__.pyc'
else:
pycname = '__main__.pyc'
outputfilename = os.path.join(destname, 'Contents', 'Resources', pycname)
if progress:
progress.label('Creating '+pycname)
progress.inc(0)
writepycfile(code, outputfilename) 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) ## macostools.touched(dest_fss)
...@@ -400,7 +447,7 @@ def copyres(input, output, skiptypes, skipowner, progress=None): ...@@ -400,7 +447,7 @@ def copyres(input, output, skiptypes, skipowner, progress=None):
ctor = type ctor = type
size = res.size size = res.size
attrs = res.GetResAttrs() attrs = res.GetResAttrs()
if DEBUG and progress: if progress:
progress.label("Copy %s %d %s"%(type, id, name)) progress.label("Copy %s %d %s"%(type, id, name))
progress.inc(progress_cur_inc) progress.inc(progress_cur_inc)
res.LoadResource() res.LoadResource()
...@@ -411,8 +458,9 @@ def copyres(input, output, skiptypes, skipowner, progress=None): ...@@ -411,8 +458,9 @@ def copyres(input, output, skiptypes, skipowner, progress=None):
except MacOS.Error: except MacOS.Error:
res2 = None res2 = None
if res2: if res2:
if DEBUG and progress: if progress:
progress.label("Overwrite %s %d %s"%(type, id, name)) progress.label("Overwrite %s %d %s"%(type, id, name))
progress.inc(0)
res2.RemoveResource() res2.RemoveResource()
res.AddResource(type, id, name) res.AddResource(type, id, name)
res.WriteResource() res.WriteResource()
...@@ -421,7 +469,7 @@ def copyres(input, output, skiptypes, skipowner, progress=None): ...@@ -421,7 +469,7 @@ def copyres(input, output, skiptypes, skipowner, progress=None):
Res.UseResFile(input) Res.UseResFile(input)
return alltypes, ctor return alltypes, ctor
def copyapptree(srctree, dsttree, exceptlist=[]): def copyapptree(srctree, dsttree, exceptlist=[], progress=None):
names = [] names = []
if os.path.exists(dsttree): if os.path.exists(dsttree):
shutil.rmtree(dsttree) shutil.rmtree(dsttree)
...@@ -443,6 +491,9 @@ def copyapptree(srctree, dsttree, exceptlist=[]): ...@@ -443,6 +491,9 @@ def copyapptree(srctree, dsttree, exceptlist=[]):
if os.path.isdir(srcpath): if os.path.isdir(srcpath):
os.mkdir(dstpath) os.mkdir(dstpath)
else: else:
if progress:
progress.label('Copy '+this)
progress.inc(0)
shutil.copy2(srcpath, dstpath) shutil.copy2(srcpath, dstpath)
def writepycfile(codeobject, cfile): def writepycfile(codeobject, cfile):
......
...@@ -23,6 +23,8 @@ ...@@ -23,6 +23,8 @@
<key>CFBundleGetInfoString</key> <key>CFBundleGetInfoString</key>
<string>%(appletname)s, a Python applet</string> <string>%(appletname)s, a Python applet</string>
%(cocoainfo)s
<key>CFBundleIconFile</key> <key>CFBundleIconFile</key>
<string>PythonApplet.icns</string> <string>PythonApplet.icns</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
......
...@@ -15,7 +15,7 @@ import macfs ...@@ -15,7 +15,7 @@ import macfs
import MacOS import MacOS
import EasyDialogs import EasyDialogs
import buildtools import buildtools
import getopt
def main(): def main():
try: try:
...@@ -54,14 +54,71 @@ def buildapplet(): ...@@ -54,14 +54,71 @@ def buildapplet():
buildtools.process(template, filename, dstfilename, 1) buildtools.process(template, filename, dstfilename, 1)
else: else:
SHORTOPTS = "o:r:ne:v?"
LONGOPTS=("output=", "resource=", "noargv", "extra=", "verbose", "help")
try:
options, args = getopt.getopt(sys.argv[1:], SHORTOPTS, LONGOPTS)
except getopt.error:
usage()
if options and len(args) > 1:
sys.stderr.write("Cannot use options when specifying multiple input files")
sys.exit(1)
dstfilename = None
rsrcfilename = None
raw = 0
extras = []
verbose = None
for opt, arg in options:
if opt in ('-o', '--output'):
dstfilename = arg
elif opt in ('-r', '--resource'):
rsrcfilename = arg
elif opt in ('-n', '--noargv'):
raw = 1
elif opt in ('-e', '--extra'):
extras.append(arg)
elif opt in ('-v', '--verbose'):
verbose = Verbose()
elif opt in ('-?', '--help'):
usage()
# Loop over all files to be processed # Loop over all files to be processed
for filename in sys.argv[1:]: for filename in args:
cr, tp = MacOS.GetCreatorAndType(filename) cr, tp = MacOS.GetCreatorAndType(filename)
if tp == 'APPL': if tp == 'APPL':
buildtools.update(template, filename, '') buildtools.update(template, filename, dstfilename)
else: else:
buildtools.process(template, filename, '', 1) buildtools.process(template, filename, dstfilename, 1,
rsrcname=rsrcfilename, others=extras, raw=raw, progress=verbose)
def usage():
print "BuildApplet creates an application from a Python source file"
print "Usage:"
print " BuildApplet interactive, single file, no options"
print " BuildApplet src1.py src2.py ... non-interactive multiple file"
print " BuildApplet [options] src.py non-interactive single file"
print "Options:"
print " --output o Output file; default based on source filename, short -o"
print " --resource r Resource file; default based on source filename, short -r"
print " --noargv Build applet without drag-and-drop sys.argv emulation, short -n, OSX only"
print " --extra f Extra file to put in .app bundle, short -e, OSX only"
print " --verbose Verbose, short -v"
print " --help This message, short -?"
sys.exit(1)
class Verbose:
"""This class mimics EasyDialogs.ProgressBar but prints to stderr"""
def __init__(self, *args):
if args and args[0]:
self.label(args[0])
def set(self, *args):
pass
def inc(self, *args):
pass
def label(self, str):
sys.stderr.write(str+'\n')
if __name__ == '__main__': if __name__ == '__main__':
main() main()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment