blob: 25f77e63ed2cfc54b0153baab885cccba040f5ac [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]
271 plistname = shortname + '.plist'
272 # Start with copying the .app framework
273 if not is_update:
274 exceptlist = ["Contents/Info.plist",
275 "Contents/Resources/English.lproj/InfoPlist.strings",
276 "Contents/Resources/python.rsrc",
277 ]
278 copyapptree(template, destname, exceptlist)
279 # Now either use the .plist file or the default
280 if plistname and os.path.exists(plistname):
281 shutil.copy2(plistname, os.path.join(destname, 'Contents/Info.plist'))
282 # XXXX Wrong. This should be parsed from plist file
283 # icnsname = 'PythonApplet.icns'
284 ownertype = 'PytA'
285 # XXXX Should copy .icns file
286 else:
287 plistname = os.path.join(template, 'Contents/Resources/Applet-Info.plist')
288 plistdata = open(plistname).read()
289 plistdata = plistdata % {'appletname':shortname}
290 ofp = open(os.path.join(destname, 'Contents/Info.plist'), 'w')
291 ofp.write(plistdata)
292 ofp.close()
293 ownertype = 'PytA'
294 # Create the PkgInfo file
295 ofp = open(os.path.join(destname, 'Contents/PkgInfo'), 'wb')
296 ofp.write('APPL' + ownertype)
297 ofp.close()
298
299
300 if DEBUG:
301 progress.label("Copy resources...")
302 progress.set(20)
303 resfilename = '%s.rsrc' % shortname
304 respartialpathname = 'Contents/Resources/%s' % resfilename
305 try:
306 output = Res.FSOpenResourceFile(
307 os.path.join(destname, respartialpathname),
308 u'', WRITE)
309 except MacOS.Error:
310 fsr, dummy = Res.FSCreateResourceFile(
311 os.path.join(destname, 'Contents/Resources'),
312 unicode(resfilename), '')
313 output = Res.FSOpenResourceFile(fsr, u'', WRITE)
314
315 # Copy the resources from the target specific resource template, if any
316 typesfound, ownertype = [], None
317 try:
318 input = macresource.open_pathname(rsrcname)
319 except (MacOS.Error, ValueError):
320 pass
321 if DEBUG:
322 progress.inc(50)
323 else:
324 typesfound, ownertype = copyres(input, output, [], 0, progress)
325 Res.CloseResFile(input)
326
327 # Check which resource-types we should not copy from the template
328 skiptypes = []
329## if 'vers' in typesfound: skiptypes.append('vers')
330## if 'SIZE' in typesfound: skiptypes.append('SIZE')
331## if 'BNDL' in typesfound: skiptypes = skiptypes + ['BNDL', 'FREF', 'icl4',
332## 'icl8', 'ics4', 'ics8', 'ICN#', 'ics#']
333## if not copy_codefragment:
334## skiptypes.append('cfrg')
335## skipowner = (ownertype <> None)
336
337 # Copy the resources from the template
338
339 input = Res.FSOpenResourceFile(
340 os.path.join(template, 'Contents/Resources/python.rsrc'), u'', READ)
341 dummy, tmplowner = copyres(input, output, skiptypes, 1, progress)
342
343 Res.CloseResFile(input)
344## if ownertype == None:
345## raise BuildError, "No owner resource found in either resource file or template"
346 # Make sure we're manipulating the output resource file now
347
348 Res.CloseResFile(output)
349
350 if code:
351 outputfilename = os.path.join(destname, 'Contents/Resources/__main__.pyc')
352 writepycfile(code, outputfilename)
353
354## macostools.touched(dest_fss)
Jack Jansen813c9971998-07-31 09:42:35 +0000355
356# Copy resources between two resource file descriptors.
Jack Jansen81da9f11999-03-17 22:57:55 +0000357# skip a resource named '__main__' or (if skipowner is set) with ID zero.
Jack Jansen813c9971998-07-31 09:42:35 +0000358# Also skip resources with a type listed in skiptypes.
359#
360def copyres(input, output, skiptypes, skipowner, progress=None):
361 ctor = None
362 alltypes = []
363 Res.UseResFile(input)
364 ntypes = Res.Count1Types()
365 progress_type_inc = 50/ntypes
366 for itype in range(1, 1+ntypes):
367 type = Res.Get1IndType(itype)
368 if type in skiptypes:
369 continue
370 alltypes.append(type)
371 nresources = Res.Count1Resources(type)
372 progress_cur_inc = progress_type_inc/nresources
373 for ires in range(1, 1+nresources):
374 res = Res.Get1IndResource(type, ires)
375 id, type, name = res.GetResInfo()
376 lcname = string.lower(name)
Jack Jansen81da9f11999-03-17 22:57:55 +0000377
378 if lcname == OWNERNAME and id == 0:
Jack Jansen813c9971998-07-31 09:42:35 +0000379 if skipowner:
380 continue # Skip this one
381 else:
382 ctor = type
383 size = res.size
384 attrs = res.GetResAttrs()
385 if DEBUG and progress:
386 progress.label("Copy %s %d %s"%(type, id, name))
387 progress.inc(progress_cur_inc)
388 res.LoadResource()
389 res.DetachResource()
390 Res.UseResFile(output)
391 try:
392 res2 = Res.Get1Resource(type, id)
393 except MacOS.Error:
394 res2 = None
395 if res2:
396 if DEBUG and progress:
397 progress.label("Overwrite %s %d %s"%(type, id, name))
398 res2.RemoveResource()
399 res.AddResource(type, id, name)
400 res.WriteResource()
401 attrs = attrs | res.GetResAttrs()
402 res.SetResAttrs(attrs)
403 Res.UseResFile(input)
404 return alltypes, ctor
405
Jack Jansenb2e33fe2002-03-29 21:21:28 +0000406def copyapptree(srctree, dsttree, exceptlist=[]):
407 names = []
408 if os.path.exists(dsttree):
409 shutil.rmtree(dsttree)
410 os.mkdir(dsttree)
411 todo = os.listdir(srctree)
412 while todo:
413 this, todo = todo[0], todo[1:]
414 if this in exceptlist:
415 continue
416 thispath = os.path.join(srctree, this)
417 if os.path.isdir(thispath):
418 thiscontent = os.listdir(thispath)
419 for t in thiscontent:
420 todo.append(os.path.join(this, t))
421 names.append(this)
422 for this in names:
423 srcpath = os.path.join(srctree, this)
424 dstpath = os.path.join(dsttree, this)
425 if os.path.isdir(srcpath):
426 os.mkdir(dstpath)
427 else:
428 shutil.copy2(srcpath, dstpath)
429
430def writepycfile(codeobject, cfile):
431 import marshal
432 fc = open(cfile, 'wb')
433 fc.write('\0\0\0\0') # MAGIC placeholder, written later
434 fc.write('\0\0\0\0') # Timestap placeholder, not needed
435 marshal.dump(codeobject, fc)
436 fc.flush()
437 fc.seek(0, 0)
438 fc.write(MAGIC)
439 fc.close()
Jack Jansen813c9971998-07-31 09:42:35 +0000440