1 """tools for BuildApplet and BuildApplication"""
18 BuildError
= "BuildError"
20 # .pyc file (and 'PYC ' resource magic number)
21 MAGIC
= imp
.get_magic()
23 # Template file (searched on sys.path)
24 TEMPLATE
= "PythonInterpreter"
26 # Specification of our resource
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"
34 # Default applet creator code
35 DEFAULT_APPLET_CREATOR
="Pyta"
37 # OpenResFile mode parameters
42 def findtemplate(template
=None):
43 """Locate the applet template along sys.path"""
44 if MacOS
.runtimemodel
== 'macho':
47 return findtemplate_macho()
51 file = os
.path
.join(p
, template
)
53 file, d1
, d2
= macfs
.ResolveAliasFile(file)
55 except (macfs
.error
, ValueError):
58 raise BuildError
, "Template %s not found on sys.path" % `template`
59 file = file.as_pathname()
62 def findtemplate_macho():
63 execpath
= sys
.executable
.split('/')
64 if not 'Contents' in execpath
:
65 raise BuildError
, "Not running from a .app bundle: %s" % sys
.executable
66 i
= execpath
.index('Contents')
67 return '/'.join(execpath
[:i
])
70 def process(template
, filename
, destname
, copy_codefragment
,
71 rsrcname
=None, others
=[], raw
=0, progress
="default"):
73 if progress
== "default":
74 progress
= EasyDialogs
.ProgressBar("Processing %s..."%os.path
.split(filename
)[1], 120)
75 progress
.label("Compiling...")
77 # check for the script name being longer than 32 chars. This may trigger a bug
78 # on OSX that can destroy your sourcefile.
79 if '#' in os
.path
.split(filename
)[1]:
80 raise BuildError
, "BuildApplet could destroy your sourcefile on OSX, please rename: %s" % filename
81 # Read the source and compile it
82 # (there's no point overwriting the destination if it has a syntax error)
84 fp
= open(filename
, 'rU')
88 code
= compile(text
, filename
, "exec")
89 except SyntaxError, arg
:
90 raise BuildError
, "Syntax error in script %s: %s" % (filename
, arg
)
92 raise BuildError
, "End-of-file in script %s" % (filename
,)
94 # Set the destination file name. Note that basename
95 # does contain the whole filepath, only a .py is stripped.
97 if string
.lower(filename
[-3:]) == ".py":
98 basename
= filename
[:-3]
99 if MacOS
.runtimemodel
!= 'macho' and not destname
:
105 if MacOS
.runtimemodel
== 'macho':
106 destname
= basename
+ '.app'
108 destname
= basename
+ '.applet'
110 rsrcname
= basename
+ '.rsrc'
112 # Try removing the output file. This fails in MachO, but it should
118 process_common(template
, progress
, code
, rsrcname
, destname
, 0,
119 copy_codefragment
, raw
, others
)
122 def update(template
, filename
, output
):
123 if MacOS
.runtimemodel
== 'macho':
124 raise BuildError
, "No updating yet for MachO applets"
126 progress
= EasyDialogs
.ProgressBar("Updating %s..."%os.path
.split(filename
)[1], 120)
130 output
= filename
+ ' (updated)'
132 # Try removing the output file
137 process_common(template
, progress
, None, filename
, output
, 1, 1)
140 def process_common(template
, progress
, code
, rsrcname
, destname
, is_update
,
141 copy_codefragment
, raw
=0, others
=[]):
142 if MacOS
.runtimemodel
== 'macho':
143 return process_common_macho(template
, progress
, code
, rsrcname
, destname
,
144 is_update
, raw
, others
)
146 raise BuildError
, "Extra files only allowed for MachoPython applets"
147 # Create FSSpecs for the various files
148 template_fss
= macfs
.FSSpec(template
)
149 template_fss
, d1
, d2
= macfs
.ResolveAliasFile(template_fss
)
150 dest_fss
= macfs
.FSSpec(destname
)
152 # Copy data (not resources, yet) from the template
154 progress
.label("Copy data fork...")
157 if copy_codefragment
:
158 tmpl
= open(template
, "rb")
159 dest
= open(destname
, "wb")
168 # Open the output resource fork
171 progress
.label("Copy resources...")
174 output
= Res
.FSpOpenResFile(dest_fss
, WRITE
)
176 Res
.FSpCreateResFile(destname
, '????', 'APPL', MACFS
.smAllScripts
)
177 output
= Res
.FSpOpenResFile(dest_fss
, WRITE
)
179 # Copy the resources from the target specific resource template, if any
180 typesfound
, ownertype
= [], None
182 input = Res
.FSpOpenResFile(rsrcname
, READ
)
183 except (MacOS
.Error
, ValueError):
189 skip_oldfile
= ['cfrg']
192 typesfound
, ownertype
= copyres(input, output
, skip_oldfile
, 0, progress
)
193 Res
.CloseResFile(input)
195 # Check which resource-types we should not copy from the template
197 if 'vers' in typesfound
: skiptypes
.append('vers')
198 if 'SIZE' in typesfound
: skiptypes
.append('SIZE')
199 if 'BNDL' in typesfound
: skiptypes
= skiptypes
+ ['BNDL', 'FREF', 'icl4',
200 'icl8', 'ics4', 'ics8', 'ICN#', 'ics#']
201 if not copy_codefragment
:
202 skiptypes
.append('cfrg')
203 ## skipowner = (ownertype <> None)
205 # Copy the resources from the template
207 input = Res
.FSpOpenResFile(template_fss
, READ
)
208 dummy
, tmplowner
= copyres(input, output
, skiptypes
, 1, progress
)
210 Res
.CloseResFile(input)
211 ## if ownertype == None:
212 ## raise BuildError, "No owner resource found in either resource file or template"
213 # Make sure we're manipulating the output resource file now
215 Res
.UseResFile(output
)
217 if ownertype
== None:
218 # No owner resource in the template. We have skipped the
219 # Python owner resource, so we have to add our own. The relevant
220 # bundle stuff is already included in the interpret/applet template.
221 newres
= Res
.Resource('\0')
222 newres
.AddResource(DEFAULT_APPLET_CREATOR
, 0, "Owner resource")
223 ownertype
= DEFAULT_APPLET_CREATOR
226 # Delete any existing 'PYC ' resource named __main__
229 res
= Res
.Get1NamedResource(RESTYPE
, RESNAME
)
234 # Create the raw data for the resource from the code object
236 progress
.label("Write PYC resource...")
239 data
= marshal
.dumps(code
)
241 data
= (MAGIC
+ '\0\0\0\0') + data
243 # Create the resource and write it
247 id = Res
.Unique1ID(RESTYPE
)
248 res
= Res
.Resource(data
)
249 res
.AddResource(RESTYPE
, id, RESNAME
)
250 attrs
= res
.GetResAttrs()
251 attrs
= attrs |
0x04 # set preload
252 res
.SetResAttrs(attrs
)
254 res
.ReleaseResource()
256 # Close the output file
258 Res
.CloseResFile(output
)
260 # Now set the creator, type and bundle bit of the destination
261 dest_finfo
= dest_fss
.GetFInfo()
262 dest_finfo
.Creator
= ownertype
263 dest_finfo
.Type
= 'APPL'
264 dest_finfo
.Flags
= dest_finfo
.Flags | MACFS
.kHasBundle | MACFS
.kIsShared
265 dest_finfo
.Flags
= dest_finfo
.Flags
& ~MACFS
.kHasBeenInited
266 dest_fss
.SetFInfo(dest_finfo
)
268 macostools
.touched(dest_fss
)
270 progress
.label("Done.")
273 def process_common_macho(template
, progress
, code
, rsrcname
, destname
, is_update
, raw
=0, others
=[]):
274 # First make sure the name ends in ".app"
275 if destname
[-4:] != '.app':
276 destname
= destname
+ '.app'
277 # Now deduce the short name
278 shortname
= os
.path
.split(destname
)[1]
279 if shortname
[-4:] == '.app':
280 # Strip the .app suffix
281 shortname
= shortname
[:-4]
282 # And deduce the .plist and .icns names
285 if rsrcname
and rsrcname
[-5:] == '.rsrc':
287 plistname
= tmp
+ '.plist'
288 if os
.path
.exists(plistname
):
289 icnsname
= tmp
+ '.icns'
290 if not os
.path
.exists(icnsname
):
294 # Start with copying the .app framework
296 exceptlist
= ["Contents/Info.plist",
297 "Contents/Resources/English.lproj/InfoPlist.strings",
298 "Contents/Resources/English.lproj/Documentation",
299 "Contents/Resources/python.rsrc",
301 copyapptree(template
, destname
, exceptlist
, progress
)
302 # Now either use the .plist file or the default
304 progress
.label('Create info.plist')
307 shutil
.copy2(plistname
, os
.path
.join(destname
, 'Contents', 'Info.plist'))
309 icnsdest
= os
.path
.split(icnsname
)[1]
310 icnsdest
= os
.path
.join(destname
,
311 os
.path
.join('Contents', 'Resources', icnsdest
))
312 shutil
.copy2(icnsname
, icnsdest
)
313 # XXXX Wrong. This should be parsed from plist file. Also a big hack:-)
314 if shortname
== 'PythonIDE':
318 # XXXX Should copy .icns file
323 nibname
= os
.path
.split(o
)[1][:-4]
325 <key>NSMainNibFile</key>
327 <key>NSPrincipalClass</key>
328 <string>NSApplication</string>""" % nibname
329 elif o
[-6:] == '.lproj':
330 files
= os
.listdir(o
)
333 nibname
= os
.path
.split(f
)[1][:-4]
335 <key>NSMainNibFile</key>
337 <key>NSPrincipalClass</key>
338 <string>NSApplication</string>""" % nibname
340 plistname
= os
.path
.join(template
, 'Contents', 'Resources', 'Applet-Info.plist')
341 plistdata
= open(plistname
).read()
342 plistdata
= plistdata
% {'appletname':shortname
, 'cocoainfo':cocoainfo
}
343 ofp
= open(os
.path
.join(destname
, 'Contents', 'Info.plist'), 'w')
347 # Create the PkgInfo file
349 progress
.label('Create PkgInfo')
351 ofp
= open(os
.path
.join(destname
, 'Contents', 'PkgInfo'), 'wb')
352 ofp
.write('APPL' + ownertype
)
357 progress
.label("Copy resources...")
359 resfilename
= 'python.rsrc' # XXXX later: '%s.rsrc' % shortname
361 output
= Res
.FSOpenResourceFile(
362 os
.path
.join(destname
, 'Contents', 'Resources', resfilename
),
365 fsr
, dummy
= Res
.FSCreateResourceFile(
366 os
.path
.join(destname
, 'Contents', 'Resources'),
367 unicode(resfilename
), '')
368 output
= Res
.FSOpenResourceFile(fsr
, u
'', WRITE
)
370 # Copy the resources from the target specific resource template, if any
371 typesfound
, ownertype
= [], None
373 input = macresource
.open_pathname(rsrcname
)
374 except (MacOS
.Error
, ValueError):
379 typesfound
, ownertype
= copyres(input, output
, [], 0, progress
)
380 Res
.CloseResFile(input)
382 # Check which resource-types we should not copy from the template
384 ## if 'vers' in typesfound: skiptypes.append('vers')
385 ## if 'SIZE' in typesfound: skiptypes.append('SIZE')
386 ## if 'BNDL' in typesfound: skiptypes = skiptypes + ['BNDL', 'FREF', 'icl4',
387 ## 'icl8', 'ics4', 'ics8', 'ICN#', 'ics#']
388 ## if not copy_codefragment:
389 ## skiptypes.append('cfrg')
390 ## skipowner = (ownertype <> None)
392 # Copy the resources from the template
394 input = Res
.FSOpenResourceFile(
395 os
.path
.join(template
, 'Contents', 'Resources', 'python.rsrc'), u
'', READ
)
397 progress
.label("Copy standard resources...")
399 ## dummy, tmplowner = copyres(input, output, skiptypes, 1, progress)
400 dummy
, tmplowner
= copyres(input, output
, skiptypes
, 1, None)
402 Res
.CloseResFile(input)
403 ## if ownertype == None:
404 ## raise BuildError, "No owner resource found in either resource file or template"
405 # Make sure we're manipulating the output resource file now
407 Res
.CloseResFile(output
)
411 pycname
= '__rawmain__.pyc'
413 pycname
= '__main__.pyc'
414 # And we also create __rawmain__.pyc
415 outputfilename
= os
.path
.join(destname
, 'Contents', 'Resources', '__rawmain__.pyc')
417 progress
.label('Creating __rawmain__.pyc')
419 rawsourcefile
= os
.path
.join(sys
.prefix
, 'Mac', 'Lib', 'appletrawmain.py')
420 rawsource
= open(rawsourcefile
, 'rU').read()
421 rawcode
= compile(rawsource
, rawsourcefile
, 'exec')
422 writepycfile(rawcode
, outputfilename
)
424 outputfilename
= os
.path
.join(destname
, 'Contents', 'Resources', pycname
)
426 progress
.label('Creating '+pycname
)
428 writepycfile(code
, outputfilename
)
429 # Copy other files the user asked for
431 oname
= os
.path
.split(osrc
)[1]
432 odst
= os
.path
.join(destname
, 'Contents', 'Resources', oname
)
434 progress
.label('Copy ' + oname
)
436 if os
.path
.isdir(osrc
):
437 copyapptree(osrc
, odst
)
439 shutil
.copy2(osrc
, odst
)
441 progress
.label('Done.')
444 ## macostools.touched(dest_fss)
446 # Copy resources between two resource file descriptors.
447 # skip a resource named '__main__' or (if skipowner is set) with ID zero.
448 # Also skip resources with a type listed in skiptypes.
450 def copyres(input, output
, skiptypes
, skipowner
, progress
=None):
453 Res
.UseResFile(input)
454 ntypes
= Res
.Count1Types()
455 progress_type_inc
= 50/ntypes
456 for itype
in range(1, 1+ntypes
):
457 type = Res
.Get1IndType(itype
)
458 if type in skiptypes
:
460 alltypes
.append(type)
461 nresources
= Res
.Count1Resources(type)
462 progress_cur_inc
= progress_type_inc
/nresources
463 for ires
in range(1, 1+nresources
):
464 res
= Res
.Get1IndResource(type, ires
)
465 id, type, name
= res
.GetResInfo()
466 lcname
= string
.lower(name
)
468 if lcname
== OWNERNAME
and id == 0:
470 continue # Skip this one
474 attrs
= res
.GetResAttrs()
476 progress
.label("Copy %s %d %s"%(type, id, name
))
477 progress
.inc(progress_cur_inc
)
480 Res
.UseResFile(output
)
482 res2
= Res
.Get1Resource(type, id)
487 progress
.label("Overwrite %s %d %s"%(type, id, name
))
489 res2
.RemoveResource()
490 res
.AddResource(type, id, name
)
492 attrs
= attrs | res
.GetResAttrs()
493 res
.SetResAttrs(attrs
)
494 Res
.UseResFile(input)
495 return alltypes
, ctor
497 def copyapptree(srctree
, dsttree
, exceptlist
=[], progress
=None):
499 if os
.path
.exists(dsttree
):
500 shutil
.rmtree(dsttree
)
502 todo
= os
.listdir(srctree
)
504 this
, todo
= todo
[0], todo
[1:]
505 if this
in exceptlist
:
507 thispath
= os
.path
.join(srctree
, this
)
508 if os
.path
.isdir(thispath
):
509 thiscontent
= os
.listdir(thispath
)
510 for t
in thiscontent
:
511 todo
.append(os
.path
.join(this
, t
))
514 srcpath
= os
.path
.join(srctree
, this
)
515 dstpath
= os
.path
.join(dsttree
, this
)
516 if os
.path
.isdir(srcpath
):
520 progress
.label('Copy '+this
)
522 shutil
.copy2(srcpath
, dstpath
)
524 def writepycfile(codeobject
, cfile
):
526 fc
= open(cfile
, 'wb')
527 fc
.write('\0\0\0\0') # MAGIC placeholder, written later
528 fc
.write('\0\0\0\0') # Timestap placeholder, not needed
529 marshal
.dump(codeobject
, fc
)