]> granicus.if.org Git - python/commitdiff
#6516 added owner/group support for tarfiles in Distutils
authorTarek Ziadé <ziade.tarek@gmail.com>
Fri, 2 Oct 2009 23:49:48 +0000 (23:49 +0000)
committerTarek Ziadé <ziade.tarek@gmail.com>
Fri, 2 Oct 2009 23:49:48 +0000 (23:49 +0000)
Doc/distutils/sourcedist.rst
Lib/distutils/archive_util.py
Lib/distutils/cmd.py
Lib/distutils/command/bdist.py
Lib/distutils/command/bdist_dumb.py
Lib/distutils/command/sdist.py
Lib/distutils/tests/test_archive_util.py
Lib/distutils/tests/test_sdist.py
Misc/NEWS

index 0c786c5a0a7e84d052920eaf2021ebe0b778f3f5..94fa338c1940c339853f5bd2364fb6426a0617a0 100644 (file)
@@ -26,16 +26,16 @@ to create a gzipped tarball and a zip file.  The available formats are:
 +===========+=========================+=========+
 | ``zip``   | zip file (:file:`.zip`) | (1),(3) |
 +-----------+-------------------------+---------+
-| ``gztar`` | gzip'ed tar file        | (2),(4) |
+| ``gztar`` | gzip'ed tar file        | \(2)    |
 |           | (:file:`.tar.gz`)       |         |
 +-----------+-------------------------+---------+
-| ``bztar`` | bzip2'ed tar file       | \(4)    |
+| ``bztar`` | bzip2'ed tar file       |         |
 |           | (:file:`.tar.bz2`)      |         |
 +-----------+-------------------------+---------+
 | ``ztar``  | compressed tar file     | \(4)    |
 |           | (:file:`.tar.Z`)        |         |
 +-----------+-------------------------+---------+
-| ``tar``   | tar file (:file:`.tar`) | \(4)    |
+| ``tar``   | tar file (:file:`.tar`) |         |
 +-----------+-------------------------+---------+
 
 Notes:
@@ -51,8 +51,16 @@ Notes:
    of the standard Python library since Python 1.6)
 
 (4)
-   requires external utilities: :program:`tar` and possibly one of :program:`gzip`,
-   :program:`bzip2`, or :program:`compress`
+   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 ``tar``), you
+can specify under Unix the ``owner`` and ``group`` names that will be set for
+each member of the archive.
+
+For example, if you want all files of the archive to be owned by root::
+
+    python setup.py sdist --owner=root --group=root
 
 
 .. _manifest:
index 62150b0dddddb0b0e0377309b565627bd0861cad..75318eba616c2df73cda3b0ccea2fe7e50db6f06 100644 (file)
@@ -14,15 +14,55 @@ from distutils.spawn import spawn
 from distutils.dir_util import mkpath
 from distutils import log
 
-def make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0):
+try:
+    from pwd import getpwnam
+except AttributeError:
+    getpwnam = None
+
+try:
+    from grp import getgrnam
+except AttributeError:
+    getgrnam = None
+
+def _get_gid(name):
+    """Returns a gid, given a group name."""
+    if getgrnam is None or name is None:
+        return None
+    try:
+        result = getgrnam(name)
+    except KeyError:
+        result = None
+    if result is not None:
+        return result[2]
+    return None
+
+def _get_uid(name):
+    """Returns an uid, given a user name."""
+    if getpwnam is None or name is None:
+        return None
+    try:
+        result = getpwnam(name)
+    except KeyError:
+        result = None
+    if result is not None:
+        return result[2]
+    return None
+
+def make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0,
+                 owner=None, group=None):
     """Create a (possibly compressed) tar file from all the files under
     'base_dir'.
 
     'compress' must be "gzip" (the default), "compress", "bzip2", or None.
-    Both "tar" and the compression utility named by 'compress' must be on
-    the default program search path, so this is probably Unix-specific.
+    (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").
+
     Returns the output filename.
     """
     tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', None: '', 'compress': ''}
@@ -44,10 +84,23 @@ def make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0):
     import tarfile  # late import so Python build itself doesn't break
 
     log.info('Creating tar archive')
