blob: 0eef5ac4e62ee0ae1eb6cfa005e82dfc9178cc61 [file] [log] [blame]
Jack Jansen813c9971998-07-31 09:42:35 +00001"""tools for BuildApplet and BuildApplication"""
2
3import sys
4import os
5import string
6import imp
7import marshal
8import macfs
Jack Jansen5a6fdcd2001-08-25 12:15:04 +00009from Carbon import Res
Jack Jansen813c9971998-07-31 09:42:35 +000010import MACFS
11import MacOS
12import macostools
Jack Jansenb2e33fe2002-03-29 21:21:28 +000013import macresource
Jack Jansen813c9971998-07-31 09:42:35 +000014import EasyDialogs
Jack Jansenb2e33fe2002-03-29 21:21:28 +000015import shutil
Jack Jansen813c9971998-07-31 09:42:35 +000016
17
18BuildError = "BuildError"
19
20DEBUG=1
21
22
23# .pyc file (and 'PYC ' resource magic number)
24MAGIC = imp.get_magic()
25
26# Template file (searched on sys.path)
Jack Jansen3b805261999-02-14 23:12:06 +000027TEMPLATE = "PythonInterpreter"
Jack Jansen813c9971998-07-31 09:42:35 +000028
29# Specification of our resource
30RESTYPE = 'PYC '
31RESNAME = '__main__'
32
33# A resource with this name sets the "owner" (creator) of the destination
Jack Jansen81da9f11999-03-17 22:57:55 +000034# It should also have ID=0. Either of these alone is not enough.
Jack Jansen813c9971998-07-31 09:42:35 +000035OWNERNAME = "owner resource"
36
Jack Jansen81da9f11999-03-17 22:57:55 +000037# Default applet creator code
38DEFAULT_APPLET_CREATOR="Pyta"
39
Jack Jansen813c9971998-07-31 09:42:35 +000040# OpenResFile mode parameters
41READ = 1
42WRITE = 2
43
44
Jack Jansena4f8e582001-02-17 23:30:19 +000045def findtemplate(template=None):
Jack Jansen813c9971998-07-31 09:42:35 +000046 """Locate the applet template along sys.path"""
Jack Jansenb2e33fe2002-03-29 21:21:28 +000047 if MacOS.runtimemodel == 'macho':
48 if template:
49 return template
50 return findtemplate_macho()
Jack Jansena4f8e582001-02-17 23:30:19 +000051 if not template:
52 template=TEMPLATE
Jack Jansen813c9971998-07-31 09:42:35 +000053 for p in sys.path:
Jack Jansena4f8e582001-02-17 23:30:19 +000054 file = os.path.join(p, template)
Jack Jansen813c9971998-07-31 09:42:35 +000055 try:
Jack Jansena4f8e582001-02-17 23:30:19 +000056 file, d1, d2 = macfs.ResolveAliasFile(file)
Jack Jansen813c9971998-07-31 09:42:35 +000057 break
58 except (macfs.error, ValueError):
59 continue
60 else:
Jack Jansena4f8e582001-02-17 23:30:19 +000061 raise BuildError, "Template %s not found on sys.path" % `template`
62 file = file.as_pathname()
63 return file
Jack Jansenb2e33fe2002-03-29 21:21:28 +000064
65def findtemplate_macho():
66 execpath = sys.executable.split('/')
67 if not 'Contents' in execpath:
68 raise BuildError, "Not running from a .app bundle: %s" % sys.executable
69 i = execpath.index('Contents')
70 return '/'.join(execpath[:i])
Jack Jansen813c9971998-07-31 09:42:35 +000071
72
73def process(template, filename, output, copy_codefragment):
74
75 if DEBUG:
76 progress = EasyDialogs.ProgressBar("Processing %s..."%os.path.split(filename)[1], 120)
77 progress.label("Compiling...")
78 else:
79 progress = None
80
81 # Read the source and compile it
82 # (there's no point overwriting the destination if it has a syntax error)
83
84 fp = open(filename)
85 text = fp.read()
86 fp.close()
87 try:
88 code = compile(text, filename, "exec")
89 except (SyntaxError, EOFError):
90 raise BuildError, "Syntax error in script %s" % `filename`
91
92 # Set the destination file name
93
94 if string.lower(filename[-3:]) == ".py":
95 destname = filename[:-3]
96 rsrcname = destname + '.rsrc'
97 else:
Jack Jansenb2e33fe2002-03-29 21:21:28 +000098 if MacOS.runtimemodel == 'macho':
99 destname = filename + '.app'
100 else:
101 destname = filename + ".applet"
Jack Jansen813c9971998-07-31 09:42:35 +0000102 rsrcname = filename + '.rsrc'
103
104 if output:
105 destname = output
106
Jack Jansenb2e33fe2002-03-29 21:21:28 +0000107 # Try removing the output file. This fails in MachO, but it should
108 # do any harm.
Jack Jansen813c9971998-07-31 09:42:35 +0000109 try:
110 os.remove(destname)
111 except os.error:
112 pass
113 process_common(template, progress, code, rsrcname, destname, 0, copy_codefragment)
114
115
116def update(template, filename, output):
Jack Jansenb2e33fe2002-03-29 21:21:28 +0000117 if MacOS.runtimemodel == 'macho':
118 raise BuildError, "No updating yet for MachO applets"
Jack Jansen813c9971998-07-31 09:42:35 +0000119 if DEBUG:
120 progress = EasyDialogs.ProgressBar("Updating %s..."%os.path.split(filename)[1], 120)
121 else:
122 progress = None
123 if not output:
124 output = filename + ' (updated)'
125
126 # Try removing the output file
127 try:
128 os.remove(output)
129 except os.error:
130 pass
131 process_common(template, progress, None, filename, output, 1, 1)
132
133
134def process_common(template, progress, code, rsrcname, destname, is_update, copy_codefragment):
Jack Jansenb2e33fe2002-03-29 21:21:28 +0000135 if MacOS.runtimemodel == 'macho':
136 return process_common_macho(template, progress, code, rsrcname, destname, is_update)
Jack Jansen813c9971998-07-31 09:42:35 +0000137 # Create FSSpecs for the various files
138 template_fss = macfs.FSSpec(template)
139 template_fss, d1, d2 = macfs.ResolveAliasFile(template_fss)
140 dest_fss = macfs.FSSpec(destname)
141
142 # Copy data (not resources, yet) from the template
143 if DEBUG:
144 progress.label("Copy data fork...")
145 progress.set(10)
146
147 if copy_codefragment:
148 tmpl = open(template, "rb")
149 dest = open(destname, "wb")
150 data = tmpl.read()
151 if data:
152 dest.write(data)
153 dest.close()
154 tmpl.close()
155 del dest
156 del tmpl
157
158 # Open the output resource fork
159
160 if DEBUG:
161 progress.label("Copy resources...")
162 progress.set(20)
163 try:
164 output = Res.FSpOpenResFile(dest_fss, WRITE)
165 except MacOS.Error:
Jack Jansen01a2d9e2001-01-29 15:32:00 +0000166 Res.FSpCreateResFile(destname, '????', 'APPL', MACFS.smAllScripts)
Jack Jansen813c9971998-07-31 09:42:35 +0000167 output = Res.FSpOpenResFile(dest_fss, WRITE)
168
169 # Copy the resources from the target specific resource template, if any
170 typesfound, ownertype = [], None
171 try:
172 input = Res.FSpOpenResFile(rsrcname, READ)
173 except (MacOS.Error, ValueError):
174 pass
175 if DEBUG:
176 progress.inc(50)
177 else:
178 if is_update:
179 skip_oldfile = ['cfrg']
180 else:
181 skip_oldfile = []
182 typesfound, ownertype = copyres(input, output, skip_oldfile, 0, progress)
183 Res.CloseResFile(input)
184
185 # Check which resource-types we should not copy from the template
Jack Jansen81da9f11999-03-17 22:57:55 +0000186 skiptypes = []
187 if 'vers' in typesfound: skiptypes.append('vers')
Jack Jansen813c9971998-07-31 09:42:35 +0000188 if 'SIZE' in typesfound: skiptypes.append('SIZE')
189 if 'BNDL' in typesfound: skiptypes = skiptypes + ['BNDL', 'FREF', 'icl4',
190 'icl8', 'ics4', 'ics8', 'ICN#', 'ics#']
191 if not copy_codefragment:
192 skiptypes.append('cfrg')
Jack Jansen81da9f11999-03-17 22:57:55 +0000193## skipowner = (ownertype <> None)
Jack Jansen813c9971998-07-31 09:42:35 +0000194
195 # Copy the resources from the template
196
197 input = Res.FSpOpenResFile(template_fss, READ)
Jack Jansen81da9f11999-03-17 22:57:55 +0000198 dummy, tmplowner = copyres(input, output, skiptypes, 1, progress)
199
Jack Jansen813c9971998-07-31 09:42:35 +0000200 Res.CloseResFile(input)
Jack Jansen81da9f11999-03-17 22:57:55 +0000201## if ownertype == None:
202## raise BuildError, "No owner resource found in either resource file or template"
Jack Jansen813c9971998-07-31 09:42:35 +0000203 # Make sure we're manipulating the output resource file now
204
205 Res.UseResFile(output)
Jack Jansen81da9f11999-03-17 22:57:55 +0000206
207 if ownertype == None:
208 # No owner resource in the template. We have skipped the
209 # Python owner resource, so we have to add our own. The relevant
210 # bundle stuff is already included in the interpret/applet template.
211 newres = Res.Resource('\0')
212 newres.AddResource(DEFAULT_APPLET_CREATOR, 0, "Owner resource")
213 ownertype = DEFAULT_APPLET_CREATOR
Jack Jansen813c9971998-07-31 09:42:35 +0000214
215 if code:
216 # Delete any existing 'PYC ' resource named __main__
217
218 try:
219 res = Res.Get1NamedResource(RESTYPE, RESNAME)
220 res.RemoveResource()
221 except Res.Error:
222 pass
223
224 # Create the raw data for the resource from the code object
225 if DEBUG:
226 progress.label("Write PYC resource...")
227 progress.set(120)
228
229 data = marshal.dumps(code)
230 del code
231 data = (MAGIC + '\0\0\0\0') + data
232
233 # Create the resource and write it
234
235 id = 0
236 while id < 128:
237 id = Res.Unique1ID(RESTYPE)
238 res = Res.Resource(data)
239 res.AddResource(RESTYPE, id, RESNAME)
Just van Rossum874f87b1999-01-30 22:31:26 +0000240 attrs = res.GetResAttrs()
241 attrs = attrs | 0x04 # set preload
242 res.SetResAttrs(attrs)
Jack Jansen813c9971998-07-31 09:42:35 +0000243 res.WriteResource()
244 res.ReleaseResource()
245
246 # Close the output file
247
248 Res.CloseResFile(output)
249
250 # Now set the creator, type and bundle bit of the destination
251 dest_finfo = dest_fss.GetFInfo()
252 dest_finfo.Creator = ownertype
253 dest_finfo.Type = 'APPL'
Jack Jansenb70699b1999-12-03 23:38:05 +0000254 dest_finfo.Flags = dest_finfo.Flags | MACFS.kHasBundle | MACFS.kIsShared
Jack Jansen813c9971998-07-31 09:42:35 +0000255 dest_finfo.Flags = dest_finfo.Flags & ~MACFS.kHasBeenInited
256 dest_fss.SetFInfo(dest_finfo)
257
258 macostools.touched(dest_fss)
259 if DEBUG:
260 progress.label("Done.")
261
Jack Jansenb2e33fe2002-03-29 21:21:28 +0000262def process_common_macho(template, progress, code, rsrcname, destname, is_update):
263 # First make sure the name ends in ".app"
264 if destname[-4:] != '.app':
265 destname = destname + '.app'
266 # Now deduce the short name
267 shortname = os.path.split(destname)[1]
268 if shortname[-4:] == '.app':
269 # Strip the .app suffix
270 shortname = shortname[:-4]
Jack Jansen9aa8fd02002-03-29 23:44:37 +0000271 # And deduce the .plist and .icns names
272 plistname = None
273 icnsname = None
274 if rsrcname and rsrcname[-5:] == '.rsrc':
275 tmp = rsrcname[:-5]
276 plistname = tmp + '.plist'
277 if os.path.exists(plistname):
278 icnsname = tmp + '.icns'
279 if not os.path.exists(icnsname):
280 icnsname = None
281 else:
282 plistname = None
Jack Jansenb2e33fe2002-03-29 21:21:28 +0000283 # Start with copying the .app framework
284 if not is_update:
285 exceptlist = ["Contents/Info.plist",
286 "Contents/Resources/English.lproj/InfoPlist.strings",
287 "Contents/Resources/python.rsrc",
288 ]
289 copyapptree(template, destname, exceptlist)
290 # Now either use the .plist file or the default
Jack Jansen9aa8fd02002-03-29 23:44:37 +0000291 if plistname:
Jack Jansenb2e33fe2002-03-29 21:21:28 +0000292 shutil.copy2(plistname, os.path.join(destname, 'Contents/Info.plist'))
Jack Jansen9aa8fd02002-03-29 23:44:37 +0000293 if icnsname:
294 icnsdest = os.path.split(icnsname)[1]
295 icnsdest = os.path.join(destname,
296 os.path.join('Contents/Resources', icnsdest))
297 shutil.copy2(icnsname, icnsdest)
298 # XXXX Wrong. This should be parsed from plist file. Also a big hack:-)
299 if shortname == 'PythonIDE':
300 ownertype = 'Pide'
301 else:
302 ownertype = 'PytA'
Jack Jansenb2e33fe2002-03-29 21:21:28 +0000303 # XXXX Should copy .icns file
304 else:
305 plistname = os.path.join(template, 'Contents/Resources/Applet-Info.plist')
306 plistdata = open(plistname).read()
307 plistdata = plistdata % {'appletname':shortname}
308 ofp = open(os.path.join(destname, 'Contents/Info.plist'), 'w')
309 ofp.write(plistdata)
310 ofp.close()
311 ownertype = 'PytA'
312 # Create the PkgInfo file
313 ofp = open(os.path.join(destname, 'Contents/PkgInfo'), 'wb')
314 ofp.write('APPL' + ownertype)
315 ofp.close()
316
317
318 if DEBUG:
319 progress.label("Copy resources...")
320 progress.set(20)
321 resfilename = '%s.rsrc' % shortname
322 respartialpathname = 'Contents/Resources/%s' % resfilename
323 try:
324 output = Res.FSOpenResourceFile(
325 os.path.join(destname, respartialpathname),
326 u'', WRITE)
327 except MacOS.Error:
328 fsr, dummy = Res.FSCreateResourceFile(
329 os.path.join(destname, 'Contents/Resources'),
330 unicode(resfilename), '')
331 output = Res.FSOpenResourceFile(fsr, u'', WRITE)
332
333 # Copy the resources from the target specific resource template, if any
334 typesfound, ownertype = [], None
335 try:
336 input = macresource.open_pathname(rsrcname)
337 except (MacOS.Error, ValueError):
338 pass
339 if DEBUG:
340 progress.inc(50)
341 else:
342 typesfound, ownertype = copyres(input, output, [], 0, progress)
343 Res.CloseResFile(input)
344
345 # Check which resource-types we should not copy from the template
346 skiptypes = []
347## if 'vers' in typesfound: skiptypes.append('vers')
348## if 'SIZE' in typesfound: skiptypes.append('SIZE')
349## if 'BNDL' in typesfound: skiptypes = skiptypes + ['BNDL', 'FREF', 'icl4',
350## 'icl8', 'ics4', 'ics8', 'ICN#', 'ics#']
351## if not copy_codefragment:
352## skiptypes.append('cfrg')
353## skipowner = (ownertype <> None)
354
355 # Copy the resources from the template
356
357 input = Res.FSOpenResourceFile(
358 os.path.join(template, 'Contents/Resources/python.rsrc'), u'', READ)
359 dummy, tmplowner = copyres(input, output, skiptypes, 1, progress)
360
361 Res.CloseResFile(input)
362## if ownertype == None:
363## raise BuildError, "No owner resource found in either resource file or template"
364 # Make sure we're manipulating the output resource file now
365
366 Res.CloseResFile(output)
367
368 if code:
369 outputfilename = os.path.join(destname, 'Contents/Resources/__main__.pyc')
370 writepycfile(code, outputfilename)
371
372## macostools.touched(dest_fss)
Jack Jansen813c9971998-07-31 09:42:35 +0000373
374# Copy resources between two resource file descriptors.
Jack Jansen81da9f11999-03-17 22:57:55 +0000375# skip a resource named '__main__' or (if skipowner is set) with ID zero.
Jack Jansen813c9971998-07-31 09:42:35 +0000376# Also skip resources with a type listed in skiptypes.
377#
378def copyres(input, output, skiptypes, skipowner, progress=None):
379 ctor = None
380 alltypes = []
381 Res.UseResFile(input)
382 ntypes = Res.Count1Types()
383 progress_type_inc = 50/ntypes
384 for itype in range(1, 1+ntypes):
385 type = Res.Get1IndType(itype)
386 if type in skiptypes:
387 continue
388 alltypes.append(type)
389 nresources = Res.Count1Resources(type)
390 progress_cur_inc = progress_type_inc/nresources
391 for ires in range(1, 1+nresources):
392 res = Res.Get1IndResource(type, ires)
393 id, type, name = res.GetResInfo()
394 lcname = string.lower(name)
Jack Jansen81da9f11999-03-17 22:57:55 +0000395
396 if lcname == OWNERNAME and id == 0:
Jack Jansen813c9971998-07-31 09:42:35 +0000397 if skipowner:
398 continue # Skip this one
399 else:
400 ctor = type
401 size = res.size
402 attrs = res.GetResAttrs()
403 if DEBUG and progress:
404 progress.label("Copy %s %d %s"%(type, id, name))
405 progress.inc(progress_cur_inc)
406 res.LoadResource()
407 res.DetachResource()
408 Res.UseResFile(output)
409 try:
410 res2 = Res.Get1Resource(type, id)
411 except MacOS.Error:
412 res2 = None
413 if res2:
414 if DEBUG and progress:
415 progress.label("Overwrite %s %d %s"%(type, id, name))
416 res2.RemoveResource()
417 res.AddResource(type, id, name)
418 res.WriteResource()
419 attrs = attrs | res.GetResAttrs()
420 res.SetResAttrs(attrs)
421 Res.UseResFile(input)
422 return alltypes, ctor
423
Jack Jansenb2e33fe2002-03-29 21:21:28 +0000424def copyapptree(srctree, dsttree, exceptlist=[]):
425 names = []
426 if os.path.exists(dsttree):
427 shutil.rmtree(dsttree)
428 os.mkdir(dsttree)
429 todo = os.listdir(srctree)
430 while todo:
431 this, todo = todo[0], todo[1:]
432 if this in exceptlist:
433 continue
434 thispath = os.path.join(srctree, this)
435 if os.path.isdir(thispath):
436 thiscontent = os.listdir(thispath)
437 for t in thiscontent:
438 todo.append(os.path.join(this, t))
439 names.append(this)
440 for this in names:
441 srcpath = os.path.join(srctree, this)
442 dstpath = os.path.join(dsttree, this)
443 if os.path.isdir(srcpath):
444 os.mkdir(dstpath)
445 else:
446 shutil.copy2(srcpath, dstpath)
447
448def writepycfile(codeobject, cfile):
449 import marshal
450 fc = open(cfile, 'wb')
451 fc.write('\0\0\0\0') # MAGIC placeholder, written later
452 fc.write('\0\0\0\0') # Timestap placeholder, not needed
453 marshal.dump(codeobject, fc)
454 fc.flush()
455 fc.seek(0, 0)
456 fc.write(MAGIC)
457 fc.close()
Jack Jansen813c9971998-07-31 09:42:35 +0000458