blob: 3480226203da05aafaf1a6c255c0aa99269142cc [file] [log] [blame]
Jack Jansen813c9971998-07-31 09:42:35 +00001"""tools for BuildApplet and BuildApplication"""
2
Benjamin Peterson23681932008-05-12 21:42:13 +00003import warnings
Benjamin Petersona6864e02008-07-14 17:42:17 +00004warnings.warnpy3k("the buildtools module is deprecated and is removed in 3.0",
5 stacklevel=2)
Benjamin Peterson23681932008-05-12 21:42:13 +00006
Jack Jansen813c9971998-07-31 09:42:35 +00007import sys
8import os
9import string
10import imp
11import marshal
Jack Jansen5a6fdcd2001-08-25 12:15:04 +000012from Carbon import Res
Jack Jansencc947642003-02-02 23:03:50 +000013import Carbon.Files
14import Carbon.File
Jack Jansen813c9971998-07-31 09:42:35 +000015import MacOS
16import macostools
Jack Jansenb2e33fe2002-03-29 21:21:28 +000017import macresource
Jack Jansen813c9971998-07-31 09:42:35 +000018import EasyDialogs
Jack Jansenb2e33fe2002-03-29 21:21:28 +000019import shutil
Jack Jansen813c9971998-07-31 09:42:35 +000020
21
22BuildError = "BuildError"
23
Jack Jansen813c9971998-07-31 09:42:35 +000024# .pyc file (and 'PYC ' resource magic number)
25MAGIC = imp.get_magic()
26
27# Template file (searched on sys.path)
Jack Jansen3b805261999-02-14 23:12:06 +000028TEMPLATE = "PythonInterpreter"
Jack Jansen813c9971998-07-31 09:42:35 +000029
30# Specification of our resource
31RESTYPE = 'PYC '
32RESNAME = '__main__'
33
34# A resource with this name sets the "owner" (creator) of the destination
Jack Jansen81da9f11999-03-17 22:57:55 +000035# It should also have ID=0. Either of these alone is not enough.
Jack Jansen813c9971998-07-31 09:42:35 +000036OWNERNAME = "owner resource"
37
Jack Jansen81da9f11999-03-17 22:57:55 +000038# Default applet creator code
39DEFAULT_APPLET_CREATOR="Pyta"
40
Jack Jansen813c9971998-07-31 09:42:35 +000041# OpenResFile mode parameters
42READ = 1
43WRITE = 2
44
Jack Jansencc947642003-02-02 23:03:50 +000045# Parameter for FSOpenResourceFile
46RESOURCE_FORK_NAME=Carbon.File.FSGetResourceForkName()
Jack Jansen813c9971998-07-31 09:42:35 +000047
Jack Jansena4f8e582001-02-17 23:30:19 +000048def findtemplate(template=None):
Jack Jansen0ae32202003-04-09 13:25:43 +000049 """Locate the applet template along sys.path"""
50 if MacOS.runtimemodel == 'macho':
51 return None
52 if not template:
53 template=TEMPLATE
54 for p in sys.path:
55 file = os.path.join(p, template)
56 try:
57 file, d1, d2 = Carbon.File.FSResolveAliasFile(file, 1)
58 break
59 except (Carbon.File.Error, ValueError):
60 continue
61 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +000062 raise BuildError, "Template %r not found on sys.path" % (template,)
Jack Jansen0ae32202003-04-09 13:25:43 +000063 file = file.as_pathname()
64 return file
Tim Peters182b5ac2004-07-18 06:16:08 +000065
66def process(template, filename, destname, copy_codefragment=0,
Jack Jansenc77f6df2004-12-27 15:51:03 +000067 rsrcname=None, others=[], raw=0, progress="default", destroot=""):
Tim Peters182b5ac2004-07-18 06:16:08 +000068
Jack Jansen0ae32202003-04-09 13:25:43 +000069 if progress == "default":
70 progress = EasyDialogs.ProgressBar("Processing %s..."%os.path.split(filename)[1], 120)
71 progress.label("Compiling...")
72 progress.inc(0)
73 # check for the script name being longer than 32 chars. This may trigger a bug
74 # on OSX that can destroy your sourcefile.
75 if '#' in os.path.split(filename)[1]:
76 raise BuildError, "BuildApplet could destroy your sourcefile on OSX, please rename: %s" % filename
77 # Read the source and compile it
78 # (there's no point overwriting the destination if it has a syntax error)
Tim Peters182b5ac2004-07-18 06:16:08 +000079
Jack Jansen0ae32202003-04-09 13:25:43 +000080 fp = open(filename, 'rU')
81 text = fp.read()
82 fp.close()
83 try:
84 code = compile(text + '\n', filename, "exec")
85 except SyntaxError, arg:
86 raise BuildError, "Syntax error in script %s: %s" % (filename, arg)
87 except EOFError:
88 raise BuildError, "End-of-file in script %s" % (filename,)
Tim Peters182b5ac2004-07-18 06:16:08 +000089
Jack Jansen0ae32202003-04-09 13:25:43 +000090 # Set the destination file name. Note that basename
91 # does contain the whole filepath, only a .py is stripped.
Tim Peters182b5ac2004-07-18 06:16:08 +000092
Jack Jansen0ae32202003-04-09 13:25:43 +000093 if string.lower(filename[-3:]) == ".py":
94 basename = filename[:-3]
95 if MacOS.runtimemodel != 'macho' and not destname:
96 destname = basename
97 else:
98 basename = filename
Tim Peters182b5ac2004-07-18 06:16:08 +000099
Jack Jansen0ae32202003-04-09 13:25:43 +0000100 if not destname:
101 if MacOS.runtimemodel == 'macho':
102 destname = basename + '.app'
103 else:
104 destname = basename + '.applet'
105 if not rsrcname:
106 rsrcname = basename + '.rsrc'
Tim Peters182b5ac2004-07-18 06:16:08 +0000107
Jack Jansen0ae32202003-04-09 13:25:43 +0000108 # Try removing the output file. This fails in MachO, but it should
109 # do any harm.
110 try:
111 os.remove(destname)
112 except os.error:
113 pass
Tim Peters182b5ac2004-07-18 06:16:08 +0000114 process_common(template, progress, code, rsrcname, destname, 0,
Jack Jansenc77f6df2004-12-27 15:51:03 +0000115 copy_codefragment, raw, others, filename, destroot)
Tim Peters182b5ac2004-07-18 06:16:08 +0000116
Jack Jansen813c9971998-07-31 09:42:35 +0000117
118def update(template, filename, output):
Jack Jansen0ae32202003-04-09 13:25:43 +0000119 if MacOS.runtimemodel == 'macho':
120 raise BuildError, "No updating yet for MachO applets"
121 if progress:
122 progress = EasyDialogs.ProgressBar("Updating %s..."%os.path.split(filename)[1], 120)
123 else:
124 progress = None
125 if not output:
126 output = filename + ' (updated)'
Tim Peters182b5ac2004-07-18 06:16:08 +0000127
Jack Jansen0ae32202003-04-09 13:25:43 +0000128 # Try removing the output file
129 try:
130 os.remove(output)
131 except os.error:
132 pass
133 process_common(template, progress, None, filename, output, 1, 1)
Jack Jansen813c9971998-07-31 09:42:35 +0000134
135
Tim Peters182b5ac2004-07-18 06:16:08 +0000136def process_common(template, progress, code, rsrcname, destname, is_update,
Jack Jansenc77f6df2004-12-27 15:51:03 +0000137 copy_codefragment, raw=0, others=[], filename=None, destroot=""):
Jack Jansen0ae32202003-04-09 13:25:43 +0000138 if MacOS.runtimemodel == 'macho':
139 return process_common_macho(template, progress, code, rsrcname, destname,
Jack Jansenc77f6df2004-12-27 15:51:03 +0000140 is_update, raw, others, filename, destroot)
Jack Jansen0ae32202003-04-09 13:25:43 +0000141 if others:
142 raise BuildError, "Extra files only allowed for MachoPython applets"
143 # Create FSSpecs for the various files
144 template_fsr, d1, d2 = Carbon.File.FSResolveAliasFile(template, 1)
145 template = template_fsr.as_pathname()
Tim Peters182b5ac2004-07-18 06:16:08 +0000146
Jack Jansen0ae32202003-04-09 13:25:43 +0000147 # Copy data (not resources, yet) from the template
148 if progress:
149 progress.label("Copy data fork...")
150 progress.set(10)
Tim Peters182b5ac2004-07-18 06:16:08 +0000151
Jack Jansen0ae32202003-04-09 13:25:43 +0000152 if copy_codefragment:
153 tmpl = open(template, "rb")
154 dest = open(destname, "wb")
155 data = tmpl.read()
156 if data:
157 dest.write(data)
158 dest.close()
159 tmpl.close()
160 del dest
161 del tmpl
Tim Peters182b5ac2004-07-18 06:16:08 +0000162
Jack Jansen0ae32202003-04-09 13:25:43 +0000163 # Open the output resource fork
Tim Peters182b5ac2004-07-18 06:16:08 +0000164
Jack Jansen0ae32202003-04-09 13:25:43 +0000165 if progress:
166 progress.label("Copy resources...")
167 progress.set(20)
168 try:
169 output = Res.FSOpenResourceFile(destname, RESOURCE_FORK_NAME, WRITE)
170 except MacOS.Error:
171 destdir, destfile = os.path.split(destname)
172 Res.FSCreateResourceFile(destdir, unicode(destfile), RESOURCE_FORK_NAME)
173 output = Res.FSOpenResourceFile(destname, RESOURCE_FORK_NAME, WRITE)
Tim Peters182b5ac2004-07-18 06:16:08 +0000174
Jack Jansen0ae32202003-04-09 13:25:43 +0000175 # Copy the resources from the target specific resource template, if any
176 typesfound, ownertype = [], None
177 try:
178 input = Res.FSOpenResourceFile(rsrcname, RESOURCE_FORK_NAME, READ)
179 except (MacOS.Error, ValueError):
180 pass
181 if progress:
182 progress.inc(50)
183 else:
184 if is_update:
185 skip_oldfile = ['cfrg']
186 else:
187 skip_oldfile = []
188 typesfound, ownertype = copyres(input, output, skip_oldfile, 0, progress)
189 Res.CloseResFile(input)
Tim Peters182b5ac2004-07-18 06:16:08 +0000190
Jack Jansen0ae32202003-04-09 13:25:43 +0000191 # Check which resource-types we should not copy from the template
192 skiptypes = []
193 if 'vers' in typesfound: skiptypes.append('vers')
194 if 'SIZE' in typesfound: skiptypes.append('SIZE')
Tim Peters182b5ac2004-07-18 06:16:08 +0000195 if 'BNDL' in typesfound: skiptypes = skiptypes + ['BNDL', 'FREF', 'icl4',
Jack Jansen0ae32202003-04-09 13:25:43 +0000196 'icl8', 'ics4', 'ics8', 'ICN#', 'ics#']
197 if not copy_codefragment:
198 skiptypes.append('cfrg')
199## skipowner = (ownertype <> None)
Tim Peters182b5ac2004-07-18 06:16:08 +0000200
Jack Jansen0ae32202003-04-09 13:25:43 +0000201 # Copy the resources from the template
Tim Peters182b5ac2004-07-18 06:16:08 +0000202
Jack Jansen0ae32202003-04-09 13:25:43 +0000203 input = Res.FSOpenResourceFile(template, RESOURCE_FORK_NAME, READ)
204 dummy, tmplowner = copyres(input, output, skiptypes, 1, progress)
Tim Peters182b5ac2004-07-18 06:16:08 +0000205
Jack Jansen0ae32202003-04-09 13:25:43 +0000206 Res.CloseResFile(input)
Benjamin Peterson5b63acd2008-03-29 15:24:25 +0000207## if ownertype is None:
Jack Jansen0ae32202003-04-09 13:25:43 +0000208## raise BuildError, "No owner resource found in either resource file or template"
209 # Make sure we're manipulating the output resource file now
Tim Peters182b5ac2004-07-18 06:16:08 +0000210
Jack Jansen0ae32202003-04-09 13:25:43 +0000211 Res.UseResFile(output)
Jack Jansen81da9f11999-03-17 22:57:55 +0000212
Benjamin Peterson5b63acd2008-03-29 15:24:25 +0000213 if ownertype is None:
Jack Jansen0ae32202003-04-09 13:25:43 +0000214 # No owner resource in the template. We have skipped the
215 # Python owner resource, so we have to add our own. The relevant
216 # bundle stuff is already included in the interpret/applet template.
217 newres = Res.Resource('\0')
218 newres.AddResource(DEFAULT_APPLET_CREATOR, 0, "Owner resource")
219 ownertype = DEFAULT_APPLET_CREATOR
Tim Peters182b5ac2004-07-18 06:16:08 +0000220
Jack Jansen0ae32202003-04-09 13:25:43 +0000221 if code:
222 # Delete any existing 'PYC ' resource named __main__
Tim Peters182b5ac2004-07-18 06:16:08 +0000223
Jack Jansen0ae32202003-04-09 13:25:43 +0000224 try:
225 res = Res.Get1NamedResource(RESTYPE, RESNAME)
226 res.RemoveResource()
227 except Res.Error:
228 pass
Tim Peters182b5ac2004-07-18 06:16:08 +0000229
Jack Jansen0ae32202003-04-09 13:25:43 +0000230 # Create the raw data for the resource from the code object
231 if progress:
232 progress.label("Write PYC resource...")
233 progress.set(120)
Tim Peters182b5ac2004-07-18 06:16:08 +0000234
Jack Jansen0ae32202003-04-09 13:25:43 +0000235 data = marshal.dumps(code)
236 del code
237 data = (MAGIC + '\0\0\0\0') + data
Tim Peters182b5ac2004-07-18 06:16:08 +0000238
Jack Jansen0ae32202003-04-09 13:25:43 +0000239 # Create the resource and write it
Tim Peters182b5ac2004-07-18 06:16:08 +0000240
Jack Jansen0ae32202003-04-09 13:25:43 +0000241 id = 0
242 while id < 128:
243 id = Res.Unique1ID(RESTYPE)
244 res = Res.Resource(data)
245 res.AddResource(RESTYPE, id, RESNAME)
246 attrs = res.GetResAttrs()
247 attrs = attrs | 0x04 # set preload
248 res.SetResAttrs(attrs)
249 res.WriteResource()
250 res.ReleaseResource()
Tim Peters182b5ac2004-07-18 06:16:08 +0000251
Jack Jansen0ae32202003-04-09 13:25:43 +0000252 # Close the output file
Tim Peters182b5ac2004-07-18 06:16:08 +0000253
Jack Jansen0ae32202003-04-09 13:25:43 +0000254 Res.CloseResFile(output)
Tim Peters182b5ac2004-07-18 06:16:08 +0000255
Jack Jansen0ae32202003-04-09 13:25:43 +0000256 # Now set the creator, type and bundle bit of the destination.
257 # Done with FSSpec's, FSRef FInfo isn't good enough yet (2.3a1+)
258 dest_fss = Carbon.File.FSSpec(destname)
259 dest_finfo = dest_fss.FSpGetFInfo()
260 dest_finfo.Creator = ownertype
261 dest_finfo.Type = 'APPL'
262 dest_finfo.Flags = dest_finfo.Flags | Carbon.Files.kHasBundle | Carbon.Files.kIsShared
263 dest_finfo.Flags = dest_finfo.Flags & ~Carbon.Files.kHasBeenInited
264 dest_fss.FSpSetFInfo(dest_finfo)
Tim Peters182b5ac2004-07-18 06:16:08 +0000265
Jack Jansen0ae32202003-04-09 13:25:43 +0000266 macostools.touched(destname)
267 if progress:
268 progress.label("Done.")
269 progress.inc(0)
Jack Jansen813c9971998-07-31 09:42:35 +0000270
Tim Peters182b5ac2004-07-18 06:16:08 +0000271def process_common_macho(template, progress, code, rsrcname, destname, is_update,
Jack Jansenc77f6df2004-12-27 15:51:03 +0000272 raw=0, others=[], filename=None, destroot=""):
Jack Jansen0ae32202003-04-09 13:25:43 +0000273 # Check that we have a filename
274 if filename is None:
275 raise BuildError, "Need source filename on MacOSX"
276 # First make sure the name ends in ".app"
277 if destname[-4:] != '.app':
278 destname = destname + '.app'
279 # Now deduce the short name
280 destdir, shortname = os.path.split(destname)
281 if shortname[-4:] == '.app':
282 # Strip the .app suffix
283 shortname = shortname[:-4]
284 # And deduce the .plist and .icns names
285 plistname = None
286 icnsname = None
287 if rsrcname and rsrcname[-5:] == '.rsrc':
288 tmp = rsrcname[:-5]
289 plistname = tmp + '.plist'
290 if os.path.exists(plistname):
291 icnsname = tmp + '.icns'
292 if not os.path.exists(icnsname):
293 icnsname = None
294 else:
295 plistname = None
Jack Jansend69b7442003-04-22 14:33:48 +0000296 if not icnsname:
297 dft_icnsname = os.path.join(sys.prefix, 'Resources/Python.app/Contents/Resources/PythonApplet.icns')
298 if os.path.exists(dft_icnsname):
299 icnsname = dft_icnsname
Jack Jansen0ae32202003-04-09 13:25:43 +0000300 if not os.path.exists(rsrcname):
301 rsrcname = None
302 if progress:
303 progress.label('Creating bundle...')
304 import bundlebuilder
305 builder = bundlebuilder.AppBuilder(verbosity=0)
306 builder.mainprogram = filename
307 builder.builddir = destdir
308 builder.name = shortname
Jack Jansenc77f6df2004-12-27 15:51:03 +0000309 builder.destroot = destroot
Jack Jansen0ae32202003-04-09 13:25:43 +0000310 if rsrcname:
311 realrsrcname = macresource.resource_pathname(rsrcname)
Tim Peters182b5ac2004-07-18 06:16:08 +0000312 builder.files.append((realrsrcname,
Jack Jansen0ae32202003-04-09 13:25:43 +0000313 os.path.join('Contents/Resources', os.path.basename(rsrcname))))
314 for o in others:
315 if type(o) == str:
316 builder.resources.append(o)
317 else:
318 builder.files.append(o)
319 if plistname:
320 import plistlib
321 builder.plist = plistlib.Plist.fromFile(plistname)
322 if icnsname:
323 builder.iconfile = icnsname
324 if not raw:
325 builder.argv_emulation = 1
326 builder.setup()
327 builder.build()
Tim Peters182b5ac2004-07-18 06:16:08 +0000328 if progress:
Jack Jansen0ae32202003-04-09 13:25:43 +0000329 progress.label('Done.')
330 progress.inc(0)
Tim Peters182b5ac2004-07-18 06:16:08 +0000331
Jack Jansen0ae32202003-04-09 13:25:43 +0000332## macostools.touched(dest_fss)
Jack Jansen813c9971998-07-31 09:42:35 +0000333
334# Copy resources between two resource file descriptors.
Jack Jansen81da9f11999-03-17 22:57:55 +0000335# skip a resource named '__main__' or (if skipowner is set) with ID zero.
Jack Jansen813c9971998-07-31 09:42:35 +0000336# Also skip resources with a type listed in skiptypes.
337#
338def copyres(input, output, skiptypes, skipowner, progress=None):
Jack Jansen0ae32202003-04-09 13:25:43 +0000339 ctor = None
340 alltypes = []
341 Res.UseResFile(input)
342 ntypes = Res.Count1Types()
343 progress_type_inc = 50/ntypes
344 for itype in range(1, 1+ntypes):
345 type = Res.Get1IndType(itype)
346 if type in skiptypes:
347 continue
348 alltypes.append(type)
349 nresources = Res.Count1Resources(type)
350 progress_cur_inc = progress_type_inc/nresources
351 for ires in range(1, 1+nresources):
352 res = Res.Get1IndResource(type, ires)
353 id, type, name = res.GetResInfo()
354 lcname = string.lower(name)
Jack Jansen81da9f11999-03-17 22:57:55 +0000355
Jack Jansen0ae32202003-04-09 13:25:43 +0000356 if lcname == OWNERNAME and id == 0:
357 if skipowner:
358 continue # Skip this one
359 else:
360 ctor = type
361 size = res.size
362 attrs = res.GetResAttrs()
363 if progress:
364 progress.label("Copy %s %d %s"%(type, id, name))
365 progress.inc(progress_cur_inc)
366 res.LoadResource()
367 res.DetachResource()
368 Res.UseResFile(output)
369 try:
370 res2 = Res.Get1Resource(type, id)
371 except MacOS.Error:
372 res2 = None
373 if res2:
374 if progress:
375 progress.label("Overwrite %s %d %s"%(type, id, name))
376 progress.inc(0)
377 res2.RemoveResource()
378 res.AddResource(type, id, name)
379 res.WriteResource()
380 attrs = attrs | res.GetResAttrs()
381 res.SetResAttrs(attrs)
382 Res.UseResFile(input)
383 return alltypes, ctor
Jack Jansen813c9971998-07-31 09:42:35 +0000384
Jack Jansen388fbf32002-06-09 22:08:52 +0000385def copyapptree(srctree, dsttree, exceptlist=[], progress=None):
Jack Jansen0ae32202003-04-09 13:25:43 +0000386 names = []
387 if os.path.exists(dsttree):
388 shutil.rmtree(dsttree)
389 os.mkdir(dsttree)
390 todo = os.listdir(srctree)
391 while todo:
392 this, todo = todo[0], todo[1:]
393 if this in exceptlist:
394 continue
395 thispath = os.path.join(srctree, this)
396 if os.path.isdir(thispath):
397 thiscontent = os.listdir(thispath)
398 for t in thiscontent:
399 todo.append(os.path.join(this, t))
400 names.append(this)
401 for this in names:
402 srcpath = os.path.join(srctree, this)
403 dstpath = os.path.join(dsttree, this)
404 if os.path.isdir(srcpath):
405 os.mkdir(dstpath)
406 elif os.path.islink(srcpath):
407 endpoint = os.readlink(srcpath)
408 os.symlink(endpoint, dstpath)
409 else:
410 if progress:
411 progress.label('Copy '+this)
412 progress.inc(0)
413 shutil.copy2(srcpath, dstpath)
Tim Peters182b5ac2004-07-18 06:16:08 +0000414
Jack Jansenb2e33fe2002-03-29 21:21:28 +0000415def writepycfile(codeobject, cfile):
Jack Jansen0ae32202003-04-09 13:25:43 +0000416 import marshal
417 fc = open(cfile, 'wb')
418 fc.write('\0\0\0\0') # MAGIC placeholder, written later
419 fc.write('\0\0\0\0') # Timestap placeholder, not needed
420 marshal.dump(codeobject, fc)
421 fc.flush()
422 fc.seek(0, 0)
423 fc.write(MAGIC)
424 fc.close()