ยปCore Development>Code coverage>Mac/Tools/bundlebuilder.py

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