blob: 15450142233329c69c775f28a452e0391b80ed7c [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
Georg Brandlc76473d2007-09-03 07:27:49 +000020class BuildError(Exception):
21 pass
Jack Jansen813c9971998-07-31 09:42:35 +000022
Jack Jansen813c9971998-07-31 09:42:35 +000023# .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
Jack Jansencc947642003-02-02 23:03:50 +000044# Parameter for FSOpenResourceFile
45RESOURCE_FORK_NAME=Carbon.File.FSGetResourceForkName()
Jack Jansen813c9971998-07-31 09:42:35 +000046
Jack Jansena4f8e582001-02-17 23:30:19 +000047def findtemplate(template=None):
Jack Jansen0ae32202003-04-09 13:25:43 +000048 """Locate the applet template along sys.path"""
49 if MacOS.runtimemodel == 'macho':
50 return None
51 if not template:
52 template=TEMPLATE
53 for p in sys.path:
54 file = os.path.join(p, template)
55 try:
56 file, d1, d2 = Carbon.File.FSResolveAliasFile(file, 1)
57 break
58 except (Carbon.File.Error, ValueError):
59 continue
60 else:
Collin Wintere45be282007-08-23 00:01:55 +000061 raise BuildError("Template %r not found on sys.path" % (template,))
Jack Jansen0ae32202003-04-09 13:25:43 +000062 file = file.as_pathname()
63 return file
Tim Peters182b5ac2004-07-18 06:16:08 +000064
65def process(template, filename, destname, copy_codefragment=0,
Jack Jansenc77f6df2004-12-27 15:51:03 +000066 rsrcname=None, others=[], raw=0, progress="default", destroot=""):
Tim Peters182b5ac2004-07-18 06:16:08 +000067
Jack Jansen0ae32202003-04-09 13:25:43 +000068 if progress == "default":
69 progress = EasyDialogs.ProgressBar("Processing %s..."%os.path.split(filename)[1], 120)
70 progress.label("Compiling...")
71 progress.inc(0)
72 # check for the script name being longer than 32 chars. This may trigger a bug
73 # on OSX that can destroy your sourcefile.
74 if '#' in os.path.split(filename)[1]:
Collin Wintere45be282007-08-23 00:01:55 +000075 raise BuildError("BuildApplet could destroy your sourcefile on OSX, please rename: %s" % filename)
Jack Jansen0ae32202003-04-09 13:25:43 +000076 # Read the source and compile it
77 # (there's no point overwriting the destination if it has a syntax error)
Tim Peters182b5ac2004-07-18 06:16:08 +000078
Jack Jansen0ae32202003-04-09 13:25:43 +000079 fp = open(filename, 'rU')
80 text = fp.read()
81 fp.close()
82 try:
83 code = compile(text + '\n', filename, "exec")
Guido van Rossumb940e112007-01-10 16:19:56 +000084 except SyntaxError as arg:
Collin Wintere45be282007-08-23 00:01:55 +000085 raise BuildError("Syntax error in script %s: %s" % (filename, arg))
Jack Jansen0ae32202003-04-09 13:25:43 +000086 except EOFError:
Collin Wintere45be282007-08-23 00:01:55 +000087 raise BuildError("End-of-file in script %s" % (filename,))
Tim Peters182b5ac2004-07-18 06:16:08 +000088
Jack Jansen0ae32202003-04-09 13:25:43 +000089 # Set the destination file name. Note that basename
90 # does contain the whole filepath, only a .py is stripped.
Tim Peters182b5ac2004-07-18 06:16:08 +000091
Neal Norwitz9d72bb42007-04-17 08:48:32 +000092 if filename[-3:].lower() == ".py":
Jack Jansen0ae32202003-04-09 13:25:43 +000093 basename = filename[:-3]
94 if MacOS.runtimemodel != 'macho' and not destname:
95 destname = basename
96 else:
97 basename = filename
Tim Peters182b5ac2004-07-18 06:16:08 +000098
Jack Jansen0ae32202003-04-09 13:25:43 +000099 if not destname:
100 if MacOS.runtimemodel == 'macho':
101 destname = basename + '.app'
102 else:
103 destname = basename + '.applet'
104 if not rsrcname:
105 rsrcname = basename + '.rsrc'
Tim Peters182b5ac2004-07-18 06:16:08 +0000106
Jack Jansen0ae32202003-04-09 13:25:43 +0000107 # Try removing the output file. This fails in MachO, but it should
108 # do any harm.
109 try:
110 os.remove(destname)
111 except os.error:
112 pass
Tim Peters182b5ac2004-07-18 06:16:08 +0000113 process_common(template, progress, code, rsrcname, destname, 0,
Jack Jansenc77f6df2004-12-27 15:51:03 +0000114 copy_codefragment, raw, others, filename, destroot)
Tim Peters182b5ac2004-07-18 06:16:08 +0000115
Jack Jansen813c9971998-07-31 09:42:35 +0000116
117def update(template, filename, output):
Jack Jansen0ae32202003-04-09 13:25:43 +0000118 if MacOS.runtimemodel == 'macho':
Collin Wintere45be282007-08-23 00:01:55 +0000119 raise BuildError("No updating yet for MachO applets")
Jack Jansen0ae32202003-04-09 13:25:43 +0000120 if progress:
121 progress = EasyDialogs.ProgressBar("Updating %s..."%os.path.split(filename)[1], 120)
122 else:
123 progress = None
124 if not output:
125 output = filename + ' (updated)'
Tim Peters182b5ac2004-07-18 06:16:08 +0000126
Jack Jansen0ae32202003-04-09 13:25:43 +0000127 # Try removing the output file
128 try:
129 os.remove(output)
130 except os.error:
131 pass
132 process_common(template, progress, None, filename, output, 1, 1)
Jack Jansen813c9971998-07-31 09:42:35 +0000133
134
Tim Peters182b5ac2004-07-18 06:16:08 +0000135def process_common(template, progress, code, rsrcname, destname, is_update,
Jack Jansenc77f6df2004-12-27 15:51:03 +0000136 copy_codefragment, raw=0, others=[], filename=None, destroot=""):
Jack Jansen0ae32202003-04-09 13:25:43 +0000137 if MacOS.runtimemodel == 'macho':
138 return process_common_macho(template, progress, code, rsrcname, destname,
Jack Jansenc77f6df2004-12-27 15:51:03 +0000139 is_update, raw, others, filename, destroot)
Jack Jansen0ae32202003-04-09 13:25:43 +0000140 if others:
Collin Wintere45be282007-08-23 00:01:55 +0000141 raise BuildError("Extra files only allowed for MachoPython applets")
Jack Jansen0ae32202003-04-09 13:25:43 +0000142 # Create FSSpecs for the various files
143 template_fsr, d1, d2 = Carbon.File.FSResolveAliasFile(template, 1)
144 template = template_fsr.as_pathname()
Tim Peters182b5ac2004-07-18 06:16:08 +0000145
Jack Jansen0ae32202003-04-09 13:25:43 +0000146 # Copy data (not resources, yet) from the template
147 if progress:
148 progress.label("Copy data fork...")
149 progress.set(10)
Tim Peters182b5ac2004-07-18 06:16:08 +0000150
Jack Jansen0ae32202003-04-09 13:25:43 +0000151 if copy_codefragment:
152 tmpl = open(template, "rb")
153 dest = open(destname, "wb")
154 data = tmpl.read()
155 if data:
156 dest.write(data)
157 dest.close()
158 tmpl.close()
159 del dest
160 del tmpl
Tim Peters182b5ac2004-07-18 06:16:08 +0000161
Jack Jansen0ae32202003-04-09 13:25:43 +0000162 # Open the output resource fork
Tim Peters182b5ac2004-07-18 06:16:08 +0000163
Jack Jansen0ae32202003-04-09 13:25:43 +0000164 if progress:
165 progress.label("Copy resources...")
166 progress.set(20)
167 try:
168 output = Res.FSOpenResourceFile(destname, RESOURCE_FORK_NAME, WRITE)
169 except MacOS.Error:
170 destdir, destfile = os.path.split(destname)
Guido van Rossumef87d6e2007-05-02 19:09:54 +0000171 Res.FSCreateResourceFile(destdir, str(destfile), RESOURCE_FORK_NAME)
Jack Jansen0ae32202003-04-09 13:25:43 +0000172 output = Res.FSOpenResourceFile(destname, RESOURCE_FORK_NAME, WRITE)
Tim Peters182b5ac2004-07-18 06:16:08 +0000173
Jack Jansen0ae32202003-04-09 13:25:43 +0000174 # Copy the resources from the target specific resource template, if any
175 typesfound, ownertype = [], None
176 try:
177 input = Res.FSOpenResourceFile(rsrcname, RESOURCE_FORK_NAME, READ)
178 except (MacOS.Error, ValueError):
179 pass
180 if progress:
181 progress.inc(50)
182 else:
183 if is_update:
184 skip_oldfile = ['cfrg']
185 else:
186 skip_oldfile = []
187 typesfound, ownertype = copyres(input, output, skip_oldfile, 0, progress)
188 Res.CloseResFile(input)
Tim Peters182b5ac2004-07-18 06:16:08 +0000189
Jack Jansen0ae32202003-04-09 13:25:43 +0000190 # Check which resource-types we should not copy from the template
191 skiptypes = []
192 if 'vers' in typesfound: skiptypes.append('vers')
193 if 'SIZE' in typesfound: skiptypes.append('SIZE')
Tim Peters182b5ac2004-07-18 06:16:08 +0000194 if 'BNDL' in typesfound: skiptypes = skiptypes + ['BNDL', 'FREF', 'icl4',
Jack Jansen0ae32202003-04-09 13:25:43 +0000195 'icl8', 'ics4', 'ics8', 'ICN#', 'ics#']
196 if not copy_codefragment:
197 skiptypes.append('cfrg')
Guido van Rossumb053cd82006-08-24 03:53:23 +0000198## skipowner = (ownertype != None)
Tim Peters182b5ac2004-07-18 06:16:08 +0000199
Jack Jansen0ae32202003-04-09 13:25:43 +0000200 # Copy the resources from the template
Tim Peters182b5ac2004-07-18 06:16:08 +0000201
Jack Jansen0ae32202003-04-09 13:25:43 +0000202 input = Res.FSOpenResourceFile(template, RESOURCE_FORK_NAME, READ)
203 dummy, tmplowner = copyres(input, output, skiptypes, 1, progress)
Tim Peters182b5ac2004-07-18 06:16:08 +0000204
Jack Jansen0ae32202003-04-09 13:25:43 +0000205 Res.CloseResFile(input)
206## if ownertype == None:
207## raise BuildError, "No owner resource found in either resource file or template"
208 # Make sure we're manipulating the output resource file now
Tim Peters182b5ac2004-07-18 06:16:08 +0000209
Jack Jansen0ae32202003-04-09 13:25:43 +0000210 Res.UseResFile(output)
Jack Jansen81da9f11999-03-17 22:57:55 +0000211
Jack Jansen0ae32202003-04-09 13:25:43 +0000212 if ownertype == None:
213 # No owner resource in the template. We have skipped the
214 # Python owner resource, so we have to add our own. The relevant
215 # bundle stuff is already included in the interpret/applet template.
216 newres = Res.Resource('\0')
217 newres.AddResource(DEFAULT_APPLET_CREATOR, 0, "Owner resource")
218 ownertype = DEFAULT_APPLET_CREATOR
Tim Peters182b5ac2004-07-18 06:16:08 +0000219
Jack Jansen0ae32202003-04-09 13:25:43 +0000220 if code:
221 # Delete any existing 'PYC ' resource named __main__
Tim Peters182b5ac2004-07-18 06:16:08 +0000222
Jack Jansen0ae32202003-04-09 13:25:43 +0000223 try:
224 res = Res.Get1NamedResource(RESTYPE, RESNAME)
225 res.RemoveResource()
226 except Res.Error:
227 pass
Tim Peters182b5ac2004-07-18 06:16:08 +0000228
Jack Jansen0ae32202003-04-09 13:25:43 +0000229 # Create the raw data for the resource from the code object
230 if progress:
231 progress.label("Write PYC resource...")
232 progress.set(120)
Tim Peters182b5ac2004-07-18 06:16:08 +0000233
Jack Jansen0ae32202003-04-09 13:25:43 +0000234 data = marshal.dumps(code)
235 del code
236 data = (MAGIC + '\0\0\0\0') + data
Tim Peters182b5ac2004-07-18 06:16:08 +0000237
Jack Jansen0ae32202003-04-09 13:25:43 +0000238 # Create the resource and write it
Tim Peters182b5ac2004-07-18 06:16:08 +0000239
Jack Jansen0ae32202003-04-09 13:25:43 +0000240 id = 0
241 while id < 128:
242 id = Res.Unique1ID(RESTYPE)
243 res = Res.Resource(data)
244 res.AddResource(RESTYPE, id, RESNAME)
245 attrs = res.GetResAttrs()
246 attrs = attrs | 0x04 # set preload
247 res.SetResAttrs(attrs)
248 res.WriteResource()
249 res.ReleaseResource()
Tim Peters182b5ac2004-07-18 06:16:08 +0000250
Jack Jansen0ae32202003-04-09 13:25:43 +0000251 # Close the output file
Tim Peters182b5ac2004-07-18 06:16:08 +0000252
Jack Jansen0ae32202003-04-09 13:25:43 +0000253 Res.CloseResFile(output)
Tim Peters182b5ac2004-07-18 06:16:08 +0000254
Jack Jansen0ae32202003-04-09 13:25:43 +0000255 # Now set the creator, type and bundle bit of the destination.
256 # Done with FSSpec's, FSRef FInfo isn't good enough yet (2.3a1+)
257 dest_fss = Carbon.File.FSSpec(destname)
258 dest_finfo = dest_fss.FSpGetFInfo()
259 dest_finfo.Creator = ownertype
260 dest_finfo.Type = 'APPL'
261 dest_finfo.Flags = dest_finfo.Flags | Carbon.Files.kHasBundle | Carbon.Files.kIsShared
262 dest_finfo.Flags = dest_finfo.Flags & ~Carbon.Files.kHasBeenInited
263 dest_fss.FSpSetFInfo(dest_finfo)
Tim Peters182b5ac2004-07-18 06:16:08 +0000264
Jack Jansen0ae32202003-04-09 13:25:43 +0000265 macostools.touched(destname)
266 if progress:
267 progress.label("Done.")
268 progress.inc(0)
Jack Jansen813c9971998-07-31 09:42:35 +0000269
Tim Peters182b5ac2004-07-18 06:16:08 +0000270def process_common_macho(template, progress, code, rsrcname, destname, is_update,
Jack Jansenc77f6df2004-12-27 15:51:03 +0000271 raw=0, others=[], filename=None, destroot=""):
Jack Jansen0ae32202003-04-09 13:25:43 +0000272 # Check that we have a filename
273 if filename is None:
Collin Wintere45be282007-08-23 00:01:55 +0000274 raise BuildError("Need source filename on MacOSX")
Jack Jansen0ae32202003-04-09 13:25:43 +0000275 # First make sure the name ends in ".app"
276 if destname[-4:] != '.app':
277 destname = destname + '.app'
278 # Now deduce the short name
279 destdir, shortname = os.path.split(destname)
280 if shortname[-4:] == '.app':
281 # Strip the .app suffix
282 shortname = shortname[:-4]
283 # And deduce the .plist and .icns names
284 plistname = None
285 icnsname = None
286 if rsrcname and rsrcname[-5:] == '.rsrc':
287 tmp = rsrcname[:-5]
288 plistname = tmp + '.plist'
289 if os.path.exists(plistname):
290 icnsname = tmp + '.icns'
291 if not os.path.exists(icnsname):
292 icnsname = None
293 else:
294 plistname = None
Jack Jansend69b7442003-04-22 14:33:48 +0000295 if not icnsname:
296 dft_icnsname = os.path.join(sys.prefix, 'Resources/Python.app/Contents/Resources/PythonApplet.icns')
297 if os.path.exists(dft_icnsname):
298 icnsname = dft_icnsname
Jack Jansen0ae32202003-04-09 13:25:43 +0000299 if not os.path.exists(rsrcname):
300 rsrcname = None
301 if progress:
302 progress.label('Creating bundle...')
303 import bundlebuilder
304 builder = bundlebuilder.AppBuilder(verbosity=0)
305 builder.mainprogram = filename
306 builder.builddir = destdir
307 builder.name = shortname
Jack Jansenc77f6df2004-12-27 15:51:03 +0000308 builder.destroot = destroot
Jack Jansen0ae32202003-04-09 13:25:43 +0000309 if rsrcname:
310 realrsrcname = macresource.resource_pathname(rsrcname)
Tim Peters182b5ac2004-07-18 06:16:08 +0000311 builder.files.append((realrsrcname,
Jack Jansen0ae32202003-04-09 13:25:43 +0000312 os.path.join('Contents/Resources', os.path.basename(rsrcname))))
313 for o in others:
314 if type(o) == str:
315 builder.resources.append(o)
316 else:
317 builder.files.append(o)
318 if plistname:
319 import plistlib
320 builder.plist = plistlib.Plist.fromFile(plistname)
321 if icnsname:
322 builder.iconfile = icnsname
323 if not raw:
324 builder.argv_emulation = 1
325 builder.setup()
326 builder.build()
Tim Peters182b5ac2004-07-18 06:16:08 +0000327 if progress:
Jack Jansen0ae32202003-04-09 13:25:43 +0000328 progress.label('Done.')
329 progress.inc(0)
Tim Peters182b5ac2004-07-18 06:16:08 +0000330
Jack Jansen0ae32202003-04-09 13:25:43 +0000331## macostools.touched(dest_fss)
Jack Jansen813c9971998-07-31 09:42:35 +0000332
333# Copy resources between two resource file descriptors.
Jack Jansen81da9f11999-03-17 22:57:55 +0000334# skip a resource named '__main__' or (if skipowner is set) with ID zero.
Jack Jansen813c9971998-07-31 09:42:35 +0000335# Also skip resources with a type listed in skiptypes.
336#
337def copyres(input, output, skiptypes, skipowner, progress=None):
Jack Jansen0ae32202003-04-09 13:25:43 +0000338 ctor = None
339 alltypes = []
340 Res.UseResFile(input)
341 ntypes = Res.Count1Types()
342 progress_type_inc = 50/ntypes
343 for itype in range(1, 1+ntypes):
344 type = Res.Get1IndType(itype)
345 if type in skiptypes:
346 continue
347 alltypes.append(type)
348 nresources = Res.Count1Resources(type)
349 progress_cur_inc = progress_type_inc/nresources
350 for ires in range(1, 1+nresources):
351 res = Res.Get1IndResource(type, ires)
352 id, type, name = res.GetResInfo()
Neal Norwitz9d72bb42007-04-17 08:48:32 +0000353 lcname = name.lower()
Jack Jansen81da9f11999-03-17 22:57:55 +0000354
Jack Jansen0ae32202003-04-09 13:25:43 +0000355 if lcname == OWNERNAME and id == 0:
356 if skipowner:
357 continue # Skip this one
358 else:
359 ctor = type
360 size = res.size
361 attrs = res.GetResAttrs()
362 if progress:
363 progress.label("Copy %s %d %s"%(type, id, name))
364 progress.inc(progress_cur_inc)
365 res.LoadResource()
366 res.DetachResource()
367 Res.UseResFile(output)
368 try:
369 res2 = Res.Get1Resource(type, id)
370 except MacOS.Error:
371 res2 = None
372 if res2:
373 if progress:
374 progress.label("Overwrite %s %d %s"%(type, id, name))
375 progress.inc(0)
376 res2.RemoveResource()
377 res.AddResource(type, id, name)
378 res.WriteResource()
379 attrs = attrs | res.GetResAttrs()
380 res.SetResAttrs(attrs)
381 Res.UseResFile(input)
382 return alltypes, ctor
Jack Jansen813c9971998-07-31 09:42:35 +0000383
Jack Jansen388fbf32002-06-09 22:08:52 +0000384def copyapptree(srctree, dsttree, exceptlist=[], progress=None):
Jack Jansen0ae32202003-04-09 13:25:43 +0000385 names = []
386 if os.path.exists(dsttree):
387 shutil.rmtree(dsttree)
388 os.mkdir(dsttree)
389 todo = os.listdir(srctree)
390 while todo:
391 this, todo = todo[0], todo[1:]
392 if this in exceptlist:
393 continue
394 thispath = os.path.join(srctree, this)
395 if os.path.isdir(thispath):
396 thiscontent = os.listdir(thispath)
397 for t in thiscontent:
398 todo.append(os.path.join(this, t))
399 names.append(this)
400 for this in names:
401 srcpath = os.path.join(srctree, this)
402 dstpath = os.path.join(dsttree, this)
403 if os.path.isdir(srcpath):
404 os.mkdir(dstpath)
405 elif os.path.islink(srcpath):
406 endpoint = os.readlink(srcpath)
407 os.symlink(endpoint, dstpath)
408 else:
409 if progress:
410 progress.label('Copy '+this)
411 progress.inc(0)
412 shutil.copy2(srcpath, dstpath)
Tim Peters182b5ac2004-07-18 06:16:08 +0000413
Jack Jansenb2e33fe2002-03-29 21:21:28 +0000414def writepycfile(codeobject, cfile):
Jack Jansen0ae32202003-04-09 13:25:43 +0000415 import marshal
416 fc = open(cfile, 'wb')
417 fc.write('\0\0\0\0') # MAGIC placeholder, written later
418 fc.write('\0\0\0\0') # Timestap placeholder, not needed
419 marshal.dump(codeobject, fc)
420 fc.flush()
421 fc.seek(0, 0)
422 fc.write(MAGIC)
423 fc.close()