blob: 62c456c38795d0012a78422343e0666d4b211be4 [file] [log] [blame]
Jack Jansen813c9971998-07-31 09:42:35 +00001"""tools for BuildApplet and BuildApplication"""
2
3import sys
4import os
Jack Jansen813c9971998-07-31 09:42:35 +00005import imp
6import marshal
Jack Jansen5a6fdcd2001-08-25 12:15:04 +00007from Carbon import Res
Jack Jansencc947642003-02-02 23:03:50 +00008import Carbon.Files
9import Carbon.File
Jack Jansen813c9971998-07-31 09:42:35 +000010import MacOS
11import macostools
Jack Jansenb2e33fe2002-03-29 21:21:28 +000012import macresource
Jack Jansen813c9971998-07-31 09:42:35 +000013import EasyDialogs
Jack Jansenb2e33fe2002-03-29 21:21:28 +000014import shutil
Jack Jansen813c9971998-07-31 09:42:35 +000015
16
17BuildError = "BuildError"
18
Jack Jansen813c9971998-07-31 09:42:35 +000019# .pyc file (and 'PYC ' resource magic number)
20MAGIC = imp.get_magic()
21
22# Template file (searched on sys.path)
Jack Jansen3b805261999-02-14 23:12:06 +000023TEMPLATE = "PythonInterpreter"
Jack Jansen813c9971998-07-31 09:42:35 +000024
25# Specification of our resource
26RESTYPE = 'PYC '
27RESNAME = '__main__'
28
29# A resource with this name sets the "owner" (creator) of the destination
Jack Jansen81da9f11999-03-17 22:57:55 +000030# It should also have ID=0. Either of these alone is not enough.
Jack Jansen813c9971998-07-31 09:42:35 +000031OWNERNAME = "owner resource"
32
Jack Jansen81da9f11999-03-17 22:57:55 +000033# Default applet creator code
34DEFAULT_APPLET_CREATOR="Pyta"
35
Jack Jansen813c9971998-07-31 09:42:35 +000036# OpenResFile mode parameters
37READ = 1
38WRITE = 2
39
Jack Jansencc947642003-02-02 23:03:50 +000040# Parameter for FSOpenResourceFile
41RESOURCE_FORK_NAME=Carbon.File.FSGetResourceForkName()
Jack Jansen813c9971998-07-31 09:42:35 +000042
Jack Jansena4f8e582001-02-17 23:30:19 +000043def findtemplate(template=None):
Jack Jansen0ae32202003-04-09 13:25:43 +000044 """Locate the applet template along sys.path"""
45 if MacOS.runtimemodel == 'macho':
46 return None
47 if not template:
48 template=TEMPLATE
49 for p in sys.path:
50 file = os.path.join(p, template)
51 try:
52 file, d1, d2 = Carbon.File.FSResolveAliasFile(file, 1)
53 break
54 except (Carbon.File.Error, ValueError):
55 continue
56 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +000057 raise BuildError, "Template %r not found on sys.path" % (template,)
Jack Jansen0ae32202003-04-09 13:25:43 +000058 file = file.as_pathname()
59 return file
Tim Peters182b5ac2004-07-18 06:16:08 +000060
61def process(template, filename, destname, copy_codefragment=0,
Jack Jansenc77f6df2004-12-27 15:51:03 +000062 rsrcname=None, others=[], raw=0, progress="default", destroot=""):
Tim Peters182b5ac2004-07-18 06:16:08 +000063
Jack Jansen0ae32202003-04-09 13:25:43 +000064 if progress == "default":
65 progress = EasyDialogs.ProgressBar("Processing %s..."%os.path.split(filename)[1], 120)
66 progress.label("Compiling...")
67 progress.inc(0)
68 # check for the script name being longer than 32 chars. This may trigger a bug
69 # on OSX that can destroy your sourcefile.
70 if '#' in os.path.split(filename)[1]:
71 raise BuildError, "BuildApplet could destroy your sourcefile on OSX, please rename: %s" % filename
72 # Read the source and compile it
73 # (there's no point overwriting the destination if it has a syntax error)
Tim Peters182b5ac2004-07-18 06:16:08 +000074
Jack Jansen0ae32202003-04-09 13:25:43 +000075 fp = open(filename, 'rU')
76 text = fp.read()
77 fp.close()
78 try:
79 code = compile(text + '\n', filename, "exec")
Guido van Rossumb940e112007-01-10 16:19:56 +000080 except SyntaxError as arg:
Jack Jansen0ae32202003-04-09 13:25:43 +000081 raise BuildError, "Syntax error in script %s: %s" % (filename, arg)
82 except EOFError:
83 raise BuildError, "End-of-file in script %s" % (filename,)
Tim Peters182b5ac2004-07-18 06:16:08 +000084
Jack Jansen0ae32202003-04-09 13:25:43 +000085 # Set the destination file name. Note that basename
86 # does contain the whole filepath, only a .py is stripped.
Tim Peters182b5ac2004-07-18 06:16:08 +000087
Neal Norwitz9d72bb42007-04-17 08:48:32 +000088 if filename[-3:].lower() == ".py":
Jack Jansen0ae32202003-04-09 13:25:43 +000089 basename = filename[:-3]
90 if MacOS.runtimemodel != 'macho' and not destname:
91 destname = basename
92 else:
93 basename = filename
Tim Peters182b5ac2004-07-18 06:16:08 +000094
Jack Jansen0ae32202003-04-09 13:25:43 +000095 if not destname:
96 if MacOS.runtimemodel == 'macho':
97 destname = basename + '.app'
98 else:
99 destname = basename + '.applet'
100 if not rsrcname:
101 rsrcname = basename + '.rsrc'
Tim Peters182b5ac2004-07-18 06:16:08 +0000102
Jack Jansen0ae32202003-04-09 13:25:43 +0000103 # Try removing the output file. This fails in MachO, but it should
104 # do any harm.
105 try:
106 os.remove(destname)
107 except os.error:
108 pass
Tim Peters182b5ac2004-07-18 06:16:08 +0000109 process_common(template, progress, code, rsrcname, destname, 0,
Jack Jansenc77f6df2004-12-27 15:51:03 +0000110 copy_codefragment, raw, others, filename, destroot)
Tim Peters182b5ac2004-07-18 06:16:08 +0000111
Jack Jansen813c9971998-07-31 09:42:35 +0000112
113def update(template, filename, output):
Jack Jansen0ae32202003-04-09 13:25:43 +0000114 if MacOS.runtimemodel == 'macho':
115 raise BuildError, "No updating yet for MachO applets"
116 if progress:
117 progress = EasyDialogs.ProgressBar("Updating %s..."%os.path.split(filename)[1], 120)
118 else:
119 progress = None
120 if not output:
121 output = filename + ' (updated)'
Tim Peters182b5ac2004-07-18 06:16:08 +0000122
Jack Jansen0ae32202003-04-09 13:25:43 +0000123 # Try removing the output file
124 try:
125 os.remove(output)
126 except os.error:
127 pass
128 process_common(template, progress, None, filename, output, 1, 1)
Jack Jansen813c9971998-07-31 09:42:35 +0000129
130
Tim Peters182b5ac2004-07-18 06:16:08 +0000131def process_common(template, progress, code, rsrcname, destname, is_update,
Jack Jansenc77f6df2004-12-27 15:51:03 +0000132 copy_codefragment, raw=0, others=[], filename=None, destroot=""):
Jack Jansen0ae32202003-04-09 13:25:43 +0000133 if MacOS.runtimemodel == 'macho':
134 return process_common_macho(template, progress, code, rsrcname, destname,
Jack Jansenc77f6df2004-12-27 15:51:03 +0000135 is_update, raw, others, filename, destroot)
Jack Jansen0ae32202003-04-09 13:25:43 +0000136 if others:
137 raise BuildError, "Extra files only allowed for MachoPython applets"
138 # Create FSSpecs for the various files
139 template_fsr, d1, d2 = Carbon.File.FSResolveAliasFile(template, 1)
140 template = template_fsr.as_pathname()
Tim Peters182b5ac2004-07-18 06:16:08 +0000141
Jack Jansen0ae32202003-04-09 13:25:43 +0000142 # Copy data (not resources, yet) from the template
143 if progress:
144 progress.label("Copy data fork...")
145 progress.set(10)
Tim Peters182b5ac2004-07-18 06:16:08 +0000146
Jack Jansen0ae32202003-04-09 13:25:43 +0000147 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
Tim Peters182b5ac2004-07-18 06:16:08 +0000157
Jack Jansen0ae32202003-04-09 13:25:43 +0000158 # Open the output resource fork
Tim Peters182b5ac2004-07-18 06:16:08 +0000159
Jack Jansen0ae32202003-04-09 13:25:43 +0000160 if progress:
161 progress.label("Copy resources...")
162 progress.set(20)
163 try:
164 output = Res.FSOpenResourceFile(destname, RESOURCE_FORK_NAME, WRITE)
165 except MacOS.Error:
166 destdir, destfile = os.path.split(destname)
Guido van Rossumef87d6e2007-05-02 19:09:54 +0000167 Res.FSCreateResourceFile(destdir, str(destfile), RESOURCE_FORK_NAME)
Jack Jansen0ae32202003-04-09 13:25:43 +0000168 output = Res.FSOpenResourceFile(destname, RESOURCE_FORK_NAME, WRITE)
Tim Peters182b5ac2004-07-18 06:16:08 +0000169
Jack Jansen0ae32202003-04-09 13:25:43 +0000170 # Copy the resources from the target specific resource template, if any
171 typesfound, ownertype = [], None
172 try:
173 input = Res.FSOpenResourceFile(rsrcname, RESOURCE_FORK_NAME, READ)
174 except (MacOS.Error, ValueError):
175 pass
176 if progress:
177 progress.inc(50)
178 else:
179 if is_update:
180 skip_oldfile = ['cfrg']
181 else:
182 skip_oldfile = []
183 typesfound, ownertype = copyres(input, output, skip_oldfile, 0, progress)
184 Res.CloseResFile(input)
Tim Peters182b5ac2004-07-18 06:16:08 +0000185
Jack Jansen0ae32202003-04-09 13:25:43 +0000186 # Check which resource-types we should not copy from the template
187 skiptypes = []
188 if 'vers' in typesfound: skiptypes.append('vers')
189 if 'SIZE' in typesfound: skiptypes.append('SIZE')
Tim Peters182b5ac2004-07-18 06:16:08 +0000190 if 'BNDL' in typesfound: skiptypes = skiptypes + ['BNDL', 'FREF', 'icl4',
Jack Jansen0ae32202003-04-09 13:25:43 +0000191 'icl8', 'ics4', 'ics8', 'ICN#', 'ics#']
192 if not copy_codefragment:
193 skiptypes.append('cfrg')
Guido van Rossumb053cd82006-08-24 03:53:23 +0000194## skipowner = (ownertype != None)
Tim Peters182b5ac2004-07-18 06:16:08 +0000195
Jack Jansen0ae32202003-04-09 13:25:43 +0000196 # Copy the resources from the template
Tim Peters182b5ac2004-07-18 06:16:08 +0000197
Jack Jansen0ae32202003-04-09 13:25:43 +0000198 input = Res.FSOpenResourceFile(template, RESOURCE_FORK_NAME, READ)
199 dummy, tmplowner = copyres(input, output, skiptypes, 1, progress)
Tim Peters182b5ac2004-07-18 06:16:08 +0000200
Jack Jansen0ae32202003-04-09 13:25:43 +0000201 Res.CloseResFile(input)
202## if ownertype == None:
203## raise BuildError, "No owner resource found in either resource file or template"
204 # Make sure we're manipulating the output resource file now
Tim Peters182b5ac2004-07-18 06:16:08 +0000205
Jack Jansen0ae32202003-04-09 13:25:43 +0000206 Res.UseResFile(output)
Jack Jansen81da9f11999-03-17 22:57:55 +0000207
Jack Jansen0ae32202003-04-09 13:25:43 +0000208 if ownertype == None:
209 # No owner resource in the template. We have skipped the
210 # Python owner resource, so we have to add our own. The relevant
211 # bundle stuff is already included in the interpret/applet template.
212 newres = Res.Resource('\0')
213 newres.AddResource(DEFAULT_APPLET_CREATOR, 0, "Owner resource")
214 ownertype = DEFAULT_APPLET_CREATOR
Tim Peters182b5ac2004-07-18 06:16:08 +0000215
Jack Jansen0ae32202003-04-09 13:25:43 +0000216 if code:
217 # Delete any existing 'PYC ' resource named __main__
Tim Peters182b5ac2004-07-18 06:16:08 +0000218
Jack Jansen0ae32202003-04-09 13:25:43 +0000219 try:
220 res = Res.Get1NamedResource(RESTYPE, RESNAME)
221 res.RemoveResource()
222 except Res.Error:
223 pass
Tim Peters182b5ac2004-07-18 06:16:08 +0000224
Jack Jansen0ae32202003-04-09 13:25:43 +0000225 # Create the raw data for the resource from the code object
226 if progress:
227 progress.label("Write PYC resource...")
228 progress.set(120)
Tim Peters182b5ac2004-07-18 06:16:08 +0000229
Jack Jansen0ae32202003-04-09 13:25:43 +0000230 data = marshal.dumps(code)
231 del code
232 data = (MAGIC + '\0\0\0\0') + data
Tim Peters182b5ac2004-07-18 06:16:08 +0000233
Jack Jansen0ae32202003-04-09 13:25:43 +0000234 # Create the resource and write it
Tim Peters182b5ac2004-07-18 06:16:08 +0000235
Jack Jansen0ae32202003-04-09 13:25:43 +0000236 id = 0
237 while id < 128:
238 id = Res.Unique1ID(RESTYPE)
239 res = Res.Resource(data)
240 res.AddResource(RESTYPE, id, RESNAME)
241 attrs = res.GetResAttrs()
242 attrs = attrs | 0x04 # set preload
243 res.SetResAttrs(attrs)
244 res.WriteResource()
245 res.ReleaseResource()
Tim Peters182b5ac2004-07-18 06:16:08 +0000246
Jack Jansen0ae32202003-04-09 13:25:43 +0000247 # Close the output file
Tim Peters182b5ac2004-07-18 06:16:08 +0000248
Jack Jansen0ae32202003-04-09 13:25:43 +0000249 Res.CloseResFile(output)
Tim Peters182b5ac2004-07-18 06:16:08 +0000250
Jack Jansen0ae32202003-04-09 13:25:43 +0000251 # Now set the creator, type and bundle bit of the destination.
252 # Done with FSSpec's, FSRef FInfo isn't good enough yet (2.3a1+)
253 dest_fss = Carbon.File.FSSpec(destname)
254 dest_finfo = dest_fss.FSpGetFInfo()
255 dest_finfo.Creator = ownertype
256 dest_finfo.Type = 'APPL'
257 dest_finfo.Flags = dest_finfo.Flags | Carbon.Files.kHasBundle | Carbon.Files.kIsShared
258 dest_finfo.Flags = dest_finfo.Flags & ~Carbon.Files.kHasBeenInited
259 dest_fss.FSpSetFInfo(dest_finfo)
Tim Peters182b5ac2004-07-18 06:16:08 +0000260
Jack Jansen0ae32202003-04-09 13:25:43 +0000261 macostools.touched(destname)
262 if progress:
263 progress.label("Done.")
264 progress.inc(0)
Jack Jansen813c9971998-07-31 09:42:35 +0000265
Tim Peters182b5ac2004-07-18 06:16:08 +0000266def process_common_macho(template, progress, code, rsrcname, destname, is_update,
Jack Jansenc77f6df2004-12-27 15:51:03 +0000267 raw=0, others=[], filename=None, destroot=""):
Jack Jansen0ae32202003-04-09 13:25:43 +0000268 # Check that we have a filename
269 if filename is None:
270 raise BuildError, "Need source filename on MacOSX"
271 # First make sure the name ends in ".app"
272 if destname[-4:] != '.app':
273 destname = destname + '.app'
274 # Now deduce the short name
275 destdir, shortname = os.path.split(destname)
276 if shortname[-4:] == '.app':
277 # Strip the .app suffix
278 shortname = shortname[:-4]
279 # And deduce the .plist and .icns names
280 plistname = None
281 icnsname = None
282 if rsrcname and rsrcname[-5:] == '.rsrc':
283 tmp = rsrcname[:-5]
284 plistname = tmp + '.plist'
285 if os.path.exists(plistname):
286 icnsname = tmp + '.icns'
287 if not os.path.exists(icnsname):
288 icnsname = None
289 else:
290 plistname = None
Jack Jansend69b7442003-04-22 14:33:48 +0000291 if not icnsname:
292 dft_icnsname = os.path.join(sys.prefix, 'Resources/Python.app/Contents/Resources/PythonApplet.icns')
293 if os.path.exists(dft_icnsname):
294 icnsname = dft_icnsname
Jack Jansen0ae32202003-04-09 13:25:43 +0000295 if not os.path.exists(rsrcname):
296 rsrcname = None
297 if progress:
298 progress.label('Creating bundle...')
299 import bundlebuilder
300 builder = bundlebuilder.AppBuilder(verbosity=0)
301 builder.mainprogram = filename
302 builder.builddir = destdir
303 builder.name = shortname
Jack Jansenc77f6df2004-12-27 15:51:03 +0000304 builder.destroot = destroot
Jack Jansen0ae32202003-04-09 13:25:43 +0000305 if rsrcname:
306 realrsrcname = macresource.resource_pathname(rsrcname)
Tim Peters182b5ac2004-07-18 06:16:08 +0000307 builder.files.append((realrsrcname,
Jack Jansen0ae32202003-04-09 13:25:43 +0000308 os.path.join('Contents/Resources', os.path.basename(rsrcname))))
309 for o in others:
310 if type(o) == str:
311 builder.resources.append(o)
312 else:
313 builder.files.append(o)
314 if plistname:
315 import plistlib
316 builder.plist = plistlib.Plist.fromFile(plistname)
317 if icnsname:
318 builder.iconfile = icnsname
319 if not raw:
320 builder.argv_emulation = 1
321 builder.setup()
322 builder.build()
Tim Peters182b5ac2004-07-18 06:16:08 +0000323 if progress:
Jack Jansen0ae32202003-04-09 13:25:43 +0000324 progress.label('Done.')
325 progress.inc(0)
Tim Peters182b5ac2004-07-18 06:16:08 +0000326
Jack Jansen0ae32202003-04-09 13:25:43 +0000327## macostools.touched(dest_fss)
Jack Jansen813c9971998-07-31 09:42:35 +0000328
329# Copy resources between two resource file descriptors.
Jack Jansen81da9f11999-03-17 22:57:55 +0000330# skip a resource named '__main__' or (if skipowner is set) with ID zero.
Jack Jansen813c9971998-07-31 09:42:35 +0000331# Also skip resources with a type listed in skiptypes.
332#
333def copyres(input, output, skiptypes, skipowner, progress=None):
Jack Jansen0ae32202003-04-09 13:25:43 +0000334 ctor = None
335 alltypes = []
336 Res.UseResFile(input)
337 ntypes = Res.Count1Types()
338 progress_type_inc = 50/ntypes
339 for itype in range(1, 1+ntypes):
340 type = Res.Get1IndType(itype)
341 if type in skiptypes:
342 continue
343 alltypes.append(type)
344 nresources = Res.Count1Resources(type)
345 progress_cur_inc = progress_type_inc/nresources
346 for ires in range(1, 1+nresources):
347 res = Res.Get1IndResource(type, ires)
348 id, type, name = res.GetResInfo()
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000349 lcname = name.lower()
Jack Jansen81da9f11999-03-17 22:57:55 +0000350
Jack Jansen0ae32202003-04-09 13:25:43 +0000351 if lcname == OWNERNAME and id == 0:
352 if skipowner:
353 continue # Skip this one
354 else:
355 ctor = type
356 size = res.size
357 attrs = res.GetResAttrs()
358 if progress:
359 progress.label("Copy %s %d %s"%(type, id, name))
360 progress.inc(progress_cur_inc)
361 res.LoadResource()
362 res.DetachResource()
363 Res.UseResFile(output)
364 try:
365 res2 = Res.Get1Resource(type, id)
366 except MacOS.Error:
367 res2 = None
368 if res2:
369 if progress:
370 progress.label("Overwrite %s %d %s"%(type, id, name))
371 progress.inc(0)
372 res2.RemoveResource()
373 res.AddResource(type, id, name)
374 res.WriteResource()
375 attrs = attrs | res.GetResAttrs()
376 res.SetResAttrs(attrs)
377 Res.UseResFile(input)
378 return alltypes, ctor
Jack Jansen813c9971998-07-31 09:42:35 +0000379
Jack Jansen388fbf32002-06-09 22:08:52 +0000380def copyapptree(srctree, dsttree, exceptlist=[], progress=None):
Jack Jansen0ae32202003-04-09 13:25:43 +0000381 names = []
382 if os.path.exists(dsttree):
383 shutil.rmtree(dsttree)
384 os.mkdir(dsttree)
385 todo = os.listdir(srctree)
386 while todo:
387 this, todo = todo[0], todo[1:]
388 if this in exceptlist:
389 continue
390 thispath = os.path.join(srctree, this)
391 if os.path.isdir(thispath):
392 thiscontent = os.listdir(thispath)
393 for t in thiscontent:
394 todo.append(os.path.join(this, t))
395 names.append(this)
396 for this in names:
397 srcpath = os.path.join(srctree, this)
398 dstpath = os.path.join(dsttree, this)
399 if os.path.isdir(srcpath):
400 os.mkdir(dstpath)
401 elif os.path.islink(srcpath):
402 endpoint = os.readlink(srcpath)
403 os.symlink(endpoint, dstpath)
404 else:
405 if progress:
406 progress.label('Copy '+this)
407 progress.inc(0)
408 shutil.copy2(srcpath, dstpath)
Tim Peters182b5ac2004-07-18 06:16:08 +0000409
Jack Jansenb2e33fe2002-03-29 21:21:28 +0000410def writepycfile(codeobject, cfile):
Jack Jansen0ae32202003-04-09 13:25:43 +0000411 import marshal
412 fc = open(cfile, 'wb')
413 fc.write('\0\0\0\0') # MAGIC placeholder, written later
414 fc.write('\0\0\0\0') # Timestap placeholder, not needed
415 marshal.dump(codeobject, fc)
416 fc.flush()
417 fc.seek(0, 0)
418 fc.write(MAGIC)
419 fc.close()