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') 210 ## skipowner = (ownertype != None) 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() 436