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