blob: f1ca5b8fec85dd9a6725f359235afef2765ed9e3 [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
Just van Rossumad33d722002-11-21 10:23:04 +000039
Just van Rossumcef32882002-11-26 00:34:52 +000040class BundleBuilderError(Exception): pass
41
42
Just van Rossumda302da2002-11-23 22:26:44 +000043class Defaults:
44
Jack Jansen0ae32202003-04-09 13:25:43 +000045 """Class attributes that don't start with an underscore and are
46 not functions or classmethods are (deep)copied to self.__dict__.
47 This allows for mutable default values.
48 """
Just van Rossumda302da2002-11-23 22:26:44 +000049
Jack Jansen0ae32202003-04-09 13:25:43 +000050 def __init__(self, **kwargs):
51 defaults = self._getDefaults()
52 defaults.update(kwargs)
53 self.__dict__.update(defaults)
Just van Rossumda302da2002-11-23 22:26:44 +000054
Jack Jansen0ae32202003-04-09 13:25:43 +000055 def _getDefaults(cls):
56 defaults = {}
Just van Rossumed8bfce2003-07-10 14:53:27 +000057 for base in cls.__bases__:
58 if hasattr(base, "_getDefaults"):
59 defaults.update(base._getDefaults())
Jack Jansen0ae32202003-04-09 13:25:43 +000060 for name, value in cls.__dict__.items():
61 if name[0] != "_" and not isinstance(value,
62 (function, classmethod)):
63 defaults[name] = deepcopy(value)
Jack Jansen0ae32202003-04-09 13:25:43 +000064 return defaults
65 _getDefaults = classmethod(_getDefaults)
Just van Rossumad33d722002-11-21 10:23:04 +000066
67
Just van Rossumda302da2002-11-23 22:26:44 +000068class BundleBuilder(Defaults):
Just van Rossumad33d722002-11-21 10:23:04 +000069
Jack Jansen0ae32202003-04-09 13:25:43 +000070 """BundleBuilder is a barebones class for assembling bundles. It
71 knows nothing about executables or icons, it only copies files
72 and creates the PkgInfo and Info.plist files.
73 """
Just van Rossumad33d722002-11-21 10:23:04 +000074
Jack Jansen0ae32202003-04-09 13:25:43 +000075 # (Note that Defaults.__init__ (deep)copies these values to
76 # instance variables. Mutable defaults are therefore safe.)
Just van Rossumda302da2002-11-23 22:26:44 +000077
Jack Jansen0ae32202003-04-09 13:25:43 +000078 # Name of the bundle, with or without extension.
79 name = None
Just van Rossumda302da2002-11-23 22:26:44 +000080
Jack Jansen0ae32202003-04-09 13:25:43 +000081 # The property list ("plist")
82 plist = Plist(CFBundleDevelopmentRegion = "English",
83 CFBundleInfoDictionaryVersion = "6.0")
Just van Rossumda302da2002-11-23 22:26:44 +000084
Jack Jansen0ae32202003-04-09 13:25:43 +000085 # The type of the bundle.
86 type = "BNDL"
87 # The creator code of the bundle.
88 creator = None
Just van Rossumda302da2002-11-23 22:26:44 +000089
Just van Rossumbe56aae2003-07-04 14:20:03 +000090 # the CFBundleIdentifier (this is used for the preferences file name)
91 bundle_id = None
92
Jack Jansen0ae32202003-04-09 13:25:43 +000093 # List of files that have to be copied to <bundle>/Contents/Resources.
94 resources = []
Just van Rossumda302da2002-11-23 22:26:44 +000095
Jack Jansen0ae32202003-04-09 13:25:43 +000096 # List of (src, dest) tuples; dest should be a path relative to the bundle
97 # (eg. "Contents/Resources/MyStuff/SomeFile.ext).
98 files = []
Just van Rossumda302da2002-11-23 22:26:44 +000099
Jack Jansen0ae32202003-04-09 13:25:43 +0000100 # List of shared libraries (dylibs, Frameworks) to bundle with the app
101 # will be placed in Contents/Frameworks
102 libs = []
Just van Rossum15624d82003-03-21 09:26:59 +0000103
Jack Jansen0ae32202003-04-09 13:25:43 +0000104 # Directory where the bundle will be assembled.
105 builddir = "build"
Just van Rossumda302da2002-11-23 22:26:44 +0000106
Jack Jansen0ae32202003-04-09 13:25:43 +0000107 # Make symlinks instead copying files. This is handy during debugging, but
108 # makes the bundle non-distributable.
109 symlink = 0
Just van Rossumda302da2002-11-23 22:26:44 +0000110
Jack Jansen0ae32202003-04-09 13:25:43 +0000111 # Verbosity level.
112 verbosity = 1
Tim Peters5a9fb3c2005-01-07 16:01:32 +0000113
Jack Jansenc77f6df2004-12-27 15:51:03 +0000114 # Destination root directory
115 destroot = ""
Just van Rossumad33d722002-11-21 10:23:04 +0000116
Jack Jansen0ae32202003-04-09 13:25:43 +0000117 def setup(self):
118 # XXX rethink self.name munging, this is brittle.
119 self.name, ext = os.path.splitext(self.name)
120 if not ext:
121 ext = ".bundle"
122 bundleextension = ext
123 # misc (derived) attributes
124 self.bundlepath = pathjoin(self.builddir, self.name + bundleextension)
Just van Rossumceeb9622002-11-21 23:19:37 +0000125
Jack Jansen0ae32202003-04-09 13:25:43 +0000126 plist = self.plist
127 plist.CFBundleName = self.name
128 plist.CFBundlePackageType = self.type
129 if self.creator is None:
130 if hasattr(plist, "CFBundleSignature"):
131 self.creator = plist.CFBundleSignature
132 else:
133 self.creator = "????"
134 plist.CFBundleSignature = self.creator
Just van Rossumbe56aae2003-07-04 14:20:03 +0000135 if self.bundle_id:
136 plist.CFBundleIdentifier = self.bundle_id
137 elif not hasattr(plist, "CFBundleIdentifier"):
Jack Jansen0ae32202003-04-09 13:25:43 +0000138 plist.CFBundleIdentifier = self.name
Just van Rossumceeb9622002-11-21 23:19:37 +0000139
Jack Jansen0ae32202003-04-09 13:25:43 +0000140 def build(self):
141 """Build the bundle."""
142 builddir = self.builddir
143 if builddir and not os.path.exists(builddir):
144 os.mkdir(builddir)
145 self.message("Building %s" % repr(self.bundlepath), 1)
146 if os.path.exists(self.bundlepath):
147 shutil.rmtree(self.bundlepath)
Thomas Wouters477c8d52006-05-27 19:21:47 +0000148 if os.path.exists(self.bundlepath + '~'):
149 shutil.rmtree(self.bundlepath + '~')
150 bp = self.bundlepath
151
152 # Create the app bundle in a temporary location and then
153 # rename the completed bundle. This way the Finder will
154 # never see an incomplete bundle (where it might pick up
155 # and cache the wrong meta data)
156 self.bundlepath = bp + '~'
157 try:
158 os.mkdir(self.bundlepath)
159 self.preProcess()
160 self._copyFiles()
161 self._addMetaFiles()
162 self.postProcess()
163 os.rename(self.bundlepath, bp)
164 finally:
165 self.bundlepath = bp
Jack Jansen0ae32202003-04-09 13:25:43 +0000166 self.message("Done.", 1)
Just van Rossumad33d722002-11-21 10:23:04 +0000167
Jack Jansen0ae32202003-04-09 13:25:43 +0000168 def preProcess(self):
169 """Hook for subclasses."""
170 pass
171 def postProcess(self):
172 """Hook for subclasses."""
173 pass
Just van Rossumad33d722002-11-21 10:23:04 +0000174
Jack Jansen0ae32202003-04-09 13:25:43 +0000175 def _addMetaFiles(self):
176 contents = pathjoin(self.bundlepath, "Contents")
177 makedirs(contents)
178 #
179 # Write Contents/PkgInfo
180 assert len(self.type) == len(self.creator) == 4, \
181 "type and creator must be 4-byte strings."
182 pkginfo = pathjoin(contents, "PkgInfo")
183 f = open(pkginfo, "wb")
184 f.write(self.type + self.creator)
185 f.close()
186 #
187 # Write Contents/Info.plist
188 infoplist = pathjoin(contents, "Info.plist")
189 self.plist.write(infoplist)
Just van Rossumad33d722002-11-21 10:23:04 +0000190
Jack Jansen0ae32202003-04-09 13:25:43 +0000191 def _copyFiles(self):
192 files = self.files[:]
193 for path in self.resources:
194 files.append((path, pathjoin("Contents", "Resources",
Just van Rossumdc31dc02003-06-20 21:43:36 +0000195 os.path.basename(path))))
Jack Jansen0ae32202003-04-09 13:25:43 +0000196 for path in self.libs:
197 files.append((path, pathjoin("Contents", "Frameworks",
Just van Rossumdc31dc02003-06-20 21:43:36 +0000198 os.path.basename(path))))
Jack Jansen0ae32202003-04-09 13:25:43 +0000199 if self.symlink:
200 self.message("Making symbolic links", 1)
201 msg = "Making symlink from"
202 else:
203 self.message("Copying files", 1)
204 msg = "Copying"
205 files.sort()
206 for src, dst in files:
207 if os.path.isdir(src):
208 self.message("%s %s/ to %s/" % (msg, src, dst), 2)
209 else:
210 self.message("%s %s to %s" % (msg, src, dst), 2)
211 dst = pathjoin(self.bundlepath, dst)
212 if self.symlink:
213 symlink(src, dst, mkdirs=1)
214 else:
215 copy(src, dst, mkdirs=1)
Just van Rossumad33d722002-11-21 10:23:04 +0000216
Jack Jansen0ae32202003-04-09 13:25:43 +0000217 def message(self, msg, level=0):
218 if level <= self.verbosity:
219 indent = ""
220 if level > 1:
221 indent = (level - 1) * " "
222 sys.stderr.write(indent + msg + "\n")
Just van Rossumceeb9622002-11-21 23:19:37 +0000223
Jack Jansen0ae32202003-04-09 13:25:43 +0000224 def report(self):
225 # XXX something decent
226 pass
Just van Rossumad33d722002-11-21 10:23:04 +0000227
228
Just van Rossumcef32882002-11-26 00:34:52 +0000229if __debug__:
Jack Jansen0ae32202003-04-09 13:25:43 +0000230 PYC_EXT = ".pyc"
Just van Rossumcef32882002-11-26 00:34:52 +0000231else:
Jack Jansen0ae32202003-04-09 13:25:43 +0000232 PYC_EXT = ".pyo"
Just van Rossumcef32882002-11-26 00:34:52 +0000233
234MAGIC = imp.get_magic()
Just van Rossum109ecbf2003-01-02 13:13:01 +0000235USE_ZIPIMPORT = "zipimport" in sys.builtin_module_names
Just van Rossumcef32882002-11-26 00:34:52 +0000236
237# For standalone apps, we have our own minimal site.py. We don't need
238# all the cruft of the real site.py.
239SITE_PY = """\
240import sys
Just van Rossum762d2cc2003-06-29 21:54:12 +0000241if not %(semi_standalone)s:
242 del sys.path[1:] # sys.path[0] is Contents/Resources/
Just van Rossumcef32882002-11-26 00:34:52 +0000243"""
244
Just van Rossum109ecbf2003-01-02 13:13:01 +0000245if USE_ZIPIMPORT:
Jack Jansen0ae32202003-04-09 13:25:43 +0000246 ZIP_ARCHIVE = "Modules.zip"
247 SITE_PY += "sys.path.append(sys.path[0] + '/%s')\n" % ZIP_ARCHIVE
248 def getPycData(fullname, code, ispkg):
249 if ispkg:
250 fullname += ".__init__"
251 path = fullname.replace(".", os.sep) + PYC_EXT
252 return path, MAGIC + '\0\0\0\0' + marshal.dumps(code)
Just van Rossumcef32882002-11-26 00:34:52 +0000253
Just van Rossum8a3ed3f2003-02-25 20:53:12 +0000254#
255# Extension modules can't be in the modules zip archive, so a placeholder
256# is added instead, that loads the extension from a specified location.
257#
Just van Rossum535ffa22002-11-29 20:06:52 +0000258EXT_LOADER = """\
Just van Rossum109ecbf2003-01-02 13:13:01 +0000259def __load():
Jack Jansen0ae32202003-04-09 13:25:43 +0000260 import imp, sys, os
261 for p in sys.path:
262 path = os.path.join(p, "%(filename)s")
263 if os.path.exists(path):
264 break
265 else:
266 assert 0, "file not found: %(filename)s"
267 mod = imp.load_dynamic("%(name)s", path)
Just van Rossum109ecbf2003-01-02 13:13:01 +0000268
269__load()
270del __load
Just van Rossum535ffa22002-11-29 20:06:52 +0000271"""
272
Just van Rossumcef32882002-11-26 00:34:52 +0000273MAYMISS_MODULES = ['mac', 'os2', 'nt', 'ntpath', 'dos', 'dospath',
Jack Jansen0ae32202003-04-09 13:25:43 +0000274 'win32api', 'ce', '_winreg', 'nturl2path', 'sitecustomize',
Skip Montanaro289bc052007-08-17 02:30:27 +0000275 'org.python.core'
Just van Rossumcef32882002-11-26 00:34:52 +0000276]
277
278STRIP_EXEC = "/usr/bin/strip"
279
Just van Rossum8a3ed3f2003-02-25 20:53:12 +0000280#
281# We're using a stock interpreter to run the app, yet we need
282# a way to pass the Python main program to the interpreter. The
283# bootstrapping script fires up the interpreter with the right
284# arguments. os.execve() is used as OSX doesn't like us to
285# start a real new process. Also, the executable name must match
286# the CFBundleExecutable value in the Info.plist, so we lie
287# deliberately with argv[0]. The actual Python executable is
288# passed in an environment variable so we can "repair"
289# sys.executable later.
290#
Just van Rossum74bdca82002-11-28 11:30:56 +0000291BOOTSTRAP_SCRIPT = """\
Just van Rossum0ff7a4e2003-02-26 11:27:56 +0000292#!%(hashbang)s
Just van Rossumad33d722002-11-21 10:23:04 +0000293
Just van Rossum7322b1a2003-02-25 20:15:40 +0000294import sys, os
295execdir = os.path.dirname(sys.argv[0])
296executable = os.path.join(execdir, "%(executable)s")
297resdir = os.path.join(os.path.dirname(execdir), "Resources")
Just van Rossum15624d82003-03-21 09:26:59 +0000298libdir = os.path.join(os.path.dirname(execdir), "Frameworks")
Just van Rossum7322b1a2003-02-25 20:15:40 +0000299mainprogram = os.path.join(resdir, "%(mainprogram)s")
300
301sys.argv.insert(1, mainprogram)
Just van Rossumbe56aae2003-07-04 14:20:03 +0000302if %(standalone)s or %(semi_standalone)s:
303 os.environ["PYTHONPATH"] = resdir
304 if %(standalone)s:
305 os.environ["PYTHONHOME"] = resdir
306else:
307 pypath = os.getenv("PYTHONPATH", "")
308 if pypath:
309 pypath = ":" + pypath
310 os.environ["PYTHONPATH"] = resdir + pypath
Just van Rossum7322b1a2003-02-25 20:15:40 +0000311os.environ["PYTHONEXECUTABLE"] = executable
Just van Rossum15624d82003-03-21 09:26:59 +0000312os.environ["DYLD_LIBRARY_PATH"] = libdir
Just van Rossum3166f592003-06-20 18:56:10 +0000313os.environ["DYLD_FRAMEWORK_PATH"] = libdir
Just van Rossum7322b1a2003-02-25 20:15:40 +0000314os.execve(executable, sys.argv, os.environ)
Just van Rossumad33d722002-11-21 10:23:04 +0000315"""
316
Just van Rossum8a3ed3f2003-02-25 20:53:12 +0000317
318#
319# Optional wrapper that converts "dropped files" into sys.argv values.
320#
321ARGV_EMULATOR = """\
Jack Jansena03adde2003-02-18 23:29:46 +0000322import argvemulator, os
323
324argvemulator.ArgvCollector().mainloop()
Neal Norwitz01688022007-08-12 00:43:29 +0000325fp = os.path.join(os.path.split(__file__)[0], "%(realmainprogram)s")
326try:
327 script = fp.read()
328finally:
329 fp.close()
330exec(script)
Jack Jansena03adde2003-02-18 23:29:46 +0000331"""
Just van Rossumcef32882002-11-26 00:34:52 +0000332
Just van Rossum3166f592003-06-20 18:56:10 +0000333#
334# When building a standalone app with Python.framework, we need to copy
335# a subset from Python.framework to the bundle. The following list
336# specifies exactly what items we'll copy.
337#
338PYTHONFRAMEWORKGOODIES = [
339 "Python", # the Python core library
340 "Resources/English.lproj",
341 "Resources/Info.plist",
342 "Resources/version.plist",
343]
344
Just van Rossum79b0ae12003-06-29 22:20:26 +0000345def isFramework():
346 return sys.exec_prefix.find("Python.framework") > 0
347
Just van Rossum8a3ed3f2003-02-25 20:53:12 +0000348
Just van Rossum762d2cc2003-06-29 21:54:12 +0000349LIB = os.path.join(sys.prefix, "lib", "python" + sys.version[:3])
350SITE_PACKAGES = os.path.join(LIB, "site-packages")
351
352
Just van Rossumad33d722002-11-21 10:23:04 +0000353class AppBuilder(BundleBuilder):
354
Jack Jansen0ae32202003-04-09 13:25:43 +0000355 # Override type of the bundle.
356 type = "APPL"
Jack Jansencc81b802003-03-05 14:42:18 +0000357
Jack Jansen0ae32202003-04-09 13:25:43 +0000358 # platform, name of the subfolder of Contents that contains the executable.
359 platform = "MacOS"
Jack Jansencc81b802003-03-05 14:42:18 +0000360
Jack Jansen0ae32202003-04-09 13:25:43 +0000361 # A Python main program. If this argument is given, the main
362 # executable in the bundle will be a small wrapper that invokes
363 # the main program. (XXX Discuss why.)
364 mainprogram = None
Just van Rossumceeb9622002-11-21 23:19:37 +0000365
Jack Jansen0ae32202003-04-09 13:25:43 +0000366 # The main executable. If a Python main program is specified
367 # the executable will be copied to Resources and be invoked
368 # by the wrapper program mentioned above. Otherwise it will
369 # simply be used as the main executable.
370 executable = None
Just van Rossumceeb9622002-11-21 23:19:37 +0000371
Jack Jansen0ae32202003-04-09 13:25:43 +0000372 # The name of the main nib, for Cocoa apps. *Must* be specified
373 # when building a Cocoa app.
374 nibname = None
Just van Rossumad33d722002-11-21 10:23:04 +0000375
Jack Jansen0ae32202003-04-09 13:25:43 +0000376 # The name of the icon file to be copied to Resources and used for
377 # the Finder icon.
378 iconfile = None
Just van Rossum2aa09562003-02-01 08:34:46 +0000379
Jack Jansen0ae32202003-04-09 13:25:43 +0000380 # Symlink the executable instead of copying it.
381 symlink_exec = 0
Just van Rossumad33d722002-11-21 10:23:04 +0000382
Jack Jansen0ae32202003-04-09 13:25:43 +0000383 # If True, build standalone app.
384 standalone = 0
Just van Rossum3166f592003-06-20 18:56:10 +0000385
Just van Rossum762d2cc2003-06-29 21:54:12 +0000386 # If True, build semi-standalone app (only includes third-party modules).
387 semi_standalone = 0
388
Jack Jansen8ba0e802003-05-25 22:00:17 +0000389 # If set, use this for #! lines in stead of sys.executable
390 python = None
Just van Rossum3166f592003-06-20 18:56:10 +0000391
Jack Jansen0ae32202003-04-09 13:25:43 +0000392 # If True, add a real main program that emulates sys.argv before calling
393 # mainprogram
394 argv_emulation = 0
Just van Rossumcef32882002-11-26 00:34:52 +0000395
Jack Jansen0ae32202003-04-09 13:25:43 +0000396 # The following attributes are only used when building a standalone app.
Just van Rossumcef32882002-11-26 00:34:52 +0000397
Jack Jansen0ae32202003-04-09 13:25:43 +0000398 # Exclude these modules.
399 excludeModules = []
Just van Rossumcef32882002-11-26 00:34:52 +0000400
Jack Jansen0ae32202003-04-09 13:25:43 +0000401 # Include these modules.
402 includeModules = []
Just van Rossumcef32882002-11-26 00:34:52 +0000403
Jack Jansen0ae32202003-04-09 13:25:43 +0000404 # Include these packages.
405 includePackages = []
Just van Rossumcef32882002-11-26 00:34:52 +0000406
Just van Rossum00a0b972003-06-20 21:18:22 +0000407 # Strip binaries from debug info.
Jack Jansen0ae32202003-04-09 13:25:43 +0000408 strip = 0
Just van Rossumcef32882002-11-26 00:34:52 +0000409
Jack Jansen0ae32202003-04-09 13:25:43 +0000410 # Found Python modules: [(name, codeobject, ispkg), ...]
411 pymodules = []
Just van Rossumcef32882002-11-26 00:34:52 +0000412
Jack Jansen0ae32202003-04-09 13:25:43 +0000413 # Modules that modulefinder couldn't find:
414 missingModules = []
415 maybeMissingModules = []
Just van Rossumcef32882002-11-26 00:34:52 +0000416
Jack Jansen0ae32202003-04-09 13:25:43 +0000417 def setup(self):
Just van Rossum762d2cc2003-06-29 21:54:12 +0000418 if ((self.standalone or self.semi_standalone)
419 and self.mainprogram is None):
Jack Jansen0ae32202003-04-09 13:25:43 +0000420 raise BundleBuilderError, ("must specify 'mainprogram' when "
421 "building a standalone application.")
422 if self.mainprogram is None and self.executable is None:
423 raise BundleBuilderError, ("must specify either or both of "
424 "'executable' and 'mainprogram'")
Just van Rossumceeb9622002-11-21 23:19:37 +0000425
Jack Jansen0ae32202003-04-09 13:25:43 +0000426 self.execdir = pathjoin("Contents", self.platform)
Jack Jansencc81b802003-03-05 14:42:18 +0000427
Jack Jansen0ae32202003-04-09 13:25:43 +0000428 if self.name is not None:
429 pass
430 elif self.mainprogram is not None:
431 self.name = os.path.splitext(os.path.basename(self.mainprogram))[0]
432 elif executable is not None:
433 self.name = os.path.splitext(os.path.basename(self.executable))[0]
434 if self.name[-4:] != ".app":
435 self.name += ".app"
Just van Rossumceeb9622002-11-21 23:19:37 +0000436
Jack Jansen0ae32202003-04-09 13:25:43 +0000437 if self.executable is None:
Just van Rossum79b0ae12003-06-29 22:20:26 +0000438 if not self.standalone and not isFramework():
Jack Jansen0ae32202003-04-09 13:25:43 +0000439 self.symlink_exec = 1
Jack Jansenbbaa0832003-07-04 11:05:35 +0000440 if self.python:
441 self.executable = self.python
442 else:
443 self.executable = sys.executable
Just van Rossum74bdca82002-11-28 11:30:56 +0000444
Jack Jansen0ae32202003-04-09 13:25:43 +0000445 if self.nibname:
446 self.plist.NSMainNibFile = self.nibname
447 if not hasattr(self.plist, "NSPrincipalClass"):
448 self.plist.NSPrincipalClass = "NSApplication"
Just van Rossumceeb9622002-11-21 23:19:37 +0000449
Just van Rossum79b0ae12003-06-29 22:20:26 +0000450 if self.standalone and isFramework():
Just van Rossum3166f592003-06-20 18:56:10 +0000451 self.addPythonFramework()
452
Jack Jansen0ae32202003-04-09 13:25:43 +0000453 BundleBuilder.setup(self)
Just van Rossumceeb9622002-11-21 23:19:37 +0000454
Jack Jansen0ae32202003-04-09 13:25:43 +0000455 self.plist.CFBundleExecutable = self.name
Just van Rossumf7aba232002-11-22 00:31:50 +0000456
Just van Rossum762d2cc2003-06-29 21:54:12 +0000457 if self.standalone or self.semi_standalone:
Jack Jansen0ae32202003-04-09 13:25:43 +0000458 self.findDependencies()
Just van Rossumcef32882002-11-26 00:34:52 +0000459
Jack Jansen0ae32202003-04-09 13:25:43 +0000460 def preProcess(self):
461 resdir = "Contents/Resources"
462 if self.executable is not None:
463 if self.mainprogram is None:
464 execname = self.name
465 else:
466 execname = os.path.basename(self.executable)
467 execpath = pathjoin(self.execdir, execname)
468 if not self.symlink_exec:
Jack Jansenc77f6df2004-12-27 15:51:03 +0000469 self.files.append((self.destroot + self.executable, execpath))
Jack Jansen0ae32202003-04-09 13:25:43 +0000470 self.execpath = execpath
Just van Rossumad33d722002-11-21 10:23:04 +0000471
Jack Jansen0ae32202003-04-09 13:25:43 +0000472 if self.mainprogram is not None:
473 mainprogram = os.path.basename(self.mainprogram)
474 self.files.append((self.mainprogram, pathjoin(resdir, mainprogram)))
475 if self.argv_emulation:
476 # Change the main program, and create the helper main program (which
477 # does argv collection and then calls the real main).
478 # Also update the included modules (if we're creating a standalone
479 # program) and the plist
480 realmainprogram = mainprogram
481 mainprogram = '__argvemulator_' + mainprogram
482 resdirpath = pathjoin(self.bundlepath, resdir)
483 mainprogrampath = pathjoin(resdirpath, mainprogram)
484 makedirs(resdirpath)
485 open(mainprogrampath, "w").write(ARGV_EMULATOR % locals())
Just van Rossum762d2cc2003-06-29 21:54:12 +0000486 if self.standalone or self.semi_standalone:
Jack Jansen0ae32202003-04-09 13:25:43 +0000487 self.includeModules.append("argvemulator")
488 self.includeModules.append("os")
Neal Norwitzf1a69c12006-08-20 16:25:10 +0000489 if "CFBundleDocumentTypes" not in self.plist:
Jack Jansen0ae32202003-04-09 13:25:43 +0000490 self.plist["CFBundleDocumentTypes"] = [
491 { "CFBundleTypeOSTypes" : [
492 "****",
493 "fold",
494 "disk"],
495 "CFBundleTypeRole": "Viewer"}]
496 # Write bootstrap script
497 executable = os.path.basename(self.executable)
498 execdir = pathjoin(self.bundlepath, self.execdir)
499 bootstrappath = pathjoin(execdir, self.name)
500 makedirs(execdir)
Just van Rossum762d2cc2003-06-29 21:54:12 +0000501 if self.standalone or self.semi_standalone:
Jack Jansen0ae32202003-04-09 13:25:43 +0000502 # XXX we're screwed when the end user has deleted
503 # /usr/bin/python
504 hashbang = "/usr/bin/python"
Jack Jansen8ba0e802003-05-25 22:00:17 +0000505 elif self.python:
506 hashbang = self.python
Jack Jansen0ae32202003-04-09 13:25:43 +0000507 else:
508 hashbang = os.path.realpath(sys.executable)
509 standalone = self.standalone
Just van Rossumbe56aae2003-07-04 14:20:03 +0000510 semi_standalone = self.semi_standalone
Jack Jansen0ae32202003-04-09 13:25:43 +0000511 open(bootstrappath, "w").write(BOOTSTRAP_SCRIPT % locals())
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000512 os.chmod(bootstrappath, 0o775)
Just van Rossumad33d722002-11-21 10:23:04 +0000513
Jack Jansen0ae32202003-04-09 13:25:43 +0000514 if self.iconfile is not None:
515 iconbase = os.path.basename(self.iconfile)
516 self.plist.CFBundleIconFile = iconbase
517 self.files.append((self.iconfile, pathjoin(resdir, iconbase)))
Just van Rossum2aa09562003-02-01 08:34:46 +0000518
Jack Jansen0ae32202003-04-09 13:25:43 +0000519 def postProcess(self):
Just van Rossum762d2cc2003-06-29 21:54:12 +0000520 if self.standalone or self.semi_standalone:
Jack Jansen0ae32202003-04-09 13:25:43 +0000521 self.addPythonModules()
522 if self.strip and not self.symlink:
523 self.stripBinaries()
Just van Rossumcef32882002-11-26 00:34:52 +0000524
Jack Jansen0ae32202003-04-09 13:25:43 +0000525 if self.symlink_exec and self.executable:
526 self.message("Symlinking executable %s to %s" % (self.executable,
527 self.execpath), 2)
528 dst = pathjoin(self.bundlepath, self.execpath)
529 makedirs(os.path.dirname(dst))
530 os.symlink(os.path.abspath(self.executable), dst)
Just van Rossum16aebf72002-11-22 11:43:10 +0000531
Jack Jansen0ae32202003-04-09 13:25:43 +0000532 if self.missingModules or self.maybeMissingModules:
533 self.reportMissing()
Just van Rossumcef32882002-11-26 00:34:52 +0000534
Just van Rossum3166f592003-06-20 18:56:10 +0000535 def addPythonFramework(self):
536 # If we're building a standalone app with Python.framework,
Just van Rossumdc31dc02003-06-20 21:43:36 +0000537 # include a minimal subset of Python.framework, *unless*
538 # Python.framework was specified manually in self.libs.
539 for lib in self.libs:
540 if os.path.basename(lib) == "Python.framework":
541 # a Python.framework was specified as a library
542 return
543
Just van Rossum3166f592003-06-20 18:56:10 +0000544 frameworkpath = sys.exec_prefix[:sys.exec_prefix.find(
545 "Python.framework") + len("Python.framework")]
Just van Rossumdc31dc02003-06-20 21:43:36 +0000546
Just van Rossum3166f592003-06-20 18:56:10 +0000547 version = sys.version[:3]
548 frameworkpath = pathjoin(frameworkpath, "Versions", version)
549 destbase = pathjoin("Contents", "Frameworks", "Python.framework",
550 "Versions", version)
551 for item in PYTHONFRAMEWORKGOODIES:
552 src = pathjoin(frameworkpath, item)
553 dst = pathjoin(destbase, item)
554 self.files.append((src, dst))
555
Just van Rossum762d2cc2003-06-29 21:54:12 +0000556 def _getSiteCode(self):
557 return compile(SITE_PY % {"semi_standalone": self.semi_standalone},
558 "<-bundlebuilder.py->", "exec")
559
Jack Jansen0ae32202003-04-09 13:25:43 +0000560 def addPythonModules(self):
561 self.message("Adding Python modules", 1)
Just van Rossumcef32882002-11-26 00:34:52 +0000562
Jack Jansen0ae32202003-04-09 13:25:43 +0000563 if USE_ZIPIMPORT:
564 # Create a zip file containing all modules as pyc.
565 import zipfile
566 relpath = pathjoin("Contents", "Resources", ZIP_ARCHIVE)
567 abspath = pathjoin(self.bundlepath, relpath)
568 zf = zipfile.ZipFile(abspath, "w", zipfile.ZIP_DEFLATED)
569 for name, code, ispkg in self.pymodules:
570 self.message("Adding Python module %s" % name, 2)
571 path, pyc = getPycData(name, code, ispkg)
572 zf.writestr(path, pyc)
573 zf.close()
574 # add site.pyc
575 sitepath = pathjoin(self.bundlepath, "Contents", "Resources",
576 "site" + PYC_EXT)
Just van Rossum762d2cc2003-06-29 21:54:12 +0000577 writePyc(self._getSiteCode(), sitepath)
Jack Jansen0ae32202003-04-09 13:25:43 +0000578 else:
579 # Create individual .pyc files.
580 for name, code, ispkg in self.pymodules:
581 if ispkg:
582 name += ".__init__"
583 path = name.split(".")
584 path = pathjoin("Contents", "Resources", *path) + PYC_EXT
Just van Rossumcef32882002-11-26 00:34:52 +0000585
Jack Jansen0ae32202003-04-09 13:25:43 +0000586 if ispkg:
587 self.message("Adding Python package %s" % path, 2)
588 else:
589 self.message("Adding Python module %s" % path, 2)
Just van Rossumcef32882002-11-26 00:34:52 +0000590
Jack Jansen0ae32202003-04-09 13:25:43 +0000591 abspath = pathjoin(self.bundlepath, path)
592 makedirs(os.path.dirname(abspath))
593 writePyc(code, abspath)
Just van Rossumcef32882002-11-26 00:34:52 +0000594
Jack Jansen0ae32202003-04-09 13:25:43 +0000595 def stripBinaries(self):
596 if not os.path.exists(STRIP_EXEC):
597 self.message("Error: can't strip binaries: no strip program at "
598 "%s" % STRIP_EXEC, 0)
599 else:
Just van Rossum00a0b972003-06-20 21:18:22 +0000600 import stat
Jack Jansen0ae32202003-04-09 13:25:43 +0000601 self.message("Stripping binaries", 1)
Just van Rossum00a0b972003-06-20 21:18:22 +0000602 def walk(top):
603 for name in os.listdir(top):
604 path = pathjoin(top, name)
605 if os.path.islink(path):
606 continue
607 if os.path.isdir(path):
608 walk(path)
609 else:
610 mod = os.stat(path)[stat.ST_MODE]
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000611 if not (mod & 0o100):
Just van Rossum00a0b972003-06-20 21:18:22 +0000612 continue
613 relpath = path[len(self.bundlepath):]
614 self.message("Stripping %s" % relpath, 2)
615 inf, outf = os.popen4("%s -S \"%s\"" %
616 (STRIP_EXEC, path))
617 output = outf.read().strip()
618 if output:
619 # usually not a real problem, like when we're
620 # trying to strip a script
621 self.message("Problem stripping %s:" % relpath, 3)
622 self.message(output, 3)
623 walk(self.bundlepath)
Just van Rossumcef32882002-11-26 00:34:52 +0000624
Jack Jansen0ae32202003-04-09 13:25:43 +0000625 def findDependencies(self):
626 self.message("Finding module dependencies", 1)
627 import modulefinder
628 mf = modulefinder.ModuleFinder(excludes=self.excludeModules)
629 if USE_ZIPIMPORT:
630 # zipimport imports zlib, must add it manually
631 mf.import_hook("zlib")
632 # manually add our own site.py
633 site = mf.add_module("site")
Just van Rossum762d2cc2003-06-29 21:54:12 +0000634 site.__code__ = self._getSiteCode()
635 mf.scan_code(site.__code__, site)
Just van Rossumcef32882002-11-26 00:34:52 +0000636
Jack Jansen0ae32202003-04-09 13:25:43 +0000637 # warnings.py gets imported implicitly from C
638 mf.import_hook("warnings")
Just van Rossum7322b1a2003-02-25 20:15:40 +0000639
Jack Jansen0ae32202003-04-09 13:25:43 +0000640 includeModules = self.includeModules[:]
641 for name in self.includePackages:
642 includeModules.extend(findPackageContents(name).keys())
643 for name in includeModules:
644 try:
645 mf.import_hook(name)
646 except ImportError:
647 self.missingModules.append(name)
Just van Rossumcef32882002-11-26 00:34:52 +0000648
Jack Jansen0ae32202003-04-09 13:25:43 +0000649 mf.run_script(self.mainprogram)
650 modules = mf.modules.items()
651 modules.sort()
652 for name, mod in modules:
Just van Rossum762d2cc2003-06-29 21:54:12 +0000653 path = mod.__file__
654 if path and self.semi_standalone:
655 # skip the standard library
656 if path.startswith(LIB) and not path.startswith(SITE_PACKAGES):
657 continue
658 if path and mod.__code__ is None:
Jack Jansen0ae32202003-04-09 13:25:43 +0000659 # C extension
Jack Jansen0ae32202003-04-09 13:25:43 +0000660 filename = os.path.basename(path)
Just van Rossum79b0ae12003-06-29 22:20:26 +0000661 pathitems = name.split(".")[:-1] + [filename]
662 dstpath = pathjoin(*pathitems)
Jack Jansen0ae32202003-04-09 13:25:43 +0000663 if USE_ZIPIMPORT:
Just van Rossum79b0ae12003-06-29 22:20:26 +0000664 if name != "zlib":
665 # neatly pack all extension modules in a subdirectory,
666 # except zlib, since it's neccesary for bootstrapping.
667 dstpath = pathjoin("ExtensionModules", dstpath)
Jack Jansen0ae32202003-04-09 13:25:43 +0000668 # Python modules are stored in a Zip archive, but put
Just van Rossum762d2cc2003-06-29 21:54:12 +0000669 # extensions in Contents/Resources/. Add a tiny "loader"
Jack Jansen0ae32202003-04-09 13:25:43 +0000670 # program in the Zip archive. Due to Thomas Heller.
Just van Rossum762d2cc2003-06-29 21:54:12 +0000671 source = EXT_LOADER % {"name": name, "filename": dstpath}
Jack Jansen0ae32202003-04-09 13:25:43 +0000672 code = compile(source, "<dynloader for %s>" % name, "exec")
673 mod.__code__ = code
Just van Rossum762d2cc2003-06-29 21:54:12 +0000674 self.files.append((path, pathjoin("Contents", "Resources", dstpath)))
Jack Jansen0ae32202003-04-09 13:25:43 +0000675 if mod.__code__ is not None:
676 ispkg = mod.__path__ is not None
677 if not USE_ZIPIMPORT or name != "site":
678 # Our site.py is doing the bootstrapping, so we must
679 # include a real .pyc file if USE_ZIPIMPORT is True.
680 self.pymodules.append((name, mod.__code__, ispkg))
Just van Rossumcef32882002-11-26 00:34:52 +0000681
Jack Jansen0ae32202003-04-09 13:25:43 +0000682 if hasattr(mf, "any_missing_maybe"):
683 missing, maybe = mf.any_missing_maybe()
684 else:
685 missing = mf.any_missing()
686 maybe = []
687 self.missingModules.extend(missing)
688 self.maybeMissingModules.extend(maybe)
Just van Rossumcef32882002-11-26 00:34:52 +0000689
Jack Jansen0ae32202003-04-09 13:25:43 +0000690 def reportMissing(self):
691 missing = [name for name in self.missingModules
692 if name not in MAYMISS_MODULES]
693 if self.maybeMissingModules:
694 maybe = self.maybeMissingModules
695 else:
696 maybe = [name for name in missing if "." in name]
697 missing = [name for name in missing if "." not in name]
698 missing.sort()
699 maybe.sort()
700 if maybe:
701 self.message("Warning: couldn't find the following submodules:", 1)
702 self.message(" (Note that these could be false alarms -- "
703 "it's not always", 1)
704 self.message(" possible to distinguish between \"from package "
705 "import submodule\" ", 1)
706 self.message(" and \"from package import name\")", 1)
707 for name in maybe:
708 self.message(" ? " + name, 1)
709 if missing:
710 self.message("Warning: couldn't find the following modules:", 1)
711 for name in missing:
712 self.message(" ? " + name, 1)
Just van Rossum74bdca82002-11-28 11:30:56 +0000713
Jack Jansen0ae32202003-04-09 13:25:43 +0000714 def report(self):
715 # XXX something decent
716 import pprint
717 pprint.pprint(self.__dict__)
Just van Rossum762d2cc2003-06-29 21:54:12 +0000718 if self.standalone or self.semi_standalone:
Jack Jansen0ae32202003-04-09 13:25:43 +0000719 self.reportMissing()
Just van Rossumcef32882002-11-26 00:34:52 +0000720
721#
722# Utilities.
723#
724
725SUFFIXES = [_suf for _suf, _mode, _tp in imp.get_suffixes()]
726identifierRE = re.compile(r"[_a-zA-z][_a-zA-Z0-9]*$")
727
728def findPackageContents(name, searchpath=None):
Jack Jansen0ae32202003-04-09 13:25:43 +0000729 head = name.split(".")[-1]
730 if identifierRE.match(head) is None:
731 return {}
732 try:
733 fp, path, (ext, mode, tp) = imp.find_module(head, searchpath)
734 except ImportError:
735 return {}
736 modules = {name: None}
737 if tp == imp.PKG_DIRECTORY and path:
738 files = os.listdir(path)
739 for sub in files:
740 sub, ext = os.path.splitext(sub)
741 fullname = name + "." + sub
742 if sub != "__init__" and fullname not in modules:
743 modules.update(findPackageContents(fullname, [path]))
744 return modules
Just van Rossumcef32882002-11-26 00:34:52 +0000745
746def writePyc(code, path):
Jack Jansen0ae32202003-04-09 13:25:43 +0000747 f = open(path, "wb")
748 f.write(MAGIC)
749 f.write("\0" * 4) # don't bother about a time stamp
750 marshal.dump(code, f)
751 f.close()
Just van Rossumad33d722002-11-21 10:23:04 +0000752
Just van Rossumad33d722002-11-21 10:23:04 +0000753def copy(src, dst, mkdirs=0):
Jack Jansen0ae32202003-04-09 13:25:43 +0000754 """Copy a file or a directory."""
755 if mkdirs:
756 makedirs(os.path.dirname(dst))
757 if os.path.isdir(src):
Just van Rossumdc31dc02003-06-20 21:43:36 +0000758 shutil.copytree(src, dst, symlinks=1)
Jack Jansen0ae32202003-04-09 13:25:43 +0000759 else:
760 shutil.copy2(src, dst)
Just van Rossumad33d722002-11-21 10:23:04 +0000761
762def copytodir(src, dstdir):
Jack Jansen0ae32202003-04-09 13:25:43 +0000763 """Copy a file or a directory to an existing directory."""
764 dst = pathjoin(dstdir, os.path.basename(src))
765 copy(src, dst)
Just van Rossumad33d722002-11-21 10:23:04 +0000766
767def makedirs(dir):
Jack Jansen0ae32202003-04-09 13:25:43 +0000768 """Make all directories leading up to 'dir' including the leaf
769 directory. Don't moan if any path element already exists."""
770 try:
771 os.makedirs(dir)
Guido van Rossumb940e112007-01-10 16:19:56 +0000772 except OSError as why:
Jack Jansen0ae32202003-04-09 13:25:43 +0000773 if why.errno != errno.EEXIST:
774 raise
Just van Rossumad33d722002-11-21 10:23:04 +0000775
776def symlink(src, dst, mkdirs=0):
Jack Jansen0ae32202003-04-09 13:25:43 +0000777 """Copy a file or a directory."""
778 if not os.path.exists(src):
779 raise IOError, "No such file or directory: '%s'" % src
780 if mkdirs:
781 makedirs(os.path.dirname(dst))
782 os.symlink(os.path.abspath(src), dst)
Just van Rossumad33d722002-11-21 10:23:04 +0000783
784def pathjoin(*args):
Jack Jansen0ae32202003-04-09 13:25:43 +0000785 """Safe wrapper for os.path.join: asserts that all but the first
786 argument are relative paths."""
787 for seg in args[1:]:
788 assert seg[0] != "/"
789 return os.path.join(*args)
Just van Rossumad33d722002-11-21 10:23:04 +0000790
791
Just van Rossumceeb9622002-11-21 23:19:37 +0000792cmdline_doc = """\
793Usage:
Just van Rossumf7aba232002-11-22 00:31:50 +0000794 python bundlebuilder.py [options] command
Just van Rossumceeb9622002-11-21 23:19:37 +0000795 python mybuildscript.py [options] command
796
797Commands:
798 build build the application
799 report print a report
800
801Options:
802 -b, --builddir=DIR the build directory; defaults to "build"
803 -n, --name=NAME application name
804 -r, --resource=FILE extra file or folder to be copied to Resources
Just van Rossum7215e082003-02-25 21:00:55 +0000805 -f, --file=SRC:DST extra file or folder to be copied into the bundle;
806 DST must be a path relative to the bundle root
Just van Rossumceeb9622002-11-21 23:19:37 +0000807 -e, --executable=FILE the executable to be used
808 -m, --mainprogram=FILE the Python main program
Jack Jansena03adde2003-02-18 23:29:46 +0000809 -a, --argv add a wrapper main program to create sys.argv
Just van Rossumceeb9622002-11-21 23:19:37 +0000810 -p, --plist=FILE .plist file (default: generate one)
811 --nib=NAME main nib name
812 -c, --creator=CCCC 4-char creator code (default: '????')
Just van Rossum9af69682003-02-02 18:56:37 +0000813 --iconfile=FILE filename of the icon (an .icns file) to be used
Just van Rossum2aa09562003-02-01 08:34:46 +0000814 as the Finder icon
Just van Rossumbe56aae2003-07-04 14:20:03 +0000815 --bundle-id=ID the CFBundleIdentifier, in reverse-dns format
816 (eg. org.python.BuildApplet; this is used for
817 the preferences file name)
Just van Rossumceeb9622002-11-21 23:19:37 +0000818 -l, --link symlink files/folder instead of copying them
Just van Rossum16aebf72002-11-22 11:43:10 +0000819 --link-exec symlink the executable instead of copying it
Just van Rossumcef32882002-11-26 00:34:52 +0000820 --standalone build a standalone application, which is fully
821 independent of a Python installation
Just van Rossum762d2cc2003-06-29 21:54:12 +0000822 --semi-standalone build a standalone application, which depends on
823 an installed Python, yet includes all third-party
824 modules.
Jack Jansen8ba0e802003-05-25 22:00:17 +0000825 --python=FILE Python to use in #! line in stead of current Python
Just van Rossum15624d82003-03-21 09:26:59 +0000826 --lib=FILE shared library or framework to be copied into
827 the bundle
Just van Rossum762d2cc2003-06-29 21:54:12 +0000828 -x, --exclude=MODULE exclude module (with --(semi-)standalone)
829 -i, --include=MODULE include module (with --(semi-)standalone)
830 --package=PACKAGE include a whole package (with --(semi-)standalone)
Just van Rossumcef32882002-11-26 00:34:52 +0000831 --strip strip binaries (remove debug info)
Just van Rossumceeb9622002-11-21 23:19:37 +0000832 -v, --verbose increase verbosity level
833 -q, --quiet decrease verbosity level
834 -h, --help print this message
835"""
836
837def usage(msg=None):
Jack Jansen0ae32202003-04-09 13:25:43 +0000838 if msg:
Guido van Rossumbe19ed72007-02-09 05:37:30 +0000839 print(msg)
840 print(cmdline_doc)
Jack Jansen0ae32202003-04-09 13:25:43 +0000841 sys.exit(1)
Just van Rossumceeb9622002-11-21 23:19:37 +0000842
843def main(builder=None):
Jack Jansen0ae32202003-04-09 13:25:43 +0000844 if builder is None:
845 builder = AppBuilder(verbosity=1)
Just van Rossumceeb9622002-11-21 23:19:37 +0000846
Jack Jansen0ae32202003-04-09 13:25:43 +0000847 shortopts = "b:n:r:f:e:m:c:p:lx:i:hvqa"
848 longopts = ("builddir=", "name=", "resource=", "file=", "executable=",
849 "mainprogram=", "creator=", "nib=", "plist=", "link",
850 "link-exec", "help", "verbose", "quiet", "argv", "standalone",
851 "exclude=", "include=", "package=", "strip", "iconfile=",
Jack Jansenc77f6df2004-12-27 15:51:03 +0000852 "lib=", "python=", "semi-standalone", "bundle-id=", "destroot=")
Just van Rossumceeb9622002-11-21 23:19:37 +0000853
Jack Jansen0ae32202003-04-09 13:25:43 +0000854 try:
855 options, args = getopt.getopt(sys.argv[1:], shortopts, longopts)
856 except getopt.error:
857 usage()
Just van Rossumceeb9622002-11-21 23:19:37 +0000858
Jack Jansen0ae32202003-04-09 13:25:43 +0000859 for opt, arg in options:
860 if opt in ('-b', '--builddir'):
861 builder.builddir = arg
862 elif opt in ('-n', '--name'):
863 builder.name = arg
864 elif opt in ('-r', '--resource'):
Just van Rossumdc31dc02003-06-20 21:43:36 +0000865 builder.resources.append(os.path.normpath(arg))
Jack Jansen0ae32202003-04-09 13:25:43 +0000866 elif opt in ('-f', '--file'):
867 srcdst = arg.split(':')
868 if len(srcdst) != 2:
869 usage("-f or --file argument must be two paths, "
870 "separated by a colon")
871 builder.files.append(srcdst)
872 elif opt in ('-e', '--executable'):
873 builder.executable = arg
874 elif opt in ('-m', '--mainprogram'):
875 builder.mainprogram = arg
876 elif opt in ('-a', '--argv'):
877 builder.argv_emulation = 1
878 elif opt in ('-c', '--creator'):
879 builder.creator = arg
Just van Rossumbe56aae2003-07-04 14:20:03 +0000880 elif opt == '--bundle-id':
881 builder.bundle_id = arg
Jack Jansen0ae32202003-04-09 13:25:43 +0000882 elif opt == '--iconfile':
883 builder.iconfile = arg
884 elif opt == "--lib":
Just van Rossumdc31dc02003-06-20 21:43:36 +0000885 builder.libs.append(os.path.normpath(arg))
Jack Jansen0ae32202003-04-09 13:25:43 +0000886 elif opt == "--nib":
887 builder.nibname = arg
888 elif opt in ('-p', '--plist'):
889 builder.plist = Plist.fromFile(arg)
890 elif opt in ('-l', '--link'):
891 builder.symlink = 1
892 elif opt == '--link-exec':
893 builder.symlink_exec = 1
894 elif opt in ('-h', '--help'):
895 usage()
896 elif opt in ('-v', '--verbose'):
897 builder.verbosity += 1
898 elif opt in ('-q', '--quiet'):
899 builder.verbosity -= 1
900 elif opt == '--standalone':
901 builder.standalone = 1
Just van Rossum762d2cc2003-06-29 21:54:12 +0000902 elif opt == '--semi-standalone':
903 builder.semi_standalone = 1
Jack Jansen8ba0e802003-05-25 22:00:17 +0000904 elif opt == '--python':
905 builder.python = arg
Jack Jansen0ae32202003-04-09 13:25:43 +0000906 elif opt in ('-x', '--exclude'):
907 builder.excludeModules.append(arg)
908 elif opt in ('-i', '--include'):
909 builder.includeModules.append(arg)
910 elif opt == '--package':
911 builder.includePackages.append(arg)
912 elif opt == '--strip':
913 builder.strip = 1
Jack Jansenc77f6df2004-12-27 15:51:03 +0000914 elif opt == '--destroot':
915 builder.destroot = arg
Just van Rossumceeb9622002-11-21 23:19:37 +0000916
Jack Jansen0ae32202003-04-09 13:25:43 +0000917 if len(args) != 1:
918 usage("Must specify one command ('build', 'report' or 'help')")
919 command = args[0]
Just van Rossumceeb9622002-11-21 23:19:37 +0000920
Jack Jansen0ae32202003-04-09 13:25:43 +0000921 if command == "build":
922 builder.setup()
923 builder.build()
924 elif command == "report":
925 builder.setup()
926 builder.report()
927 elif command == "help":
928 usage()
929 else:
930 usage("Unknown command '%s'" % command)
Just van Rossumceeb9622002-11-21 23:19:37 +0000931
932
Just van Rossumad33d722002-11-21 10:23:04 +0000933def buildapp(**kwargs):
Jack Jansen0ae32202003-04-09 13:25:43 +0000934 builder = AppBuilder(**kwargs)
935 main(builder)
Just van Rossumad33d722002-11-21 10:23:04 +0000936
937
938if __name__ == "__main__":
Jack Jansen0ae32202003-04-09 13:25:43 +0000939 main()