Issue #6644: Fix compile error on AIX.
[python.git] / Lib / plat-mac / buildtools.py
blob3480226203da05aafaf1a6c255c0aa99269142cc
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 import EasyDialogs
19 import shutil
22 BuildError = "BuildError"
24 # .pyc file (and 'PYC ' resource magic number)
25 MAGIC = imp.get_magic()
27 # Template file (searched on sys.path)
28 TEMPLATE = "PythonInterpreter"
30 # Specification of our resource
31 RESTYPE = 'PYC '
32 RESNAME = '__main__'
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"
38 # Default applet creator code
39 DEFAULT_APPLET_CREATOR="Pyta"
41 # OpenResFile mode parameters
42 READ = 1
43 WRITE = 2
45 # Parameter for FSOpenResourceFile
46 RESOURCE_FORK_NAME=Carbon.File.FSGetResourceForkName()
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
66 def process(template, filename, destname, copy_codefragment=0,
67 rsrcname=None, others=[], raw=0, progress="default", destroot=""):
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)
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,)
90 # Set the destination file name. Note that basename
91 # does contain the whole filepath, only a .py is stripped.
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
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'
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)
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)'
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)
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()
147 # Copy data (not resources, yet) from the template
148 if progress:
149 progress.label("Copy data fork...")
150 progress.set(10)
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
163 # Open the output resource fork
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)
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)
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)
201 # Copy the resources from the template
203 input = Res.FSOpenResourceFile(template, RESOURCE_FORK_NAME, READ)
204 dummy, tmplowner = copyres(input, output, skiptypes, 1, progress)
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
211 Res.UseResFile(output)
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
221 if code:
222 # Delete any existing 'PYC ' resource named __main__
224 try:
225 res = Res.Get1NamedResource(RESTYPE, RESNAME)
226 res.RemoveResource()
227 except Res.Error:
228 pass
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)
235 data = marshal.dumps(code)
236 del code
237 data = (MAGIC + '\0\0\0\0') + data
239 # Create the resource and write it
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()
252 # Close the output file
254 Res.CloseResFile(output)
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)
266 macostools.touched(destname)
267 if progress:
268 progress.label("Done.")
269 progress.inc(0)
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)
332 ## macostools.touched(dest_fss)
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.
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)
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
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)
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()