[Python-checkins] r75192 - in python/trunk: 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

tarek.ziade python-checkins at python.org
Sat Oct 3 01:49:48 CEST 2009


Author: tarek.ziade
Date: Sat Oct  3 01:49:48 2009
New Revision: 75192

Log:
#6516 added owner/group support for tarfiles in Distutils

Modified:
   python/trunk/Doc/distutils/sourcedist.rst
   python/trunk/Lib/distutils/archive_util.py
   python/trunk/Lib/distutils/cmd.py
   python/trunk/Lib/distutils/command/bdist.py
   python/trunk/Lib/distutils/command/bdist_dumb.py
   python/trunk/Lib/distutils/command/sdist.py
   python/trunk/Lib/distutils/tests/test_archive_util.py
   python/trunk/Lib/distutils/tests/test_sdist.py
   python/trunk/Misc/NEWS

Modified: python/trunk/Doc/distutils/sourcedist.rst
==============================================================================
--- python/trunk/Doc/distutils/sourcedist.rst	(original)
+++ python/trunk/Doc/distutils/sourcedist.rst	Sat Oct  3 01:49:48 2009
@@ -26,16 +26,16 @@
 +===========+=========================+=========+
 | ``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 @@
    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:

Modified: python/trunk/Lib/distutils/archive_util.py
==============================================================================
--- python/trunk/Lib/distutils/archive_util.py	(original)
+++ python/trunk/Lib/distutils/archive_util.py	Sat Oct  3 01:49:48 2009
@@ -14,15 +14,55 @@
 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 @@
     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 @@
     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 @@
     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 @@
     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)

Modified: python/trunk/Lib/distutils/cmd.py
==============================================================================
--- python/trunk/Lib/distutils/cmd.py	(original)
+++ python/trunk/Lib/distutils/cmd.py	Sat Oct  3 01:49:48 2009
@@ -385,10 +385,11 @@
         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):

Modified: python/trunk/Lib/distutils/command/bdist.py
==============================================================================
--- python/trunk/Lib/distutils/command/bdist.py	(original)
+++ python/trunk/Lib/distutils/command/bdist.py	Sat Oct  3 01:49:48 2009
@@ -40,6 +40,12 @@
                      "[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 @@
         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 @@
             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:]:

Modified: python/trunk/Lib/distutils/command/bdist_dumb.py
==============================================================================
--- python/trunk/Lib/distutils/command/bdist_dumb.py	(original)
+++ python/trunk/Lib/distutils/command/bdist_dumb.py	Sat Oct  3 01:49:48 2009
@@ -36,6 +36,12 @@
                     ('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 @@
         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 @@
                                    ('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 @@
 
         # 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:

Modified: python/trunk/Lib/distutils/command/sdist.py
==============================================================================
--- python/trunk/Lib/distutils/command/sdist.py	(original)
+++ python/trunk/Lib/distutils/command/sdist.py	Sat Oct  3 01:49:48 2009
@@ -74,6 +74,10 @@
         ('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 @@
 
         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 @@
             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))
 

Modified: python/trunk/Lib/distutils/tests/test_archive_util.py
==============================================================================
--- python/trunk/Lib/distutils/tests/test_archive_util.py	(original)
+++ python/trunk/Lib/distutils/tests/test_archive_util.py	Sat Oct  3 01:49:48 2009
@@ -14,6 +14,13 @@
 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
 except ImportError:
@@ -30,7 +37,7 @@
                           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 @@
 
         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 @@
         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)
 

Modified: python/trunk/Lib/distutils/tests/test_sdist.py
==============================================================================
--- python/trunk/Lib/distutils/tests/test_sdist.py	(original)
+++ python/trunk/Lib/distutils/tests/test_sdist.py	Sat Oct  3 01:49:48 2009
@@ -3,6 +3,7 @@
 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 @@
 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 @@
         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)

Modified: python/trunk/Misc/NEWS
==============================================================================
--- python/trunk/Misc/NEWS	(original)
+++ python/trunk/Misc/NEWS	Sat Oct  3 01:49:48 2009
@@ -390,6 +390,9 @@
 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


More information about the Python-checkins mailing list