[Zope-CVS] SVN: zpkgtools/trunk/ New extensibility features
Fred L. Drake, Jr.
fdrake at gmail.com
Wed Aug 25 00:45:59 EDT 2004
Log message for revision 27257:
New extensibility features
- Allow additional support code to be loaded into the distribution's
Support/ directory. This can be used in conjunction with the next
feature:
- Allow use of an alternate distribution class. This allows a new
class to be specified to be passed into distutils.core.setup(),
allowing more customization of distutils.
Minimal documentation is included, along with functional tests of the
new behavior.
Changed:
U zpkgtools/trunk/doc/zpkg.txt
U zpkgtools/trunk/zpkgtools/app.py
U zpkgtools/trunk/zpkgtools/tests/test_app.py
-=-
Modified: zpkgtools/trunk/doc/zpkg.txt
===================================================================
--- zpkgtools/trunk/doc/zpkg.txt 2004-08-24 22:06:52 UTC (rev 27256)
+++ zpkgtools/trunk/doc/zpkg.txt 2004-08-25 04:45:57 UTC (rev 27257)
@@ -34,6 +34,11 @@
Load the configuration file `FILE` instead of the default
configuration file. `FILE` must exist.
+--distribution CLASS
+ Set the distribution class to be used by distutils. By default, the
+ zpkgsetup.dist.ZPkgDistribution class is used, but this allows an
+ alternate class to be used instead.
+
-f
Don't load any configuration file, not even the default.
@@ -62,6 +67,13 @@
This can be used to generate smaller distributions when the
``zpkgsetup`` package is known to be available on target systems.
+--support RESOURCE
+ Include an additional resource in the Support/ directory of the
+ distribution, if the Support/ directory is included at all. The
+ additional resource should normally be a top-level Python package.
+ This can be used to provide additional software to be used when
+ distutils is running for the distributed package.
+
-v VERSION
Set the version number of the release to `VERSION`.
Modified: zpkgtools/trunk/zpkgtools/app.py
===================================================================
--- zpkgtools/trunk/zpkgtools/app.py 2004-08-24 22:06:52 UTC (rev 27256)
+++ zpkgtools/trunk/zpkgtools/app.py 2004-08-25 04:45:57 UTC (rev 27257)
@@ -35,6 +35,11 @@
from zpkgtools import runlog
+DEFAULT_SUPPORT_PACKAGES = [
+ ("zpkgsetup", ("svn://svn.zope.org/repos/main/zpkgtools/trunk/zpkgsetup")),
+ ]
+
+
class Application:
"""Application state and logic for **zpkg**."""
@@ -78,6 +83,7 @@
options.release_name = self.resource
# Create a new directory for all temporary files to go in:
self.tmpdir = tempfile.mkdtemp(prefix=options.program + "-")
+ self.old_tmpdir = tempfile.tempdir
tempfile.tempdir = self.tmpdir
if options.revision_tag:
self.loader = loader.Loader(tag=options.revision_tag)
@@ -94,6 +100,9 @@
self.target_file = self.target_name + ".tgz"
self.destination = os.path.join(self.tmpdir, self.target_name)
os.mkdir(self.destination)
+ self.support_packages = DEFAULT_SUPPORT_PACKAGES[:]
+ self.support_packages.extend(
+ [(pkg, None) for pkg in options.support_packages])
def build_distribution(self):
"""Create the distribution tree.
@@ -104,6 +113,7 @@
dep_sources = {}
top = self.get_component(self.resource, self.resource_url)
top.write_package(self.destination)
+ distclass = self.options.distribution_class
if self.options.collect:
depsdir = os.path.join(self.destination, "Dependencies")
first = True
@@ -136,15 +146,18 @@
destination = os.path.join(depsdir, fullname)
self.add_manifest(destination)
component.write_package(destination)
- component.write_setup_py(pathparts=["..", ".."])
+ component.write_setup_py(pathparts=["..", ".."],
+ distclass=distclass)
component.write_setup_cfg()
self.add_headers(component)
if self.options.application:
top.write_setup_py(filename="install.py",
- version=self.options.version)
+ version=self.options.version,
+ distclass=distclass)
self.write_application_support(top)
else:
- top.write_setup_py(version=self.options.version)
+ top.write_setup_py(version=self.options.version,
+ distclass=distclass)
top.write_setup_cfg()
def get_component(self, resource, location):
@@ -222,19 +235,20 @@
directory, but they won't be added to the set of packages that
will be installed by the resulting distribution.
"""
- old_loader = self.loader
- if self.options.revision_tag:
+ cleanup = False
+ if self.options.revision_tag and self.options.revision_tag != "HEAD":
# we really don't want the tagged version of the support code
- self.loader = loader.Loader()
+ old_loader = self.loader
+ self.loader = loader.Loader("HEAD")
+ cleanup = True
supportdest = os.path.join(self.destination, "Support")
os.mkdir(supportdest)
self.add_manifest(supportdest)
- self.include_support_package(
- "zpkgsetup", ("svn://svn.zope.org/repos/main/zpkgtools/trunk/"
- "zpkgsetup"))
- if self.options.revision_tag:
+ for name, fallback_url in self.support_packages:
+ self.include_support_package(name, fallback_url)
+ if cleanup:
self.loader.cleanup()
- self.loader = old_loader
+ self.loader = old_loader
source = os.path.join(zpkgtools.__path__[0], "support")
dest = os.path.join(self.destination, "Support")
files = os.listdir(source)
@@ -263,6 +277,10 @@
url = self.locations[name]
else:
url = fallback
+ if not url:
+ self.logger.warning("resource %s not configured;"
+ " no fallback URL" % name)
+ return
self.logger.info("resource %s not configured;"
" using fallback URL" % name)
if source is None:
@@ -300,6 +318,8 @@
def cleanup(self):
"""Remove all temporary data storage."""
shutil.rmtree(self.tmpdir)
+ if self.tmpdir == tempfile.tempdir:
+ tempfile.tempdir = self.old_tmpdir
def run(self):
"""Run the application, using the other methods of the
@@ -452,7 +472,8 @@
f.write("optimize = 1\n")
f.close()
- def write_setup_py(self, filename="setup.py", version=None, pathparts=[]):
+ def write_setup_py(self, filename="setup.py", version=None, pathparts=[],
+ distclass=None):
setup_py = os.path.join(self.destination, filename)
self.ip.add_output(setup_py)
f = open(setup_py, "w")
@@ -462,7 +483,11 @@
extrapath = ""
print >>f, SETUP_HEADER % extrapath
print >>f, "context = zpkgsetup.setup.SetupContext("
- print >>f, " %r, %r, __file__)" % (self.name, version)
+ if distclass:
+ print >>f, " %r, %r, __file__," % (self.name, version)
+ print >>f, " %r)" % distclass
+ else:
+ print >>f, " %r, %r, __file__)" % (self.name, version)
print >>f
print >>f, "context.initialize()"
print >>f, "context.setup()"
@@ -568,6 +593,13 @@
parser.add_option(
"-v", dest="version",
help="version label for the new distribution")
+ parser.add_option(
+ "--support", dest="support_packages", action="append",
+ default=[],
+ help="name additional support package resource", metavar="RESOURCE")
+ parser.add_option(
+ "--distribution", dest="distribution_class",
+ help="name of the distribution class", metavar="CLASS")
options, args = parser.parse_args(argv[1:])
if len(args) != 1:
Modified: zpkgtools/trunk/zpkgtools/tests/test_app.py
===================================================================
--- zpkgtools/trunk/zpkgtools/tests/test_app.py 2004-08-24 22:06:52 UTC (rev 27256)
+++ zpkgtools/trunk/zpkgtools/tests/test_app.py 2004-08-25 04:45:57 UTC (rev 27257)
@@ -21,6 +21,8 @@
from StringIO import StringIO
+import zpkgsetup
+
from zpkgsetup import package
from zpkgsetup import publication
from zpkgsetup.tests import tempfileapi as tempfile
@@ -234,7 +236,26 @@
self.failUnless(options.application)
self.failUnless(options.collect)
+ def test_distribution_class(self):
+ options = self.parse_args([])
+ self.failIf(options.distribution_class)
+ # long arg:
+ options = self.parse_args(["--distribution", "pkg.mod.Cls"])
+ self.assertEqual(options.distribution_class, "pkg.mod.Cls")
+ options = self.parse_args(["--distribution=pkg.mod.Cls"])
+ self.assertEqual(options.distribution_class, "pkg.mod.Cls")
+ def test_support_packages(self):
+ options = self.parse_args([])
+ self.assertEqual(options.support_packages, [])
+ # one package:
+ options = self.parse_args(["--support", "pkg"])
+ self.assertEqual(options.support_packages, ["pkg"])
+ # two packages
+ options = self.parse_args(["--support", "pkg1", "--support=pkg2"])
+ self.assertEqual(options.support_packages, ["pkg1", "pkg2"])
+
+
class ComponentTestCase(unittest.TestCase):
def setUp(self):
@@ -329,6 +350,136 @@
shutil.rmtree(dest)
+class BuilderApplicationTestCase(unittest.TestCase):
+ """Tests of the BuilderApplication object.
+
+ These are pretty much functional tests since they start with a
+ command line, though they don't run in an external process.
+
+ """
+
+ def setUp(self):
+ self.app = None
+ self.extra_files = []
+
+ def tearDown(self):
+ if self.app is not None:
+ self.app.delayed_cleanup()
+ self.app = None
+ for fn in self.extra_files:
+ os.unlink(fn)
+
+ def createApplication(self, args):
+ options = app.parse_args([CMD] + args)
+ self.app = DelayedCleanupBuilderApplication(options)
+ return self.app
+
+ def createPackageMap(self):
+ input = os.path.join(os.path.dirname(__file__), "input")
+ input = os.path.abspath(input)
+ package_map = os.path.join(input, "tmp-packages.map")
+ shutil.copy(os.path.join(input, "packages.map"), package_map)
+ self.extra_files.append(package_map)
+ zpkgsetup_path = os.path.abspath(zpkgsetup.__path__[0])
+ zpkgsetup_path = "file://" + urllib.pathname2url(zpkgsetup_path)
+ f = open(package_map, "a")
+ print >>f
+ print >>f, "zpkgsetup", zpkgsetup_path
+ f.close()
+ # convert package_map to URL so relative names are resolved properly
+ return "file://" + urllib.pathname2url(package_map)
+
+ def test_adding_extra_support_code(self):
+ package_map = self.createPackageMap()
+ app = self.createApplication(
+ ["-f", "-m", package_map, "--support", "package", "package"])
+ app.run()
+ # make sure the extra support code is actually present:
+ support_dir = os.path.join(app.destination, "Support")
+ package_dir = os.path.join(support_dir, "package")
+ self.assert_(os.path.isdir(package_dir))
+ self.assert_(isfile(package_dir, "__init__.py"))
+
+ def test_alternate_distclass(self):
+ # create the distribution tree:
+ package_map = self.createPackageMap()
+ app = self.createApplication(
+ ["-f", "-m", package_map,
+ "--distribution", "mysupport.MyDistribution", "package"])
+ app.run()
+
+ # Add the example distribution class to the built distro:
+ mysupport = os.path.join(app.destination, "Support", "mysupport.py")
+ f = open(mysupport, "w")
+ print >>f, "import distutils.dist"
+ print >>f, "class MyDistribution(distutils.dist.Distribution):"
+ print >>f, " this_is_mine = 42"
+ f.close()
+
+ # Run the setup.py from the distro and check that we got the
+ # specialized distribution class. This is really tricky since
+ # we're running what's intended to be an script run in it's
+ # own process inside our process, so we're trying to make
+ # __main__ really look like it's running a script. And then
+ # we have to restore it. There's something general lurking
+ # here....
+ import __builtin__
+ import __main__
+ old_stdout = sys.stdout
+ old_stderr = sys.stderr
+ old_sys_argv = sys.argv[:]
+ old_sys_path = sys.path[:]
+ old_main_dict = __main__.__dict__.copy()
+ old_cwd = os.getcwd()
+ setup_script = os.path.join(app.destination, "setup.py")
+ sio = StringIO()
+ d = {"__file__": setup_script,
+ "__name__": "__main__",
+ "__doc__": None,
+ "__builtins__": __builtin__.__dict__}
+ try:
+ sys.stdout = sys.stderr = sio
+ sys.path.insert(0, os.path.join(app.destination, "Support"))
+ sys.argv[:] = [setup_script, "-q", "-n", "--name"]
+ import mysupport
+ __main__.__dict__.clear()
+ __main__.__dict__.update(d)
+ os.chdir(app.destination)
+
+ execfile(setup_script, __main__.__dict__)
+ context = __main__.context
+
+ finally:
+ os.chdir(old_cwd)
+ sys.stdout = old_stdout
+ sys.stderr = old_stderr
+ sys.argv[:] = old_sys_argv
+ sys.path[:] = old_sys_path
+ __main__.__dict__.clear()
+ __main__.__dict__.update(old_main_dict)
+
+ # This makes sure that context.get_distribution_class()
+ # returns the expected class; the actual instance of the class
+ # that's used isn't available to us here, or we'd look at the
+ # instance instead.
+ #
+ cls = context.get_distribution_class()
+ self.assert_(cls is mysupport.MyDistribution)
+
+
+class DelayedCleanupBuilderApplication(app.BuilderApplication):
+
+ def create_tarball(self):
+ pass
+
+ def cleanup(self):
+ pass
+
+ def delayed_cleanup(self):
+ app.BuilderApplication.cleanup(self)
+
+
+
def isfile(path, *args):
if args:
path = os.path.join(path, *args)
@@ -338,6 +489,7 @@
def test_suite():
suite = unittest.makeSuite(CommandLineTestCase)
suite.addTest(unittest.makeSuite(ComponentTestCase))
+ suite.addTest(unittest.makeSuite(BuilderApplicationTestCase))
return suite
if __name__ == "__main__":
More information about the Zope-CVS
mailing list