1 | """tools for BuildApplet and BuildApplication"""
|
---|
2 |
|
---|
3 | import warnings
|
---|
4 | warnings.warnpy3k("the buildtools module is deprecated and is removed in 3.0",
|
---|
5 | stacklevel=2)
|
---|
6 |
|
---|
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
|
---|
23 |
|
---|
24 |
|
---|
25 | BuildError = "BuildError"
|
---|
26 |
|
---|
27 | # .pyc file (and 'PYC ' resource magic number)
|
---|
28 | MAGIC = imp.get_magic()
|
---|
29 |
|
---|
30 | # Template file (searched on sys.path)
|
---|
31 | TEMPLATE = "PythonInterpreter"
|
---|
32 |
|
---|
33 | # Specification of our resource
|
---|
34 | RESTYPE = 'PYC '
|
---|
35 | RESNAME = '__main__'
|
---|
36 |
|
---|
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"
|
---|
40 |
|
---|
41 | # Default applet creator code
|
---|
42 | DEFAULT_APPLET_CREATOR="Pyta"
|
---|
43 |
|
---|
44 | # OpenResFile mode parameters
|
---|
45 | READ = 1
|
---|
46 | WRITE = 2
|
---|
47 |
|
---|
48 | # Parameter for FSOpenResourceFile
|
---|
49 | RESOURCE_FORK_NAME=Carbon.File.FSGetResourceForkName()
|
---|
50 |
|
---|
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
|
---|
68 |
|
---|
69 | def process(template, filename, destname, copy_codefragment=0,
|
---|
70 | rsrcname=None, others=[], raw=0, progress="default", destroot=""):
|
---|
71 |
|
---|
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)
|
---|
86 |
|
---|
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,)
|
---|
96 |
|
---|
97 | # Set the destination file name. Note that basename
|
---|
98 | # does contain the whole filepath, only a .py is stripped.
|
---|
99 |
|
---|
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
|
---|
106 |
|
---|
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'
|
---|
114 |
|
---|
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)
|
---|
123 |
|
---|
124 |
|
---|
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)'
|
---|
138 |
|
---|
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)
|
---|
145 |
|
---|
146 |
|
---|
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()
|
---|
157 |
|
---|
158 | # Copy data (not resources, yet) from the template
|
---|
159 | if progress:
|
---|
160 | progress.label("Copy data fork...")
|
---|
161 | progress.set(10)
|
---|
162 |
|
---|
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
|
---|
173 |
|
---|
174 | # Open the output resource fork
|
---|
175 |
|
---|
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)
|
---|
185 |
|
---|
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)
|
---|
201 |
|
---|
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)
|
---|
211 |
|
---|
212 | # Copy the resources from the template
|
---|
213 |
|
---|
214 | input = Res.FSOpenResourceFile(template, RESOURCE_FORK_NAME, READ)
|
---|
215 | dummy, tmplowner = copyres(input, output, skiptypes, 1, progress)
|
---|
216 |
|
---|
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
|
---|
221 |
|
---|
222 | Res.UseResFile(output)
|
---|
223 |
|
---|
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
|
---|
231 |
|
---|
232 | if code:
|
---|
233 | # Delete any existing 'PYC ' resource named __main__
|
---|
234 |
|
---|
235 | try:
|
---|
236 | res = Res.Get1NamedResource(RESTYPE, RESNAME)
|
---|
237 | res.RemoveResource()
|
---|
238 | except Res.Error:
|
---|
239 | pass
|
---|
240 |
|
---|
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)
|
---|
245 |
|
---|
246 | data = marshal.dumps(code)
|
---|
247 | del code
|
---|
248 | data = (MAGIC + '\0\0\0\0') + data
|
---|
249 |
|
---|
250 | # Create the resource and write it
|
---|
251 |
|
---|
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()
|
---|
262 |
|
---|
263 | # Close the output file
|
---|
264 |
|
---|
265 | Res.CloseResFile(output)
|
---|
266 |
|
---|
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)
|
---|
276 |
|
---|
277 | macostools.touched(destname)
|
---|
278 | if progress:
|
---|
279 | progress.label("Done.")
|
---|
280 | progress.inc(0)
|
---|
281 |
|
---|
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)
|
---|
342 |
|
---|
343 | ## macostools.touched(dest_fss)
|
---|
344 |
|
---|
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.
|
---|
348 | #
|
---|
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)
|
---|
366 |
|
---|
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
|
---|
395 |
|
---|
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)
|
---|
425 |
|
---|
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()
|
---|