Bump to 2.3.1 to pick up the missing file.
[python/dscho.git] / Mac / Lib / buildtools.py
bloba02c609901baea45d5e622e1f62cd5a5b08c2cdc
1 """tools for BuildApplet and BuildApplication"""
3 import sys
4 import os
5 import string
6 import imp
7 import marshal
8 import macfs
9 from Carbon import Res
10 import MACFS
11 import MacOS
12 import macostools
13 import macresource
14 import EasyDialogs
15 import shutil
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
27 RESTYPE = 'PYC '
28 RESNAME = '__main__'
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
38 READ = 1
39 WRITE = 2
42 def findtemplate(template=None):
43 """Locate the applet template along sys.path"""
44 if MacOS.runtimemodel == 'macho':
45 if template:
46 return template
47 return findtemplate_macho()
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 = macfs.ResolveAliasFile(file)
54 break
55 except (macfs.error, ValueError):
56 continue
57 else:
58 raise BuildError, "Template %s not found on sys.path" % `template`
59 file = file.as_pathname()
60 return file
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...")
76 progress.inc(0)
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')
85 text = fp.read()
86 fp.close()
87 try:
88 code = compile(text, filename, "exec")
89 except SyntaxError, arg:
90 raise BuildError, "Syntax error in script %s: %s" % (filename, arg)
91 except EOFError:
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:
100 destname = basename
101 else:
102 basename = filename
104 if not destname:
105 if MacOS.runtimemodel == 'macho':
106 destname = basename + '.app'
107 else:
108 destname = basename + '.applet'
109 if not rsrcname:
110 rsrcname = basename + '.rsrc'
112 # Try removing the output file. This fails in MachO, but it should
113 # do any harm.
114 try:
115 os.remove(destname)
116 except os.error:
117 pass
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"
125 if progress:
126 progress = EasyDialogs.ProgressBar("Updating %s..."%os.path.split(filename)[1], 120)
127 else:
128 progress = None
129 if not output:
130 output = filename + ' (updated)'
132 # Try removing the output file
133 try:
134 os.remove(output)
135 except os.error:
136 pass
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)
145 if 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
153 if progress:
154 progress.label("Copy data fork...")
155 progress.set(10)
157 if copy_codefragment:
158 tmpl = open(template, "rb")
159 dest = open(destname, "wb")
160 data = tmpl.read()
161 if data:
162 dest.write(data)
163 dest.close()
164 tmpl.close()
165 del dest
166 del tmpl
168 # Open the output resource fork
170 if progress:
171 progress.label("Copy resources...")
172 progress.set(20)
173 try:
174 output = Res.FSpOpenResFile(dest_fss, WRITE)
175 except MacOS.Error:
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
181 try:
182 input = Res.FSpOpenResFile(rsrcname, READ)
183 except (MacOS.Error, ValueError):
184 pass
185 if progress:
186 progress.inc(50)
187 else:
188 if is_update:
189 skip_oldfile = ['cfrg']
190 else:
191 skip_oldfile = []
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
196 skiptypes = []
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
225 if code:
226 # Delete any existing 'PYC ' resource named __main__
228 try:
229 res = Res.Get1NamedResource(RESTYPE, RESNAME)
230 res.RemoveResource()
231 except Res.Error:
232 pass
234 # Create the raw data for the resource from the code object
235 if progress:
236 progress.label("Write PYC resource...")
237 progress.set(120)
239 data = marshal.dumps(code)
240 del code
241 data = (MAGIC + '\0\0\0\0') + data
243 # Create the resource and write it
245 id = 0
246 while id < 128:
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)
253 res.WriteResource()
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)
269 if progress:
270 progress.label("Done.")
271 progress.inc(0)
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
283 plistname = None
284 icnsname = None
285 if rsrcname and rsrcname[-5:] == '.rsrc':
286 tmp = rsrcname[:-5]
287 plistname = tmp + '.plist'
288 if os.path.exists(plistname):
289 icnsname = tmp + '.icns'
290 if not os.path.exists(icnsname):
291 icnsname = None
292 else:
293 plistname = None
294 # Start with copying the .app framework
295 if not is_update:
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
303 if progress:
304 progress.label('Create info.plist')
305 progress.inc(0)
306 if plistname:
307 shutil.copy2(plistname, os.path.join(destname, 'Contents', 'Info.plist'))
308 if icnsname:
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':
315 ownertype = 'Pide'
316 else:
317 ownertype = 'PytA'
318 # XXXX Should copy .icns file
319 else:
320 cocoainfo = ''
321 for o in others:
322 if o[-4:] == '.nib':
323 nibname = os.path.split(o)[1][:-4]
324 cocoainfo = """
325 <key>NSMainNibFile</key>
326 <string>%s</string>
327 <key>NSPrincipalClass</key>
328 <string>NSApplication</string>""" % nibname
329 elif o[-6:] == '.lproj':
330 files = os.listdir(o)
331 for f in files:
332 if f[-4:] == '.nib':
333 nibname = os.path.split(f)[1][:-4]
334 cocoainfo = """
335 <key>NSMainNibFile</key>
336 <string>%s</string>
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')
344 ofp.write(plistdata)
345 ofp.close()
346 ownertype = 'PytA'
347 # Create the PkgInfo file
348 if progress:
349 progress.label('Create PkgInfo')
350 progress.inc(0)
351 ofp = open(os.path.join(destname, 'Contents', 'PkgInfo'), 'wb')
352 ofp.write('APPL' + ownertype)
353 ofp.close()
356 if progress:
357 progress.label("Copy resources...")
358 progress.set(20)
359 resfilename = 'python.rsrc' # XXXX later: '%s.rsrc' % shortname
360 try:
361 output = Res.FSOpenResourceFile(
362 os.path.join(destname, 'Contents', 'Resources', resfilename),
363 u'', WRITE)
364 except MacOS.Error:
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
372 try:
373 input = macresource.open_pathname(rsrcname)
374 except (MacOS.Error, ValueError):
375 pass
376 if progress:
377 progress.inc(50)
378 else:
379 typesfound, ownertype = copyres(input, output, [], 0, progress)
380 Res.CloseResFile(input)
382 # Check which resource-types we should not copy from the template
383 skiptypes = []
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)
396 if progress:
397 progress.label("Copy standard resources...")
398 progress.inc(0)
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)
409 if code:
410 if raw:
411 pycname = '__rawmain__.pyc'
412 else:
413 pycname = '__main__.pyc'
414 # And we also create __rawmain__.pyc
415 outputfilename = os.path.join(destname, 'Contents', 'Resources', '__rawmain__.pyc')
416 if progress:
417 progress.label('Creating __rawmain__.pyc')
418 progress.inc(0)
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)
425 if progress:
426 progress.label('Creating '+pycname)
427 progress.inc(0)
428 writepycfile(code, outputfilename)
429 # Copy other files the user asked for
430 for osrc in others:
431 oname = os.path.split(osrc)[1]
432 odst = os.path.join(destname, 'Contents', 'Resources', oname)
433 if progress:
434 progress.label('Copy ' + oname)
435 progress.inc(0)
436 if os.path.isdir(osrc):
437 copyapptree(osrc, odst)
438 else:
439 shutil.copy2(osrc, odst)
440 if progress:
441 progress.label('Done.')
442 progress.inc(0)
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):
451 ctor = None
452 alltypes = []
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:
459 continue
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:
469 if skipowner:
470 continue # Skip this one
471 else:
472 ctor = type
473 size = res.size
474 attrs = res.GetResAttrs()
475 if progress:
476 progress.label("Copy %s %d %s"%(type, id, name))
477 progress.inc(progress_cur_inc)
478 res.LoadResource()
479 res.DetachResource()
480 Res.UseResFile(output)
481 try:
482 res2 = Res.Get1Resource(type, id)
483 except MacOS.Error:
484 res2 = None
485 if res2:
486 if progress:
487 progress.label("Overwrite %s %d %s"%(type, id, name))
488 progress.inc(0)
489 res2.RemoveResource()
490 res.AddResource(type, id, name)
491 res.WriteResource()
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):
498 names = []
499 if os.path.exists(dsttree):
500 shutil.rmtree(dsttree)
501 os.mkdir(dsttree)
502 todo = os.listdir(srctree)
503 while todo:
504 this, todo = todo[0], todo[1:]
505 if this in exceptlist:
506 continue
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))
512 names.append(this)
513 for this in names:
514 srcpath = os.path.join(srctree, this)
515 dstpath = os.path.join(dsttree, this)
516 if os.path.isdir(srcpath):
517 os.mkdir(dstpath)
518 else:
519 if progress:
520 progress.label('Copy '+this)
521 progress.inc(0)
522 shutil.copy2(srcpath, dstpath)
524 def writepycfile(codeobject, cfile):
525 import marshal
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)
530 fc.flush()
531 fc.seek(0, 0)
532 fc.write(MAGIC)
533 fc.close()