|
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 import EasyDialogs |
|
19 import shutil |
|
20 |
|
21 |
|
22 BuildError = "BuildError" |
|
23 |
|
24 # .pyc file (and 'PYC ' resource magic number) |
|
25 MAGIC = imp.get_magic() |
|
26 |
|
27 # Template file (searched on sys.path) |
|
28 TEMPLATE = "PythonInterpreter" |
|
29 |
|
30 # Specification of our resource |
|
31 RESTYPE = 'PYC ' |
|
32 RESNAME = '__main__' |
|
33 |
|
34 # A resource with this name sets the "owner" (creator) of the destination |
|
35 # It should also have ID=0. Either of these alone is not enough. |
|
36 OWNERNAME = "owner resource" |
|
37 |
|
38 # Default applet creator code |
|
39 DEFAULT_APPLET_CREATOR="Pyta" |
|
40 |
|
41 # OpenResFile mode parameters |
|
42 READ = 1 |
|
43 WRITE = 2 |
|
44 |
|
45 # Parameter for FSOpenResourceFile |
|
46 RESOURCE_FORK_NAME=Carbon.File.FSGetResourceForkName() |
|
47 |
|
48 def findtemplate(template=None): |
|
49 """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: |
|
62 raise BuildError, "Template %r not found on sys.path" % (template,) |
|
63 file = file.as_pathname() |
|
64 return file |
|
65 |
|
66 def process(template, filename, destname, copy_codefragment=0, |
|
67 rsrcname=None, others=[], raw=0, progress="default", destroot=""): |
|
68 |
|
69 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) |
|
79 |
|
80 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,) |
|
89 |
|
90 # Set the destination file name. Note that basename |
|
91 # does contain the whole filepath, only a .py is stripped. |
|
92 |
|
93 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 |
|
99 |
|
100 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' |
|
107 |
|
108 # 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 |
|
114 process_common(template, progress, code, rsrcname, destname, 0, |
|
115 copy_codefragment, raw, others, filename, destroot) |
|
116 |
|
117 |
|
118 def update(template, filename, output): |
|
119 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)' |
|
127 |
|
128 # 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) |
|
134 |
|
135 |
|
136 def process_common(template, progress, code, rsrcname, destname, is_update, |
|
137 copy_codefragment, raw=0, others=[], filename=None, destroot=""): |
|
138 if MacOS.runtimemodel == 'macho': |
|
139 return process_common_macho(template, progress, code, rsrcname, destname, |
|
140 is_update, raw, others, filename, destroot) |
|
141 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() |
|
146 |
|
147 # Copy data (not resources, yet) from the template |
|
148 if progress: |
|
149 progress.label("Copy data fork...") |
|
150 progress.set(10) |
|
151 |
|
152 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 |
|
162 |
|
163 # Open the output resource fork |
|
164 |
|
165 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) |
|
174 |
|
175 # 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) |
|
190 |
|
191 # 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') |
|
195 if 'BNDL' in typesfound: skiptypes = skiptypes + ['BNDL', 'FREF', 'icl4', |
|
196 'icl8', 'ics4', 'ics8', 'ICN#', 'ics#'] |
|
197 if not copy_codefragment: |
|
198 skiptypes.append('cfrg') |
|
199 ## skipowner = (ownertype <> None) |
|
200 |
|
201 # Copy the resources from the template |
|
202 |
|
203 input = Res.FSOpenResourceFile(template, RESOURCE_FORK_NAME, READ) |
|
204 dummy, tmplowner = copyres(input, output, skiptypes, 1, progress) |
|
205 |
|
206 Res.CloseResFile(input) |
|
207 ## if ownertype is None: |
|
208 ## raise BuildError, "No owner resource found in either resource file or template" |
|
209 # Make sure we're manipulating the output resource file now |
|
210 |
|
211 Res.UseResFile(output) |
|
212 |
|
213 if ownertype is None: |
|
214 # 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 |
|
220 |
|
221 if code: |
|
222 # Delete any existing 'PYC ' resource named __main__ |
|
223 |
|
224 try: |
|
225 res = Res.Get1NamedResource(RESTYPE, RESNAME) |
|
226 res.RemoveResource() |
|
227 except Res.Error: |
|
228 pass |
|
229 |
|
230 # Create the raw data for the resource from the code object |
|
231 if progress: |
|
232 progress.label("Write PYC resource...") |
|
233 progress.set(120) |
|
234 |
|
235 data = marshal.dumps(code) |
|
236 del code |
|
237 data = (MAGIC + '\0\0\0\0') + data |
|
238 |
|
239 # Create the resource and write it |
|
240 |
|
241 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() |
|
251 |
|
252 # Close the output file |
|
253 |
|
254 Res.CloseResFile(output) |
|
255 |
|
256 # 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) |
|
265 |
|
266 macostools.touched(destname) |
|
267 if progress: |
|
268 progress.label("Done.") |
|
269 progress.inc(0) |
|
270 |
|
271 def process_common_macho(template, progress, code, rsrcname, destname, is_update, |
|
272 raw=0, others=[], filename=None, destroot=""): |
|
273 # 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 |
|
296 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 |
|
300 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 |
|
309 builder.destroot = destroot |
|
310 if rsrcname: |
|
311 realrsrcname = macresource.resource_pathname(rsrcname) |
|
312 builder.files.append((realrsrcname, |
|
313 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() |
|
328 if progress: |
|
329 progress.label('Done.') |
|
330 progress.inc(0) |
|
331 |
|
332 ## macostools.touched(dest_fss) |
|
333 |
|
334 # Copy resources between two resource file descriptors. |
|
335 # skip a resource named '__main__' or (if skipowner is set) with ID zero. |
|
336 # Also skip resources with a type listed in skiptypes. |
|
337 # |
|
338 def copyres(input, output, skiptypes, skipowner, progress=None): |
|
339 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) |
|
355 |
|
356 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 |
|
384 |
|
385 def copyapptree(srctree, dsttree, exceptlist=[], progress=None): |
|
386 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) |
|
414 |
|
415 def writepycfile(codeobject, cfile): |
|
416 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() |