# HG changeset patch # Parent 30e5ce0775547fea5e345b965d6964d8c565a30f Create a bdist_simple format diff -r 30e5ce077554 Lib/packaging/command/__init__.py --- a/Lib/packaging/command/__init__.py Fri Oct 14 17:38:10 2011 +0200 +++ b/Lib/packaging/command/__init__.py Sun Oct 16 22:14:29 2011 +0100 @@ -25,6 +25,7 @@ 'sdist': 'packaging.command.sdist.sdist', 'bdist': 'packaging.command.bdist.bdist', 'bdist_dumb': 'packaging.command.bdist_dumb.bdist_dumb', + 'bdist_simple': 'packaging.command.bdist_simple.bdist_simple', 'bdist_wininst': 'packaging.command.bdist_wininst.bdist_wininst', 'register': 'packaging.command.register.register', 'upload': 'packaging.command.upload.upload', diff -r 30e5ce077554 Lib/packaging/command/bdist_simple.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/packaging/command/bdist_simple.py Sun Oct 16 22:14:29 2011 +0100 @@ -0,0 +1,153 @@ +"""Create a simple built distribution. + +A simple distribution is an archive with distinguished subdirectories to be +installed to purelib, platlib, etc. +""" + +import os +from shutil import rmtree +from sysconfig import get_python_version + +from packaging.util import get_platform +from packaging.command.cmd import Command +from packaging.errors import PackagingPlatformError +from packaging import logger + + +class bdist_simple(Command): + + description = 'create a simple built distribution' + + user_options = [('bdist-dir=', 'd', + "temporary directory for creating the distribution"), + ('plat-name=', 'p', + "platform name to embed in generated filenames " + "(default: %s)" % get_platform()), + ('format=', 'f', + "archive format to create (tar, gztar, bztar, zip)"), + ('keep-temp', 'k', + "keep the pseudo-installation tree around after " + + "creating the distribution archive"), + ('dist-dir=', 'd', + "directory to put final built distributions in"), + ('skip-build', None, + "skip rebuilding everything (for testing/debugging)"), + ('relative', None, + "build the archive using relative paths" + "(default: false)"), + ('owner=', 'u', + "Owner name used when creating a tar file" + " [default: current user]"), + ('group=', 'g', + "Group name used when creating a tar file" + " [default: current group]"), + ] + + boolean_options = ['keep-temp', 'skip-build', 'relative'] + + default_format = {'posix': 'gztar', + 'nt': 'zip', + 'os2': 'zip'} + + def initialize_options(self): + self.bdist_dir = None + self.plat_name = None + self.format = None + self.keep_temp = False + self.dist_dir = None + self.skip_build = None + self.relative = False + self.owner = None + self.group = None + + def finalize_options(self): + if self.bdist_dir is None: + bdist_base = self.get_finalized_command('bdist').bdist_base + self.bdist_dir = os.path.join(bdist_base, 'simple') + + if self.format is None: + try: + self.format = self.default_format[os.name] + except KeyError: + raise PackagingPlatformError( + "don't know how to create simple built distributions " + "on platform %s" % os.name) + + self.set_undefined_options('bdist', + 'dist_dir', 'plat_name', 'skip_build') + + def run(self): + if not self.skip_build: + self.run_command('build') + + install = self.get_reinitialized_command('install_dist', + reinit_subcommands=True) + install.root = self.bdist_dir + install.compile = False + install.skip_build = self.skip_build + install.warn_dir = False + + # Use a custom scheme for the zip-file, because we have to decide + # at installation time which scheme to use. + for key in ('purelib', 'platlib', 'headers', 'scripts', 'data'): + # Slight bodge here - add an extra dummy directory level so that + # install_from_archive (which goes into one level of directory) + # doesn't get confused... + value = self.distribution.get_fullname() + '/' + key.upper() + if key == 'headers': + value = value + '/Include/$dist_name' + setattr(install, + 'install_' + key, + value) + + logger.info("installing to %s", self.bdist_dir) + self.run_command('install_dist') + + # And make an archive relative to the root of the + # pseudo-installation tree. + archive_basename = "%s.%s" % (self.distribution.get_fullname(), + self.plat_name) + + # OS/2 objects to any ":" characters in a filename (such as when + # a timestamp is used in a version) so change them to hyphens. + if os.name == "os2": + archive_basename = archive_basename.replace(":", "-") + + pseudoinstall_root = os.path.join(self.dist_dir, archive_basename) + if not self.relative: + archive_root = self.bdist_dir + else: + if (self.distribution.has_ext_modules() and + (install.install_base != install.install_platbase)): + raise PackagingPlatformError( + "can't make a simple built distribution where base and " + "platbase are different (%r, %r)" % + (install.install_base, install.install_platbase)) + else: + archive_root = os.path.join( + self.bdist_dir, + self._ensure_relative(install.install_base)) + + # Make the archive + filename = self.make_archive(pseudoinstall_root, + self.format, root_dir=archive_root, + owner=self.owner, group=self.group) + if self.distribution.has_ext_modules(): + pyversion = get_python_version() + else: + pyversion = 'any' + self.distribution.dist_files.append(('bdist_simple', pyversion, + filename)) + + if not self.keep_temp: + if self.dry_run: + logger.info('removing %s', self.bdist_dir) + else: + rmtree(self.bdist_dir) + + def _ensure_relative(self, path): + # copied from dir_util, deleted + drive, path = os.path.splitdrive(path) + if path[0:1] == os.sep: + path = drive + path[1:] + return path diff -r 30e5ce077554 Lib/packaging/install.py --- a/Lib/packaging/install.py Fri Oct 14 17:38:10 2011 +0200 +++ b/Lib/packaging/install.py Sun Oct 16 22:14:29 2011 +0100 @@ -18,7 +18,7 @@ from packaging import logger from packaging.dist import Distribution from packaging.util import (_is_archive_file, ask, get_install_method, - egginfo_to_distinfo) + egginfo_to_distinfo, _write_record_file) from packaging.pypi import wrapper from packaging.version import get_version_predicate from packaging.database import get_distributions, get_distribution @@ -90,6 +90,39 @@ except (IOError, os.error, PackagingError, CCompilerError) as msg: raise ValueError("Failed to install, " + str(msg)) +def _all_files_rel(path): + for dp, dn, fn in os.walk(path): + for f in fn: + yield os.path.relpath(os.path.join(dp,f), path) + +def _run_binary_install(path): + # XXX Copy files and install metadata + # Copy + # PURELIB -> Lib/site-packages + # PLATLIB -> Lib/site-packages + # HEADERS -> . [Include/distname already included] + # SCRIPTS -> Scripts + # DATA -> . + files = [] + record_path = None + # HEADERS not handled yet - I need to understand the Include/distname bit + # first... + sections = ('purelib', 'platlib', 'scripts', 'data') + for section in sections: + src = section.upper() + dest = get_path(section) + for file_name in _all_files_rel(src): + sf = os.path.join(src, file_name) + df = os.path.join(dest, file_name) + dirname, filename = os.path.split(df) + # Remember and skip the RECORD file + if filename == 'RECORD' and dirname.endswith('.dist-info'): + record_path = df + os.makedirs(os.path.dirname(df), exist_ok=True) + shutil.copy2(sf, df) + files.append(df) + if record_path: + _write_record_file(record_path, files) def _install_dist(dist, path): """Install a distribution into a path. @@ -144,6 +177,7 @@ install_methods = { + 'binary': _run_binary_install, 'packaging': _run_packaging_install, 'setuptools': _run_setuptools_install, 'distutils': _run_distutils_install} diff -r 30e5ce077554 Lib/packaging/tests/test_command_bdist_simple.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/packaging/tests/test_command_bdist_simple.py Sun Oct 16 22:14:29 2011 +0100 @@ -0,0 +1,97 @@ +"""Tests for distutils.command.bdist_simple.""" + +import os +import packaging.util + +from packaging.dist import Distribution +from packaging.command.bdist_simple import bdist_simple +from packaging.tests import unittest, support +from packaging.tests.support import requires_zlib + +from zipfile import ZipFile + +class BuildSimpleTestCase(support.TempdirManager, + support.LoggingCatcher, + unittest.TestCase): + + def setUp(self): + super(BuildSimpleTestCase, self).setUp() + self.old_location = os.getcwd() + + def tearDown(self): + os.chdir(self.old_location) + packaging.util._path_created.clear() + super(BuildSimpleTestCase, self).tearDown() + + @requires_zlib + def test_simple_built(self): + + # let's create a simple package + tmp_dir = self.mkdtemp() + pkg_dir = os.path.join(tmp_dir, 'foo') + os.mkdir(pkg_dir) + self.write_file((pkg_dir, 'foo.py'), '#') + self.write_file((pkg_dir, 'MANIFEST.in'), 'include foo.py') + self.write_file((pkg_dir, 'README'), '') + + dist = Distribution({'name': 'foo', 'version': '0.1', + 'py_modules': ['foo'], + 'home_page': 'xxx', 'author': 'xxx', + 'author_email': 'xxx'}) + os.chdir(pkg_dir) + cmd = bdist_simple(dist) + + # so the output is the same no matter + # what is the platform + cmd.format = 'zip' + + cmd.ensure_finalized() + cmd.run() + + # see what we have + dist_created = os.listdir(os.path.join(pkg_dir, 'dist')) + base = "%s.%s" % (dist.get_fullname(), cmd.plat_name) + if os.name == 'os2': + base = base.replace(':', '-') + + wanted = ['%s.zip' % base] + self.assertEqual(dist_created, wanted) + + # now let's check what we have in the zip file + zipfile_name = os.path.join(pkg_dir, 'dist', '%s.zip' % base) + valid_parts = ['PURELIB','PLATLIB','HEADERS','SCRIPTS','DATA'] + valid_metadata = {'INSTALLER','METADATA','RECORD','REQUESTED'} + with ZipFile(zipfile_name, 'r') as zipfile: + names = zipfile.namelist() + metadata = set() + for name in names: + parts = name.split('/') + self.assertGreater(len(parts), 2) + self.assertEqual(parts[0], dist.get_fullname()) + self.assertIn(parts[1], valid_parts) + if parts[-2] == dist.get_fullname() + '.dist-info': + metadata.add(parts[-1]) + self.assertEqual(metadata, valid_metadata) + + def test_finalize_options(self): + pkg_dir, dist = self.create_dist() + os.chdir(pkg_dir) + cmd = bdist_simple(dist) + self.assertEqual(cmd.bdist_dir, None) + cmd.finalize_options() + + # bdist_dir is initialized to bdist_base/simple if not set + base = cmd.get_finalized_command('bdist').bdist_base + self.assertEqual(cmd.bdist_dir, os.path.join(base, 'simple')) + + # the format is set to a default value depending on the os.name + default = cmd.default_format[os.name] + self.assertEqual(cmd.format, default) + + +def test_suite(): + return unittest.makeSuite(BuildSimpleTestCase) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') + diff -r 30e5ce077554 Lib/packaging/util.py --- a/Lib/packaging/util.py Fri Oct 14 17:38:10 2011 +0200 +++ b/Lib/packaging/util.py Sun Oct 16 22:14:29 2011 +0100 @@ -1272,6 +1272,21 @@ return _has_setup_cfg(srcdir) and _has_required_metadata(setup_cfg) +def is_binary(path): + """Check if the project is a binary distribution + + :param path: path to source directory containing a PLATLIB and/or PURELIB + directory. + + Return True if the project has a valid binary distribution layout, else False. + """ + srcdir = os.path.abspath(path) + purelib = os.path.join(srcdir, 'PURELIB') + platlib = os.path.join(srcdir, 'PLATLIB') + + return os.path.isdir(purelib) or os.path.isdir(platlib) + + def get_install_method(path): """Check if the project is based on packaging, setuptools, or distutils @@ -1280,7 +1295,9 @@ Returns a string representing the best install method to use. """ - if is_packaging(path): + if is_binary(path): + return "binary" + elif is_packaging(path): return "packaging" elif is_setuptools(path): return "setuptools"