blob: bbb37abeda52cc7532599ac1d4b5912e47fd5aaf [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
Ronald Oussoren25967582009-09-06 10:00:26 +000018try:
19 import EasyDialogs
20except ImportError:
21 EasyDialogs = None
Jack Jansenb2e33fe2002-03-29 21:21:28 +000022import shutil
Jack Jansen813c9971998-07-31 09:42:35 +000023
24
25BuildError = "BuildError"
26
Jack Jansen813c9971998-07-31 09:42:35 +000027# .pyc file (and 'PYC ' resource magic number)
28MAGIC = imp.get_magic()
29
30# Template file (searched on sys.path)
Jack Jansen3b805261999-02-14 23:12:06 +000031TEMPLATE = "PythonInterpreter"
Jack Jansen813c9971998-07-31 09:42:35 +000032
33# Specification of our resource
34RESTYPE = 'PYC '
35RESNAME = '__main__'
36
37# A resource with this name sets the "owner" (creator) of the destination
Jack Jansen81da9f11999-03-17 22:57:55 +000038# It should also have ID=0. Either of these alone is not enough.
Jack Jansen813c9971998-07-31 09:42:35 +000039OWNERNAME = "owner resource"
40
Jack Jansen81da9f11999-03-17 22:57:55 +000041# Default applet creator code
42DEFAULT_APPLET_CREATOR="Pyta"
43
Jack Jansen813c9971998-07-31 09:42:35 +000044# OpenResFile mode parameters
45READ = 1
46WRITE = 2
47
Jack Jansencc947642003-02-02 23:03:50 +000048# Parameter for FSOpenResourceFile
49RESOURCE_FORK_NAME=Carbon.File.FSGetResourceForkName()
Jack Jansen813c9971998-07-31 09:42:35 +000050
Jack Jansena4f8e582001-02-17 23:30:19 +000051def findtemplate(template=None):
Jack Jansen0ae32202003-04-09 13:25:43 +000052 """Locate the applet template along sys.path"""
53 if MacOS.runtimemodel == 'macho':
54 return None
55 if not template:
56 template=TEMPLATE
57 for p in sys.path:
58 file = os.path.join(p, template)
59 try:
60 file, d1, d2 = Carbon.File.FSResolveAliasFile(file, 1)
61 break
62 except (Carbon.File.Error, ValueError):
63 continue
64 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +000065 raise BuildError, "Template %r not found on sys.path" % (template,)
Jack Jansen0ae32202003-04-09 13:25:43 +000066 file = file.as_pathname()
67 return file
Tim Peters182b5ac2004-07-18 06:16:08 +000068
69def process(template, filename, destname, copy_codefragment=0,
Jack Jansenc77f6df2004-12-27 15:51:03 +000070 rsrcname=None, others=[], raw=0, progress="default", destroot=""):
Tim Peters182b5ac2004-07-18 06:16:08 +000071
Jack Jansen0ae32202003-04-09 13:25:43 +000072 if progress == "default":
Ronald Oussoren25967582009-09-06 10:00:26 +000073 if EasyDialogs is None:
74 print "Compiling %s"%(os.path.split(filename)[1],)
75 process = None
76 else:
77 progress = EasyDialogs.ProgressBar("Processing %s..."%os.path.split(filename)[1], 120)
78 progress.label("Compiling...")
79 progress.inc(0)
Jack Jansen0ae32202003-04-09 13:25:43 +000080 # check for the script name being longer than 32 chars. This may trigger a bug
81 # on OSX that can destroy your sourcefile.
82 if '#' in os.path.split(filename)[1]:
83 raise BuildError, "BuildApplet could destroy your sourcefile on OSX, please rename: %s" % filename
84 # Read the source and compile it
85 # (there's no point overwriting the destination if it has a syntax error)
Tim Peters182b5ac2004-07-18 06:16:08 +000086
Jack Jansen0ae32202003-04-09 13:25:43 +000087 fp = open(filename, 'rU')
88 text = fp.read()
89 fp.close()
90 try:
91 code = compile(text + '\n', filename, "exec")
92 except SyntaxError, arg:
93 raise BuildError, "Syntax error in script %s: %s" % (filename, arg)
94 except EOFError:
95 raise BuildError, "End-of-file in script %s" % (filename,)
Tim Peters182b5ac2004-07-18 06:16:08 +000096
Jack Jansen0ae32202003-04-09 13:25:43 +000097 # Set the destination file name. Note that basename
98 # does contain the whole filepath, only a .py is stripped.
Tim Peters182b5ac2004-07-18 06:16:08 +000099
Jack Jansen0ae32202003-04-09 13:25:43 +0000100 if string.lower(filename[-3:]) == ".py":
101 basename = filename[:-3]
102 if MacOS.runtimemodel != 'macho' and not destname:
103 destname = basename
104 else:
105 basename = filename
Tim Peters182b5ac2004-07-18 06:16:08 +0000106
Jack Jansen0ae32202003-04-09 13:25:43 +0000107 if not destname:
108 if MacOS.runtimemodel == 'macho':
109 destname = basename + '.app'
110 else:
111 destname = basename + '.applet'
112 if not rsrcname:
113 rsrcname = basename + '.rsrc'
Tim Peters182b5ac2004-07-18 06:16:08 +0000114
Jack Jansen0ae32202003-04-09 13:25:43 +0000115 # Try removing the output file. This fails in MachO, but it should
116 # do any harm.
117 try:
118 os.remove(destname)
119 except os.error:
120 pass
Tim Peters182b5ac2004-07-18 06:16:08 +0000121 process_common(template, progress, code, rsrcname, destname, 0,
Jack Jansenc77f6df2004-12-27 15:51:03 +0000122 copy_codefragment, raw, others, filename, destroot)
Tim Peters182b5ac2004-07-18 06:16:08 +0000123
Jack Jansen813c9971998-07-31 09:42:35 +0000124
125def update(template, filename, output):
Jack Jansen0ae32202003-04-09 13:25:43 +0000126 if MacOS.runtimemodel == 'macho':
127 raise BuildError, "No updating yet for MachO applets"
128 if progress:
Ronald Oussoren25967582009-09-06 10:00:26 +0000129 if EasyDialogs is None:
130 print "Updating %s"%(os.path.split(filename)[1],)
131 progress = None
132 else:
133 progress = EasyDialogs.ProgressBar("Updating %s..."%os.path.split(filename)[1], 120)
Jack Jansen0ae32202003-04-09 13:25:43 +0000134 else:
135 progress = None
136 if not output:
137 output = filename + ' (updated)'
Tim Peters182b5ac2004-07-18 06:16:08 +0000138
Jack Jansen0ae32202003-04-09 13:25:43 +0000139 # Try removing the output file
140 try:
141 os.remove(output)
142 except os.error:
143 pass
144 process_common(template, progress, None, filename, output, 1, 1)
Jack Jansen813c9971998-07-31 09:42:35 +0000145
146
Tim Peters182b5ac2004-07-18 06:16:08 +0000147def process_common(template, progress, code, rsrcname, destname, is_update,
Jack Jansenc77f6df2004-12-27 15:51:03 +0000148 copy_codefragment, raw=0, others=[], filename=None, destroot=""):
Jack Jansen0ae32202003-04-09 13:25:43 +0000149 if MacOS.runtimemodel == 'macho':
150 return process_common_macho(template, progress, code, rsrcname, destname,
Jack Jansenc77f6df2004-12-27 15:51:03 +0000151 is_update, raw, others, filename, destroot)
Jack Jansen0ae32202003-04-09 13:25:43 +0000152 if others:
153 raise BuildError, "Extra files only allowed for MachoPython applets"
154 # Create FSSpecs for the various files
155 template_fsr, d1, d2 = Carbon.File.FSResolveAliasFile(template, 1)
156 template = template_fsr.as_pathname()
Tim Peters182b5ac2004-07-18 06:16:08 +0000157
Jack Jansen0ae32202003-04-09 13:25:43 +0000158 # Copy data (not resources, yet) from the template
159 if progress:
160 progress.label("Copy data fork...")
161 progress.set(10)
Tim Peters182b5ac2004-07-18 06:16:08 +0000162
Jack Jansen0ae32202003-04-09 13:25:43 +0000163 if copy_codefragment:
164 tmpl = open(template, "rb")
165 dest = open(destname, "wb")
166 data = tmpl.read()
167 if data:
168 dest.write(data)
169 dest.close()
170 tmpl.close()
171 del dest
172 del tmpl
Tim Peters182b5ac2004-07-18 06:16:08 +0000173
Jack Jansen0ae32202003-04-09 13:25:43 +0000174 # Open the output resource fork
Tim Peters182b5ac2004-07-18 06:16:08 +0000175
Jack Jansen0ae32202003-04-09 13:25:43 +0000176 if progress:
177 progress.label("Copy resources...")
178 progress.set(20)
179 try:
180 output = Res.FSOpenResourceFile(destname, RESOURCE_FORK_NAME, WRITE)
181 except MacOS.Error:
182 destdir, destfile = os.path.split(destname)
183 Res.FSCreateResourceFile(destdir, unicode(destfile), RESOURCE_FORK_NAME)
184 output = Res.FSOpenResourceFile(destname, RESOURCE_FORK_NAME, WRITE)
Tim Peters182b5ac2004-07-18 06:16:08 +0000185
Jack Jansen0ae32202003-04-09 13:25:43 +0000186 # Copy the resources from the target specific resource template, if any
187 typesfound, ownertype = [], None
188 try:
189 input = Res.FSOpenResourceFile(rsrcname, RESOURCE_FORK_NAME, READ)
190 except (MacOS.Error, ValueError):
191 pass
192 if progress:
193 progress.inc(50)
194 else:
195 if is_update:
196 skip_oldfile = ['cfrg']
197 else:
198 skip_oldfile = []
199 typesfound, ownertype = copyres(input, output, skip_oldfile, 0, progress)
200 Res.CloseResFile(input)
Tim Peters182b5ac2004-07-18 06:16:08 +0000201
Jack Jansen0ae32202003-04-09 13:25:43 +0000202 # Check which resource-types we should not copy from the template
203 skiptypes = []
204 if 'vers' in typesfound: skiptypes.append('vers')
205 if 'SIZE' in typesfound: skiptypes.append('SIZE')
Tim Peters182b5ac2004-07-18 06:16:08 +0000206 if 'BNDL' in typesfound: skiptypes = skiptypes + ['BNDL', 'FREF', 'icl4',
Jack Jansen0ae32202003-04-09 13:25:43 +0000207 'icl8', 'ics4', 'ics8', 'ICN#', 'ics#']
208 if not copy_codefragment:
209 skiptypes.append('cfrg')
Mark Dickinsona3a50502010-04-03 18:17:54 +0000210## skipowner = (ownertype != None)
Tim Peters182b5ac2004-07-18 06:16:08 +0000211
Jack Jansen0ae32202003-04-09 13:25:43 +0000212 # Copy the resources from the template
Tim Peters182b5ac2004-07-18 06:16:08 +0000213
Jack Jansen0ae32202003-04-09 13:25:43 +0000214 input = Res.FSOpenResourceFile(template, RESOURCE_FORK_NAME, READ)
215 dummy, tmplowner = copyres(input, output, skiptypes, 1, progress)
Tim Peters182b5ac2004-07-18 06:16:08 +0000216
Jack Jansen0ae32202003-04-09 13:25:43 +0000217 Res.CloseResFile(input)
Benjamin Peterson5b63acd2008-03-29 15:24:25 +0000218## if ownertype is None:
Jack Jansen0ae32202003-04-09 13:25:43 +0000219## raise BuildError, "No owner resource found in either resource file or template"
220 # Make sure we're manipulating the output resource file now
Tim Peters182b5ac2004-07-18 06:16:08 +0000221
Jack Jansen0ae32202003-04-09 13:25:43 +0000222 Res.UseResFile(output)
Jack Jansen81da9f11999-03-17 22:57:55 +0000223
Benjamin Peterson5b63acd2008-03-29 15:24:25 +0000224 if ownertype is None:
Jack Jansen0ae32202003-04-09 13:25:43 +0000225 # No owner resource in the template. We have skipped the
226 # Python owner resource, so we have to add our own. The relevant
227 # bundle stuff is already included in the interpret/applet template.
228 newres = Res.Resource('\0')
229 newres.AddResource(DEFAULT_APPLET_CREATOR, 0, "Owner resource")
230 ownertype = DEFAULT_APPLET_CREATOR
Tim Peters182b5ac2004-07-18 06:16:08 +0000231
Jack Jansen0ae32202003-04-09 13:25:43 +0000232 if code:
233 # Delete any existing 'PYC ' resource named __main__
Tim Peters182b5ac2004-07-18 06:16:08 +0000234
Jack Jansen0ae32202003-04-09 13:25:43 +0000235 try:
236 res = Res.Get1NamedResource(RESTYPE, RESNAME)
237 res.RemoveResource()
238 except Res.Error:
239 pass
Tim Peters182b5ac2004-07-18 06:16:08 +0000240
Jack Jansen0ae32202003-04-09 13:25:43 +0000241 # Create the raw data for the resource from the code object
242 if progress:
243 progress.label("Write PYC resource...")
244 progress.set(120)
Tim Peters182b5ac2004-07-18 06:16:08 +0000245
Jack Jansen0ae32202003-04-09 13:25:43 +0000246 data = marshal.dumps(code)
247 del code
248 data = (MAGIC + '\0\0\0\0') + data
Tim Peters182b5ac2004-07-18 06:16:08 +0000249
Jack Jansen0ae32202003-04-09 13:25:43 +0000250 # Create the resource and write it
Tim Peters182b5ac2004-07-18 06:16:08 +0000251
Jack Jansen0ae32202003-04-09 13:25:43 +0000252 id = 0
253 while id < 128:
254 id = Res.Unique1ID(RESTYPE)
255 res = Res.Resource(data)
256 res.AddResource(RESTYPE, id, RESNAME)
257 attrs = res.GetResAttrs()
258 attrs = attrs | 0x04 # set preload
259 res.SetResAttrs(attrs)
260 res.WriteResource()
261 res.ReleaseResource()
Tim Peters182b5ac2004-07-18 06:16:08 +0000262
Jack Jansen0ae32202003-04-09 13:25:43 +0000263 # Close the output file
Tim Peters182b5ac2004-07-18 06:16:08 +0000264
Jack Jansen0ae32202003-04-09 13:25:43 +0000265 Res.CloseResFile(output)
Tim Peters182b5ac2004-07-18 06:16:08 +0000266
Jack Jansen0ae32202003-04-09 13:25:43 +0000267 # Now set the creator, type and bundle bit of the destination.
268 # Done with FSSpec's, FSRef FInfo isn't good enough yet (2.3a1+)
269 dest_fss = Carbon.File.FSSpec(destname)
270 dest_finfo = dest_fss.FSpGetFInfo()
271 dest_finfo.Creator = ownertype
272 dest_finfo.Type = 'APPL'
273 dest_finfo.Flags = dest_finfo.Flags | Carbon.Files.kHasBundle | Carbon.Files.kIsShared
274 dest_finfo.Flags = dest_finfo.Flags & ~Carbon.Files.kHasBeenInited
275 dest_fss.FSpSetFInfo(dest_finfo)
Tim Peters182b5ac2004-07-18 06:16:08 +0000276
Jack Jansen0ae32202003-04-09 13:25:43 +0000277 macostools.touched(destname)
278 if progress:
279 progress.label("Done.")
280 progress.inc(0)
Jack Jansen813c9971998-07-31 09:42:35 +0000281
Tim Peters182b5ac2004-07-18 06:16:08 +0000282def process_common_macho(template, progress, code, rsrcname, destname, is_update,
Jack Jansenc77f6df2004-12-27 15:51:03 +0000283 raw=0, others=[], filename=None, destroot=""):
Jack Jansen0ae32202003-04-09 13:25:43 +0000284 # Check that we have a filename
285 if filename is None:
286 raise BuildError, "Need source filename on MacOSX"
287 # First make sure the name ends in ".app"
288 if destname[-4:] != '.app':
289 destname = destname + '.app'
290 # Now deduce the short name
291 destdir, shortname = os.path.split(destname)
292 if shortname[-4:] == '.app':
293 # Strip the .app suffix
294 shortname = shortname[:-4]
295 # And deduce the .plist and .icns names
296 plistname = None
297 icnsname = None
298 if rsrcname and rsrcname[-5:] == '.rsrc':
299 tmp = rsrcname[:-5]
300 plistname = tmp + '.plist'
301 if os.path.exists(plistname):
302 icnsname = tmp + '.icns'
303 if not os.path.exists(icnsname):
304 icnsname = None
305 else:
306 plistname = None
Jack Jansend69b7442003-04-22 14:33:48 +0000307 if not icnsname:
308 dft_icnsname = os.path.join(sys.prefix, 'Resources/Python.app/Contents/Resources/PythonApplet.icns')
309 if os.path.exists(dft_icnsname):
310 icnsname = dft_icnsname
Jack Jansen0ae32202003-04-09 13:25:43 +0000311 if not os.path.exists(rsrcname):
312 rsrcname = None
313 if progress:
314 progress.label('Creating bundle...')
315 import bundlebuilder
316 builder = bundlebuilder.AppBuilder(verbosity=0)
317 builder.mainprogram = filename
318 builder.builddir = destdir
319 builder.name = shortname
Jack Jansenc77f6df2004-12-27 15:51:03 +0000320 builder.destroot = destroot
Jack Jansen0ae32202003-04-09 13:25:43 +0000321 if rsrcname:
322 realrsrcname = macresource.resource_pathname(rsrcname)
Tim Peters182b5ac2004-07-18 06:16:08 +0000323 builder.files.append((realrsrcname,
Jack Jansen0ae32202003-04-09 13:25:43 +0000324 os.path.join('Contents/Resources', os.path.basename(rsrcname))))
325 for o in others:
326 if type(o) == str:
327 builder.resources.append(o)
328 else:
329 builder.files.append(o)
330 if plistname:
331 import plistlib
332 builder.plist = plistlib.Plist.fromFile(plistname)
333 if icnsname:
334 builder.iconfile = icnsname
335 if not raw:
336 builder.argv_emulation = 1
337 builder.setup()
338 builder.build()
Tim Peters182b5ac2004-07-18 06:16:08 +0000339 if progress:
Jack Jansen0ae32202003-04-09 13:25:43 +0000340 progress.label('Done.')
341 progress.inc(0)
Tim Peters182b5ac2004-07-18 06:16:08 +0000342
Jack Jansen0ae32202003-04-09 13:25:43 +0000343## macostools.touched(dest_fss)
Jack Jansen813c9971998-07-31 09:42:35 +0000344
345# Copy resources between two resource file descriptors.
Jack Jansen81da9f11999-03-17 22:57:55 +0000346# skip a resource named '__main__' or (if skipowner is set) with ID zero.
Jack Jansen813c9971998-07-31 09:42:35 +0000347# Also skip resources with a type listed in skiptypes.
348#
349def copyres(input, output, skiptypes, skipowner, progress=None):
Jack Jansen0ae32202003-04-09 13:25:43 +0000350 ctor = None
351 alltypes = []
352 Res.UseResFile(input)
353 ntypes = Res.Count1Types()
354 progress_type_inc = 50/ntypes
355 for itype in range(1, 1+ntypes):
356 type = Res.Get1IndType(itype)
357 if type in skiptypes:
358 continue
359 alltypes.append(type)
360 nresources = Res.Count1Resources(type)
361 progress_cur_inc = progress_type_inc/nresources
362 for ires in range(1, 1+nresources):
363 res = Res.Get1IndResource(type, ires)
364 id, type, name = res.GetResInfo()
365 lcname = string.lower(name)
Jack Jansen81da9f11999-03-17 22:57:55 +0000366
Jack Jansen0ae32202003-04-09 13:25:43 +0000367 if lcname == OWNERNAME and id == 0:
368 if skipowner:
369 continue # Skip this one
370 else:
371 ctor = type
372 size = res.size
373 attrs = res.GetResAttrs()
374 if progress:
375 progress.label("Copy %s %d %s"%(type, id, name))
376 progress.inc(progress_cur_inc)
377 res.LoadResource()
378 res.DetachResource()
379 Res.UseResFile(output)
380 try:
381 res2 = Res.Get1Resource(type, id)
382 except MacOS.Error:
383 res2 = None
384 if res2:
385 if progress:
386 progress.label("Overwrite %s %d %s"%(type, id, name))
387 progress.inc(0)
388 res2.RemoveResource()
389 res.AddResource(type, id, name)
390 res.WriteResource()
391 attrs = attrs | res.GetResAttrs()
392 res.SetResAttrs(attrs)
393 Res.UseResFile(input)
394 return alltypes, ctor
Jack Jansen813c9971998-07-31 09:42:35 +0000395
Jack Jansen388fbf32002-06-09 22:08:52 +0000396def copyapptree(srctree, dsttree, exceptlist=[], progress=None):
Jack Jansen0ae32202003-04-09 13:25:43 +0000397 names = []
398 if os.path.exists(dsttree):
399 shutil.rmtree(dsttree)
400 os.mkdir(dsttree)
401 todo = os.listdir(srctree)
402 while todo:
403 this, todo = todo[0], todo[1:]
404 if this in exceptlist:
405 continue
406 thispath = os.path.join(srctree, this)
407 if os.path.isdir(thispath):
408 thiscontent = os.listdir(thispath)
409 for t in thiscontent:
410 todo.append(os.path.join(this, t))
411 names.append(this)
412 for this in names:
413 srcpath = os.path.join(srctree, this)
414 dstpath = os.path.join(dsttree, this)
415 if os.path.isdir(srcpath):
416 os.mkdir(dstpath)
417 elif os.path.islink(srcpath):
418 endpoint = os.readlink(srcpath)
419 os.symlink(endpoint, dstpath)
420 else:
421 if progress:
422 progress.label('Copy '+this)
423 progress.inc(0)
424 shutil.copy2(srcpath, dstpath)
Tim Peters182b5ac2004-07-18 06:16:08 +0000425
Jack Jansenb2e33fe2002-03-29 21:21:28 +0000426def writepycfile(codeobject, cfile):
Jack Jansen0ae32202003-04-09 13:25:43 +0000427 import marshal
428 fc = open(cfile, 'wb')
429 fc.write('\0\0\0\0') # MAGIC placeholder, written later
430 fc.write('\0\0\0\0') # Timestap placeholder, not needed
431 marshal.dump(codeobject, fc)
432 fc.flush()
433 fc.seek(0, 0)
434 fc.write(MAGIC)
435 fc.close()