]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
Issue #19544 and Issue #6516: Restore support for --user and --group parameters to...
authorAndrew Kuchling <amk@amk.ca>
Fri, 15 Nov 2013 18:01:52 +0000 (13:01 -0500)
committerAndrew Kuchling <amk@amk.ca>
Fri, 15 Nov 2013 18:01:52 +0000 (13:01 -0500)
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 1666436be0b1d15dd74d26d04f15c0a4f1dec2c3..9f7a38eda66d0431ec9fedb363541e6be27789a4 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``), under Unix you can specify 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 fcda08e20a2740a8702bf97a4f58ec8856781f98..306ef6a838d294c823ae5f30a46b4ac77a75cd0a 100644 (file)
@@ -18,15 +18,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': ''}
@@ -48,10 +88,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()
 
@@ -140,7 +193,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
@@ -153,6 +206,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:
@@ -174,6 +230,11 @@ 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
+
+    if format != 'zip':
+        kwargs['owner'] = owner
+        kwargs['group'] = group
+
     try:
         filename = func(base_name, base_dir, **kwargs)
     finally:
index 3ea08101acbfe418f7846f98d76276922e2667aa..c89d5efc45eb19e1cbd714871d3c2748545b2de6 100644 (file)
@@ -365,9 +365,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):
+    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)
+                                         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 38b169afd10eaabc15b8ddc2152017c0984921f2..6814a1c38200d177c35e13e067ebe0f65a38ff5c 100644 (file)
@@ -37,6 +37,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']
@@ -77,6 +83,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'
@@ -122,6 +130,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 eefdfea5ad3e6a19417fa0147878a9c27b8a450c..4405d12c058f0954d4f4f7da0d19bdd43135e17a 100644 (file)
@@ -33,6 +33,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']
@@ -48,6 +54,8 @@ class bdist_dumb(Command):
         self.dist_dir = None
         self.skip_build = None
         self.relative = 0
+        self.owner = None
+        self.group = None
 
     def finalize_options(self):
         if self.bdist_dir is None:
@@ -101,7 +109,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 116f67ef9be4d3742dd7be0113a3851edcf8beea..7ea3d5fa27ebb5371ce66b81eb5c9bd29cad59a9 100644 (file)
@@ -74,6 +74,10 @@ class sdist(Command):
         ('metadata-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:
@@ -444,7 +450,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 1afdd46225447d5518c191f92d439a80051ffc46..581c0cc841cc2321f5ba802496e703f61f7f5314 100644 (file)
@@ -15,6 +15,13 @@ from distutils.spawn import find_executable, spawn
 from distutils.tests import support
 from test.support import check_warnings, run_unittest, patch
 
+try:
+    import grp
+    import pwd
+    UID_GID_SUPPORT = True
+except ImportError:
+    UID_GID_SUPPORT = False
+
 try:
     import zipfile
     ZIP_SUPPORT = True
@@ -77,7 +84,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, target_name)
 
@@ -275,6 +282,58 @@ class ArchiveUtilTestCase(support.TempdirManager,
         finally:
             del ARCHIVE_FORMATS['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 e6359d6a8afae2e81bcf218668faf61101e2e7e6..6170a48fea0e08608ca2d04ba9e5afe36207e6b4 100644 (file)
@@ -14,6 +14,12 @@ try:
 except ImportError:
     ZLIB_SUPPORT = False
 
+try:
+    import grp
+    import pwd
+    UID_GID_SUPPORT = True
+except ImportError:
+    UID_GID_SUPPORT = False
 
 from distutils.command.sdist import sdist, show_formats
 from distutils.core import Distribution
@@ -425,6 +431,53 @@ class SDistTestCase(PyPIRCCommandTestCase):
         self.assertEqual(sorted(filenames), ['fake-1.0', 'fake-1.0/PKG-INFO',
                                              'fake-1.0/README.manual'])
 
+    @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 0083fe05b1d2353605cff36908f3b9910d5fca40..70988fee54d228b006dbaeab05da1475181ca076 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -47,6 +47,9 @@ Core and Builtins
 Library
 -------
 
+- Issue #19544 and #6516: Restore support for --user and --group parameters to
+  sdist command accidentally rolled back as part of the distutils2 rollback.
+
 - Issue #13674: Prevented time.strftime from crashing on Windows when given
   a year before 1900 and a format of %y.