Issue #14061: Misc fixes and cleanups in archiving code in shutil.

Improved the documentation and tests for make_archive().
Improved error handling when corresponding compress module is not available.
External zip executable is now used if the zlib module is not available.
diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst
index 527cbd0..6bd8f05 100644
--- a/Doc/library/shutil.rst
+++ b/Doc/library/shutil.rst
@@ -267,7 +267,9 @@
 
    *base_name* is the name of the file to create, including the path, minus
    any format-specific extension. *format* is the archive format: one of
-   "zip", "tar", "bztar" or "gztar".
+   "zip" (if the :mod:`zlib` module or external ``zip`` executable is
+   available), "tar", "gztar" (if the :mod:`zlib` module is available), or
+   "bztar" (if the :mod:`bz2` module is available).
 
    *root_dir* is a directory that will be the root directory of the
    archive; ie. we typically chdir into *root_dir* before creating the
@@ -295,10 +297,11 @@
 
    By default :mod:`shutil` provides these formats:
 
-   - *gztar*: gzip'ed tar-file
-   - *bztar*: bzip2'ed tar-file
-   - *tar*: uncompressed tar file
-   - *zip*: ZIP file
+   - *zip*: ZIP file (if the :mod:`zlib` module or external ``zip``
+     executable is available).
+   - *tar*: uncompressed tar file.
+   - *gztar*: gzip'ed tar-file (if the :mod:`zlib` module is available).
+   - *bztar*: bzip2'ed tar-file (if the :mod:`bz2` module is available).
 
    You can register new formats or provide your own archiver for any existing
    formats, by using :func:`register_archive_format`.
diff --git a/Lib/shutil.py b/Lib/shutil.py
index 388906f..3462f7c 100644
--- a/Lib/shutil.py
+++ b/Lib/shutil.py
@@ -13,6 +13,20 @@
 import errno
 
 try:
+    import zlib
+    del zlib
+    _ZLIB_SUPPORTED = True
+except ImportError:
+    _ZLIB_SUPPORTED = False
+
+try:
+    import bz2
+    del bz2
+    _BZ2_SUPPORTED = True
+except ImportError:
+    _BZ2_SUPPORTED = False
+
+try:
     from pwd import getpwnam
 except ImportError:
     getpwnam = None
@@ -351,15 +365,18 @@
 
     Returns the output filename.
     """
-    tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', None: ''}
-    compress_ext = {'gzip': '.gz', 'bzip2': '.bz2'}
+    if compress is None:
+        tar_compression = ''
+    elif _ZLIB_SUPPORTED and compress == 'gzip':
+        tar_compression = 'gz'
+    elif _BZ2_SUPPORTED and compress == 'bzip2':
+        tar_compression = 'bz2'
+    else:
+        raise ValueError("bad value for 'compress', or compression format not "
+                         "supported : {0}".format(compress))
 
-    # 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' or 'bzip2'")
-
-    archive_name = base_name + '.tar' + compress_ext.get(compress, '')
+    compress_ext = '.' + tar_compression if compress else ''
+    archive_name = base_name + '.tar' + compress_ext
     archive_dir = os.path.dirname(archive_name)
 
     if archive_dir and not os.path.exists(archive_dir):
@@ -388,7 +405,7 @@
         return tarinfo
 
     if not dry_run:
-        tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
+        tar = tarfile.open(archive_name, 'w|%s' % tar_compression)
         try:
             tar.add(base_dir, filter=_set_uid_gid)
         finally:
@@ -435,6 +452,7 @@
     # If zipfile module is not available, try spawning an external 'zip'
     # command.
     try:
+        import zlib
         import zipfile
     except ImportError:
         zipfile = None
@@ -470,11 +488,17 @@
     return zip_filename
 
 _ARCHIVE_FORMATS = {
-    'gztar': (_make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"),
-    'bztar': (_make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"),
     'tar':   (_make_tarball, [('compress', None)], "uncompressed tar file"),
-    'zip':   (_make_zipfile, [],"ZIP file")
-    }
+    'zip':   (_make_zipfile, [], "ZIP file")
+}
+
+if _ZLIB_SUPPORTED:
+    _ARCHIVE_FORMATS['gztar'] = (_make_tarball, [('compress', 'gzip')],
+                                "gzip'ed tar-file")
+
+if _BZ2_SUPPORTED:
+    _ARCHIVE_FORMATS['bztar'] = (_make_tarball, [('compress', 'bzip2')],
+                                "bzip2'ed tar-file")
 
 def get_archive_formats():
     """Returns a list of supported formats for archiving and unarchiving.
@@ -515,8 +539,8 @@
     """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", "bztar"
-    or "gztar".
+    extension; 'format' is the archive format: one of "zip", "tar", "gztar",
+    or "bztar".  Or any other registered format.
 
     '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 --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py
index 0869a9e..8338712 100644
--- a/Lib/test/test_shutil.py
+++ b/Lib/test/test_shutil.py
@@ -35,6 +35,7 @@
 
 try:
     import zipfile
+    import zlib
     ZIP_SUPPORT = True
 except ImportError:
     ZIP_SUPPORT = find_executable('zip')
@@ -460,7 +461,6 @@
         self.assertEqual(tarball, base_name + '.tar')
         self.assertTrue(os.path.isfile(tarball))
 
-    @unittest.skipUnless(zlib, "Requires zlib")
     @unittest.skipUnless(ZIP_SUPPORT, 'Need zip support to run')
     def test_make_zipfile(self):
         # creating something to zip
@@ -485,6 +485,7 @@
                     ['dist/', 'dist/file1', 'dist/file2',
                      'dist/sub/', 'dist/sub/file3', 'dist/sub2/',
                      'outer'])
+        support.unlink(res)
 
         with support.change_cwd(work_dir):
             base_name = os.path.abspath(rel_base_name)
@@ -498,7 +499,6 @@
                     ['dist/', 'dist/file1', 'dist/file2',
                      'dist/sub/', 'dist/sub/file3', 'dist/sub2/'])
 
-    @unittest.skipUnless(zlib, "Requires zlib")
     @unittest.skipUnless(ZIP_SUPPORT, 'Need zip support to run')
     @unittest.skipUnless(find_executable('zip'),
                          'Need the zip command to run')
@@ -524,7 +524,6 @@
             names2 = zf.namelist()
         self.assertEqual(sorted(names), sorted(names2))
 
-    @unittest.skipUnless(zlib, "Requires zlib")
     @unittest.skipUnless(ZIP_SUPPORT, 'Need zip support to run')
     @unittest.skipUnless(find_executable('unzip'),
                          'Need the unzip command to run')