+
+    uid = _get_uid(owner)
+    gid = _get_gid(group)
+
+    def _set_uid_gid(tarinfo):
+        if gid is not None:
+            tarinfo.gid = gid
+            tarinfo.gname = group
+        if uid is not None:
+            tarinfo.uid = uid
+            tarinfo.uname = owner
+        return tarinfo
+
     if not dry_run:
         tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
         try:
-            tar.add(base_dir)
+            tar.add(base_dir, filter=_set_uid_gid)
         finally:
             tar.close()
 
@@ -138,7 +191,7 @@ def check_archive_formats(formats):
     return None
 
 def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
-                 dry_run=0):
+                 dry_run=0, owner=None, group=None):
     """Create an archive file (eg. zip or tar).
 
     'base_name' is the name of the file to create, minus any format-specific
@@ -151,6 +204,9 @@ def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
     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.
+
+    'owner' and 'group' are used when creating a tar archive. By default,
+    uses the current owner and group.
     """
     save_cwd = os.getcwd()
     if root_dir is not None:
@@ -172,8 +228,12 @@ def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
     func = format_info[0]
     for arg, val in format_info[1]:
         kwargs[arg] = val
-    filename = apply(func, (base_name, base_dir), kwargs)
 
+    if format != 'zip':
+        kwargs['owner'] = owner
+        kwargs['group'] = group
+
+    filename = apply(func, (base_name, base_dir), kwargs)
     if root_dir is not None:
         log.debug("changing back to '%s'", save_cwd)
         os.chdir(save_cwd)
index 869258babb309a315f74cfb4a59b293e60976928..dc40c14d61fd0f919c6d0d928a0c85d14a803562 100644 (file)
@@ -385,10 +385,11 @@ class Command:
         from distutils.spawn import spawn
         spawn(cmd, search_path, dry_run= self.dry_run)
 
-    def make_archive (self, base_name, format,
-                      root_dir=None, base_dir=None):
-        return archive_util.make_archive(
-            base_name, format, root_dir, base_dir, dry_run=self.dry_run)
+    def make_archive(self, base_name, format, root_dir=None, base_dir=None,
+                     owner=None, group=None):
+        return archive_util.make_archive(base_name, format, root_dir,
+                                         base_dir, dry_run=self.dry_run,
+                                         owner=owner, group=group)
 
     def make_file(self, infiles, outfile, func, args,
                   exec_msg=None, skip_msg=None, level=1):
index ec3375e5c457f5f76be00b1c2010ba87dd5cb51f..8e0ad080b916f1514a55106a69170bb56d8f3ef9 100644 (file)
@@ -40,6 +40,12 @@ class bdist(Command):
                      "[default: dist]"),
                     ('skip-build', None,
                      "skip rebuilding everything (for testing/debugging)"),
+                    ('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 = ['skip-build']
@@ -81,6 +87,8 @@ class bdist(Command):
         self.formats = None
         self.dist_dir = None
         self.skip_build = 0
+        self.group = None
+        self.owner = None
 
     def finalize_options(self):
         # have to finalize 'plat_name' before 'bdist_base'
@@ -126,6 +134,11 @@ class bdist(Command):
             if cmd_name not in self.no_format_option:
                 sub_cmd.format = self.formats[i]
 
+            # passing the owner and group names for tar archiving
+            if cmd_name == 'bdist_dumb':
+                sub_cmd.owner = self.owner
+                sub_cmd.group = self.group
+
             # If we're going to need to run this command again, tell it to
             # keep its temporary files around so subsequent runs go faster.
             if cmd_name in commands[i+1:]:
index 3ab0330f830041cc288e9c9c7970a728c4892527..358a70eacf0b2e158fc75edb08c1692c1373d511 100644 (file)
@@ -36,6 +36,12 @@ class bdist_dumb (Command):
                     ('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']
@@ -53,6 +59,8 @@ class bdist_dumb (Command):
         self.dist_dir = None
         self.skip_build = 0
         self.relative = 0
+        self.owner = None
+        self.group = None
 
     def finalize_options(self):
         if self.bdist_dir is None:
@@ -71,7 +79,7 @@ class bdist_dumb (Command):
                                    ('dist_dir', 'dist_dir'),
                                    ('plat_name', 'plat_name'))
 
