ยปCore Development>Code coverage>Lib/plat-mac/bundlebuilder.py

Python code coverage for Lib/plat-mac/bundlebuilder.py

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