blob: 5678a9ff830c95a071004a6a35adb74435bb38a2 [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
Guido van Rossume7ba4952007-06-06 23:52:48 +000016import warnings
17warnings.warn("the buildtools module is deprecated", DeprecationWarning, 2)
18
Jack Jansen813c9971998-07-31 09:42:35 +000019
20BuildError = "BuildError"
21
Jack Jansen813c9971998-07-31 09:42:35 +000022# .pyc file (and 'PYC ' resource magic number)
23MAGIC = imp.get_magic()
24
25# Template file (searched on sys.path)
Jack Jansen3b805261999-02-14 23:12:06 +000026TEMPLATE = "PythonInterpreter"
Jack Jansen813c9971998-07-31 09:42:35 +000027
28# Specification of our resource
29RESTYPE = 'PYC '
30RESNAME = '__main__'
31
32# A resource with this name sets the "owner" (creator) of the destination
Jack Jansen81da9f11999-03-17 22:57:55 +000033# It should also have ID=0. Either of these alone is not enough.
Jack Jansen813c9971998-07-31 09:42:35 +000034OWNERNAME = "owner resource"
35
Jack Jansen81da9f11999-03-17 22:57:55 +000036# Default applet creator code
37DEFAULT_APPLET_CREATOR="Pyta"
38
Jack Jansen813c9971998-07-31 09:42:35 +000039# OpenResFile mode parameters
40READ = 1
41WRITE = 2
42
Jack Jansencc947642003-02-02 23:03:50 +000043# Parameter for FSOpenResourceFile
44RESOURCE_FORK_NAME=Carbon.File.FSGetResourceForkName()
Jack Jansen813c9971998-07-31 09:42:35 +000045
Jack Jansena4f8e582001-02-17 23:30:19 +000046def findtemplate(template=None):
Jack Jansen0ae32202003-04-09 13:25:43 +000047 """Locate the applet template along sys.path"""
48 if MacOS.runtimemodel == 'macho':
49 return None
50 if not template:
51 template=TEMPLATE
52 for p in sys.path:
53 file = os.path.join(p, template)
54 try:
55 file, d1, d2 = Carbon.File.FSResolveAliasFile(file, 1)
56 break
57 except (Carbon.File.Error, ValueError):
58 continue
59 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +000060 raise BuildError, "Template %r not found on sys.path" % (template,)
Jack Jansen0ae32202003-04-09 13:25:43 +000061 file = file.as_pathname()
62 return file
Tim Peters182b5ac2004-07-18 06:16:08 +000063
64def process(template, filename, destname, copy_codefragment=0,
Jack Jansenc77f6df2004-12-27 15:51:03 +000065 rsrcname=None, others=[], raw=0, progress="default", destroot=""):
Tim Peters182b5ac2004-07-18 06:16:08 +000066
Jack Jansen0ae32202003-04-09 13:25:43 +000067 if progress == "default":
68 progress = EasyDialogs.ProgressBar("Processing %s..."%os.path.split(filename)[1], 120)
69 progress.label("Compiling...")
70 progress.inc(0)
71 # check for the script name being longer than 32 chars. This may trigger a bug
72 # on OSX that can destroy your sourcefile.
73 if '#' in os.path.split(filename)[1]:
74 raise BuildError, "BuildApplet could destroy your sourcefile on OSX, please rename: %s" % filename
75 # Read the source and compile it
76 # (there's no point overwriting the destination if it has a syntax error)
Tim Peters182b5ac2004-07-18 06:16:08 +000077
Jack Jansen0ae32202003-04-09 13:25:43 +000078 fp = open(filename, 'rU')
79 text = fp.read()
80 fp.close()
81 try:
82 code = compile(text + '\n', filename, "exec")
Guido van Rossumb940e112007-01-10 16:19:56 +000083 except SyntaxError as arg:
Jack Jansen0ae32202003-04-09 13:25:43 +000084 raise BuildError, "Syntax error in script %s: %s" % (filename, arg)
85 except EOFError:
86 raise BuildError, "End-of-file in script %s" % (filename,)
Tim Peters182b5ac2004-07-18 06:16:08 +000087
Jack Jansen0ae32202003-04-09 13:25:43 +000088 # Set the destination file name. Note that basename
89 # does contain the whole filepath, only a .py is stripped.
Tim Peters182b5ac2004-07-18 06:16:08 +000090
Neal Norwitz9d72bb42007-04-17 08:48:32 +000091 if filename[-3:].lower() == ".py":
Jack Jansen0ae32202003-04-09 13:25:43 +000092 basename = filename[:-3]
93 if MacOS.runtimemodel != 'macho' and not destname:
94 destname = basename
95 else:
96 basename = filename
Tim Peters182b5ac2004-07-18 06:16:08 +000097
Jack Jansen0ae32202003-04-09 13:25:43 +000098 if not destname:
99 if MacOS.runtimemodel == 'macho':
100 destname = basename + '.app'
101 else:
102 destname = basename + '.applet'
103 if not rsrcname:
104 rsrcname = basename + '.rsrc'
Tim Peters182b5ac2004-07-18 06:16:08 +0000105
Jack Jansen0ae32202003-04-09 13:25:43 +0000106 # Try removing the output file. This fails in MachO, but it should
107 # do any harm.
108 try:
109 os.remove(destname)
110 except os.error:
111 pass
Tim Peters182b5ac2004-07-18 06:16:08 +0000112 process_common(template, progress, code, rsrcname, destname, 0,
Jack Jansenc77f6df2004-12-27 15:51:03 +0000113 copy_codefragment, raw, others, filename, destroot)
Tim Peters182b5ac2004-07-18 06:16:08 +0000114
Jack Jansen813c9971998-07-31 09:42:35 +0000115
116def update(template, filename, output):
Jack Jansen0ae32202003-04-09 13:25:43 +0000117 if MacOS.runtimemodel == 'macho':
118 raise BuildError, "No updating yet for MachO applets"
119 if progress:
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)'
Tim Peters182b5ac2004-07-18 06:16:08 +0000125
Jack Jansen0ae32202003-04-09 13:25:43 +0000126 # 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)
Jack Jansen813c9971998-07-31 09:42:35 +0000132
133
Tim Peters182b5ac2004-07-18 06:16:08 +0000134def process_common(template, progress, code, rsrcname, destname, is_update,
Jack Jansenc77f6df2004-12-27 15:51:03 +0000135 copy_codefragment, raw=0, others=[], filename=None, destroot=""):
Jack Jansen0ae32202003-04-09 13:25:43 +0000136 if MacOS.runtimemodel == 'macho':
137 return process_common_macho(template, progress, code, rsrcname, destname,
Jack Jansenc77f6df2004-12-27 15:51:03 +0000138 is_update, raw, others, filename, destroot)
Jack Jansen0ae32202003-04-09 13:25:43 +0000139 if others:
140 raise BuildError, "Extra files only allowed for MachoPython applets"
141 # Create FSSpecs for the various files
142 template_fsr, d1, d2 = Carbon.File.FSResolveAliasFile(template, 1)
143 template = template_fsr.as_pathname()
Tim Peters182b5ac2004-07-18 06:16:08 +0000144
Jack Jansen0ae32202003-04-09 13:25:43 +0000145 # Copy data (not resources, yet) from the template
146 if progress:
147 progress.label("Copy data fork...")
148 progress.set(10)
Tim Peters182b5ac2004-07-18 06:16:08 +0000149
Jack Jansen0ae32202003-04-09 13:25:43 +0000150 if copy_codefragment:
151 tmpl = open(template, "rb")
152 dest = open(destname, "wb")
153 data = tmpl.read()
154 if data:
155 dest.write(data)
156 dest.close()
157 tmpl.close()
158 del dest
159 del tmpl
Tim Peters182b5ac2004-07-18 06:16:08 +0000160
Jack Jansen0ae32202003-04-09 13:25:43 +0000161 # Open the output resource fork
Tim Peters182b5ac2004-07-18 06:16:08 +0000162
Jack Jansen0ae32202003-04-09 13:25:43 +0000163 if progress:
164 progress.label("Copy resources...")
165 progress.set(20)
166 try:
167 output = Res.FSOpenResourceFile(destname, RESOURCE_FORK_NAME, WRITE)
168 except MacOS.Error:
169 destdir, destfile = os.path.split(destname)
Guido van Rossumef87d6e2007-05-02 19:09:54 +0000170 Res.FSCreateResourceFile(destdir, str(destfile), RESOURCE_FORK_NAME)
Jack Jansen0ae32202003-04-09 13:25:43 +0000171 output = Res.FSOpenResourceFile(destname, RESOURCE_FORK_NAME, WRITE)
Tim Peters182b5ac2004-07-18 06:16:08 +0000172
Jack Jansen0ae32202003-04-09 13:25:43 +0000173 # Copy the resources from the target specific resource template, if any
174 typesfound, ownertype = [], None
175 try:
176 input = Res.FSOpenResourceFile(rsrcname, RESOURCE_FORK_NAME, READ)
177 except (MacOS.Error, ValueError):
178 pass
179 if progress:
180 progress.inc(50)
181 else:
182 if is_update:
183 skip_oldfile = ['cfrg']
184 else:
185 skip_oldfile = []
186 typesfound, ownertype = copyres(input, output, skip_oldfile, 0, progress)
187 Res.CloseResFile(input)
Tim Peters182b5ac2004-07-18 06:16:08 +0000188
Jack Jansen0ae32202003-04-09 13:25:43 +0000189 # Check which resource-types we should not copy from the template
190 skiptypes = []
191 if 'vers' in typesfound: skiptypes.append('vers')
192 if 'SIZE' in typesfound: skiptypes.append('SIZE')
Tim Peters182b5ac2004-07-18 06:16:08 +0000193 if 'BNDL' in typesfound: skiptypes = skiptypes + ['BNDL', 'FREF', 'icl4',
Jack Jansen0ae32202003-04-09 13:25:43 +0000194 'icl8', 'ics4', 'ics8', 'ICN#', 'ics#']
195 if not copy_codefragment:
196 skiptypes.append('cfrg')
Guido van Rossumb053cd82006-08-24 03:53:23 +0000197## skipowner = (ownertype != None)
Tim Peters182b5ac2004-07-18 06:16:08 +0000198
Jack Jansen0ae32202003-04-09 13:25:43 +0000199 # Copy the resources from the template
Tim Peters182b5ac2004-07-18 06:16:08 +0000200
Jack Jansen0ae32202003-04-09 13:25:43 +0000201 input = Res.FSOpenResourceFile(template, RESOURCE_FORK_NAME, READ)
202 dummy, tmplowner = copyres(input, output, skiptypes, 1, progress)
Tim Peters182b5ac2004-07-18 06:16:08 +0000203
Jack Jansen0ae32202003-04-09 13:25:43 +0000204 Res.CloseResFile(input)
205## if ownertype == None:
206## raise BuildError, "No owner resource found in either resource file or template"
207 # Make sure we're manipulating the output resource file now
Tim Peters182b5ac2004-07-18 06:16:08 +0000208
Jack Jansen0ae32202003-04-09 13:25:43 +0000209 Res.UseResFile(output)
Jack Jansen81da9f11999-03-17 22:57:55 +0000210
Jack Jansen0ae32202003-04-09 13:25:43 +0000211 if ownertype == None:
212 # No owner resource in the template. We have skipped the
213 # Python owner resource, so we have to add our own. The relevant
214 # bundle stuff is already included in the interpret/applet template.
215 newres = Res.Resource('\0')
216 newres.AddResource(DEFAULT_APPLET_CREATOR, 0, "Owner resource")
217 ownertype = DEFAULT_APPLET_CREATOR
Tim Peters182b5ac2004-07-18 06:16:08 +0000218
Jack Jansen0ae32202003-04-09 13:25:43 +0000219 if code:
220 # Delete any existing 'PYC ' resource named __main__
Tim Peters182b5ac2004-07-18 06:16:08 +0000221
Jack Jansen0ae32202003-04-09 13:25:43 +0000222 try:
223 res = Res.Get1NamedResource(RESTYPE, RESNAME)
224 res.RemoveResource()
225 except Res.Error:
226 pass
Tim Peters182b5ac2004-07-18 06:16:08 +0000227
Jack Jansen0ae32202003-04-09 13:25:43 +0000228 # Create the raw data for the resource from the code object
229 if progress:
230 progress.label("Write PYC resource...")
231 progress.set(120)
Tim Peters182b5ac2004-07-18 06:16:08 +0000232
Jack Jansen0ae32202003-04-09 13:25:43 +0000233 data = marshal.dumps(code)
234 del code
235 data = (MAGIC + '\0\0\0\0') + data
Tim Peters182b5ac2004-07-18 06:16:08 +0000236
Jack Jansen0ae32202003-04-09 13:25:43 +0000237 # Create the resource and write it
Tim Peters182b5ac2004-07-18 06:16:08 +0000238
Jack Jansen0ae32202003-04-09 13:25:43 +0000239 id = 0
240 while id < 128:
241 id = Res.Unique1ID(RESTYPE)
242 res = Res.Resource(data)
243 res.AddResource(RESTYPE, id, RESNAME)
244 attrs = res.GetResAttrs()
245 attrs = attrs | 0x04 # set preload
246 res.SetResAttrs(attrs)
247 res.WriteResource()
248 res.ReleaseResource()
Tim Peters182b5ac2004-07-18 06:16:08 +0000249
Jack Jansen0ae32202003-04-09 13:25:43 +0000250 # Close the output file
Tim Peters182b5ac2004-07-18 06:16:08 +0000251
Jack Jansen0ae32202003-04-09 13:25:43 +0000252 Res.CloseResFile(output)
Tim Peters182b5ac2004-07-18 06:16:08 +0000253
Jack Jansen0ae32202003-04-09 13:25:43 +0000254 # Now set the creator, type and bundle bit of the destination.
255 # Done with FSSpec's, FSRef FInfo isn't good enough yet (2.3a1+)
256 dest_fss = Carbon.File.FSSpec(destname)
257 dest_finfo = dest_fss.FSpGetFInfo()
258 dest_finfo.Creator = ownertype
259 dest_finfo.Type = 'APPL'
260 dest_finfo.Flags = dest_finfo.Flags | Carbon.Files.kHasBundle | Carbon.Files.kIsShared
261 dest_finfo.Flags = dest_finfo.Flags & ~Carbon.Files.kHasBeenInited
262 dest_fss.FSpSetFInfo(dest_finfo)
Tim Peters182b5ac2004-07-18 06:16:08 +0000263
Jack Jansen0ae32202003-04-09 13:25:43 +0000264 macostools.touched(destname)
265 if progress:
266 progress.label("Done.")
267 progress.inc(0)
Jack Jansen813c9971998-07-31 09:42:35 +0000268
Tim Peters182b5ac2004-07-18 06:16:08 +0000269def process_common_macho(template, progress, code, rsrcname, destname, is_update,
Jack Jansenc77f6df2004-12-27 15:51:03 +0000270 raw=0, others=[], filename=None, destroot=""):
Jack Jansen0ae32202003-04-09 13:25:43 +0000271 # Check that we have a filename
272 if filename is None:
273 raise BuildError, "Need source filename on MacOSX"
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 destdir, shortname = os.path.split(destname)
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
Jack Jansend69b7442003-04-22 14:33:48 +0000294 if not icnsname:
295 dft_icnsname = os.path.join(sys.prefix, 'Resources/Python.app/Contents/Resources/PythonApplet.icns')
296 if os.path.exists(dft_icnsname):
297 icnsname = dft_icnsname
Jack Jansen0ae32202003-04-09 13:25:43 +0000298 if not os.path.exists(rsrcname):
299 rsrcname = None
300 if progress:
301 progress.label('Creating bundle...')
302 import bundlebuilder
303 builder = bundlebuilder.AppBuilder(verbosity=0)
304 builder.mainprogram = filename
305 builder.builddir = destdir
306 builder.name = shortname
Jack Jansenc77f6df2004-12-27 15:51:03 +0000307 builder.destroot = destroot
Jack Jansen0ae32202003-04-09 13:25:43 +0000308 if rsrcname:
309 realrsrcname = macresource.resource_pathname(rsrcname)
Tim Peters182b5ac2004-07-18 06:16:08 +0000310 builder.files.append((realrsrcname,
Jack Jansen0ae32202003-04-09 13:25:43 +0000311 os.path.join('Contents/Resources', os.path.basename(rsrcname))))
312 for o in others:
313 if type(o) == str:
314 builder.resources.append(o)
315 else:
316 builder.files.append(o)
317 if plistname:
318 import plistlib
319 builder.plist = plistlib.Plist.fromFile(plistname)
320 if icnsname:
321 builder.iconfile = icnsname
322 if not raw:
323 builder.argv_emulation = 1
324 builder.setup()
325 builder.build()
Tim Peters182b5ac2004-07-18 06:16:08 +0000326 if progress:
Jack Jansen0ae32202003-04-09 13:25:43 +0000327 progress.label('Done.')
328 progress.inc(0)
Tim Peters182b5ac2004-07-18 06:16:08 +0000329
Jack Jansen0ae32202003-04-09 13:25:43 +0000330## macostools.touched(dest_fss)
Jack Jansen813c9971998-07-31 09:42:35 +0000331
332# Copy resources between two resource file descriptors.
Jack Jansen81da9f11999-03-17 22:57:55 +0000333# skip a resource named '__main__' or (if skipowner is set) with ID zero.
Jack Jansen813c9971998-07-31 09:42:35 +0000334# Also skip resources with a type listed in skiptypes.
335#
336def copyres(input, output, skiptypes, skipowner, progress=None):
Jack Jansen0ae32202003-04-09 13:25:43 +0000337 ctor = None
338 alltypes = []
339 Res.UseResFile(input)
340 ntypes = Res.Count1Types()
341 progress_type_inc = 50/ntypes
342 for itype in range(1, 1+ntypes):
343 type = Res.Get1IndType(itype)
344 if type in skiptypes:
345 continue
346 alltypes.append(type)
347 nresources = Res.Count1Resources(type)
348 progress_cur_inc = progress_type_inc/nresources
349 for ires in range(1, 1+nresources):
350 res = Res.Get1IndResource(type, ires)
351 id, type, name = res.GetResInfo()
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000352 lcname = name.lower()
Jack Jansen81da9f11999-03-17 22:57:55 +0000353
Jack Jansen0ae32202003-04-09 13:25:43 +0000354 if lcname == OWNERNAME and id == 0:
355 if skipowner:
356 continue # Skip this one
357 else:
358 ctor = type
359 size = res.size
360 attrs = res.GetResAttrs()
361 if progress:
362 progress.label("Copy %s %d %s"%(type, id, name))
363 progress.inc(progress_cur_inc)
364 res.LoadResource()
365 res.DetachResource()
366 Res.UseResFile(output)
367 try:
368 res2 = Res.Get1Resource(type, id)
369 except MacOS.Error:
370 res2 = None
371 if res2:
372 if progress:
373 progress.label("Overwrite %s %d %s"%(type, id, name))
374 progress.inc(0)
375 res2.RemoveResource()
376 res.AddResource(type, id, name)
377 res.WriteResource()
378 attrs = attrs | res.GetResAttrs()
379 res.SetResAttrs(attrs)
380 Res.UseResFile(input)
381 return alltypes, ctor
Jack Jansen813c9971998-07-31 09:42:35 +0000382
Jack Jansen388fbf32002-06-09 22:08:52 +0000383def copyapptree(srctree, dsttree, exceptlist=[], progress=None):
Jack Jansen0ae32202003-04-09 13:25:43 +0000384 names = []
385 if os.path.exists(dsttree):
386 shutil.rmtree(dsttree)
387 os.mkdir(dsttree)
388 todo = os.listdir(srctree)
389 while todo:
390 this, todo = todo[0], todo[1:]
391 if this in exceptlist:
392 continue
393 thispath = os.path.join(srctree, this)
394 if os.path.isdir(thispath):
395 thiscontent = os.listdir(thispath)
396 for t in thiscontent:
397 todo.append(os.path.join(this, t))
398 names.append(this)
399 for this in names:
400 srcpath = os.path.join(srctree, this)
401 dstpath = os.path.join(dsttree, this)
402 if os.path.isdir(srcpath):
403 os.mkdir(dstpath)
404 elif os.path.islink(srcpath):
405 endpoint = os.readlink(srcpath)
406 os.symlink(endpoint, dstpath)
407 else:
408 if progress:
409 progress.label('Copy '+this)
410 progress.inc(0)
411 shutil.copy2(srcpath, dstpath)
Tim Peters182b5ac2004-07-18 06:16:08 +0000412
Jack Jansenb2e33fe2002-03-29 21:21:28 +0000413def writepycfile(codeobject, cfile):
Jack Jansen0ae32202003-04-09 13:25:43 +0000414 import marshal
415 fc = open(cfile, 'wb')
416 fc.write('\0\0\0\0') # MAGIC placeholder, written later
417 fc.write('\0\0\0\0') # Timestap placeholder, not needed
418 marshal.dump(codeobject, fc)
419 fc.flush()
420 fc.seek(0, 0)
421 fc.write(MAGIC)
422 fc.close()