-    def run (self):
+    def run(self):
         if not self.skip_build:
             self.run_command('build')
 
@@ -110,7 +118,8 @@ class bdist_dumb (Command):
 
         # Make the archive
         filename = self.make_archive(pseudoinstall_root,
-                                     self.format, root_dir=archive_root)
+                                     self.format, root_dir=archive_root,
+                                     owner=self.owner, group=self.group)
         if self.distribution.has_ext_modules():
             pyversion = get_python_version()
         else:
index c21f3917b67104b7da82b57d6bf55fdc5cf421fa..5c74e5329fc5df7fac0245c594ad9c8ba3e50423 100644 (file)
@@ -74,6 +74,10 @@ class sdist(Command):
         ('medata-check', None,
          "Ensure that all required elements of meta-data "
          "are supplied. Warn if any missing. [default]"),
+        ('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 = ['use-defaults', 'prune',
@@ -113,6 +117,8 @@ class sdist(Command):
 
         self.archive_files = None
         self.metadata_check = 1
+        self.owner = None
+        self.group = None
 
     def finalize_options(self):
         if self.manifest is None:
@@ -455,7 +461,8 @@ class sdist(Command):
             self.formats.append(self.formats.pop(self.formats.index('tar')))
 
         for fmt in self.formats:
-            file = self.make_archive(base_name, fmt, base_dir=base_dir)
+            file = self.make_archive(base_name, fmt, base_dir=base_dir,
+                                     owner=self.owner, group=self.group)
             archive_files.append(file)
             self.distribution.dist_files.append(('sdist', '', file))
 
index d6fb6769206367f3f786416c3ac2aa69b9308b34..3faf5e0d3bf90095529c1756b5cd2f138b07c91d 100644 (file)
@@ -13,6 +13,13 @@ from distutils.spawn import find_executable, spawn
 from distutils.tests import support
 from test.test_support import check_warnings
 
+try:
+    import grp
+    import pwd
+    UID_GID_SUPPORT = True
+except ImportError:
+    UID_GID_SUPPORT = False
+
 try:
     import zipfile
     ZIP_SUPPORT = True
@@ -30,7 +37,7 @@ class ArchiveUtilTestCase(support.TempdirManager,
                           support.LoggingSilencer,
                           unittest.TestCase):
 
-    @unittest.skipUnless(zlib, "Requires zlib")
+    @unittest.skipUnless(zlib, "requires zlib")
     def test_make_tarball(self):
         # creating something to tar
         tmpdir = self.mkdtemp()
@@ -41,7 +48,7 @@ class ArchiveUtilTestCase(support.TempdirManager,
 
         tmpdir2 = self.mkdtemp()
         unittest.skipUnless(splitdrive(tmpdir)[0] == splitdrive(tmpdir2)[0],
-                            "Source and target should be on same drive")
+                            "source and target should be on same drive")
 
         base_name = os.path.join(tmpdir2, 'archive')
 
@@ -202,6 +209,58 @@ class ArchiveUtilTestCase(support.TempdirManager,
         base_name = os.path.join(tmpdir, 'archive')
         self.assertRaises(ValueError, make_archive, base_name, 'xxx')
 
+    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
+        if UID_GID_SUPPORT:
+            group = grp.getgrgid(0)[0]
+            owner = pwd.getpwuid(0)[0]
+        else:
+            group = owner = 'root'
+
+        base_dir, root_dir, base_name =  self._create_files()
+        base_name = os.path.join(self.mkdtemp() , 'archive')
+        res = make_archive(base_name, 'zip', root_dir, base_dir, owner=owner,
+                           group=group)
+        self.assertTrue(os.path.exists(res))
+
+        res = make_archive(base_name, 'zip', root_dir, base_dir)
+        self.assertTrue(os.path.exists(res))
+
+        res = make_archive(base_name, 'tar', root_dir, base_dir,
+                           owner=owner, group=group)
+        self.assertTrue(os.path.exists(res))
+
+        res = make_archive(base_name, 'tar', root_dir, base_dir,
+                           owner='kjhkjhkjg', group='oihohoh')
+        self.assertTrue(os.path.exists(res))
+
+    @unittest.skipUnless(zlib, "Requires zlib")
+    @unittest.skipUnless(UID_GID_SUPPORT, "Requires grp and pwd support")
+    def test_tarfile_root_owner(self):
+        tmpdir, tmpdir2, base_name =  self._create_files()
+        old_dir = os.getcwd()
+        os.chdir(tmpdir)
+        group = grp.getgrgid(0)[0]
+        owner = pwd.getpwuid(0)[0]
+        try:
+            archive_name = make_tarball(base_name, 'dist', compress=None,
+                                        owner=owner, group=group)
+        finally:
+            os.chdir(old_dir)
+
+        # check if the compressed tarball was created
+        self.assertTrue(os.path.exists(archive_name))
+
+        # now checks the rights
+        archive = tarfile.open(archive_name)
+        try:
+            for member in archive.getmembers():
+                self.assertEquals(member.uid, 0)
+                self.assertEquals(member.gid, 0)
+        finally:
+            archive.close()
+
 def test_suite():
     return unittest.makeSuite(ArchiveUtilTestCase)
 
index c2feccb3ceade6f118fae5bc43d9fc2b945f8656..b8e3dca560e4445423978253a5fc07a71cea9839 100644 (file)
@@ -3,6 +3,7 @@ import os
 import unittest
 import shutil
 import zipfile
+import tarfile
 
 # zlib is not used here, but if it's not available
 # the tests that use zipfile may fail
@@ -11,6 +12,13 @@ try:
 except ImportError:
     zlib = None
 
+try:
+    import grp
+    import pwd
+    UID_GID_SUPPORT = True
+except ImportError:
+    UID_GID_SUPPORT = False
+
 from os.path import join
 import sys
 import tempfile
@@ -288,6 +296,52 @@ class SDistTestCase(PyPIRCCommandTestCase):
         cmd.formats = 'supazipa'
         self.assertRaises(DistutilsOptionError, cmd.finalize_options)
 
+    @unittest.skipUnless(zlib, "requires zlib")
+    @unittest.skipUnless(UID_GID_SUPPORT, "Requires grp and pwd support")
+    def test_make_distribution_owner_group(self):
+
+        # check if tar and gzip are installed
+        if (find_executable('tar') is None or
+            find_executable('gzip') is None):
+            return
+
+        # now building a sdist
+        dist, cmd = self.get_cmd()
+
+        # creating a gztar and specifying the owner+group
+        cmd.formats = ['gztar']
+        cmd.owner = pwd.getpwuid(0)[0]
+        cmd.group = grp.getgrgid(0)[0]
+        cmd.ensure_finalized()
+        cmd.run()
+
+        # making sure we have the good rights
+        archive_name = join(self.tmp_dir, 'dist', 'fake-1.0.tar.gz')
+        archive = tarfile.open(archive_name)
+        try:
+            for member in archive.getmembers():
+                self.assertEquals(member.uid, 0)
+                self.assertEquals(member.gid, 0)
+        finally:
+            archive.close()
+
+        # building a sdist again
+        dist, cmd = self.get_cmd()
+
+        # creating a gztar
+        cmd.formats = ['gztar']
+        cmd.ensure_finalized()
+        cmd.run()
+
+        # making sure we have the good rights
+        archive_name = join(self.tmp_dir, 'dist', 'fake-1.0.tar.gz')
+        archive = tarfile.open(archive_name)
+        try:
+            for member in archive.getmembers():
+                self.assertEquals(member.uid, os.getuid())
+                self.assertEquals(member.gid, os.getgid())
+        finally:
+            archive.close()
 
 def test_suite():
     return unittest.makeSuite(SDistTestCase)
index 50cf4d7256c4e02705f0a9fa3c40992c2a4e9223..dc54329db9a08eaf12b67d22418d3d298fd0f427 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -390,6 +390,9 @@ Core and Builtins
 Library
 -------
 
+- Issue #6516: Added owner/group support when creating tar archives in 
+  Distutils.
+
 - Issue #7031: Add TestCase.assert(Not)IsInstance() methods.
 
 - Issue #6790: Make it possible again to pass an `array.array` to