]> git.saurik.com Git - wxWidgets.git/blob - wxPython/distrib/mac/bundlebuilder.py
Fixed the code to actually work.
[wxWidgets.git] / wxPython / distrib / mac / bundlebuilder.py
1 #! /usr/bin/env python
2
3 """\
4 bundlebuilder.py -- Tools to assemble MacOS X (application) bundles.
5
6 This module contains two classes to build so called "bundles" for
7 MacOS X. BundleBuilder is a general tool, AppBuilder is a subclass
8 specialized in building application bundles.
9
10 [Bundle|App]Builder objects are instantiated with a bunch of keyword
11 arguments, and have a build() method that will do all the work. See
12 the class doc strings for a description of the constructor arguments.
13
14 The 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
19 Where "buildapp.py" is a user-supplied setup.py-like script following
20 this model:
21
22 from bundlebuilder import buildapp
23 buildapp(<lots-of-keyword-args>)
24
25 """
26
27
28 __all__ = ["BundleBuilder", "BundleBuilderError", "AppBuilder", "buildapp"]
29
30
31 import sys
32 import os, errno, shutil
33 import imp, marshal
34 import re
35 from copy import deepcopy
36 import getopt
37 from plistlib import Plist
38 from types import FunctionType as function
39
40 class BundleBuilderError(Exception): pass
41
42
43 class Defaults:
44
45 """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 """
49
50 def __init__(self, **kwargs):
51 defaults = self._getDefaults()
52 defaults.update(kwargs)
53 self.__dict__.update(defaults)
54
55 def _getDefaults(cls):
56 defaults = {}
57 for name, value in cls.__dict__.items():
58 if name[0] != "_" and not isinstance(value,
59 (function, classmethod)):
60 defaults[name] = deepcopy(value)
61 for base in cls.__bases__:
62 if hasattr(base, "_getDefaults"):
63 defaults.update(base._getDefaults())
64 return defaults
65 _getDefaults = classmethod(_getDefaults)
66
67
68 class BundleBuilder(Defaults):
69
70 """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 """
74
75 # (Note that Defaults.__init__ (deep)copies these values to
76 # instance variables. Mutable defaults are therefore safe.)
77
78 # Name of the bundle, with or without extension.
79 name = None
80
81 # The property list ("plist")
82 plist = Plist(CFBundleDevelopmentRegion = "English",
83 CFBundleInfoDictionaryVersion = "6.0")
84
85 # The type of the bundle.
86 type = "BNDL"
87 # The creator code of the bundle.
88 creator = None
89
90 # List of files that have to be copied to <bundle>/Contents/Resources.
91 resources = []
92
93 # List of (src, dest) tuples; dest should be a path relative to the bundle
94 # (eg. "Contents/Resources/MyStuff/SomeFile.ext).
95 files = []
96
97 # List of shared libraries (dylibs, Frameworks) to bundle with the app
98 # will be placed in Contents/Frameworks
99 libs = []
100
101 # Directory where the bundle will be assembled.
102 builddir = "build"
103
104 # Make symlinks instead copying files. This is handy during debugging, but
105 # makes the bundle non-distributable.
106 symlink = 0
107
108 # Verbosity level.
109 verbosity = 1
110
111 def setup(self):
112 # XXX rethink self.name munging, this is brittle.
113 self.name, ext = os.path.splitext(self.name)
114 if not ext:
115 ext = ".bundle"
116 bundleextension = ext
117 # misc (derived) attributes
118 self.bundlepath = pathjoin(self.builddir, self.name + bundleextension)
119
120 plist = self.plist
121 plist.CFBundleName = self.name
122 plist.CFBundlePackageType = self.type
123 if self.creator is None:
124 if hasattr(plist, "CFBundleSignature"):
125 self.creator = plist.CFBundleSignature
126 else:
127 self.creator = "????"
128 plist.CFBundleSignature = self.creator
129 if not hasattr(plist, "CFBundleIdentifier"):
130 plist.CFBundleIdentifier = self.name
131
132 def build(self):
133 """Build the bundle."""
134 builddir = self.builddir
135 if builddir and not os.path.exists(builddir):
136 os.mkdir(builddir)
137 self.message("Building %s" % repr(self.bundlepath), 1)
138 if os.path.exists(self.bundlepath):
139 shutil.rmtree(self.bundlepath)
140 os.mkdir(self.bundlepath)
141 self.preProcess()
142 self._copyFiles()
143 self._addMetaFiles()
144 self.postProcess()
145 self.message("Done.", 1)
146
147 def preProcess(self):
148 """Hook for subclasses."""
149 pass
150 def postProcess(self):
151 """Hook for subclasses."""
152 pass
153
154 def _addMetaFiles(self):
155 contents = pathjoin(self.bundlepath, "Contents")
156 makedirs(contents)
157 #
158 # Write Contents/PkgInfo
159 assert len(self.type) == len(self.creator) == 4, \
160 "type and creator must be 4-byte strings."
161 pkginfo = pathjoin(contents, "PkgInfo")
162 f = open(pkginfo, "wb")
163 f.write(self.type + self.creator)
164 f.close()
165 #
166 # Write Contents/Info.plist
167 infoplist = pathjoin(contents, "Info.plist")
168 self.plist.write(infoplist)
169
170 def _copyFiles(self):
171 files = self.files[:]
172 for path in self.resources:
173 files.append((path, pathjoin("Contents", "Resources",
174 os.path.basename(path))))
175 for path in self.libs:
176 files.append((path, pathjoin("Contents", "Frameworks",
177 os.path.basename(path))))
178 if self.symlink:
179 self.message("Making symbolic links", 1)
180 msg = "Making symlink from"
181 else:
182 self.message("Copying files", 1)
183 msg = "Copying"
184 files.sort()
185 for src, dst in files:
186 if os.path.isdir(src):
187 self.message("%s %s/ to %s/" % (msg, src, dst), 2)
188 else:
189 self.message("%s %s to %s" % (msg, src, dst), 2)
190 dst = pathjoin(self.bundlepath, dst)
191 if self.symlink:
192 symlink(src, dst, mkdirs=1)
193 else:
194 copy(src, dst, mkdirs=1)
195
196 def message(self, msg, level=0):
197 if level <= self.verbosity:
198 indent = ""
199 if level > 1:
200 indent = (level - 1) * " "
201 sys.stderr.write(indent + msg + "\n")
202
203 def report(self):
204 # XXX something decent
205 pass
206
207
208 if __debug__:
209 PYC_EXT = ".pyc"
210 else:
211 PYC_EXT = ".pyo"
212
213 MAGIC = imp.get_magic()
214 USE_ZIPIMPORT = "zipimport" in sys.builtin_module_names
215
216 # For standalone apps, we have our own minimal site.py. We don't need
217 # all the cruft of the real site.py.
218 SITE_PY = """\
219 import sys
220 del sys.path[1:] # sys.path[0] is Contents/Resources/
221 """
222
223 if USE_ZIPIMPORT:
224 ZIP_ARCHIVE = "Modules.zip"
225 SITE_PY += "sys.path.append(sys.path[0] + '/%s')\n" % ZIP_ARCHIVE
226 def getPycData(fullname, code, ispkg):
227 if ispkg:
228 fullname += ".__init__"
229 path = fullname.replace(".", os.sep) + PYC_EXT
230 return path, MAGIC + '\0\0\0\0' + marshal.dumps(code)
231
232 SITE_CO = compile(SITE_PY, "<-bundlebuilder.py->", "exec")
233
234 #
235 # Extension modules can't be in the modules zip archive, so a placeholder
236 # is added instead, that loads the extension from a specified location.
237 #
238 EXT_LOADER = """\
239 def __load():
240 import imp, sys, os
241 for p in sys.path:
242 path = os.path.join(p, "%(filename)s")
243 if os.path.exists(path):
244 break
245 else:
246 assert 0, "file not found: %(filename)s"
247 mod = imp.load_dynamic("%(name)s", path)
248
249 __load()
250 del __load
251 """
252
253 MAYMISS_MODULES = ['mac', 'os2', 'nt', 'ntpath', 'dos', 'dospath',
254 'win32api', 'ce', '_winreg', 'nturl2path', 'sitecustomize',
255 'org.python.core', 'riscos', 'riscosenviron', 'riscospath'
256 ]
257
258 STRIP_EXEC = "/usr/bin/strip"
259
260 #
261 # We're using a stock interpreter to run the app, yet we need
262 # a way to pass the Python main program to the interpreter. The
263 # bootstrapping script fires up the interpreter with the right
264 # arguments. os.execve() is used as OSX doesn't like us to
265 # start a real new process. Also, the executable name must match
266 # the CFBundleExecutable value in the Info.plist, so we lie
267 # deliberately with argv[0]. The actual Python executable is
268 # passed in an environment variable so we can "repair"
269 # sys.executable later.
270 #
271 BOOTSTRAP_SCRIPT = """\
272 #!%(hashbang)s
273
274 import sys, os
275 execdir = os.path.dirname(sys.argv[0])
276 executable = os.path.join(execdir, "%(executable)s")
277 resdir = os.path.join(os.path.dirname(execdir), "Resources")
278 libdir = os.path.join(os.path.dirname(execdir), "Frameworks")
279 mainprogram = os.path.join(resdir, "%(mainprogram)s")
280
281 sys.argv.insert(1, mainprogram)
282 os.environ["PYTHONPATH"] = resdir
283 %(pythonhome)s
284 os.environ["PYTHONEXECUTABLE"] = executable
285 os.environ["DYLD_LIBRARY_PATH"] = libdir
286 os.execve(executable, sys.argv, os.environ)
287 """
288
289
290 #
291 # Optional wrapper that converts "dropped files" into sys.argv values.
292 #
293 ARGV_EMULATOR = """\
294 import argvemulator, os
295
296 argvemulator.ArgvCollector().mainloop()
297 execfile(os.path.join(os.path.split(__file__)[0], "%(realmainprogram)s"))
298 """
299
300
301 class AppBuilder(BundleBuilder):
302
303 # Override type of the bundle.
304 type = "APPL"
305
306 # platform, name of the subfolder of Contents that contains the executable.
307 platform = "MacOS"
308
309 # A Python main program. If this argument is given, the main
310 # executable in the bundle will be a small wrapper that invokes
311 # the main program. (XXX Discuss why.)
312 mainprogram = None
313
314 # The main executable. If a Python main program is specified
315 # the executable will be copied to Resources and be invoked
316 # by the wrapper program mentioned above. Otherwise it will
317 # simply be used as the main executable.
318 executable = None
319
320 # The name of the main nib, for Cocoa apps. *Must* be specified
321 # when building a Cocoa app.
322 nibname = None
323
324 # The name of the icon file to be copied to Resources and used for
325 # the Finder icon.
326 iconfile = None
327
328 # Symlink the executable instead of copying it.
329 symlink_exec = 0
330
331 # If True, build standalone app.
332 standalone = 0
333
334 # If True, add a real main program that emulates sys.argv before calling
335 # mainprogram
336 argv_emulation = 0
337
338 # The following attributes are only used when building a standalone app.
339
340 # Exclude these modules.
341 excludeModules = []
342
343 # Include these modules.
344 includeModules = []
345
346 # Include these packages.
347 includePackages = []
348
349 # Strip binaries.
350 strip = 0
351
352 # Found Python modules: [(name, codeobject, ispkg), ...]
353 pymodules = []
354
355 # Modules that modulefinder couldn't find:
356 missingModules = []
357 maybeMissingModules = []
358
359 # List of all binaries (executables or shared libs), for stripping purposes
360 binaries = []
361
362 def setup(self):
363 if self.standalone and self.mainprogram is None:
364 raise BundleBuilderError, ("must specify 'mainprogram' when "
365 "building a standalone application.")
366 if self.mainprogram is None and self.executable is None:
367 raise BundleBuilderError, ("must specify either or both of "
368 "'executable' and 'mainprogram'")
369
370 self.execdir = pathjoin("Contents", self.platform)
371
372 if self.name is not None:
373 pass
374 elif self.mainprogram is not None:
375 self.name = os.path.splitext(os.path.basename(self.mainprogram))[0]
376 elif executable is not None:
377 self.name = os.path.splitext(os.path.basename(self.executable))[0]
378 if self.name[-4:] != ".app":
379 self.name += ".app"
380
381 if self.executable is None:
382 if not self.standalone:
383 self.symlink_exec = 1
384 self.executable = sys.executable
385
386 if self.nibname:
387 self.plist.NSMainNibFile = self.nibname
388 if not hasattr(self.plist, "NSPrincipalClass"):
389 self.plist.NSPrincipalClass = "NSApplication"
390
391 BundleBuilder.setup(self)
392
393 self.plist.CFBundleExecutable = self.name
394
395 if self.standalone:
396 self.findDependencies()
397
398 def preProcess(self):
399 resdir = "Contents/Resources"
400 if self.executable is not None:
401 if self.mainprogram is None:
402 execname = self.name
403 else:
404 execname = os.path.basename(self.executable)
405 execpath = pathjoin(self.execdir, execname)
406 if not self.symlink_exec:
407 self.files.append((self.executable, execpath))
408 self.binaries.append(execpath)
409 self.execpath = execpath
410
411 if self.mainprogram is not None:
412 mainprogram = os.path.basename(self.mainprogram)
413 self.files.append((self.mainprogram, pathjoin(resdir, mainprogram)))
414 if self.argv_emulation:
415 # Change the main program, and create the helper main program (which
416 # does argv collection and then calls the real main).
417 # Also update the included modules (if we're creating a standalone
418 # program) and the plist
419 realmainprogram = mainprogram
420 mainprogram = '__argvemulator_' + mainprogram
421 resdirpath = pathjoin(self.bundlepath, resdir)
422 mainprogrampath = pathjoin(resdirpath, mainprogram)
423 makedirs(resdirpath)
424 open(mainprogrampath, "w").write(ARGV_EMULATOR % locals())
425 if self.standalone:
426 self.includeModules.append("argvemulator")
427 self.includeModules.append("os")
428 if not self.plist.has_key("CFBundleDocumentTypes"):
429 self.plist["CFBundleDocumentTypes"] = [
430 { "CFBundleTypeOSTypes" : [
431 "****",
432 "fold",
433 "disk"],
434 "CFBundleTypeRole": "Viewer"}]
435 # Write bootstrap script
436 executable = os.path.basename(self.executable)
437 execdir = pathjoin(self.bundlepath, self.execdir)
438 bootstrappath = pathjoin(execdir, self.name)
439 makedirs(execdir)
440 if self.standalone:
441 # XXX we're screwed when the end user has deleted
442 # /usr/bin/python
443 hashbang = "/usr/bin/python"
444 pythonhome = 'os.environ["PYTHONHOME"] = resdir'
445 else:
446 hashbang = sys.executable
447 while os.path.islink(hashbang):
448 hashbang = os.readlink(hashbang)
449 pythonhome = ''
450 open(bootstrappath, "w").write(BOOTSTRAP_SCRIPT % locals())
451 os.chmod(bootstrappath, 0775)
452
453 if self.iconfile is not None:
454 iconbase = os.path.basename(self.iconfile)
455 self.plist.CFBundleIconFile = iconbase
456 self.files.append((self.iconfile, pathjoin(resdir, iconbase)))
457
458 def postProcess(self):
459 if self.standalone:
460 self.addPythonModules()
461 if self.strip and not self.symlink:
462 self.stripBinaries()
463
464 if self.symlink_exec and self.executable:
465 self.message("Symlinking executable %s to %s" % (self.executable,
466 self.execpath), 2)
467 dst = pathjoin(self.bundlepath, self.execpath)
468 makedirs(os.path.dirname(dst))
469 os.symlink(os.path.abspath(self.executable), dst)
470
471 if self.missingModules or self.maybeMissingModules:
472 self.reportMissing()
473
474 def addPythonModules(self):
475 self.message("Adding Python modules", 1)
476
477 if USE_ZIPIMPORT:
478 # Create a zip file containing all modules as pyc.
479 import zipfile
480 relpath = pathjoin("Contents", "Resources", ZIP_ARCHIVE)
481 abspath = pathjoin(self.bundlepath, relpath)
482 zf = zipfile.ZipFile(abspath, "w", zipfile.ZIP_DEFLATED)
483 for name, code, ispkg in self.pymodules:
484 self.message("Adding Python module %s" % name, 2)
485 path, pyc = getPycData(name, code, ispkg)
486 zf.writestr(path, pyc)
487 zf.close()
488 # add site.pyc
489 sitepath = pathjoin(self.bundlepath, "Contents", "Resources",
490 "site" + PYC_EXT)
491 writePyc(SITE_CO, sitepath)
492 else:
493 # Create individual .pyc files.
494 for name, code, ispkg in self.pymodules:
495 if ispkg:
496 name += ".__init__"
497 path = name.split(".")
498 path = pathjoin("Contents", "Resources", *path) + PYC_EXT
499
500 if ispkg:
501 self.message("Adding Python package %s" % path, 2)
502 else:
503 self.message("Adding Python module %s" % path, 2)
504
505 abspath = pathjoin(self.bundlepath, path)
506 makedirs(os.path.dirname(abspath))
507 writePyc(code, abspath)
508
509 def stripBinaries(self):
510 if not os.path.exists(STRIP_EXEC):
511 self.message("Error: can't strip binaries: no strip program at "
512 "%s" % STRIP_EXEC, 0)
513 else:
514 self.message("Stripping binaries", 1)
515 for relpath in self.binaries:
516 self.message("Stripping %s" % relpath, 2)
517 abspath = pathjoin(self.bundlepath, relpath)
518 assert not os.path.islink(abspath)
519 rv = os.system("%s -S \"%s\"" % (STRIP_EXEC, abspath))
520
521 def findDependencies(self):
522 self.message("Finding module dependencies", 1)
523 import modulefinder
524 mf = modulefinder.ModuleFinder(excludes=self.excludeModules)
525 if USE_ZIPIMPORT:
526 # zipimport imports zlib, must add it manually
527 mf.import_hook("zlib")
528 # manually add our own site.py
529 site = mf.add_module("site")
530 site.__code__ = SITE_CO
531 mf.scan_code(SITE_CO, site)
532
533 # warnings.py gets imported implicitly from C
534 mf.import_hook("warnings")
535
536 includeModules = self.includeModules[:]
537 for name in self.includePackages:
538 includeModules.extend(findPackageContents(name).keys())
539 for name in includeModules:
540 try:
541 mf.import_hook(name)
542 except ImportError:
543 self.missingModules.append(name)
544
545 mf.run_script(self.mainprogram)
546 modules = mf.modules.items()
547 modules.sort()
548 for name, mod in modules:
549 if mod.__file__ and mod.__code__ is None:
550 # C extension
551 path = mod.__file__
552 filename = os.path.basename(path)
553 if USE_ZIPIMPORT:
554 # Python modules are stored in a Zip archive, but put
555 # extensions in Contents/Resources/.a and add a tiny "loader"
556 # program in the Zip archive. Due to Thomas Heller.
557 dstpath = pathjoin("Contents", "Resources", filename)
558 source = EXT_LOADER % {"name": name, "filename": filename}
559 code = compile(source, "<dynloader for %s>" % name, "exec")
560 mod.__code__ = code
561 else:
562 # just copy the file
563 dstpath = name.split(".")[:-1] + [filename]
564 dstpath = pathjoin("Contents", "Resources", *dstpath)
565 self.files.append((path, dstpath))
566 self.binaries.append(dstpath)
567 if mod.__code__ is not None:
568 ispkg = mod.__path__ is not None
569 if not USE_ZIPIMPORT or name != "site":
570 # Our site.py is doing the bootstrapping, so we must
571 # include a real .pyc file if USE_ZIPIMPORT is True.
572 self.pymodules.append((name, mod.__code__, ispkg))
573
574 if hasattr(mf, "any_missing_maybe"):
575 missing, maybe = mf.any_missing_maybe()
576 else:
577 missing = mf.any_missing()
578 maybe = []
579 self.missingModules.extend(missing)
580 self.maybeMissingModules.extend(maybe)
581
582 def reportMissing(self):
583 missing = [name for name in self.missingModules
584 if name not in MAYMISS_MODULES]
585 if self.maybeMissingModules:
586 maybe = self.maybeMissingModules
587 else:
588 maybe = [name for name in missing if "." in name]
589 missing = [name for name in missing if "." not in name]
590 missing.sort()
591 maybe.sort()
592 if maybe:
593 self.message("Warning: couldn't find the following submodules:", 1)
594 self.message(" (Note that these could be false alarms -- "
595 "it's not always", 1)
596 self.message(" possible to distinguish between \"from package "
597 "import submodule\" ", 1)
598 self.message(" and \"from package import name\")", 1)
599 for name in maybe:
600 self.message(" ? " + name, 1)
601 if missing:
602 self.message("Warning: couldn't find the following modules:", 1)
603 for name in missing:
604 self.message(" ? " + name, 1)
605
606 def report(self):
607 # XXX something decent
608 import pprint
609 pprint.pprint(self.__dict__)
610 if self.standalone:
611 self.reportMissing()
612
613 #
614 # Utilities.
615 #
616
617 SUFFIXES = [_suf for _suf, _mode, _tp in imp.get_suffixes()]
618 identifierRE = re.compile(r"[_a-zA-z][_a-zA-Z0-9]*$")
619
620 def findPackageContents(name, searchpath=None):
621 head = name.split(".")[-1]
622 if identifierRE.match(head) is None:
623 return {}
624 try:
625 fp, path, (ext, mode, tp) = imp.find_module(head, searchpath)
626 except ImportError:
627 return {}
628 modules = {name: None}
629 if tp == imp.PKG_DIRECTORY and path:
630 files = os.listdir(path)
631 for sub in files:
632 sub, ext = os.path.splitext(sub)
633 fullname = name + "." + sub
634 if sub != "__init__" and fullname not in modules:
635 modules.update(findPackageContents(fullname, [path]))
636 return modules
637
638 def writePyc(code, path):
639 f = open(path, "wb")
640 f.write(MAGIC)
641 f.write("\0" * 4) # don't bother about a time stamp
642 marshal.dump(code, f)
643 f.close()
644
645 def copy(src, dst, mkdirs=0):
646 """Copy a file or a directory."""
647 if mkdirs:
648 makedirs(os.path.dirname(dst))
649 if os.path.isdir(src):
650 shutil.copytree(src, dst)
651 else:
652 shutil.copy2(src, dst)
653
654 def copytodir(src, dstdir):
655 """Copy a file or a directory to an existing directory."""
656 dst = pathjoin(dstdir, os.path.basename(src))
657 copy(src, dst)
658
659 def makedirs(dir):
660 """Make all directories leading up to 'dir' including the leaf
661 directory. Don't moan if any path element already exists."""
662 try:
663 os.makedirs(dir)
664 except OSError, why:
665 if why.errno != errno.EEXIST:
666 raise
667
668 def symlink(src, dst, mkdirs=0):
669 """Copy a file or a directory."""
670 if not os.path.exists(src):
671 raise IOError, "No such file or directory: '%s'" % src
672 if mkdirs:
673 makedirs(os.path.dirname(dst))
674 os.symlink(os.path.abspath(src), dst)
675
676 def pathjoin(*args):
677 """Safe wrapper for os.path.join: asserts that all but the first
678 argument are relative paths."""
679 for seg in args[1:]:
680 assert seg[0] != "/"
681 return os.path.join(*args)
682
683
684 cmdline_doc = """\
685 Usage:
686 python bundlebuilder.py [options] command
687 python mybuildscript.py [options] command
688
689 Commands:
690 build build the application
691 report print a report
692
693 Options:
694 -b, --builddir=DIR the build directory; defaults to "build"
695 -n, --name=NAME application name
696 -r, --resource=FILE extra file or folder to be copied to Resources
697 -f, --file=SRC:DST extra file or folder to be copied into the bundle;
698 DST must be a path relative to the bundle root
699 -e, --executable=FILE the executable to be used
700 -m, --mainprogram=FILE the Python main program
701 -a, --argv add a wrapper main program to create sys.argv
702 -p, --plist=FILE .plist file (default: generate one)
703 --nib=NAME main nib name
704 -c, --creator=CCCC 4-char creator code (default: '????')
705 --iconfile=FILE filename of the icon (an .icns file) to be used
706 as the Finder icon
707 -l, --link symlink files/folder instead of copying them
708 --link-exec symlink the executable instead of copying it
709 --standalone build a standalone application, which is fully
710 independent of a Python installation
711 --lib=FILE shared library or framework to be copied into
712 the bundle
713 -x, --exclude=MODULE exclude module (with --standalone)
714 -i, --include=MODULE include module (with --standalone)
715 --package=PACKAGE include a whole package (with --standalone)
716 --strip strip binaries (remove debug info)
717 -v, --verbose increase verbosity level
718 -q, --quiet decrease verbosity level
719 -h, --help print this message
720 """
721
722 def usage(msg=None):
723 if msg:
724 print msg
725 print cmdline_doc
726 sys.exit(1)
727
728 def main(builder=None):
729 if builder is None:
730 builder = AppBuilder(verbosity=1)
731
732 shortopts = "b:n:r:f:e:m:c:p:lx:i:hvqa"
733 longopts = ("builddir=", "name=", "resource=", "file=", "executable=",
734 "mainprogram=", "creator=", "nib=", "plist=", "link",
735 "link-exec", "help", "verbose", "quiet", "argv", "standalone",
736 "exclude=", "include=", "package=", "strip", "iconfile=",
737 "lib=")
738
739 try:
740 options, args = getopt.getopt(sys.argv[1:], shortopts, longopts)
741 except getopt.error:
742 usage()
743
744 for opt, arg in options:
745 if opt in ('-b', '--builddir'):
746 builder.builddir = arg
747 elif opt in ('-n', '--name'):
748 builder.name = arg
749 elif opt in ('-r', '--resource'):
750 builder.resources.append(arg)
751 elif opt in ('-f', '--file'):
752 srcdst = arg.split(':')
753 if len(srcdst) != 2:
754 usage("-f or --file argument must be two paths, "
755 "separated by a colon")
756 builder.files.append(srcdst)
757 elif opt in ('-e', '--executable'):
758 builder.executable = arg
759 elif opt in ('-m', '--mainprogram'):
760 builder.mainprogram = arg
761 elif opt in ('-a', '--argv'):
762 builder.argv_emulation = 1
763 elif opt in ('-c', '--creator'):
764 builder.creator = arg
765 elif opt == '--iconfile':
766 builder.iconfile = arg
767 elif opt == "--lib":
768 builder.libs.append(arg)
769 elif opt == "--nib":
770 builder.nibname = arg
771 elif opt in ('-p', '--plist'):
772 builder.plist = Plist.fromFile(arg)
773 elif opt in ('-l', '--link'):
774 builder.symlink = 1
775 elif opt == '--link-exec':
776 builder.symlink_exec = 1
777 elif opt in ('-h', '--help'):
778 usage()
779 elif opt in ('-v', '--verbose'):
780 builder.verbosity += 1
781 elif opt in ('-q', '--quiet'):
782 builder.verbosity -= 1
783 elif opt == '--standalone':
784 builder.standalone = 1
785 elif opt in ('-x', '--exclude'):
786 builder.excludeModules.append(arg)
787 elif opt in ('-i', '--include'):
788 builder.includeModules.append(arg)
789 elif opt == '--package':
790 builder.includePackages.append(arg)
791 elif opt == '--strip':
792 builder.strip = 1
793
794 if len(args) != 1:
795 usage("Must specify one command ('build', 'report' or 'help')")
796 command = args[0]
797
798 if command == "build":
799 builder.setup()
800 builder.build()
801 elif command == "report":
802 builder.setup()
803 builder.report()
804 elif command == "help":
805 usage()
806 else:
807 usage("Unknown command '%s'" % command)
808
809
810 def buildapp(**kwargs):
811 builder = AppBuilder(**kwargs)
812 main(builder)
813
814
815 if __name__ == "__main__":
816 main()