| 1 | """tools for BuildApplet and BuildApplication"""
|
|---|
| 2 |
|
|---|
| 3 | import sys
|
|---|
| 4 | import os
|
|---|
| 5 | import string
|
|---|
| 6 | import imp
|
|---|
| 7 | import marshal
|
|---|
| 8 | from Carbon import Res
|
|---|
| 9 | import Carbon.Files
|
|---|
| 10 | import Carbon.File
|
|---|
| 11 | import MacOS
|
|---|
| 12 | import macostools
|
|---|
| 13 | import macresource
|
|---|
| 14 | import EasyDialogs
|
|---|
| 15 | import shutil
|
|---|
| 16 |
|
|---|
| 17 |
|
|---|
| 18 | BuildError = "BuildError"
|
|---|
| 19 |
|
|---|
| 20 | # .pyc file (and 'PYC ' resource magic number)
|
|---|
| 21 | MAGIC = imp.get_magic()
|
|---|
| 22 |
|
|---|
| 23 | # Template file (searched on sys.path)
|
|---|
| 24 | TEMPLATE = "PythonInterpreter"
|
|---|
| 25 |
|
|---|
| 26 | # Specification of our resource
|
|---|
| 27 | RESTYPE = 'PYC '
|
|---|
| 28 | RESNAME = '__main__'
|
|---|
| 29 |
|
|---|
| 30 | # A resource with this name sets the "owner" (creator) of the destination
|
|---|
| 31 | # It should also have ID=0. Either of these alone is not enough.
|
|---|
| 32 | OWNERNAME = "owner resource"
|
|---|
| 33 |
|
|---|
| 34 | # Default applet creator code
|
|---|
| 35 | DEFAULT_APPLET_CREATOR="Pyta"
|
|---|
| 36 |
|
|---|
| 37 | # OpenResFile mode parameters
|
|---|
| 38 | READ = 1
|
|---|
| 39 | WRITE = 2
|
|---|
| 40 |
|
|---|
| 41 | # Parameter for FSOpenResourceFile
|
|---|
| 42 | RESOURCE_FORK_NAME=Carbon.File.FSGetResourceForkName()
|
|---|
| 43 |
|
|---|
| 44 | def findtemplate(template=None):
|
|---|
| 45 | """Locate the applet template along sys.path"""
|
|---|
| 46 | if MacOS.runtimemodel == 'macho':
|
|---|
| 47 | return None
|
|---|
| 48 | if not template:
|
|---|
| 49 | template=TEMPLATE
|
|---|
| 50 | for p in sys.path:
|
|---|
| 51 | file = os.path.join(p, template)
|
|---|
| 52 | try:
|
|---|
| 53 | file, d1, d2 = Carbon.File.FSResolveAliasFile(file, 1)
|
|---|
| 54 | break
|
|---|
| 55 | except (Carbon.File.Error, ValueError):
|
|---|
| 56 | continue
|
|---|
| 57 | else:
|
|---|
| 58 | raise BuildError, "Template %r not found on sys.path" % (template,)
|
|---|
| 59 | file = file.as_pathname()
|
|---|
| 60 | return file
|
|---|
| 61 |
|
|---|
| 62 | def process(template, filename, destname, copy_codefragment=0,
|
|---|
| 63 | rsrcname=None, others=[], raw=0, progress="default", destroot=""):
|
|---|
| 64 |
|
|---|
| 65 | if progress == "default":
|
|---|
| 66 | progress = EasyDialogs.ProgressBar("Processing %s..."%os.path.split(filename)[1], 120)
|
|---|
| 67 | progress.label("Compiling...")
|
|---|
| 68 | progress.inc(0)
|
|---|
| 69 | # check for the script name being longer than 32 chars. This may trigger a bug
|
|---|
| 70 | # on OSX that can destroy your sourcefile.
|
|---|
| 71 | if '#' in os.path.split(filename)[1]:
|
|---|
| 72 | raise BuildError, "BuildApplet could destroy your sourcefile on OSX, please rename: %s" % filename
|
|---|
| 73 | # Read the source and compile it
|
|---|
| 74 | # (there's no point overwriting the destination if it has a syntax error)
|
|---|
| 75 |
|
|---|
| 76 | fp = open(filename, 'rU')
|
|---|
| 77 | text = fp.read()
|
|---|
| 78 | fp.close()
|
|---|
| 79 | try:
|
|---|
| 80 | code = compile(text + '\n', filename, "exec")
|
|---|
| 81 | except SyntaxError, arg:
|
|---|
| 82 | raise BuildError, "Syntax error in script %s: %s" % (filename, arg)
|
|---|
| 83 | except EOFError:
|
|---|
| 84 | raise BuildError, "End-of-file in script %s" % (filename,)
|
|---|
| 85 |
|
|---|
| 86 | # Set the destination file name. Note that basename
|
|---|
| 87 | # does contain the whole filepath, only a .py is stripped.
|
|---|
| 88 |
|
|---|
| 89 | if string.lower(filename[-3:]) == ".py":
|
|---|
| 90 | basename = filename[:-3]
|
|---|
| 91 | if MacOS.runtimemodel != 'macho' and not destname:
|
|---|
| 92 | destname = basename
|
|---|
| 93 | else:
|
|---|
| 94 | basename = filename
|
|---|
| 95 |
|
|---|
| 96 | if not destname:
|
|---|
| 97 | if MacOS.runtimemodel == 'macho':
|
|---|
| 98 | destname = basename + '.app'
|
|---|
| 99 | else:
|
|---|
| 100 | destname = basename + '.applet'
|
|---|
| 101 | if not rsrcname:
|
|---|
| 102 | rsrcname = basename + '.rsrc'
|
|---|
| 103 |
|
|---|
| 104 | # Try removing the output file. This fails in MachO, but it should
|
|---|
| 105 | # do any harm.
|
|---|
| 106 | try:
|
|---|
| 107 | os.remove(destname)
|
|---|
| 108 | except os.error:
|
|---|
| 109 | pass
|
|---|
| 110 | process_common(template, progress, code, rsrcname, destname, 0,
|
|---|
| 111 | copy_codefragment, raw, others, filename, destroot)
|
|---|
| 112 |
|
|---|
| 113 |
|
|---|
| 114 | def update(template, filename, output):
|
|---|
| 115 | if MacOS.runtimemodel == 'macho':
|
|---|
| 116 | raise BuildError, "No updating yet for MachO applets"
|
|---|
| 117 | if progress:
|
|---|
| 118 | progress = EasyDialogs.ProgressBar("Updating %s..."%os.path.split(filename)[1], 120)
|
|---|
| 119 | else:
|
|---|
| 120 | progress = None
|
|---|
| 121 | if not output:
|
|---|
| 122 | output = filename + ' (updated)'
|
|---|
| 123 |
|
|---|
| 124 | # Try removing the output file
|
|---|
| 125 | try:
|
|---|
| 126 | os.remove(output)
|
|---|
| 127 | except os.error:
|
|---|
| 128 | pass
|
|---|
| 129 | process_common(template, progress, None, filename, output, 1, 1)
|
|---|
| 130 |
|
|---|
| 131 |
|
|---|
| 132 | def process_common(template, progress, code, rsrcname, destname, is_update,
|
|---|
| 133 | copy_codefragment, raw=0, others=[], filename=None, destroot=""):
|
|---|
| 134 | if MacOS.runtimemodel == 'macho':
|
|---|
| 135 | return process_common_macho(template, progress, code, rsrcname, destname,
|
|---|
| 136 | is_update, raw, others, filename, destroot)
|
|---|
| 137 | if others:
|
|---|
| 138 | raise BuildError, "Extra files only allowed for MachoPython applets"
|
|---|
| 139 | # Create FSSpecs for the various files
|
|---|
| 140 | template_fsr, d1, d2 = Carbon.File.FSResolveAliasFile(template, 1)
|
|---|
| 141 | template = template_fsr.as_pathname()
|
|---|
| 142 |
|
|---|
| 143 | # Copy data (not resources, yet) from the template
|
|---|
| 144 | if progress:
|
|---|
| 145 | progress.label("Copy data fork...")
|
|---|
| 146 | progress.set(10)
|
|---|
| 147 |
|
|---|
| 148 | if copy_codefragment:
|
|---|
| 149 | tmpl = open(template, "rb")
|
|---|
| 150 | dest = open(destname, "wb")
|
|---|
| 151 | data = tmpl.read()
|
|---|
| 152 | if data:
|
|---|
| 153 | dest.write(data)
|
|---|
| 154 | dest.close()
|
|---|
| 155 | tmpl.close()
|
|---|
| 156 | del dest
|
|---|
| 157 | del tmpl
|
|---|
| 158 |
|
|---|
| 159 | # Open the output resource fork
|
|---|
| 160 |
|
|---|
| 161 | if progress:
|
|---|
| 162 | progress.label("Copy resources...")
|
|---|
| 163 | progress.set(20)
|
|---|
| 164 | try:
|
|---|
| 165 | output = Res.FSOpenResourceFile(destname, RESOURCE_FORK_NAME, WRITE)
|
|---|
| 166 | except MacOS.Error:
|
|---|
| 167 | destdir, destfile = os.path.split(destname)
|
|---|
| 168 | Res.FSCreateResourceFile(destdir, unicode(destfile), RESOURCE_FORK_NAME)
|
|---|
| 169 | output = Res.FSOpenResourceFile(destname, RESOURCE_FORK_NAME, WRITE)
|
|---|
| 170 |
|
|---|
| 171 | # Copy the resources from the target specific resource template, if any
|
|---|
| 172 | typesfound, ownertype = [], None
|
|---|
| 173 | try:
|
|---|
| 174 | input = Res.FSOpenResourceFile(rsrcname, RESOURCE_FORK_NAME, READ)
|
|---|
| 175 | except (MacOS.Error, ValueError):
|
|---|
| 176 | pass
|
|---|
| 177 | if progress:
|
|---|
| 178 | progress.inc(50)
|
|---|
| 179 | else:
|
|---|
| 180 | if is_update:
|
|---|
| 181 | skip_oldfile = ['cfrg']
|
|---|
| 182 | else:
|
|---|
| 183 | skip_oldfile = []
|
|---|
| 184 | typesfound, ownertype = copyres(input, output, skip_oldfile, 0, progress)
|
|---|
| 185 | Res.CloseResFile(input)
|
|---|
| 186 |
|
|---|
| 187 | # Check which resource-types we should not copy from the template
|
|---|
| 188 | skiptypes = []
|
|---|
| 189 | if 'vers' in typesfound: skiptypes.append('vers')
|
|---|
| 190 | if 'SIZE' in typesfound: skiptypes.append('SIZE')
|
|---|
| 191 | if 'BNDL' in typesfound: skiptypes = skiptypes + ['BNDL', 'FREF', 'icl4',
|
|---|
| 192 | 'icl8', 'ics4', 'ics8', 'ICN#', 'ics#']
|
|---|
| 193 | if not copy_codefragment:
|
|---|
| 194 | skiptypes.append('cfrg')
|
|---|
| 195 | ## skipowner = (ownertype <> None)
|
|---|
| 196 |
|
|---|
| 197 | # Copy the resources from the template
|
|---|
| 198 |
|
|---|
| 199 | input = Res.FSOpenResourceFile(template, RESOURCE_FORK_NAME, READ)
|
|---|
| 200 | dummy, tmplowner = copyres(input, output, skiptypes, 1, progress)
|
|---|
| 201 |
|
|---|
| 202 | Res.CloseResFile(input)
|
|---|
| 203 | ## if ownertype == None:
|
|---|
| 204 | ## raise BuildError, "No owner resource found in either resource file or template"
|
|---|
| 205 | # Make sure we're manipulating the output resource file now
|
|---|
| 206 |
|
|---|
| 207 | Res.UseResFile(output)
|
|---|
| 208 |
|
|---|
| 209 | if ownertype == None:
|
|---|
| 210 | # No owner resource in the template. We have skipped the
|
|---|
| 211 | # Python owner resource, so we have to add our own. The relevant
|
|---|
| 212 | # bundle stuff is already included in the interpret/applet template.
|
|---|
| 213 | newres = Res.Resource('\0')
|
|---|
| 214 | newres.AddResource(DEFAULT_APPLET_CREATOR, 0, "Owner resource")
|
|---|
| 215 | ownertype = DEFAULT_APPLET_CREATOR
|
|---|
| 216 |
|
|---|
| 217 | if code:
|
|---|
| 218 | # Delete any existing 'PYC ' resource named __main__
|
|---|
| 219 |
|
|---|
| 220 | try:
|
|---|
| 221 | res = Res.Get1NamedResource(RESTYPE, RESNAME)
|
|---|
| 222 | res.RemoveResource()
|
|---|
| 223 | except Res.Error:
|
|---|
| 224 | pass
|
|---|
| 225 |
|
|---|
| 226 | # Create the raw data for the resource from the code object
|
|---|
| 227 | if progress:
|
|---|
| 228 | progress.label("Write PYC resource...")
|
|---|
| 229 | progress.set(120)
|
|---|
| 230 |
|
|---|
| 231 | data = marshal.dumps(code)
|
|---|
| 232 | del code
|
|---|
| 233 | data = (MAGIC + '\0\0\0\0') + data
|
|---|
| 234 |
|
|---|
| 235 | # Create the resource and write it
|
|---|
| 236 |
|
|---|
| 237 | id = 0
|
|---|
| 238 | while id < 128:
|
|---|
| 239 | id = Res.Unique1ID(RESTYPE)
|
|---|
| 240 | res = Res.Resource(data)
|
|---|
| 241 | res.AddResource(RESTYPE, id, RESNAME)
|
|---|
| 242 | attrs = res.GetResAttrs()
|
|---|
| 243 | attrs = attrs | 0x04 # set preload
|
|---|
| 244 | res.SetResAttrs(attrs)
|
|---|
| 245 | res.WriteResource()
|
|---|
| 246 | res.ReleaseResource()
|
|---|
| 247 |
|
|---|
| 248 | # Close the output file
|
|---|
| 249 |
|
|---|
| 250 | Res.CloseResFile(output)
|
|---|
| 251 |
|
|---|
| 252 | # Now set the creator, type and bundle bit of the destination.
|
|---|
| 253 | # Done with FSSpec's, FSRef FInfo isn't good enough yet (2.3a1+)
|
|---|
| 254 | dest_fss = Carbon.File.FSSpec(destname)
|
|---|
| 255 | dest_finfo = dest_fss.FSpGetFInfo()
|
|---|
| 256 | dest_finfo.Creator = ownertype
|
|---|
| 257 | dest_finfo.Type = 'APPL'
|
|---|
| 258 | dest_finfo.Flags = dest_finfo.Flags | Carbon.Files.kHasBundle | Carbon.Files.kIsShared
|
|---|
| 259 | dest_finfo.Flags = dest_finfo.Flags & ~Carbon.Files.kHasBeenInited
|
|---|
| 260 | dest_fss.FSpSetFInfo(dest_finfo)
|
|---|
| 261 |
|
|---|
| 262 | macostools.touched(destname)
|
|---|
| 263 | if progress:
|
|---|
| 264 | progress.label("Done.")
|
|---|
| 265 | progress.inc(0)
|
|---|
| 266 |
|
|---|
| 267 | def process_common_macho(template, progress, code, rsrcname, destname, is_update,
|
|---|
| 268 | raw=0, others=[], filename=None, destroot=""):
|
|---|
| 269 | # Check that we have a filename
|
|---|
| 270 | if filename is None:
|
|---|
| 271 | raise BuildError, "Need source filename on MacOSX"
|
|---|
| 272 | # First make sure the name ends in ".app"
|
|---|
| 273 | if destname[-4:] != '.app':
|
|---|
| 274 | destname = destname + '.app'
|
|---|
| 275 | # Now deduce the short name
|
|---|
| 276 | destdir, shortname = os.path.split(destname)
|
|---|
| 277 | if shortname[-4:] == '.app':
|
|---|
| 278 | # Strip the .app suffix
|
|---|
| 279 | shortname = shortname[:-4]
|
|---|
| 280 | # And deduce the .plist and .icns names
|
|---|
| 281 | plistname = None
|
|---|
| 282 | icnsname = None
|
|---|
| 283 | if rsrcname and rsrcname[-5:] == '.rsrc':
|
|---|
| 284 | tmp = rsrcname[:-5]
|
|---|
| 285 | plistname = tmp + '.plist'
|
|---|
| 286 | if os.path.exists(plistname):
|
|---|
| 287 | icnsname = tmp + '.icns'
|
|---|
| 288 | if not os.path.exists(icnsname):
|
|---|
| 289 | icnsname = None
|
|---|
| 290 | else:
|
|---|
| 291 | plistname = None
|
|---|
| 292 | if not icnsname:
|
|---|
| 293 | dft_icnsname = os.path.join(sys.prefix, 'Resources/Python.app/Contents/Resources/PythonApplet.icns')
|
|---|
| 294 | if os.path.exists(dft_icnsname):
|
|---|
| 295 | icnsname = dft_icnsname
|
|---|
| 296 | if not os.path.exists(rsrcname):
|
|---|
| 297 | rsrcname = None
|
|---|
| 298 | if progress:
|
|---|
| 299 | progress.label('Creating bundle...')
|
|---|
| 300 | import bundlebuilder
|
|---|
| 301 | builder = bundlebuilder.AppBuilder(verbosity=0)
|
|---|
| 302 | builder.mainprogram = filename
|
|---|
| 303 | builder.builddir = destdir
|
|---|
| 304 | builder.name = shortname
|
|---|
| 305 | builder.destroot = destroot
|
|---|
| 306 | if rsrcname:
|
|---|
| 307 | realrsrcname = macresource.resource_pathname(rsrcname)
|
|---|
| 308 | builder.files.append((realrsrcname,
|
|---|
| 309 | os.path.join('Contents/Resources', os.path.basename(rsrcname))))
|
|---|
| 310 | for o in others:
|
|---|
| 311 | if type(o) == str:
|
|---|
| 312 | builder.resources.append(o)
|
|---|
| 313 | else:
|
|---|
| 314 | builder.files.append(o)
|
|---|
| 315 | if plistname:
|
|---|
| 316 | import plistlib
|
|---|
| 317 | builder.plist = plistlib.Plist.fromFile(plistname)
|
|---|
| 318 | if icnsname:
|
|---|
| 319 | builder.iconfile = icnsname
|
|---|
| 320 | if not raw:
|
|---|
| 321 | builder.argv_emulation = 1
|
|---|
| 322 | builder.setup()
|
|---|
| 323 | builder.build()
|
|---|
| 324 | if progress:
|
|---|
| 325 | progress.label('Done.')
|
|---|
| 326 | progress.inc(0)
|
|---|
| 327 |
|
|---|
| 328 | ## macostools.touched(dest_fss)
|
|---|
| 329 |
|
|---|
| 330 | # Copy resources between two resource file descriptors.
|
|---|
| 331 | # skip a resource named '__main__' or (if skipowner is set) with ID zero.
|
|---|
| 332 | # Also skip resources with a type listed in skiptypes.
|
|---|
| 333 | #
|
|---|
| 334 | def copyres(input, output, skiptypes, skipowner, progress=None):
|
|---|
| 335 | ctor = None
|
|---|
| 336 | alltypes = []
|
|---|
| 337 | Res.UseResFile(input)
|
|---|
| 338 | ntypes = Res.Count1Types()
|
|---|
| 339 | progress_type_inc = 50/ntypes
|
|---|
| 340 | for itype in range(1, 1+ntypes):
|
|---|
| 341 | type = Res.Get1IndType(itype)
|
|---|
| 342 | if type in skiptypes:
|
|---|
| 343 | continue
|
|---|
| 344 | alltypes.append(type)
|
|---|
| 345 | nresources = Res.Count1Resources(type)
|
|---|
| 346 | progress_cur_inc = progress_type_inc/nresources
|
|---|
| 347 | for ires in range(1, 1+nresources):
|
|---|
| 348 | res = Res.Get1IndResource(type, ires)
|
|---|
| 349 | id, type, name = res.GetResInfo()
|
|---|
| 350 | lcname = string.lower(name)
|
|---|
| 351 |
|
|---|
| 352 | if lcname == OWNERNAME and id == 0:
|
|---|
| 353 | if skipowner:
|
|---|
| 354 | continue # Skip this one
|
|---|
| 355 | else:
|
|---|
| 356 | ctor = type
|
|---|
| 357 | size = res.size
|
|---|
| 358 | attrs = res.GetResAttrs()
|
|---|
| 359 | if progress:
|
|---|
| 360 | progress.label("Copy %s %d %s"%(type, id, name))
|
|---|
| 361 | progress.inc(progress_cur_inc)
|
|---|
| 362 | res.LoadResource()
|
|---|
| 363 | res.DetachResource()
|
|---|
| 364 | Res.UseResFile(output)
|
|---|
| 365 | try:
|
|---|
| 366 | res2 = Res.Get1Resource(type, id)
|
|---|
| 367 | except MacOS.Error:
|
|---|
| 368 | res2 = None
|
|---|
| 369 | if res2:
|
|---|
| 370 | if progress:
|
|---|
| 371 | progress.label("Overwrite %s %d %s"%(type, id, name))
|
|---|
| 372 | progress.inc(0)
|
|---|
| 373 | res2.RemoveResource()
|
|---|
| 374 | res.AddResource(type, id, name)
|
|---|
| 375 | res.WriteResource()
|
|---|
| 376 | attrs = attrs | res.GetResAttrs()
|
|---|
| 377 | res.SetResAttrs(attrs)
|
|---|
| 378 | Res.UseResFile(input)
|
|---|
| 379 | return alltypes, ctor
|
|---|
| 380 |
|
|---|
| 381 | def copyapptree(srctree, dsttree, exceptlist=[], progress=None):
|
|---|
| 382 | names = []
|
|---|
| 383 | if os.path.exists(dsttree):
|
|---|
| 384 | shutil.rmtree(dsttree)
|
|---|
| 385 | os.mkdir(dsttree)
|
|---|
| 386 | todo = os.listdir(srctree)
|
|---|
| 387 | while todo:
|
|---|
| 388 | this, todo = todo[0], todo[1:]
|
|---|
| 389 | if this in exceptlist:
|
|---|
| 390 | continue
|
|---|
| 391 | thispath = os.path.join(srctree, this)
|
|---|
| 392 | if os.path.isdir(thispath):
|
|---|
| 393 | thiscontent = os.listdir(thispath)
|
|---|
| 394 | for t in thiscontent:
|
|---|
| 395 | todo.append(os.path.join(this, t))
|
|---|
| 396 | names.append(this)
|
|---|
| 397 | for this in names:
|
|---|
| 398 | srcpath = os.path.join(srctree, this)
|
|---|
| 399 | dstpath = os.path.join(dsttree, this)
|
|---|
| 400 | if os.path.isdir(srcpath):
|
|---|
| 401 | os.mkdir(dstpath)
|
|---|
| 402 | elif os.path.islink(srcpath):
|
|---|
| 403 | endpoint = os.readlink(srcpath)
|
|---|
| 404 | os.symlink(endpoint, dstpath)
|
|---|
| 405 | else:
|
|---|
| 406 | if progress:
|
|---|
| 407 | progress.label('Copy '+this)
|
|---|
| 408 | progress.inc(0)
|
|---|
| 409 | shutil.copy2(srcpath, dstpath)
|
|---|
| 410 |
|
|---|
| 411 | def writepycfile(codeobject, cfile):
|
|---|
| 412 | import marshal
|
|---|
| 413 | fc = open(cfile, 'wb')
|
|---|
| 414 | fc.write('\0\0\0\0') # MAGIC placeholder, written later
|
|---|
| 415 | fc.write('\0\0\0\0') # Timestap placeholder, not needed
|
|---|
| 416 | marshal.dump(codeobject, fc)
|
|---|
| 417 | fc.flush()
|
|---|
| 418 | fc.seek(0, 0)
|
|---|
| 419 | fc.write(MAGIC)
|
|---|
| 420 | fc.close()
|
|---|