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