Add better error reporting for MemoryErrors caused by str->float conversions.
[python.git] / Lib / plat-mac / buildtools.py
blobf137411184b2dfa7a897d793053677613bc7e206
1 """tools for BuildApplet and BuildApplication"""
3 import warnings
4 warnings.warnpy3k("the buildtools module is deprecated and is removed in 3.0",
5 stacklevel=2)
7 import sys
8 import os
9 import string
10 import imp
11 import marshal
12 from Carbon import Res
13 import Carbon.Files
14 import Carbon.File
15 import MacOS
16 import macostools
17 import macresource
18 try:
19 import EasyDialogs
20 except ImportError:
21 EasyDialogs = None
22 import shutil
25 BuildError = "BuildError"
27 # .pyc file (and 'PYC ' resource magic number)
28 MAGIC = imp.get_magic()
30 # Template file (searched on sys.path)
31 TEMPLATE = "PythonInterpreter"
33 # Specification of our resource
34 RESTYPE = 'PYC '
35 RESNAME = '__main__'
37 # A resource with this name sets the "owner" (creator) of the destination
38 # It should also have ID=0. Either of these alone is not enough.
39 OWNERNAME = "owner resource"
41 # Default applet creator code
42 DEFAULT_APPLET_CREATOR="Pyta"
44 # OpenResFile mode parameters
45 READ = 1
46 WRITE = 2
48 # Parameter for FSOpenResourceFile
49 RESOURCE_FORK_NAME=Carbon.File.FSGetResourceForkName()
51 def findtemplate(template=None):
52 """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:
65 raise BuildError, "Template %r not found on sys.path" % (template,)
66 file = file.as_pathname()
67 return file
69 def process(template, filename, destname, copy_codefragment=0,
70 rsrcname=None, others=[], raw=0, progress="default", destroot=""):
72 if progress == "default":
73 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)
80 # 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)
87 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,)
97 # Set the destination file name. Note that basename
98 # does contain the whole filepath, only a .py is stripped.
100 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
107 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'
115 # 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
121 process_common(template, progress, code, rsrcname, destname, 0,
122 copy_codefragment, raw, others, filename, destroot)
125 def update(template, filename, output):
126 if MacOS.runtimemodel == 'macho':
127 raise BuildError, "No updating yet for MachO applets"
128 if progress:
129 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)
134 else:
135 progress = None
136 if not output:
137 output = filename + ' (updated)'
139 # 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)
147 def process_common(template, progress, code, rsrcname, destname, is_update,
148 copy_codefragment, raw=0, others=[], filename=None, destroot=""):
149 if MacOS.runtimemodel == 'macho':
150 return process_common_macho(template, progress, code, rsrcname, destname,
151 is_update, raw, others, filename, destroot)
152 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()
158 # Copy data (not resources, yet) from the template
159 if progress:
160 progress.label("Copy data fork...")
161 progress.set(10)
163 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
174 # Open the output resource fork
176 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)
186 # 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)
202 # 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')
206 if 'BNDL' in typesfound: skiptypes = skiptypes + ['BNDL', 'FREF', 'icl4',
207 'icl8', 'ics4', 'ics8', 'ICN#', 'ics#']
208 if not copy_codefragment:
209 skiptypes.append('cfrg')
210 ## skipowner = (ownertype <> None)
212 # Copy the resources from the template
214 input = Res.FSOpenResourceFile(template, RESOURCE_FORK_NAME, READ)
215 dummy, tmplowner = copyres(input, output, skiptypes, 1, progress)
217 Res.CloseResFile(input)
218 ## if ownertype is None:
219 ## raise BuildError, "No owner resource found in either resource file or template"
220 # Make sure we're manipulating the output resource file now
222 Res.UseResFile(output)
224 if ownertype is None:
225 # 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
232 if code:
233 # Delete any existing 'PYC ' resource named __main__
235 try:
236 res = Res.Get1NamedResource(RESTYPE, RESNAME)
237 res.RemoveResource()
238 except Res.Error:
239 pass
241 # Create the raw data for the resource from the code object
242 if progress:
243 progress.label("Write PYC resource...")
244 progress.set(120)
246 data = marshal.dumps(code)
247 del code
248 data = (MAGIC + '\0\0\0\0') + data
250 # Create the resource and write it
252 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()
263 # Close the output file
265 Res.CloseResFile(output)
267 # 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)
277 macostools.touched(destname)
278 if progress:
279 progress.label("Done.")
280 progress.inc(0)
282 def process_common_macho(template, progress, code, rsrcname, destname, is_update,
283 raw=0, others=[], filename=None, destroot=""):
284 # 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
307 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
311 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
320 builder.destroot = destroot
321 if rsrcname:
322 realrsrcname = macresource.resource_pathname(rsrcname)
323 builder.files.append((realrsrcname,
324 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()
339 if progress:
340 progress.label('Done.')
341 progress.inc(0)
343 ## macostools.touched(dest_fss)
345 # Copy resources between two resource file descriptors.
346 # skip a resource named '__main__' or (if skipowner is set) with ID zero.
347 # Also skip resources with a type listed in skiptypes.
349 def copyres(input, output, skiptypes, skipowner, progress=None):
350 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)
367 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
396 def copyapptree(srctree, dsttree, exceptlist=[], progress=None):
397 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)
426 def writepycfile(codeobject, cfile):
427 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()