blob: fd19cbb0ed6de496ed5d55d813498218e24d7b4f [file] [log] [blame]
Just van Rossumad33d722002-11-21 10:23:04 +00001#! /usr/bin/env python
2
3"""\
4bundlebuilder.py -- Tools to assemble MacOS X (application) bundles.
5
Just van Rossumceeb9622002-11-21 23:19:37 +00006This module contains two classes to build so called "bundles" for
Just van Rossumad33d722002-11-21 10:23:04 +00007MacOS X. BundleBuilder is a general tool, AppBuilder is a subclass
Just van Rossumceeb9622002-11-21 23:19:37 +00008specialized in building application bundles.
Just van Rossumad33d722002-11-21 10:23:04 +00009
Just van Rossumceeb9622002-11-21 23:19:37 +000010[Bundle|App]Builder objects are instantiated with a bunch of keyword
11arguments, and have a build() method that will do all the work. See
12the class doc strings for a description of the constructor arguments.
13
14The module contains a main program that can be used in two ways:
15
16 % python bundlebuilder.py [options] build
17 % python buildapp.py [options] build
18
19Where "buildapp.py" is a user-supplied setup.py-like script following
20this model:
21
22 from bundlebuilder import buildapp
23 buildapp(<lots-of-keyword-args>)
Just van Rossumad33d722002-11-21 10:23:04 +000024
25"""
26
Just van Rossumad33d722002-11-21 10:23:04 +000027
Just van Rossumcef32882002-11-26 00:34:52 +000028__all__ = ["BundleBuilder", "BundleBuilderError", "AppBuilder", "buildapp"]
Just van Rossumad33d722002-11-21 10:23:04 +000029
30
31import sys
32import os, errno, shutil
Just van Rossumcef32882002-11-26 00:34:52 +000033import imp, marshal
34import re
Just van Rossumda302da2002-11-23 22:26:44 +000035from copy import deepcopy
Just van Rossumceeb9622002-11-21 23:19:37 +000036import getopt
Just van Rossumad33d722002-11-21 10:23:04 +000037from plistlib import Plist
Just van Rossumda302da2002-11-23 22:26:44 +000038from types import FunctionType as function
Jack Jansen946c1942003-02-17 16:47:12 +000039import macresource
Just van Rossumad33d722002-11-21 10:23:04 +000040
41
Just van Rossumcef32882002-11-26 00:34:52 +000042class BundleBuilderError(Exception): pass
43
44
Just van Rossumda302da2002-11-23 22:26:44 +000045class Defaults:
46
47 """Class attributes that don't start with an underscore and are
48 not functions or classmethods are (deep)copied to self.__dict__.
49 This allows for mutable default values.
50 """
51
52 def __init__(self, **kwargs):
53 defaults = self._getDefaults()
54 defaults.update(kwargs)
55 self.__dict__.update(defaults)
56
57 def _getDefaults(cls):
58 defaults = {}
59 for name, value in cls.__dict__.items():
60 if name[0] != "_" and not isinstance(value,
61 (function, classmethod)):
62 defaults[name] = deepcopy(value)
63 for base in cls.__bases__:
64 if hasattr(base, "_getDefaults"):
65 defaults.update(base._getDefaults())
66 return defaults
67 _getDefaults = classmethod(_getDefaults)
Just van Rossumad33d722002-11-21 10:23:04 +000068
69
Just van Rossumda302da2002-11-23 22:26:44 +000070class BundleBuilder(Defaults):
Just van Rossumad33d722002-11-21 10:23:04 +000071
72 """BundleBuilder is a barebones class for assembling bundles. It
73 knows nothing about executables or icons, it only copies files
74 and creates the PkgInfo and Info.plist files.
Just van Rossumad33d722002-11-21 10:23:04 +000075 """
76
Just van Rossumda302da2002-11-23 22:26:44 +000077 # (Note that Defaults.__init__ (deep)copies these values to
78 # instance variables. Mutable defaults are therefore safe.)
79
80 # Name of the bundle, with or without extension.
81 name = None
82
83 # The property list ("plist")
84 plist = Plist(CFBundleDevelopmentRegion = "English",
85 CFBundleInfoDictionaryVersion = "6.0")
86
87 # The type of the bundle.
88 type = "APPL"
89 # The creator code of the bundle.
Just van Rossume6b49022002-11-24 01:23:45 +000090 creator = None
Just van Rossumda302da2002-11-23 22:26:44 +000091
92 # List of files that have to be copied to <bundle>/Contents/Resources.
93 resources = []
94
95 # List of (src, dest) tuples; dest should be a path relative to the bundle
96 # (eg. "Contents/Resources/MyStuff/SomeFile.ext).
97 files = []
98
99 # Directory where the bundle will be assembled.
100 builddir = "build"
101
102 # platform, name of the subfolder of Contents that contains the executable.
103 platform = "MacOS"
104
105 # Make symlinks instead copying files. This is handy during debugging, but
106 # makes the bundle non-distributable.
107 symlink = 0
108
109 # Verbosity level.
110 verbosity = 1
Just van Rossumad33d722002-11-21 10:23:04 +0000111
Just van Rossumceeb9622002-11-21 23:19:37 +0000112 def setup(self):
Just van Rossumda302da2002-11-23 22:26:44 +0000113 # XXX rethink self.name munging, this is brittle.
Just van Rossumceeb9622002-11-21 23:19:37 +0000114 self.name, ext = os.path.splitext(self.name)
115 if not ext:
116 ext = ".bundle"
Just van Rossumda302da2002-11-23 22:26:44 +0000117 bundleextension = ext
Just van Rossumceeb9622002-11-21 23:19:37 +0000118 # misc (derived) attributes
Just van Rossumda302da2002-11-23 22:26:44 +0000119 self.bundlepath = pathjoin(self.builddir, self.name + bundleextension)
Just van Rossumceeb9622002-11-21 23:19:37 +0000120 self.execdir = pathjoin("Contents", self.platform)
121
Just van Rossumda302da2002-11-23 22:26:44 +0000122 plist = self.plist
Just van Rossumceeb9622002-11-21 23:19:37 +0000123 plist.CFBundleName = self.name
124 plist.CFBundlePackageType = self.type
Just van Rossume6b49022002-11-24 01:23:45 +0000125 if self.creator is None:
126 if hasattr(plist, "CFBundleSignature"):
127 self.creator = plist.CFBundleSignature
128 else:
129 self.creator = "????"
Just van Rossumceeb9622002-11-21 23:19:37 +0000130 plist.CFBundleSignature = self.creator
Just van Rossum9896ea22003-01-13 23:30:04 +0000131 if not hasattr(plist, "CFBundleIdentifier"):
132 plist.CFBundleIdentifier = self.name
Just van Rossumceeb9622002-11-21 23:19:37 +0000133
Just van Rossumad33d722002-11-21 10:23:04 +0000134 def build(self):
135 """Build the bundle."""
136 builddir = self.builddir
137 if builddir and not os.path.exists(builddir):
138 os.mkdir(builddir)
139 self.message("Building %s" % repr(self.bundlepath), 1)
140 if os.path.exists(self.bundlepath):
141 shutil.rmtree(self.bundlepath)
142 os.mkdir(self.bundlepath)
143 self.preProcess()
144 self._copyFiles()
145 self._addMetaFiles()
146 self.postProcess()
Just van Rossum535ffa22002-11-29 20:06:52 +0000147 self.message("Done.", 1)
Just van Rossumad33d722002-11-21 10:23:04 +0000148
149 def preProcess(self):
150 """Hook for subclasses."""
151 pass
152 def postProcess(self):
153 """Hook for subclasses."""
154 pass
155
156 def _addMetaFiles(self):
157 contents = pathjoin(self.bundlepath, "Contents")
158 makedirs(contents)
159 #
160 # Write Contents/PkgInfo
161 assert len(self.type) == len(self.creator) == 4, \
162 "type and creator must be 4-byte strings."
163 pkginfo = pathjoin(contents, "PkgInfo")
164 f = open(pkginfo, "wb")
165 f.write(self.type + self.creator)
166 f.close()
167 #
168 # Write Contents/Info.plist
Just van Rossumad33d722002-11-21 10:23:04 +0000169 infoplist = pathjoin(contents, "Info.plist")
Just van Rossumceeb9622002-11-21 23:19:37 +0000170 self.plist.write(infoplist)
Just van Rossumad33d722002-11-21 10:23:04 +0000171
172 def _copyFiles(self):
173 files = self.files[:]
174 for path in self.resources:
175 files.append((path, pathjoin("Contents", "Resources",
176 os.path.basename(path))))
177 if self.symlink:
178 self.message("Making symbolic links", 1)
179 msg = "Making symlink from"
180 else:
181 self.message("Copying files", 1)
182 msg = "Copying"
Just van Rossumcef32882002-11-26 00:34:52 +0000183 files.sort()
Just van Rossumad33d722002-11-21 10:23:04 +0000184 for src, dst in files:
Just van Rossumceeb9622002-11-21 23:19:37 +0000185 if os.path.isdir(src):
186 self.message("%s %s/ to %s/" % (msg, src, dst), 2)
187 else:
188 self.message("%s %s to %s" % (msg, src, dst), 2)
Just van Rossumad33d722002-11-21 10:23:04 +0000189 dst = pathjoin(self.bundlepath, dst)
190 if self.symlink:
191 symlink(src, dst, mkdirs=1)
Jack Jansen946c1942003-02-17 16:47:12 +0000192 elif os.path.splitext(src)[1] == '.rsrc':
193 macresource.install(src, dst, mkdirs=1)
Just van Rossumad33d722002-11-21 10:23:04 +0000194 else:
195 copy(src, dst, mkdirs=1)
196
197 def message(self, msg, level=0):
198 if level <= self.verbosity:
Just van Rossumceeb9622002-11-21 23:19:37 +0000199 indent = ""
200 if level > 1:
201 indent = (level - 1) * " "
202 sys.stderr.write(indent + msg + "\n")
203
204 def report(self):
205 # XXX something decent
Just van Rossum74bdca82002-11-28 11:30:56 +0000206 pass
Just van Rossumad33d722002-11-21 10:23:04 +0000207
208
Just van Rossumcef32882002-11-26 00:34:52 +0000209if __debug__:
210 PYC_EXT = ".pyc"
211else:
212 PYC_EXT = ".pyo"
213
214MAGIC = imp.get_magic()
Just van Rossum109ecbf2003-01-02 13:13:01 +0000215USE_ZIPIMPORT = "zipimport" in sys.builtin_module_names
Just van Rossumcef32882002-11-26 00:34:52 +0000216
217# For standalone apps, we have our own minimal site.py. We don't need
218# all the cruft of the real site.py.
219SITE_PY = """\
220import sys
221del sys.path[1:] # sys.path[0] is Contents/Resources/
222"""
223
Just van Rossum109ecbf2003-01-02 13:13:01 +0000224if USE_ZIPIMPORT:
225 ZIP_ARCHIVE = "Modules.zip"
226 SITE_PY += "sys.path.append(sys.path[0] + '/%s')\n" % ZIP_ARCHIVE
227 def getPycData(fullname, code, ispkg):
228 if ispkg:
229 fullname += ".__init__"
230 path = fullname.replace(".", os.sep) + PYC_EXT
231 return path, MAGIC + '\0\0\0\0' + marshal.dumps(code)
Just van Rossumcef32882002-11-26 00:34:52 +0000232
Just van Rossum74bdca82002-11-28 11:30:56 +0000233SITE_CO = compile(SITE_PY, "<-bundlebuilder.py->", "exec")
Just van Rossumcef32882002-11-26 00:34:52 +0000234
Just van Rossum535ffa22002-11-29 20:06:52 +0000235EXT_LOADER = """\
Just van Rossum109ecbf2003-01-02 13:13:01 +0000236def __load():
237 import imp, sys, os
238 for p in sys.path:
239 path = os.path.join(p, "%(filename)s")
240 if os.path.exists(path):
241 break
242 else:
243 assert 0, "file not found: %(filename)s"
244 mod = imp.load_dynamic("%(name)s", path)
245
246__load()
247del __load
Just van Rossum535ffa22002-11-29 20:06:52 +0000248"""
249
Just van Rossumcef32882002-11-26 00:34:52 +0000250MAYMISS_MODULES = ['mac', 'os2', 'nt', 'ntpath', 'dos', 'dospath',
251 'win32api', 'ce', '_winreg', 'nturl2path', 'sitecustomize',
252 'org.python.core', 'riscos', 'riscosenviron', 'riscospath'
253]
254
255STRIP_EXEC = "/usr/bin/strip"
256
Just van Rossum74bdca82002-11-28 11:30:56 +0000257BOOTSTRAP_SCRIPT = """\
258#!/bin/sh
Just van Rossumad33d722002-11-21 10:23:04 +0000259
Just van Rossum9af69682003-02-02 18:56:37 +0000260execdir=$(dirname "${0}")
Just van Rossumc96d6ce2003-02-12 16:19:39 +0000261executable="${execdir}/%(executable)s"
Just van Rossum9af69682003-02-02 18:56:37 +0000262resdir=$(dirname "${execdir}")/Resources
Just van Rossumc96d6ce2003-02-12 16:19:39 +0000263main="${resdir}/%(mainprogram)s"
264PYTHONPATH="$resdir"
Just van Rossum74bdca82002-11-28 11:30:56 +0000265export PYTHONPATH
Jack Jansend6a3f932003-02-18 11:24:31 +0000266exec "${executable}" "${main}" "$@"
Just van Rossumad33d722002-11-21 10:23:04 +0000267"""
268
Jack Jansena03adde2003-02-18 23:29:46 +0000269ARGVEMULATOR="""\
270import argvemulator, os
271
272argvemulator.ArgvCollector().mainloop()
273execfile(os.path.join(os.path.split(__file__)[0], "%(realmainprogram)s"))
274"""
Just van Rossumcef32882002-11-26 00:34:52 +0000275
Just van Rossumad33d722002-11-21 10:23:04 +0000276class AppBuilder(BundleBuilder):
277
Just van Rossumda302da2002-11-23 22:26:44 +0000278 # A Python main program. If this argument is given, the main
279 # executable in the bundle will be a small wrapper that invokes
280 # the main program. (XXX Discuss why.)
281 mainprogram = None
Just van Rossumceeb9622002-11-21 23:19:37 +0000282
Just van Rossumda302da2002-11-23 22:26:44 +0000283 # The main executable. If a Python main program is specified
284 # the executable will be copied to Resources and be invoked
285 # by the wrapper program mentioned above. Otherwise it will
286 # simply be used as the main executable.
287 executable = None
Just van Rossumceeb9622002-11-21 23:19:37 +0000288
Just van Rossumda302da2002-11-23 22:26:44 +0000289 # The name of the main nib, for Cocoa apps. *Must* be specified
290 # when building a Cocoa app.
291 nibname = None
Just van Rossumad33d722002-11-21 10:23:04 +0000292
Just van Rossum2aa09562003-02-01 08:34:46 +0000293 # The name of the icon file to be copied to Resources and used for
294 # the Finder icon.
295 iconfile = None
296
Just van Rossumda302da2002-11-23 22:26:44 +0000297 # Symlink the executable instead of copying it.
298 symlink_exec = 0
Just van Rossumad33d722002-11-21 10:23:04 +0000299
Just van Rossumcef32882002-11-26 00:34:52 +0000300 # If True, build standalone app.
301 standalone = 0
Jack Jansena03adde2003-02-18 23:29:46 +0000302
303 # If True, add a real main program that emulates sys.argv before calling
304 # mainprogram
305 argv_emulation = 0
Just van Rossumcef32882002-11-26 00:34:52 +0000306
307 # The following attributes are only used when building a standalone app.
308
309 # Exclude these modules.
310 excludeModules = []
311
312 # Include these modules.
313 includeModules = []
314
315 # Include these packages.
316 includePackages = []
317
318 # Strip binaries.
319 strip = 0
320
Just van Rossumcef32882002-11-26 00:34:52 +0000321 # Found Python modules: [(name, codeobject, ispkg), ...]
322 pymodules = []
323
324 # Modules that modulefinder couldn't find:
325 missingModules = []
Just van Rossum74bdca82002-11-28 11:30:56 +0000326 maybeMissingModules = []
Just van Rossumcef32882002-11-26 00:34:52 +0000327
328 # List of all binaries (executables or shared libs), for stripping purposes
329 binaries = []
330
Just van Rossumceeb9622002-11-21 23:19:37 +0000331 def setup(self):
Just van Rossumcef32882002-11-26 00:34:52 +0000332 if self.standalone and self.mainprogram is None:
333 raise BundleBuilderError, ("must specify 'mainprogram' when "
334 "building a standalone application.")
Just van Rossumceeb9622002-11-21 23:19:37 +0000335 if self.mainprogram is None and self.executable is None:
Just van Rossumcef32882002-11-26 00:34:52 +0000336 raise BundleBuilderError, ("must specify either or both of "
Just van Rossumceeb9622002-11-21 23:19:37 +0000337 "'executable' and 'mainprogram'")
338
339 if self.name is not None:
340 pass
341 elif self.mainprogram is not None:
342 self.name = os.path.splitext(os.path.basename(self.mainprogram))[0]
343 elif executable is not None:
344 self.name = os.path.splitext(os.path.basename(self.executable))[0]
345 if self.name[-4:] != ".app":
346 self.name += ".app"
Just van Rossumceeb9622002-11-21 23:19:37 +0000347
Just van Rossum74bdca82002-11-28 11:30:56 +0000348 if self.executable is None:
349 if not self.standalone:
350 self.symlink_exec = 1
351 self.executable = sys.executable
352
Just van Rossumceeb9622002-11-21 23:19:37 +0000353 if self.nibname:
354 self.plist.NSMainNibFile = self.nibname
355 if not hasattr(self.plist, "NSPrincipalClass"):
356 self.plist.NSPrincipalClass = "NSApplication"
357
358 BundleBuilder.setup(self)
359
Just van Rossum7fd69ad2002-11-22 00:08:47 +0000360 self.plist.CFBundleExecutable = self.name
Just van Rossumf7aba232002-11-22 00:31:50 +0000361
Just van Rossumcef32882002-11-26 00:34:52 +0000362 if self.standalone:
Just van Rossumcef32882002-11-26 00:34:52 +0000363 self.findDependencies()
364
Just van Rossumf7aba232002-11-22 00:31:50 +0000365 def preProcess(self):
Just van Rossumcef32882002-11-26 00:34:52 +0000366 resdir = "Contents/Resources"
Just van Rossumad33d722002-11-21 10:23:04 +0000367 if self.executable is not None:
368 if self.mainprogram is None:
Just van Rossum74bdca82002-11-28 11:30:56 +0000369 execname = self.name
Just van Rossumad33d722002-11-21 10:23:04 +0000370 else:
Just van Rossum74bdca82002-11-28 11:30:56 +0000371 execname = os.path.basename(self.executable)
372 execpath = pathjoin(self.execdir, execname)
Just van Rossum16aebf72002-11-22 11:43:10 +0000373 if not self.symlink_exec:
374 self.files.append((self.executable, execpath))
Just van Rossumcef32882002-11-26 00:34:52 +0000375 self.binaries.append(execpath)
Just van Rossumda302da2002-11-23 22:26:44 +0000376 self.execpath = execpath
Just van Rossumad33d722002-11-21 10:23:04 +0000377
378 if self.mainprogram is not None:
Just van Rossum24884f72002-11-29 21:22:33 +0000379 mainprogram = os.path.basename(self.mainprogram)
380 self.files.append((self.mainprogram, pathjoin(resdir, mainprogram)))
Jack Jansena03adde2003-02-18 23:29:46 +0000381 if self.argv_emulation:
382 # Change the main program, and create the helper main program (which
383 # does argv collection and then calls the real main).
384 # Also update the included modules (if we're creating a standalone
385 # program) and the plist
386 realmainprogram = mainprogram
387 mainprogram = '__argvemulator_' + mainprogram
388 resdirpath = pathjoin(self.bundlepath, resdir)
389 mainprogrampath = pathjoin(resdirpath, mainprogram)
390 makedirs(resdirpath)
391 open(mainprogrampath, "w").write(ARGVEMULATOR % locals())
392 if self.standalone:
393 self.includeModules.append("argvemulator")
394 self.includeModules.append("os")
395 if not self.plist.has_key("CFBundleDocumentTypes"):
396 self.plist["CFBundleDocumentTypes"] = [
397 { "CFBundleTypeOSTypes" : [
398 "****",
399 "fold",
400 "disk"],
401 "CFBundleTypeRole": "Viewer"}]
Just van Rossum24884f72002-11-29 21:22:33 +0000402 # Write bootstrap script
Just van Rossum74bdca82002-11-28 11:30:56 +0000403 executable = os.path.basename(self.executable)
Just van Rossumad33d722002-11-21 10:23:04 +0000404 execdir = pathjoin(self.bundlepath, self.execdir)
Just van Rossum24884f72002-11-29 21:22:33 +0000405 bootstrappath = pathjoin(execdir, self.name)
Just van Rossumad33d722002-11-21 10:23:04 +0000406 makedirs(execdir)
Just van Rossum24884f72002-11-29 21:22:33 +0000407 open(bootstrappath, "w").write(BOOTSTRAP_SCRIPT % locals())
408 os.chmod(bootstrappath, 0775)
Just van Rossumad33d722002-11-21 10:23:04 +0000409
Just van Rossum2aa09562003-02-01 08:34:46 +0000410 if self.iconfile is not None:
411 iconbase = os.path.basename(self.iconfile)
412 self.plist.CFBundleIconFile = iconbase
413 self.files.append((self.iconfile, pathjoin(resdir, iconbase)))
414
Just van Rossum16aebf72002-11-22 11:43:10 +0000415 def postProcess(self):
Just van Rossum888e1002002-11-30 19:56:14 +0000416 if self.standalone:
417 self.addPythonModules()
Just van Rossumcef32882002-11-26 00:34:52 +0000418 if self.strip and not self.symlink:
419 self.stripBinaries()
420
Just van Rossum16aebf72002-11-22 11:43:10 +0000421 if self.symlink_exec and self.executable:
422 self.message("Symlinking executable %s to %s" % (self.executable,
423 self.execpath), 2)
424 dst = pathjoin(self.bundlepath, self.execpath)
425 makedirs(os.path.dirname(dst))
426 os.symlink(os.path.abspath(self.executable), dst)
427
Just van Rossum74bdca82002-11-28 11:30:56 +0000428 if self.missingModules or self.maybeMissingModules:
Just van Rossumcef32882002-11-26 00:34:52 +0000429 self.reportMissing()
430
431 def addPythonModules(self):
432 self.message("Adding Python modules", 1)
Just van Rossumcef32882002-11-26 00:34:52 +0000433
Just van Rossum109ecbf2003-01-02 13:13:01 +0000434 if USE_ZIPIMPORT:
435 # Create a zip file containing all modules as pyc.
436 import zipfile
437 relpath = pathjoin("Contents", "Resources", ZIP_ARCHIVE)
Just van Rossumcef32882002-11-26 00:34:52 +0000438 abspath = pathjoin(self.bundlepath, relpath)
Just van Rossum109ecbf2003-01-02 13:13:01 +0000439 zf = zipfile.ZipFile(abspath, "w", zipfile.ZIP_DEFLATED)
440 for name, code, ispkg in self.pymodules:
441 self.message("Adding Python module %s" % name, 2)
442 path, pyc = getPycData(name, code, ispkg)
443 zf.writestr(path, pyc)
444 zf.close()
Just van Rossumcef32882002-11-26 00:34:52 +0000445 # add site.pyc
Just van Rossum535ffa22002-11-29 20:06:52 +0000446 sitepath = pathjoin(self.bundlepath, "Contents", "Resources",
447 "site" + PYC_EXT)
Just van Rossumcef32882002-11-26 00:34:52 +0000448 writePyc(SITE_CO, sitepath)
449 else:
450 # Create individual .pyc files.
Just van Rossum535ffa22002-11-29 20:06:52 +0000451 for name, code, ispkg in self.pymodules:
Just van Rossumcef32882002-11-26 00:34:52 +0000452 if ispkg:
453 name += ".__init__"
454 path = name.split(".")
Just van Rossum535ffa22002-11-29 20:06:52 +0000455 path = pathjoin("Contents", "Resources", *path) + PYC_EXT
Just van Rossumcef32882002-11-26 00:34:52 +0000456
457 if ispkg:
458 self.message("Adding Python package %s" % path, 2)
459 else:
460 self.message("Adding Python module %s" % path, 2)
461
462 abspath = pathjoin(self.bundlepath, path)
463 makedirs(os.path.dirname(abspath))
464 writePyc(code, abspath)
465
466 def stripBinaries(self):
467 if not os.path.exists(STRIP_EXEC):
468 self.message("Error: can't strip binaries: no strip program at "
469 "%s" % STRIP_EXEC, 0)
470 else:
471 self.message("Stripping binaries", 1)
472 for relpath in self.binaries:
473 self.message("Stripping %s" % relpath, 2)
474 abspath = pathjoin(self.bundlepath, relpath)
475 assert not os.path.islink(abspath)
476 rv = os.system("%s -S \"%s\"" % (STRIP_EXEC, abspath))
477
478 def findDependencies(self):
479 self.message("Finding module dependencies", 1)
480 import modulefinder
481 mf = modulefinder.ModuleFinder(excludes=self.excludeModules)
Just van Rossum109ecbf2003-01-02 13:13:01 +0000482 if USE_ZIPIMPORT:
483 # zipimport imports zlib, must add it manually
484 mf.import_hook("zlib")
Just van Rossumcef32882002-11-26 00:34:52 +0000485 # manually add our own site.py
486 site = mf.add_module("site")
487 site.__code__ = SITE_CO
488 mf.scan_code(SITE_CO, site)
489
490 includeModules = self.includeModules[:]
491 for name in self.includePackages:
492 includeModules.extend(findPackageContents(name).keys())
493 for name in includeModules:
494 try:
495 mf.import_hook(name)
496 except ImportError:
497 self.missingModules.append(name)
498
Just van Rossumcef32882002-11-26 00:34:52 +0000499 mf.run_script(self.mainprogram)
500 modules = mf.modules.items()
501 modules.sort()
502 for name, mod in modules:
503 if mod.__file__ and mod.__code__ is None:
504 # C extension
505 path = mod.__file__
Just van Rossum535ffa22002-11-29 20:06:52 +0000506 filename = os.path.basename(path)
Just van Rossum109ecbf2003-01-02 13:13:01 +0000507 if USE_ZIPIMPORT:
508 # Python modules are stored in a Zip archive, but put
509 # extensions in Contents/Resources/.a and add a tiny "loader"
510 # program in the Zip archive. Due to Thomas Heller.
Just van Rossum535ffa22002-11-29 20:06:52 +0000511 dstpath = pathjoin("Contents", "Resources", filename)
512 source = EXT_LOADER % {"name": name, "filename": filename}
513 code = compile(source, "<dynloader for %s>" % name, "exec")
514 mod.__code__ = code
Just van Rossumcef32882002-11-26 00:34:52 +0000515 else:
Just van Rossum535ffa22002-11-29 20:06:52 +0000516 # just copy the file
517 dstpath = name.split(".")[:-1] + [filename]
518 dstpath = pathjoin("Contents", "Resources", *dstpath)
Just van Rossumcef32882002-11-26 00:34:52 +0000519 self.files.append((path, dstpath))
Just van Rossumcef32882002-11-26 00:34:52 +0000520 self.binaries.append(dstpath)
Just van Rossum535ffa22002-11-29 20:06:52 +0000521 if mod.__code__ is not None:
Just van Rossumcef32882002-11-26 00:34:52 +0000522 ispkg = mod.__path__ is not None
Just van Rossum109ecbf2003-01-02 13:13:01 +0000523 if not USE_ZIPIMPORT or name != "site":
Just van Rossumcef32882002-11-26 00:34:52 +0000524 # Our site.py is doing the bootstrapping, so we must
Just van Rossum109ecbf2003-01-02 13:13:01 +0000525 # include a real .pyc file if USE_ZIPIMPORT is True.
Just van Rossumcef32882002-11-26 00:34:52 +0000526 self.pymodules.append((name, mod.__code__, ispkg))
527
Just van Rossum74bdca82002-11-28 11:30:56 +0000528 if hasattr(mf, "any_missing_maybe"):
529 missing, maybe = mf.any_missing_maybe()
530 else:
531 missing = mf.any_missing()
532 maybe = []
533 self.missingModules.extend(missing)
534 self.maybeMissingModules.extend(maybe)
Just van Rossumcef32882002-11-26 00:34:52 +0000535
536 def reportMissing(self):
537 missing = [name for name in self.missingModules
538 if name not in MAYMISS_MODULES]
Just van Rossum74bdca82002-11-28 11:30:56 +0000539 if self.maybeMissingModules:
540 maybe = self.maybeMissingModules
541 else:
542 maybe = [name for name in missing if "." in name]
543 missing = [name for name in missing if "." not in name]
Just van Rossumcef32882002-11-26 00:34:52 +0000544 missing.sort()
Just van Rossum74bdca82002-11-28 11:30:56 +0000545 maybe.sort()
546 if maybe:
547 self.message("Warning: couldn't find the following submodules:", 1)
548 self.message(" (Note that these could be false alarms -- "
549 "it's not always", 1)
Just van Rossumad692cc2002-11-28 18:56:50 +0000550 self.message(" possible to distinguish between \"from package "
551 "import submodule\" ", 1)
Just van Rossum74bdca82002-11-28 11:30:56 +0000552 self.message(" and \"from package import name\")", 1)
553 for name in maybe:
554 self.message(" ? " + name, 1)
Just van Rossumcef32882002-11-26 00:34:52 +0000555 if missing:
556 self.message("Warning: couldn't find the following modules:", 1)
Just van Rossum74bdca82002-11-28 11:30:56 +0000557 for name in missing:
558 self.message(" ? " + name, 1)
559
560 def report(self):
561 # XXX something decent
562 import pprint
563 pprint.pprint(self.__dict__)
564 if self.standalone:
565 self.reportMissing()
Just van Rossumcef32882002-11-26 00:34:52 +0000566
567#
568# Utilities.
569#
570
571SUFFIXES = [_suf for _suf, _mode, _tp in imp.get_suffixes()]
572identifierRE = re.compile(r"[_a-zA-z][_a-zA-Z0-9]*$")
573
574def findPackageContents(name, searchpath=None):
575 head = name.split(".")[-1]
576 if identifierRE.match(head) is None:
577 return {}
578 try:
579 fp, path, (ext, mode, tp) = imp.find_module(head, searchpath)
580 except ImportError:
581 return {}
582 modules = {name: None}
583 if tp == imp.PKG_DIRECTORY and path:
584 files = os.listdir(path)
585 for sub in files:
586 sub, ext = os.path.splitext(sub)
587 fullname = name + "." + sub
588 if sub != "__init__" and fullname not in modules:
589 modules.update(findPackageContents(fullname, [path]))
590 return modules
591
592def writePyc(code, path):
593 f = open(path, "wb")
Just van Rossumcef32882002-11-26 00:34:52 +0000594 f.write(MAGIC)
Just van Rossum109ecbf2003-01-02 13:13:01 +0000595 f.write("\0" * 4) # don't bother about a time stamp
596 marshal.dump(code, f)
Just van Rossumcef32882002-11-26 00:34:52 +0000597 f.close()
Just van Rossumad33d722002-11-21 10:23:04 +0000598
Just van Rossumad33d722002-11-21 10:23:04 +0000599def copy(src, dst, mkdirs=0):
600 """Copy a file or a directory."""
601 if mkdirs:
602 makedirs(os.path.dirname(dst))
603 if os.path.isdir(src):
604 shutil.copytree(src, dst)
605 else:
606 shutil.copy2(src, dst)
607
608def copytodir(src, dstdir):
609 """Copy a file or a directory to an existing directory."""
610 dst = pathjoin(dstdir, os.path.basename(src))
611 copy(src, dst)
612
613def makedirs(dir):
614 """Make all directories leading up to 'dir' including the leaf
615 directory. Don't moan if any path element already exists."""
616 try:
617 os.makedirs(dir)
618 except OSError, why:
619 if why.errno != errno.EEXIST:
620 raise
621
622def symlink(src, dst, mkdirs=0):
623 """Copy a file or a directory."""
Just van Rossum504377d2003-01-17 20:02:06 +0000624 if not os.path.exists(src):
625 raise IOError, "No such file or directory: '%s'" % src
Just van Rossumad33d722002-11-21 10:23:04 +0000626 if mkdirs:
627 makedirs(os.path.dirname(dst))
628 os.symlink(os.path.abspath(src), dst)
629
630def pathjoin(*args):
631 """Safe wrapper for os.path.join: asserts that all but the first
632 argument are relative paths."""
633 for seg in args[1:]:
634 assert seg[0] != "/"
635 return os.path.join(*args)
636
637
Just van Rossumceeb9622002-11-21 23:19:37 +0000638cmdline_doc = """\
639Usage:
Just van Rossumf7aba232002-11-22 00:31:50 +0000640 python bundlebuilder.py [options] command
Just van Rossumceeb9622002-11-21 23:19:37 +0000641 python mybuildscript.py [options] command
642
643Commands:
644 build build the application
645 report print a report
646
647Options:
648 -b, --builddir=DIR the build directory; defaults to "build"
649 -n, --name=NAME application name
650 -r, --resource=FILE extra file or folder to be copied to Resources
Jack Jansen00cbf072003-02-24 16:27:08 +0000651 -f, --copyfile=SRC:DST extra file or folder to be copied into the bundle
Just van Rossumceeb9622002-11-21 23:19:37 +0000652 -e, --executable=FILE the executable to be used
653 -m, --mainprogram=FILE the Python main program
Jack Jansena03adde2003-02-18 23:29:46 +0000654 -a, --argv add a wrapper main program to create sys.argv
Just van Rossumceeb9622002-11-21 23:19:37 +0000655 -p, --plist=FILE .plist file (default: generate one)
656 --nib=NAME main nib name
657 -c, --creator=CCCC 4-char creator code (default: '????')
Just van Rossum9af69682003-02-02 18:56:37 +0000658 --iconfile=FILE filename of the icon (an .icns file) to be used
Just van Rossum2aa09562003-02-01 08:34:46 +0000659 as the Finder icon
Just van Rossumceeb9622002-11-21 23:19:37 +0000660 -l, --link symlink files/folder instead of copying them
Just van Rossum16aebf72002-11-22 11:43:10 +0000661 --link-exec symlink the executable instead of copying it
Just van Rossumcef32882002-11-26 00:34:52 +0000662 --standalone build a standalone application, which is fully
663 independent of a Python installation
664 -x, --exclude=MODULE exclude module (with --standalone)
665 -i, --include=MODULE include module (with --standalone)
666 --package=PACKAGE include a whole package (with --standalone)
667 --strip strip binaries (remove debug info)
Just van Rossumceeb9622002-11-21 23:19:37 +0000668 -v, --verbose increase verbosity level
669 -q, --quiet decrease verbosity level
670 -h, --help print this message
671"""
672
673def usage(msg=None):
674 if msg:
675 print msg
676 print cmdline_doc
677 sys.exit(1)
678
679def main(builder=None):
680 if builder is None:
681 builder = AppBuilder(verbosity=1)
682
Jack Jansen00cbf072003-02-24 16:27:08 +0000683 shortopts = "b:n:r:f:e:m:c:p:lx:i:hvqa"
684 longopts = ("builddir=", "name=", "resource=", "copyfile=", "executable=",
Just van Rossum16aebf72002-11-22 11:43:10 +0000685 "mainprogram=", "creator=", "nib=", "plist=", "link",
Jack Jansena03adde2003-02-18 23:29:46 +0000686 "link-exec", "help", "verbose", "quiet", "argv", "standalone",
Just van Rossum2aa09562003-02-01 08:34:46 +0000687 "exclude=", "include=", "package=", "strip", "iconfile=")
Just van Rossumceeb9622002-11-21 23:19:37 +0000688
689 try:
690 options, args = getopt.getopt(sys.argv[1:], shortopts, longopts)
691 except getopt.error:
692 usage()
693
694 for opt, arg in options:
695 if opt in ('-b', '--builddir'):
696 builder.builddir = arg
697 elif opt in ('-n', '--name'):
698 builder.name = arg
699 elif opt in ('-r', '--resource'):
700 builder.resources.append(arg)
Jack Jansen00cbf072003-02-24 16:27:08 +0000701 elif opt in ('-f', '--copyfile'):
702 srcdst = arg.split(':')
703 if len(srcdst) != 2:
704 usage()
705 builder.files.append(srcdst)
Just van Rossumceeb9622002-11-21 23:19:37 +0000706 elif opt in ('-e', '--executable'):
707 builder.executable = arg
708 elif opt in ('-m', '--mainprogram'):
709 builder.mainprogram = arg
Jack Jansena03adde2003-02-18 23:29:46 +0000710 elif opt in ('-a', '--argv'):
711 builder.argv_emulation = 1
Just van Rossumceeb9622002-11-21 23:19:37 +0000712 elif opt in ('-c', '--creator'):
713 builder.creator = arg
Just van Rossum2aa09562003-02-01 08:34:46 +0000714 elif opt == '--iconfile':
715 builder.iconfile = arg
Just van Rossumceeb9622002-11-21 23:19:37 +0000716 elif opt == "--nib":
717 builder.nibname = arg
718 elif opt in ('-p', '--plist'):
719 builder.plist = Plist.fromFile(arg)
720 elif opt in ('-l', '--link'):
721 builder.symlink = 1
Just van Rossum16aebf72002-11-22 11:43:10 +0000722 elif opt == '--link-exec':
723 builder.symlink_exec = 1
Just van Rossumceeb9622002-11-21 23:19:37 +0000724 elif opt in ('-h', '--help'):
725 usage()
726 elif opt in ('-v', '--verbose'):
727 builder.verbosity += 1
728 elif opt in ('-q', '--quiet'):
729 builder.verbosity -= 1
Just van Rossumcef32882002-11-26 00:34:52 +0000730 elif opt == '--standalone':
731 builder.standalone = 1
732 elif opt in ('-x', '--exclude'):
733 builder.excludeModules.append(arg)
734 elif opt in ('-i', '--include'):
735 builder.includeModules.append(arg)
736 elif opt == '--package':
737 builder.includePackages.append(arg)
738 elif opt == '--strip':
739 builder.strip = 1
Just van Rossumceeb9622002-11-21 23:19:37 +0000740
741 if len(args) != 1:
742 usage("Must specify one command ('build', 'report' or 'help')")
743 command = args[0]
744
745 if command == "build":
746 builder.setup()
747 builder.build()
748 elif command == "report":
749 builder.setup()
750 builder.report()
751 elif command == "help":
752 usage()
753 else:
754 usage("Unknown command '%s'" % command)
755
756
Just van Rossumad33d722002-11-21 10:23:04 +0000757def buildapp(**kwargs):
Just van Rossumad33d722002-11-21 10:23:04 +0000758 builder = AppBuilder(**kwargs)
Just van Rossumceeb9622002-11-21 23:19:37 +0000759 main(builder)
Just van Rossumad33d722002-11-21 10:23:04 +0000760
761
762if __name__ == "__main__":
Just van Rossumceeb9622002-11-21 23:19:37 +0000763 main()