source: trunk/essentials/dev-lang/python/Lib/plat-mac/buildtools.py@ 3393

Last change on this file since 3393 was 3225, checked in by bird, 19 years ago

Python 2.5

File size: 13.5 KB
Line 
1"""tools for BuildApplet and BuildApplication"""
2
3import sys
4import os
5import string
6import imp
7import marshal
8from Carbon import Res
9import Carbon.Files
10import Carbon.File
11import MacOS
12import macostools
13import macresource
14import EasyDialogs
15import shutil
16
17
18BuildError = "BuildError"
19
20# .pyc file (and 'PYC ' resource magic number)
21MAGIC = imp.get_magic()
22
23# Template file (searched on sys.path)
24TEMPLATE = "PythonInterpreter"
25
26# Specification of our resource
27RESTYPE = 'PYC '
28RESNAME = '__main__'
29
30# A resource with this name sets the "owner" (creator) of the destination
31# It should also have ID=0. Either of these alone is not enough.
32OWNERNAME = "owner resource"
33
34# Default applet creator code
35DEFAULT_APPLET_CREATOR="Pyta"
36
37# OpenResFile mode parameters
38READ = 1
39WRITE = 2
40
41# Parameter for FSOpenResourceFile
42RESOURCE_FORK_NAME=Carbon.File.FSGetResourceForkName()
43
44def findtemplate(template=None):
45 """Locate the applet template along sys.path"""
46 if MacOS.runtimemodel == 'macho':
47 return None
48 if not template:
49 template=TEMPLATE
50 for p in sys.path:
51 file = os.path.join(p, template)
52 try:
53 file, d1, d2 = Carbon.File.FSResolveAliasFile(file, 1)
54 break
55 except (Carbon.File.Error, ValueError):
56 continue
57 else:
58 raise BuildError, "Template %r not found on sys.path" % (template,)
59 file = file.as_pathname()
60 return file
61
62def process(template, filename, destname, copy_codefragment=0,
63 rsrcname=None, others=[], raw=0, progress="default", destroot=""):
64
65 if progress == "default":
66 progress = EasyDialogs.ProgressBar("Processing %s..."%os.path.split(filename)[1], 120)
67 progress.label("Compiling...")
68 progress.inc(0)
69 # check for the script name being longer than 32 chars. This may trigger a bug
70 # on OSX that can destroy your sourcefile.
71 if '#' in os.path.split(filename)[1]:
72 raise BuildError, "BuildApplet could destroy your sourcefile on OSX, please rename: %s" % filename
73 # Read the source and compile it
74 # (there's no point overwriting the destination if it has a syntax error)
75
76 fp = open(filename, 'rU')
77 text = fp.read()
78 fp.close()
79 try:
80 code = compile(text + '\n', filename, "exec")
81 except SyntaxError, arg:
82 raise BuildError, "Syntax error in script %s: %s" % (filename, arg)
83 except EOFError:
84 raise BuildError, "End-of-file in script %s" % (filename,)
85
86 # Set the destination file name. Note that basename
87 # does contain the whole filepath, only a .py is stripped.
88
89 if string.lower(filename[-3:]) == ".py":
90 basename = filename[:-3]
91 if MacOS.runtimemodel != 'macho' and not destname:
92 destname = basename
93 else:
94 basename = filename
95
96 if not destname:
97 if MacOS.runtimemodel == 'macho':
98 destname = basename + '.app'
99 else:
100 destname = basename + '.applet'
101 if not rsrcname:
102 rsrcname = basename + '.rsrc'
103
104 # Try removing the output file. This fails in MachO, but it should
105 # do any harm.
106 try:
107 os.remove(destname)
108 except os.error:
109 pass
110 process_common(template, progress, code, rsrcname, destname, 0,
111 copy_codefragment, raw, others, filename, destroot)
112
113
114def update(template, filename, output):
115 if MacOS.runtimemodel == 'macho':
116 raise BuildError, "No updating yet for MachO applets"
117 if progress:
118 progress = EasyDialogs.ProgressBar("Updating %s..."%os.path.split(filename)[1], 120)
119 else:
120 progress = None
121 if not output:
122 output = filename + ' (updated)'
123
124 # Try removing the output file
125 try:
126 os.remove(output)
127 except os.error:
128 pass
129 process_common(template, progress, None, filename, output, 1, 1)
130
131
132def process_common(template, progress, code, rsrcname, destname, is_update,
133 copy_codefragment, raw=0, others=[], filename=None, destroot=""):
134 if MacOS.runtimemodel == 'macho':
135 return process_common_macho(template, progress, code, rsrcname, destname,
136 is_update, raw, others, filename, destroot)
137 if others:
138 raise BuildError, "Extra files only allowed for MachoPython applets"
139 # Create FSSpecs for the various files
140 template_fsr, d1, d2 = Carbon.File.FSResolveAliasFile(template, 1)
141 template = template_fsr.as_pathname()
142
143 # Copy data (not resources, yet) from the template
144 if progress:
145 progress.label("Copy data fork...")
146 progress.set(10)
147
148 if copy_codefragment:
149 tmpl = open(template, "rb")
150 dest = open(destname, "wb")
151 data = tmpl.read()
152 if data:
153 dest.write(data)
154 dest.close()
155 tmpl.close()
156 del dest
157 del tmpl
158
159 # Open the output resource fork
160
161 if progress:
162 progress.label("Copy resources...")
163 progress.set(20)
164 try:
165 output = Res.FSOpenResourceFile(destname, RESOURCE_FORK_NAME, WRITE)
166 except MacOS.Error:
167 destdir, destfile = os.path.split(destname)
168 Res.FSCreateResourceFile(destdir, unicode(destfile), RESOURCE_FORK_NAME)
169 output = Res.FSOpenResourceFile(destname, RESOURCE_FORK_NAME, WRITE)
170
171 # Copy the resources from the target specific resource template, if any
172 typesfound, ownertype = [], None
173 try:
174 input = Res.FSOpenResourceFile(rsrcname, RESOURCE_FORK_NAME, READ)
175 except (MacOS.Error, ValueError):
176 pass
177 if progress:
178 progress.inc(50)
179 else:
180 if is_update:
181 skip_oldfile = ['cfrg']
182 else:
183 skip_oldfile = []
184 typesfound, ownertype = copyres(input, output, skip_oldfile, 0, progress)
185 Res.CloseResFile(input)
186
187 # Check which resource-types we should not copy from the template
188 skiptypes = []
189 if 'vers' in typesfound: skiptypes.append('vers')
190 if 'SIZE' in typesfound: skiptypes.append('SIZE')
191 if 'BNDL' in typesfound: skiptypes = skiptypes + ['BNDL', 'FREF', 'icl4',
192 'icl8', 'ics4', 'ics8', 'ICN#', 'ics#']
193 if not copy_codefragment:
194 skiptypes.append('cfrg')
195## skipowner = (ownertype <> None)
196
197 # Copy the resources from the template
198
199 input = Res.FSOpenResourceFile(template, RESOURCE_FORK_NAME, READ)
200 dummy, tmplowner = copyres(input, output, skiptypes, 1, progress)
201
202 Res.CloseResFile(input)
203## if ownertype == None:
204## raise BuildError, "No owner resource found in either resource file or template"
205 # Make sure we're manipulating the output resource file now
206
207 Res.UseResFile(output)
208
209 if ownertype == None:
210 # No owner resource in the template. We have skipped the
211 # Python owner resource, so we have to add our own. The relevant
212 # bundle stuff is already included in the interpret/applet template.
213 newres = Res.Resource('\0')
214 newres.AddResource(DEFAULT_APPLET_CREATOR, 0, "Owner resource")
215 ownertype = DEFAULT_APPLET_CREATOR
216
217 if code:
218 # Delete any existing 'PYC ' resource named __main__
219
220 try:
221 res = Res.Get1NamedResource(RESTYPE, RESNAME)
222 res.RemoveResource()
223 except Res.Error:
224 pass
225
226 # Create the raw data for the resource from the code object
227 if progress:
228 progress.label("Write PYC resource...")
229 progress.set(120)
230
231 data = marshal.dumps(code)
232 del code
233 data = (MAGIC + '\0\0\0\0') + data
234
235 # Create the resource and write it
236
237 id = 0
238 while id < 128:
239 id = Res.Unique1ID(RESTYPE)
240 res = Res.Resource(data)
241 res.AddResource(RESTYPE, id, RESNAME)
242 attrs = res.GetResAttrs()
243 attrs = attrs | 0x04 # set preload
244 res.SetResAttrs(attrs)
245 res.WriteResource()
246 res.ReleaseResource()
247
248 # Close the output file
249
250 Res.CloseResFile(output)
251
252 # Now set the creator, type and bundle bit of the destination.
253 # Done with FSSpec's, FSRef FInfo isn't good enough yet (2.3a1+)
254 dest_fss = Carbon.File.FSSpec(destname)
255 dest_finfo = dest_fss.FSpGetFInfo()
256 dest_finfo.Creator = ownertype
257 dest_finfo.Type = 'APPL'
258 dest_finfo.Flags = dest_finfo.Flags | Carbon.Files.kHasBundle | Carbon.Files.kIsShared
259 dest_finfo.Flags = dest_finfo.Flags & ~Carbon.Files.kHasBeenInited
260 dest_fss.FSpSetFInfo(dest_finfo)
261
262 macostools.touched(destname)
263 if progress:
264 progress.label("Done.")
265 progress.inc(0)
266
267def process_common_macho(template, progress, code, rsrcname, destname, is_update,
268 raw=0, others=[], filename=None, destroot=""):
269 # Check that we have a filename
270 if filename is None:
271 raise BuildError, "Need source filename on MacOSX"
272 # First make sure the name ends in ".app"
273 if destname[-4:] != '.app':
274 destname = destname + '.app'
275 # Now deduce the short name
276 destdir, shortname = os.path.split(destname)
277 if shortname[-4:] == '.app':
278 # Strip the .app suffix
279 shortname = shortname[:-4]
280 # And deduce the .plist and .icns names
281 plistname = None
282 icnsname = None
283 if rsrcname and rsrcname[-5:] == '.rsrc':
284 tmp = rsrcname[:-5]
285 plistname = tmp + '.plist'
286 if os.path.exists(plistname):
287 icnsname = tmp + '.icns'
288 if not os.path.exists(icnsname):
289 icnsname = None
290 else:
291 plistname = None
292 if not icnsname:
293 dft_icnsname = os.path.join(sys.prefix, 'Resources/Python.app/Contents/Resources/PythonApplet.icns')
294 if os.path.exists(dft_icnsname):
295 icnsname = dft_icnsname
296 if not os.path.exists(rsrcname):
297 rsrcname = None
298 if progress:
299 progress.label('Creating bundle...')
300 import bundlebuilder
301 builder = bundlebuilder.AppBuilder(verbosity=0)
302 builder.mainprogram = filename
303 builder.builddir = destdir
304 builder.name = shortname
305 builder.destroot = destroot
306 if rsrcname:
307 realrsrcname = macresource.resource_pathname(rsrcname)
308 builder.files.append((realrsrcname,
309 os.path.join('Contents/Resources', os.path.basename(rsrcname))))
310 for o in others:
311 if type(o) == str:
312 builder.resources.append(o)
313 else:
314 builder.files.append(o)
315 if plistname:
316 import plistlib
317 builder.plist = plistlib.Plist.fromFile(plistname)
318 if icnsname:
319 builder.iconfile = icnsname
320 if not raw:
321 builder.argv_emulation = 1
322 builder.setup()
323 builder.build()
324 if progress:
325 progress.label('Done.')
326 progress.inc(0)
327
328## macostools.touched(dest_fss)
329
330# Copy resources between two resource file descriptors.
331# skip a resource named '__main__' or (if skipowner is set) with ID zero.
332# Also skip resources with a type listed in skiptypes.
333#
334def copyres(input, output, skiptypes, skipowner, progress=None):
335 ctor = None
336 alltypes = []
337 Res.UseResFile(input)
338 ntypes = Res.Count1Types()
339 progress_type_inc = 50/ntypes
340 for itype in range(1, 1+ntypes):
341 type = Res.Get1IndType(itype)
342 if type in skiptypes:
343 continue
344 alltypes.append(type)
345 nresources = Res.Count1Resources(type)
346 progress_cur_inc = progress_type_inc/nresources
347 for ires in range(1, 1+nresources):
348 res = Res.Get1IndResource(type, ires)
349 id, type, name = res.GetResInfo()
350 lcname = string.lower(name)
351
352 if lcname == OWNERNAME and id == 0:
353 if skipowner:
354 continue # Skip this one
355 else:
356 ctor = type
357 size = res.size
358 attrs = res.GetResAttrs()
359 if progress:
360 progress.label("Copy %s %d %s"%(type, id, name))
361 progress.inc(progress_cur_inc)
362 res.LoadResource()
363 res.DetachResource()
364 Res.UseResFile(output)
365 try:
366 res2 = Res.Get1Resource(type, id)
367 except MacOS.Error:
368 res2 = None
369 if res2:
370 if progress:
371 progress.label("Overwrite %s %d %s"%(type, id, name))
372 progress.inc(0)
373 res2.RemoveResource()
374 res.AddResource(type, id, name)
375 res.WriteResource()
376 attrs = attrs | res.GetResAttrs()
377 res.SetResAttrs(attrs)
378 Res.UseResFile(input)
379 return alltypes, ctor
380
381def copyapptree(srctree, dsttree, exceptlist=[], progress=None):
382 names = []
383 if os.path.exists(dsttree):
384 shutil.rmtree(dsttree)
385 os.mkdir(dsttree)
386 todo = os.listdir(srctree)
387 while todo:
388 this, todo = todo[0], todo[1:]
389 if this in exceptlist:
390 continue
391 thispath = os.path.join(srctree, this)
392 if os.path.isdir(thispath):
393 thiscontent = os.listdir(thispath)
394 for t in thiscontent:
395 todo.append(os.path.join(this, t))
396 names.append(this)
397 for this in names:
398 srcpath = os.path.join(srctree, this)
399 dstpath = os.path.join(dsttree, this)
400 if os.path.isdir(srcpath):
401 os.mkdir(dstpath)
402 elif os.path.islink(srcpath):
403 endpoint = os.readlink(srcpath)
404 os.symlink(endpoint, dstpath)
405 else:
406 if progress:
407 progress.label('Copy '+this)
408 progress.inc(0)
409 shutil.copy2(srcpath, dstpath)
410
411def writepycfile(codeobject, cfile):
412 import marshal
413 fc = open(cfile, 'wb')
414 fc.write('\0\0\0\0') # MAGIC placeholder, written later
415 fc.write('\0\0\0\0') # Timestap placeholder, not needed
416 marshal.dump(codeobject, fc)
417 fc.flush()
418 fc.seek(0, 0)
419 fc.write(MAGIC)
420 fc.close()
Note: See TracBrowser for help on using the repository browser.