diff -r 19f36a2a34ec Doc/distutils/apiref.rst --- a/Doc/distutils/apiref.rst Mon Mar 23 01:09:35 2015 +0200 +++ b/Doc/distutils/apiref.rst Mon Mar 23 10:56:07 2015 +0200 @@ -868,23 +868,31 @@ tarballs or zipfiles. Create an archive file (eg. ``zip`` or ``tar``). *base_name* is the name of the file to create, minus any format-specific extension; *format* is the - archive format: one of ``zip``, ``tar``, ``ztar``, or ``gztar``. *root_dir* is - a directory that will be the root directory of the archive; ie. we typically - ``chdir`` into *root_dir* before creating the archive. *base_dir* is the - directory where we start archiving from; ie. *base_dir* will be the common - prefix of all files and directories in the archive. *root_dir* and *base_dir* - both default to the current directory. Returns the name of the archive file. + archive format: one of ``zip``, ``tar``, ``ztar``, ``gztar``, ``bztar``, or + ``xztar``. *root_dir* is a directory that will be the root directory of the + archive; ie. we typically ``chdir`` into *root_dir* before creating the + archive. *base_dir* is the directory where we start archiving from; ie. + *base_dir* will be the common prefix of all files and directories in the + archive. *root_dir* and *base_dir* both default to the current directory. + Returns the name of the archive file. + + .. versionchanged: 3.5 + Added support for the ``xztar`` format. .. function:: make_tarball(base_name, base_dir[, compress='gzip', verbose=0, dry_run=0]) 'Create an (optional compressed) archive as a tar file from all files in and - under *base_dir*. *compress* must be ``'gzip'`` (the default), ``'compress'``, - ``'bzip2'``, or ``None``. Both :program:`tar` and the compression utility named - by *compress* must be on the default program search path, so this is probably - Unix-specific. The output tar file will be named :file:`base_dir.tar`, - possibly plus the appropriate compression extension (:file:`.gz`, :file:`.bz2` - or :file:`.Z`). Return the output filename. + under *base_dir*. *compress* must be ``'gzip'`` (the default), + ``'compress'``, ``'bzip2'``, ``'xz'``, or ``None``. Both :program:`tar` + and the compression utility named by *compress* must be on the default + program search path, so this is probably Unix-specific. The output tar + file will be named :file:`base_dir.tar`, possibly plus the appropriate + compression extension (:file:`.gz`, :file:`.bz2`, :file:`.xz` or + :file:`.Z`). Return the output filename. + + .. versionchanged: 3.5 + Added support for the ``xz`` compression. .. function:: make_zipfile(base_name, base_dir[, verbose=0, dry_run=0]) diff -r 19f36a2a34ec Doc/distutils/builtdist.rst --- a/Doc/distutils/builtdist.rst Mon Mar 23 01:09:35 2015 +0200 +++ b/Doc/distutils/builtdist.rst Mon Mar 23 10:56:07 2015 +0200 @@ -75,6 +75,12 @@ The available formats for built distribu | ``gztar`` | gzipped tar file | (1),(3) | | | (:file:`.tar.gz`) | | +-------------+------------------------------+---------+ +| ``bztar`` | bzipped tar file | (1),(3) | +| | (:file:`.tar.bz2`) | | ++-------------+------------------------------+---------+ +| ``xztar`` | xzipped tar file | (1),(3) | +| | (:file:`.tar.xz`) | | ++-------------+------------------------------+---------+ | ``ztar`` | compressed tar file | \(3) | | | (:file:`.tar.Z`) | | +-------------+------------------------------+---------+ @@ -94,6 +100,9 @@ The available formats for built distribu | ``msi`` | Microsoft Installer. | | +-------------+------------------------------+---------+ +.. versionchanged: 3.5 + Added support for the ``xztar`` format. + Notes: @@ -105,7 +114,7 @@ Notes: (3) requires external utilities: :program:`tar` and possibly one of :program:`gzip`, - :program:`bzip2`, or :program:`compress` + :program:`bzip2`, :program:`xz`, or :program:`compress` (4) requires either external :program:`zip` utility or :mod:`zipfile` module (part @@ -119,21 +128,22 @@ You don't have to use the :command:`bdis option; you can also use the command that directly implements the format you're interested in. Some of these :command:`bdist` "sub-commands" actually generate several similar formats; for instance, the :command:`bdist_dumb` command -generates all the "dumb" archive formats (``tar``, ``ztar``, ``gztar``, and -``zip``), and :command:`bdist_rpm` generates both binary and source RPMs. The -:command:`bdist` sub-commands, and the formats generated by each, are: +generates all the "dumb" archive formats (``tar``, ``ztar``, ``gztar``, +``bztar``, ``xztar``, and ``zip``), and :command:`bdist_rpm` generates both +binary and source RPMs. The :command:`bdist` sub-commands, and the formats +generated by each, are: -+--------------------------+-----------------------+ -| Command | Formats | -+==========================+=======================+ -| :command:`bdist_dumb` | tar, ztar, gztar, zip | -+--------------------------+-----------------------+ -| :command:`bdist_rpm` | rpm, srpm | -+--------------------------+-----------------------+ -| :command:`bdist_wininst` | wininst | -+--------------------------+-----------------------+ -| :command:`bdist_msi` | msi | -+--------------------------+-----------------------+ ++--------------------------+-------------------------------------+ +| Command | Formats | ++==========================+=====================================+ +| :command:`bdist_dumb` | tar, ztar, gztar, bztar, xztar, zip | ++--------------------------+-------------------------------------+ +| :command:`bdist_rpm` | rpm, srpm | ++--------------------------+-------------------------------------+ +| :command:`bdist_wininst` | wininst | ++--------------------------+-------------------------------------+ +| :command:`bdist_msi` | msi | ++--------------------------+-------------------------------------+ The following sections give details on the individual :command:`bdist_\*` commands. diff -r 19f36a2a34ec Doc/distutils/sourcedist.rst --- a/Doc/distutils/sourcedist.rst Mon Mar 23 01:09:35 2015 +0200 +++ b/Doc/distutils/sourcedist.rst Mon Mar 23 10:56:07 2015 +0200 @@ -32,12 +32,18 @@ to create a gzipped tarball and a zip fi | ``bztar`` | bzip2'ed tar file | | | | (:file:`.tar.bz2`) | | +-----------+-------------------------+---------+ +| ``xztar`` | xz'ed tar file | | +| | (:file:`.tar.xz`) | | ++-----------+-------------------------+---------+ | ``ztar`` | compressed tar file | \(4) | | | (:file:`.tar.Z`) | | +-----------+-------------------------+---------+ | ``tar`` | tar file (:file:`.tar`) | | +-----------+-------------------------+---------+ +.. versionchanged: 3.5 + Added support for the ``xztar`` format. + Notes: (1) @@ -54,7 +60,7 @@ Notes: requires the :program:`compress` program. Notice that this format is now pending for deprecation and will be removed in the future versions of Python. -When using any ``tar`` format (``gztar``, ``bztar``, ``ztar`` or +When using any ``tar`` format (``gztar``, ``bztar``, ``xztar``, ``ztar`` or ``tar``), under Unix you can specify the ``owner`` and ``group`` names that will be set for each member of the archive. diff -r 19f36a2a34ec Doc/whatsnew/3.5.rst --- a/Doc/whatsnew/3.5.rst Mon Mar 23 01:09:35 2015 +0200 +++ b/Doc/whatsnew/3.5.rst Mon Mar 23 10:56:07 2015 +0200 @@ -242,6 +242,9 @@ distutils option to enable parallel building of extension modules. (Contributed by Antoine Pitrou in :issue:`5309`.) +* Added support for the LZMA compression. + (Contributed by Serhiy Storchaka in :issue:`16314`.) + doctest ------- diff -r 19f36a2a34ec Lib/distutils/archive_util.py --- a/Lib/distutils/archive_util.py Mon Mar 23 01:09:35 2015 +0200 +++ b/Lib/distutils/archive_util.py Mon Mar 23 10:56:07 2015 +0200 @@ -57,26 +57,28 @@ def make_tarball(base_name, base_dir, co """Create a (possibly compressed) tar file from all the files under 'base_dir'. - 'compress' must be "gzip" (the default), "compress", "bzip2", or None. - (compress will be deprecated in Python 3.2) + 'compress' must be "gzip" (the default), "compress", "bzip2", "xz", or + None. ("compress" will be deprecated in Python 3.2) 'owner' and 'group' can be used to define an owner and a group for the archive that is being built. If not provided, the current owner and group will be used. The output tar file will be named 'base_dir' + ".tar", possibly plus - the appropriate compression extension (".gz", ".bz2" or ".Z"). + the appropriate compression extension (".gz", ".bz2", ".xz" or ".Z"). Returns the output filename. """ - tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', None: '', 'compress': ''} - compress_ext = {'gzip': '.gz', 'bzip2': '.bz2', 'compress': '.Z'} + tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', 'xz': 'xz', None: '', + 'compress': ''} + compress_ext = {'gzip': '.gz', 'bzip2': '.bz2', 'xz': '.xz', + 'compress': '.Z'} # flags for compression program, each element of list will be an argument if compress is not None and compress not in compress_ext.keys(): raise ValueError( - "bad value for 'compress': must be None, 'gzip', 'bzip2' " - "or 'compress'") + "bad value for 'compress': must be None, 'gzip', 'bzip2', " + "'xz' or 'compress'") archive_name = base_name + '.tar' if compress != 'compress': @@ -177,6 +179,7 @@ def make_zipfile(base_name, base_dir, ve ARCHIVE_FORMATS = { 'gztar': (make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"), 'bztar': (make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"), + 'xztar': (make_tarball, [('compress', 'xz')], "xz'ed tar-file"), 'ztar': (make_tarball, [('compress', 'compress')], "compressed tar file"), 'tar': (make_tarball, [('compress', None)], "uncompressed tar file"), 'zip': (make_zipfile, [],"ZIP file") @@ -198,7 +201,7 @@ def make_archive(base_name, format, root 'base_name' is the name of the file to create, minus any format-specific extension; 'format' is the archive format: one of "zip", "tar", "ztar", - or "gztar". + "gztar", "bztar", or "xztar". 'root_dir' is a directory that will be the root directory of the archive; ie. we typically chdir into 'root_dir' before creating the diff -r 19f36a2a34ec Lib/distutils/command/bdist.py --- a/Lib/distutils/command/bdist.py Mon Mar 23 01:09:35 2015 +0200 +++ b/Lib/distutils/command/bdist.py Mon Mar 23 10:56:07 2015 +0200 @@ -61,13 +61,14 @@ class bdist(Command): 'nt': 'zip'} # Establish the preferred order (for the --help-formats option). - format_commands = ['rpm', 'gztar', 'bztar', 'ztar', 'tar', + format_commands = ['rpm', 'gztar', 'bztar', 'xztar', 'ztar', 'tar', 'wininst', 'zip', 'msi'] # And the real information. format_command = {'rpm': ('bdist_rpm', "RPM distribution"), 'gztar': ('bdist_dumb', "gzip'ed tar file"), 'bztar': ('bdist_dumb', "bzip2'ed tar file"), + 'xztar': ('bdist_dumb', "xz'ed tar file"), 'ztar': ('bdist_dumb', "compressed tar file"), 'tar': ('bdist_dumb', "tar file"), 'wininst': ('bdist_wininst', diff -r 19f36a2a34ec Lib/distutils/command/bdist_dumb.py --- a/Lib/distutils/command/bdist_dumb.py Mon Mar 23 01:09:35 2015 +0200 +++ b/Lib/distutils/command/bdist_dumb.py Mon Mar 23 10:56:07 2015 +0200 @@ -22,7 +22,8 @@ class bdist_dumb(Command): "platform name to embed in generated filenames " "(default: %s)" % get_platform()), ('format=', 'f', - "archive format to create (tar, ztar, gztar, zip)"), + "archive format to create (tar, ztar, gztar, bztar, " + "xztar, zip)"), ('keep-temp', 'k', "keep the pseudo-installation tree around after " + "creating the distribution archive"), diff -r 19f36a2a34ec Lib/distutils/tests/test_archive_util.py --- a/Lib/distutils/tests/test_archive_util.py Mon Mar 23 01:09:35 2015 +0200 +++ b/Lib/distutils/tests/test_archive_util.py Mon Mar 23 10:56:07 2015 +0200 @@ -13,7 +13,7 @@ from distutils.archive_util import (chec ARCHIVE_FORMATS) from distutils.spawn import find_executable, spawn from distutils.tests import support -from test.support import check_warnings, run_unittest, patch +from test.support import check_warnings, run_unittest, patch, change_cwd try: import grp @@ -34,6 +34,16 @@ try: except ImportError: ZLIB_SUPPORT = False +try: + import bz2 +except ImportError: + bz2 = None + +try: + import lzma +except ImportError: + lzma = None + def can_fs_encode(filename): """ Return True if the filename can be saved in the file system. @@ -51,11 +61,25 @@ class ArchiveUtilTestCase(support.Tempdi support.LoggingSilencer, unittest.TestCase): - @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run') def test_make_tarball(self): self._make_tarball('archive') @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run') + def test_make_tarball_gzip(self): + tmpdir = self._create_files1() + self._make_tarball1(tmpdir, 'archive', '.tar.gz', compress='gzip') + + @unittest.skipUnless(bz2, 'Need bz2 support to run') + def test_make_tarball_bzip2(self): + tmpdir = self._create_files1() + self._make_tarball1(tmpdir, 'archive', '.tar.bz2', compress='bzip2') + + @unittest.skipUnless(lzma, 'Need lzma support to run') + def test_make_tarball_xz(self): + tmpdir = self._create_files1() + self._make_tarball1(tmpdir, 'archive', '.tar.xz', compress='xz') + + @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run') @unittest.skipUnless(can_fs_encode('årchiv'), 'File system cannot handle this filename') def test_make_tarball_latin1(self): @@ -74,14 +98,7 @@ class ArchiveUtilTestCase(support.Tempdi """ self._make_tarball('のアーカイブ') # japanese for archive - def _make_tarball(self, target_name): - # creating something to tar - tmpdir = self.mkdtemp() - self.write_file([tmpdir, 'file1'], 'xxx') - self.write_file([tmpdir, 'file2'], 'xxx') - os.mkdir(os.path.join(tmpdir, 'sub')) - self.write_file([tmpdir, 'sub', 'file3'], 'xxx') - + def _make_tarball1(self, tmpdir, target_name, suffix, **kwargs): tmpdir2 = self.mkdtemp() unittest.skipUnless(splitdrive(tmpdir)[0] == splitdrive(tmpdir2)[0], "source and target should be on same drive") @@ -89,27 +106,20 @@ class ArchiveUtilTestCase(support.Tempdi base_name = os.path.join(tmpdir2, target_name) # working with relative paths to avoid tar warnings - old_dir = os.getcwd() - os.chdir(tmpdir) - try: - make_tarball(splitdrive(base_name)[1], '.') - finally: - os.chdir(old_dir) + with change_cwd(tmpdir): + make_tarball(splitdrive(base_name)[1], 'dist', **kwargs) # check if the compressed tarball was created - tarball = base_name + '.tar.gz' + tarball = base_name + suffix self.assertTrue(os.path.exists(tarball)) + self.assertEqual(self._tarinfo(tarball), self._created_files) + def _make_tarball(self, target_name): + # creating something to tar + tmpdir = self._create_files1() + self._make_tarball1(tmpdir, target_name, '.tar.gz') # trying an uncompressed one - base_name = os.path.join(tmpdir2, target_name) - old_dir = os.getcwd() - os.chdir(tmpdir) - try: - make_tarball(splitdrive(base_name)[1], '.', compress=None) - finally: - os.chdir(old_dir) - tarball = base_name + '.tar' - self.assertTrue(os.path.exists(tarball)) + self._make_tarball1(tmpdir, target_name, '.tar', compress=None) def _tarinfo(self, path): tar = tarfile.open(path) @@ -120,7 +130,10 @@ class ArchiveUtilTestCase(support.Tempdi finally: tar.close() - def _create_files(self): + _created_files = ('dist', 'dist/file1', 'dist/file2', + 'dist/sub', 'dist/sub/file3', 'dist/sub2') + + def _create_files1(self): # creating something to tar tmpdir = self.mkdtemp() dist = os.path.join(tmpdir, 'dist') @@ -130,6 +143,11 @@ class ArchiveUtilTestCase(support.Tempdi os.mkdir(os.path.join(dist, 'sub')) self.write_file([dist, 'sub', 'file3'], 'xxx') os.mkdir(os.path.join(dist, 'sub2')) + return tmpdir + + def _create_files(self): + # creating something to tar + tmpdir = self._create_files1() tmpdir2 = self.mkdtemp() base_name = os.path.join(tmpdir2, 'archive') return tmpdir, tmpdir2, base_name @@ -164,7 +182,8 @@ class ArchiveUtilTestCase(support.Tempdi self.assertTrue(os.path.exists(tarball2)) # let's compare both tarballs - self.assertEqual(self._tarinfo(tarball), self._tarinfo(tarball2)) + self.assertEqual(self._tarinfo(tarball), self._created_files) + self.assertEqual(self._tarinfo(tarball2), self._created_files) # trying an uncompressed one base_name = os.path.join(tmpdir2, 'archive') @@ -224,17 +243,21 @@ class ArchiveUtilTestCase(support.Tempdi 'Need zip and zlib support to run') def test_make_zipfile(self): # creating something to tar - tmpdir = self.mkdtemp() - self.write_file([tmpdir, 'file1'], 'xxx') - self.write_file([tmpdir, 'file2'], 'xxx') + tmpdir = self._create_files1() + #self.write_file([tmpdir, 'file1'], 'xxx') + #self.write_file([tmpdir, 'file2'], 'xxx') tmpdir2 = self.mkdtemp() base_name = os.path.join(tmpdir2, 'archive') - make_zipfile(base_name, tmpdir) + with change_cwd(tmpdir): + make_zipfile(base_name, 'dist') # check if the compressed tarball was created tarball = base_name + '.zip' self.assertTrue(os.path.exists(tarball)) + with zipfile.ZipFile(tarball) as zf: + self.assertEqual(sorted(zf.namelist()), + ['dist/file1', 'dist/file2', 'dist/sub/file3']) @unittest.skipUnless(ZIP_SUPPORT, 'Need zip support to run') def test_make_zipfile_no_zlib(self): @@ -251,17 +274,22 @@ class ArchiveUtilTestCase(support.Tempdi # create something to tar and compress tmpdir, tmpdir2, base_name = self._create_files() - make_zipfile(base_name, tmpdir) + with change_cwd(tmpdir): + make_zipfile(base_name, 'dist') tarball = base_name + '.zip' self.assertEqual(called, [((tarball, "w"), {'compression': zipfile.ZIP_STORED})]) self.assertTrue(os.path.exists(tarball)) + with zipfile.ZipFile(tarball) as zf: + self.assertEqual(sorted(zf.namelist()), + ['dist/file1', 'dist/file2', 'dist/sub/file3']) def test_check_archive_formats(self): self.assertEqual(check_archive_formats(['gztar', 'xxx', 'zip']), 'xxx') - self.assertEqual(check_archive_formats(['gztar', 'zip']), None) + self.assertIsNone(check_archive_formats(['gztar', 'bztar', 'xztar', + 'ztar', 'tar', 'zip'])) def test_make_archive(self): tmpdir = self.mkdtemp() @@ -282,6 +310,41 @@ class ArchiveUtilTestCase(support.Tempdi finally: del ARCHIVE_FORMATS['xxx'] + def test_make_archive_tar(self): + base_dir = self._create_files1() + base_name = os.path.join(self.mkdtemp() , 'archive') + res = make_archive(base_name, 'tar', base_dir, 'dist') + self.assertTrue(os.path.exists(res)) + self.assertEqual(os.path.basename(res), 'archive.tar') + self.assertEqual(self._tarinfo(res), self._created_files) + + @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run') + def test_make_archive_gztar(self): + base_dir = self._create_files1() + base_name = os.path.join(self.mkdtemp() , 'archive') + res = make_archive(base_name, 'gztar', base_dir, 'dist') + self.assertTrue(os.path.exists(res)) + self.assertEqual(os.path.basename(res), 'archive.tar.gz') + self.assertEqual(self._tarinfo(res), self._created_files) + + @unittest.skipUnless(bz2, 'Need bz2 support to run') + def test_make_archive_bztar(self): + base_dir = self._create_files1() + base_name = os.path.join(self.mkdtemp() , 'archive') + res = make_archive(base_name, 'bztar', base_dir, 'dist') + self.assertTrue(os.path.exists(res)) + self.assertEqual(os.path.basename(res), 'archive.tar.bz2') + self.assertEqual(self._tarinfo(res), self._created_files) + + @unittest.skipUnless(bz2, 'Need xz support to run') + def test_make_archive_xztar(self): + base_dir = self._create_files1() + base_name = os.path.join(self.mkdtemp() , 'archive') + res = make_archive(base_name, 'xztar', base_dir, 'dist') + self.assertTrue(os.path.exists(res)) + self.assertEqual(os.path.basename(res), 'archive.tar.xz') + self.assertEqual(self._tarinfo(res), self._created_files) + def test_make_archive_owner_group(self): # testing make_archive with owner and group, with various combinations # this works even if there's not gid/uid support diff -r 19f36a2a34ec Lib/distutils/tests/test_bdist.py --- a/Lib/distutils/tests/test_bdist.py Mon Mar 23 01:09:35 2015 +0200 +++ b/Lib/distutils/tests/test_bdist.py Mon Mar 23 10:56:07 2015 +0200 @@ -21,7 +21,7 @@ class BuildTestCase(support.TempdirManag # what formats does bdist offer? formats = ['bztar', 'gztar', 'msi', 'rpm', 'tar', - 'wininst', 'zip', 'ztar'] + 'wininst', 'xztar', 'zip', 'ztar'] found = sorted(cmd.format_command) self.assertEqual(found, formats)