blob: 470f016981188b0f30facfe684c58d7e76472692 [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
Jack Jansen5a6fdcd2001-08-25 12:15:04 +00008from Carbon import Res
Jack Jansencc947642003-02-02 23:03:50 +00009import Carbon.Files
10import Carbon.File
Jack Jansen813c9971998-07-31 09:42:35 +000011import 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
Jack Jansen813c9971998-07-31 09:42:35 +000020# .pyc file (and 'PYC ' resource magic number)
21MAGIC = imp.get_magic()
22
23# Template file (searched on sys.path)
Jack Jansen3b805261999-02-14 23:12:06 +000024TEMPLATE = "PythonInterpreter"
Jack Jansen813c9971998-07-31 09:42:35 +000025
26# Specification of our resource
27RESTYPE = 'PYC '
28RESNAME = '__main__'
29
30# A resource with this name sets the "owner" (creator) of the destination
Jack Jansen81da9f11999-03-17 22:57:55 +000031# It should also have ID=0. Either of these alone is not enough.
Jack Jansen813c9971998-07-31 09:42:35 +000032OWNERNAME = "owner resource"
33
Jack Jansen81da9f11999-03-17 22:57:55 +000034# Default applet creator code
35DEFAULT_APPLET_CREATOR="Pyta"
36
Jack Jansen813c9971998-07-31 09:42:35 +000037# OpenResFile mode parameters
38READ = 1
39WRITE = 2
40
Jack Jansencc947642003-02-02 23:03:50 +000041# Parameter for FSOpenResourceFile
42RESOURCE_FORK_NAME=Carbon.File.FSGetResourceForkName()
Jack Jansen813c9971998-07-31 09:42:35 +000043
Jack Jansena4f8e582001-02-17 23:30:19 +000044def findtemplate(template=None):
Jack Jansen813c9971998-07-31 09:42:35 +000045 """Locate the applet template along sys.path"""
Jack Jansenb2e33fe2002-03-29 21:21:28 +000046 if MacOS.runtimemodel == 'macho':
Jack Jansenf59c6fa2003-02-12 15:37:26 +000047 return None
Jack Jansena4f8e582001-02-17 23:30:19 +000048 if not template:
49 template=TEMPLATE
Jack Jansen813c9971998-07-31 09:42:35 +000050 for p in sys.path:
Jack Jansena4f8e582001-02-17 23:30:19 +000051 file = os.path.join(p, template)
Jack Jansen813c9971998-07-31 09:42:35 +000052 try:
Jack Jansencc947642003-02-02 23:03:50 +000053 file, d1, d2 = Carbon.File.FSResolveAliasFile(file, 1)
Jack Jansen813c9971998-07-31 09:42:35 +000054 break
Jack Jansen769e1ff2003-02-05 13:39:04 +000055 except (Carbon.File.Error, ValueError):
Jack Jansen813c9971998-07-31 09:42:35 +000056 continue
57 else:
Jack Jansena4f8e582001-02-17 23:30:19 +000058 raise BuildError, "Template %s not found on sys.path" % `template`
59 file = file.as_pathname()
60 return file
Jack Jansenb2e33fe2002-03-29 21:21:28 +000061
Jack Jansenf59c6fa2003-02-12 15:37:26 +000062def process(template, filename, destname, copy_codefragment=0,
Jack Jansen388fbf32002-06-09 22:08:52 +000063 rsrcname=None, others=[], raw=0, progress="default"):
Jack Jansen813c9971998-07-31 09:42:35 +000064
Jack Jansen388fbf32002-06-09 22:08:52 +000065 if progress == "default":
Jack Jansen813c9971998-07-31 09:42:35 +000066 progress = EasyDialogs.ProgressBar("Processing %s..."%os.path.split(filename)[1], 120)
67 progress.label("Compiling...")
Jack Jansen388fbf32002-06-09 22:08:52 +000068 progress.inc(0)
Jack Jansen58ba80a2002-08-18 21:57:09 +000069 # check for the script name being longer than 32 chars. This may trigger a bug
70 # on OSX that can destroy your sourcefile.
71 if '#' in os.path.split(filename)[1]:
72 raise BuildError, "BuildApplet could destroy your sourcefile on OSX, please rename: %s" % filename
Jack Jansen813c9971998-07-31 09:42:35 +000073 # Read the source and compile it
74 # (there's no point overwriting the destination if it has a syntax error)
75
Jack Jansen5d099042002-06-20 20:42:07 +000076 fp = open(filename, 'rU')
Jack Jansen813c9971998-07-31 09:42:35 +000077 text = fp.read()
78 fp.close()
79 try:
Just van Rossum4e051d42003-01-09 10:47:20 +000080 code = compile(text + '\n', filename, "exec")
Jack Jansen2eb4b182002-08-02 14:04:15 +000081 except SyntaxError, arg:
82 raise BuildError, "Syntax error in script %s: %s" % (filename, arg)
83 except EOFError:
84 raise BuildError, "End-of-file in script %s" % (filename,)
Jack Jansen813c9971998-07-31 09:42:35 +000085
Jack Jansen388fbf32002-06-09 22:08:52 +000086 # Set the destination file name. Note that basename
87 # does contain the whole filepath, only a .py is stripped.
Jack Jansen813c9971998-07-31 09:42:35 +000088
89 if string.lower(filename[-3:]) == ".py":
Jack Jansen388fbf32002-06-09 22:08:52 +000090 basename = filename[:-3]
91 if MacOS.runtimemodel != 'macho' and not destname:
92 destname = basename
Jack Jansen813c9971998-07-31 09:42:35 +000093 else:
Jack Jansen388fbf32002-06-09 22:08:52 +000094 basename = filename
95
96 if not destname:
Jack Jansenb2e33fe2002-03-29 21:21:28 +000097 if MacOS.runtimemodel == 'macho':
Jack Jansen388fbf32002-06-09 22:08:52 +000098 destname = basename + '.app'
Jack Jansenb2e33fe2002-03-29 21:21:28 +000099 else:
Jack Jansen388fbf32002-06-09 22:08:52 +0000100 destname = basename + '.applet'
101 if not rsrcname:
102 rsrcname = basename + '.rsrc'
103
Jack Jansenb2e33fe2002-03-29 21:21:28 +0000104 # Try removing the output file. This fails in MachO, but it should
105 # do any harm.
Jack Jansen813c9971998-07-31 09:42:35 +0000106 try:
107 os.remove(destname)
108 except os.error:
109 pass
Jack Jansen388fbf32002-06-09 22:08:52 +0000110 process_common(template, progress, code, rsrcname, destname, 0,
Jack Jansenf59c6fa2003-02-12 15:37:26 +0000111 copy_codefragment, raw, others, filename)
Jack Jansen813c9971998-07-31 09:42:35 +0000112
113
114def update(template, filename, output):
Jack Jansenb2e33fe2002-03-29 21:21:28 +0000115 if MacOS.runtimemodel == 'macho':
116 raise BuildError, "No updating yet for MachO applets"
Jack Jansen388fbf32002-06-09 22:08:52 +0000117 if progress:
Jack Jansen813c9971998-07-31 09:42:35 +0000118 progress = EasyDialogs.ProgressBar("Updating %s..."%os.path.split(filename)[1], 120)
119 else:
120 progress = None
121 if not output:
122 output = filename + ' (updated)'
123
124 # Try removing the output file
125 try:
126 os.remove(output)
127 except os.error:
128 pass
129 process_common(template, progress, None, filename, output, 1, 1)
130
131
Jack Jansen388fbf32002-06-09 22:08:52 +0000132def process_common(template, progress, code, rsrcname, destname, is_update,
Jack Jansenf59c6fa2003-02-12 15:37:26 +0000133 copy_codefragment, raw=0, others=[], filename=None):
Jack Jansenb2e33fe2002-03-29 21:21:28 +0000134 if MacOS.runtimemodel == 'macho':
Jack Jansen388fbf32002-06-09 22:08:52 +0000135 return process_common_macho(template, progress, code, rsrcname, destname,
Jack Jansenf59c6fa2003-02-12 15:37:26 +0000136 is_update, raw, others, filename)
Jack Jansen388fbf32002-06-09 22:08:52 +0000137 if others:
138 raise BuildError, "Extra files only allowed for MachoPython applets"
Jack Jansen813c9971998-07-31 09:42:35 +0000139 # Create FSSpecs for the various files
Jack Jansencc947642003-02-02 23:03:50 +0000140 template_fsr, d1, d2 = Carbon.File.FSResolveAliasFile(template, 1)
141 template = template_fsr.as_pathname()
Jack Jansen813c9971998-07-31 09:42:35 +0000142
143 # Copy data (not resources, yet) from the template
Jack Jansen388fbf32002-06-09 22:08:52 +0000144 if progress:
Jack Jansen813c9971998-07-31 09:42:35 +0000145 progress.label("Copy data fork...")
146 progress.set(10)
147
148 if copy_codefragment:
149 tmpl = open(template, "rb")
150 dest = open(destname, "wb")
151 data = tmpl.read()
152 if data:
153 dest.write(data)
154 dest.close()
155 tmpl.close()
156 del dest
157 del tmpl
158
159 # Open the output resource fork
160
Jack Jansen388fbf32002-06-09 22:08:52 +0000161 if progress:
Jack Jansen813c9971998-07-31 09:42:35 +0000162 progress.label("Copy resources...")
163 progress.set(20)
164 try:
Jack Jansencc947642003-02-02 23:03:50 +0000165 output = Res.FSOpenResourceFile(destname, RESOURCE_FORK_NAME, WRITE)
Jack Jansen813c9971998-07-31 09:42:35 +0000166 except MacOS.Error:
Jack Jansencc947642003-02-02 23:03:50 +0000167 destdir, destfile = os.path.split(destname)
Jack Jansen769e1ff2003-02-05 13:39:04 +0000168 Res.FSCreateResourceFile(destdir, unicode(destfile), RESOURCE_FORK_NAME)
Jack Jansencc947642003-02-02 23:03:50 +0000169 output = Res.FSOpenResourceFile(destname, RESOURCE_FORK_NAME, WRITE)
Jack Jansen813c9971998-07-31 09:42:35 +0000170
171 # Copy the resources from the target specific resource template, if any
172 typesfound, ownertype = [], None
173 try:
Jack Jansencc947642003-02-02 23:03:50 +0000174 input = Res.FSOpenResourceFile(rsrcname, RESOURCE_FORK_NAME, READ)
Jack Jansen813c9971998-07-31 09:42:35 +0000175 except (MacOS.Error, ValueError):
176 pass
Jack Jansen388fbf32002-06-09 22:08:52 +0000177 if progress:
Jack Jansen813c9971998-07-31 09:42:35 +0000178 progress.inc(50)
179 else:
180 if is_update:
181 skip_oldfile = ['cfrg']
182 else:
183 skip_oldfile = []
184 typesfound, ownertype = copyres(input, output, skip_oldfile, 0, progress)
185 Res.CloseResFile(input)
186
187 # Check which resource-types we should not copy from the template
Jack Jansen81da9f11999-03-17 22:57:55 +0000188 skiptypes = []
189 if 'vers' in typesfound: skiptypes.append('vers')
Jack Jansen813c9971998-07-31 09:42:35 +0000190 if 'SIZE' in typesfound: skiptypes.append('SIZE')
191 if 'BNDL' in typesfound: skiptypes = skiptypes + ['BNDL', 'FREF', 'icl4',
192 'icl8', 'ics4', 'ics8', 'ICN#', 'ics#']
193 if not copy_codefragment:
194 skiptypes.append('cfrg')
Jack Jansen81da9f11999-03-17 22:57:55 +0000195## skipowner = (ownertype <> None)
Jack Jansen813c9971998-07-31 09:42:35 +0000196
197 # Copy the resources from the template
198
Jack Jansencc947642003-02-02 23:03:50 +0000199 input = Res.FSOpenResourceFile(template, RESOURCE_FORK_NAME, READ)
Jack Jansen81da9f11999-03-17 22:57:55 +0000200 dummy, tmplowner = copyres(input, output, skiptypes, 1, progress)
201
Jack Jansen813c9971998-07-31 09:42:35 +0000202 Res.CloseResFile(input)
Jack Jansen81da9f11999-03-17 22:57:55 +0000203## if ownertype == None:
204## raise BuildError, "No owner resource found in either resource file or template"
Jack Jansen813c9971998-07-31 09:42:35 +0000205 # Make sure we're manipulating the output resource file now
206
207 Res.UseResFile(output)
Jack Jansen81da9f11999-03-17 22:57:55 +0000208
209 if ownertype == None:
210 # No owner resource in the template. We have skipped the
211 # Python owner resource, so we have to add our own. The relevant
212 # bundle stuff is already included in the interpret/applet template.
213 newres = Res.Resource('\0')
214 newres.AddResource(DEFAULT_APPLET_CREATOR, 0, "Owner resource")
215 ownertype = DEFAULT_APPLET_CREATOR
Jack Jansen813c9971998-07-31 09:42:35 +0000216
217 if code:
218 # Delete any existing 'PYC ' resource named __main__
219
220 try:
221 res = Res.Get1NamedResource(RESTYPE, RESNAME)
222 res.RemoveResource()
223 except Res.Error:
224 pass
225
226 # Create the raw data for the resource from the code object
Jack Jansen388fbf32002-06-09 22:08:52 +0000227 if progress:
Jack Jansen813c9971998-07-31 09:42:35 +0000228 progress.label("Write PYC resource...")
229 progress.set(120)
230
231 data = marshal.dumps(code)
232 del code
233 data = (MAGIC + '\0\0\0\0') + data
234
235 # Create the resource and write it
236
237 id = 0
238 while id < 128:
239 id = Res.Unique1ID(RESTYPE)
240 res = Res.Resource(data)
241 res.AddResource(RESTYPE, id, RESNAME)
Just van Rossum874f87b1999-01-30 22:31:26 +0000242 attrs = res.GetResAttrs()
243 attrs = attrs | 0x04 # set preload
244 res.SetResAttrs(attrs)
Jack Jansen813c9971998-07-31 09:42:35 +0000245 res.WriteResource()
246 res.ReleaseResource()
247
248 # Close the output file
249
250 Res.CloseResFile(output)
251
Jack Jansencc947642003-02-02 23:03:50 +0000252 # Now set the creator, type and bundle bit of the destination.
253 # Done with FSSpec's, FSRef FInfo isn't good enough yet (2.3a1+)
Jack Jansen769e1ff2003-02-05 13:39:04 +0000254 dest_fss = Carbon.File.FSSpec(destname)
Jack Jansencc947642003-02-02 23:03:50 +0000255 dest_finfo = dest_fss.FSpGetFInfo()
Jack Jansen813c9971998-07-31 09:42:35 +0000256 dest_finfo.Creator = ownertype
257 dest_finfo.Type = 'APPL'
Jack Jansencc947642003-02-02 23:03:50 +0000258 dest_finfo.Flags = dest_finfo.Flags | Carbon.Files.kHasBundle | Carbon.Files.kIsShared
259 dest_finfo.Flags = dest_finfo.Flags & ~Carbon.Files.kHasBeenInited
260 dest_fss.FSpSetFInfo(dest_finfo)
Jack Jansen813c9971998-07-31 09:42:35 +0000261
Jack Jansencc947642003-02-02 23:03:50 +0000262 macostools.touched(destname)
Jack Jansen388fbf32002-06-09 22:08:52 +0000263 if progress:
Jack Jansen813c9971998-07-31 09:42:35 +0000264 progress.label("Done.")
Jack Jansen388fbf32002-06-09 22:08:52 +0000265 progress.inc(0)
Jack Jansen813c9971998-07-31 09:42:35 +0000266
Jack Jansenf59c6fa2003-02-12 15:37:26 +0000267def process_common_macho(template, progress, code, rsrcname, destname, is_update,
268 raw=0, others=[], filename=None):
269 # Check that we have a filename
270 if filename is None:
271 raise BuildError, "Need source filename on MacOSX"
Jack Jansenb2e33fe2002-03-29 21:21:28 +0000272 # First make sure the name ends in ".app"
273 if destname[-4:] != '.app':
274 destname = destname + '.app'
275 # Now deduce the short name
Jack Jansenf59c6fa2003-02-12 15:37:26 +0000276 destdir, shortname = os.path.split(destname)
Jack Jansenb2e33fe2002-03-29 21:21:28 +0000277 if shortname[-4:] == '.app':
278 # Strip the .app suffix
279 shortname = shortname[:-4]
Jack Jansen9aa8fd02002-03-29 23:44:37 +0000280 # And deduce the .plist and .icns names
281 plistname = None
282 icnsname = None
283 if rsrcname and rsrcname[-5:] == '.rsrc':
284 tmp = rsrcname[:-5]
285 plistname = tmp + '.plist'
286 if os.path.exists(plistname):
287 icnsname = tmp + '.icns'
288 if not os.path.exists(icnsname):
289 icnsname = None
290 else:
291 plistname = None
Jack Jansenf59c6fa2003-02-12 15:37:26 +0000292 if not os.path.exists(rsrcname):
293 rsrcname = None
Jack Jansen388fbf32002-06-09 22:08:52 +0000294 if progress:
Jack Jansenf59c6fa2003-02-12 15:37:26 +0000295 progress.label('Creating bundle...')
296 import bundlebuilder
297 builder = bundlebuilder.AppBuilder(verbosity=0)
298 builder.mainprogram = filename
299 builder.builddir = destdir
300 builder.name = shortname
301 if rsrcname:
302 builder.resources.append(rsrcname)
303 for o in others:
Jack Jansen00cbf072003-02-24 16:27:08 +0000304 if type(o) == str:
305 builder.resources.append(o)
306 else:
307 builder.files.append(o)
Jack Jansen9aa8fd02002-03-29 23:44:37 +0000308 if plistname:
Jack Jansen9f59d522003-02-18 23:30:27 +0000309 import plistlib
310 builder.plist = plistlib.Plist.fromFile(plistname)
Jack Jansenf59c6fa2003-02-12 15:37:26 +0000311 if icnsname:
312 builder.iconfile = icnsname
Jack Jansen9f59d522003-02-18 23:30:27 +0000313 if not raw:
314 builder.argv_emulation = 1
Jack Jansenf59c6fa2003-02-12 15:37:26 +0000315 builder.setup()
316 builder.build()
Jack Jansen388fbf32002-06-09 22:08:52 +0000317 if progress:
318 progress.label('Done.')
319 progress.inc(0)
Jack Jansenb2e33fe2002-03-29 21:21:28 +0000320
321## macostools.touched(dest_fss)
Jack Jansen813c9971998-07-31 09:42:35 +0000322
323# Copy resources between two resource file descriptors.
Jack Jansen81da9f11999-03-17 22:57:55 +0000324# skip a resource named '__main__' or (if skipowner is set) with ID zero.
Jack Jansen813c9971998-07-31 09:42:35 +0000325# Also skip resources with a type listed in skiptypes.
326#
327def copyres(input, output, skiptypes, skipowner, progress=None):
328 ctor = None
329 alltypes = []
330 Res.UseResFile(input)
331 ntypes = Res.Count1Types()
332 progress_type_inc = 50/ntypes
333 for itype in range(1, 1+ntypes):
334 type = Res.Get1IndType(itype)
335 if type in skiptypes:
336 continue
337 alltypes.append(type)
338 nresources = Res.Count1Resources(type)
339 progress_cur_inc = progress_type_inc/nresources
340 for ires in range(1, 1+nresources):
341 res = Res.Get1IndResource(type, ires)
342 id, type, name = res.GetResInfo()
343 lcname = string.lower(name)
Jack Jansen81da9f11999-03-17 22:57:55 +0000344
345 if lcname == OWNERNAME and id == 0:
Jack Jansen813c9971998-07-31 09:42:35 +0000346 if skipowner:
347 continue # Skip this one
348 else:
349 ctor = type
350 size = res.size
351 attrs = res.GetResAttrs()
Jack Jansen388fbf32002-06-09 22:08:52 +0000352 if progress:
Jack Jansen813c9971998-07-31 09:42:35 +0000353 progress.label("Copy %s %d %s"%(type, id, name))
354 progress.inc(progress_cur_inc)
355 res.LoadResource()
356 res.DetachResource()
357 Res.UseResFile(output)
358 try:
359 res2 = Res.Get1Resource(type, id)
360 except MacOS.Error:
361 res2 = None
362 if res2:
Jack Jansen388fbf32002-06-09 22:08:52 +0000363 if progress:
Jack Jansen813c9971998-07-31 09:42:35 +0000364 progress.label("Overwrite %s %d %s"%(type, id, name))
Jack Jansen388fbf32002-06-09 22:08:52 +0000365 progress.inc(0)
Jack Jansen813c9971998-07-31 09:42:35 +0000366 res2.RemoveResource()
367 res.AddResource(type, id, name)
368 res.WriteResource()
369 attrs = attrs | res.GetResAttrs()
370 res.SetResAttrs(attrs)
371 Res.UseResFile(input)
372 return alltypes, ctor
373
Jack Jansen388fbf32002-06-09 22:08:52 +0000374def copyapptree(srctree, dsttree, exceptlist=[], progress=None):
Jack Jansenb2e33fe2002-03-29 21:21:28 +0000375 names = []
376 if os.path.exists(dsttree):
377 shutil.rmtree(dsttree)
378 os.mkdir(dsttree)
379 todo = os.listdir(srctree)
380 while todo:
381 this, todo = todo[0], todo[1:]
382 if this in exceptlist:
383 continue
384 thispath = os.path.join(srctree, this)
385 if os.path.isdir(thispath):
386 thiscontent = os.listdir(thispath)
387 for t in thiscontent:
388 todo.append(os.path.join(this, t))
389 names.append(this)
390 for this in names:
391 srcpath = os.path.join(srctree, this)
392 dstpath = os.path.join(dsttree, this)
393 if os.path.isdir(srcpath):
394 os.mkdir(dstpath)
Jack Janseneb342292002-11-11 00:06:14 +0000395 elif os.path.islink(srcpath):
396 endpoint = os.readlink(srcpath)
397 os.symlink(endpoint, dstpath)
Jack Jansenb2e33fe2002-03-29 21:21:28 +0000398 else:
Jack Jansen388fbf32002-06-09 22:08:52 +0000399 if progress:
400 progress.label('Copy '+this)
401 progress.inc(0)
Jack Jansenb2e33fe2002-03-29 21:21:28 +0000402 shutil.copy2(srcpath, dstpath)
403
404def writepycfile(codeobject, cfile):
405 import marshal
406 fc = open(cfile, 'wb')
407 fc.write('\0\0\0\0') # MAGIC placeholder, written later
408 fc.write('\0\0\0\0') # Timestap placeholder, not needed
409 marshal.dump(codeobject, fc)
410 fc.flush()
411 fc.seek(0, 0)
412 fc.write(MAGIC)
413 fc.close()
Jack Jansen813c9971998-07-31 09:42:35 +0000414