Merged revisions 75192 via svnmerge from

svn+ssh://pythondev@svn.python.org/python/trunk

........
  r75192 | tarek.ziade | 2009-10-03 01:49:48 +0200 (Sat, 03 Oct 2009) | 1 line

  #6516 added owner/group support for tarfiles in Distutils
........
This commit is contained in:
Tarek Ziadé 2009-10-02 23:56:02 +00:00
parent 574b1d6a60
commit 05b303443b
9 changed files with 233 additions and 17 deletions

View 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) | | ``zip`` | zip file (:file:`.zip`) | (1),(3) |
+-----------+-------------------------+---------+ +-----------+-------------------------+---------+
| ``gztar`` | gzip'ed tar file | (2),(4) | | ``gztar`` | gzip'ed tar file | \(2) |
| | (:file:`.tar.gz`) | | | | (:file:`.tar.gz`) | |
+-----------+-------------------------+---------+ +-----------+-------------------------+---------+
| ``bztar`` | bzip2'ed tar file | \(4) | | ``bztar`` | bzip2'ed tar file | |
| | (:file:`.tar.bz2`) | | | | (:file:`.tar.bz2`) | |
+-----------+-------------------------+---------+ +-----------+-------------------------+---------+
| ``ztar`` | compressed tar file | \(4) | | ``ztar`` | compressed tar file | \(4) |
| | (:file:`.tar.Z`) | | | | (:file:`.tar.Z`) | |
+-----------+-------------------------+---------+ +-----------+-------------------------+---------+
| ``tar`` | tar file (:file:`.tar`) | \(4) | | ``tar`` | tar file (:file:`.tar`) | |
+-----------+-------------------------+---------+ +-----------+-------------------------+---------+
Notes: Notes:
@ -51,8 +51,16 @@ Notes:
of the standard Python library since Python 1.6) of the standard Python library since Python 1.6)
(4) (4)
requires external utilities: :program:`tar` and possibly one of :program:`gzip`, requires the :program:`compress` program. Notice that this format is now
:program:`bzip2`, or :program:`compress` 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: .. _manifest:

View File

@ -14,15 +14,55 @@ from distutils.spawn import spawn
from distutils.dir_util import mkpath from distutils.dir_util import mkpath
from distutils import log 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 """Create a (possibly compressed) tar file from all the files under
'base_dir'. 'base_dir'.
'compress' must be "gzip" (the default), "compress", "bzip2", or None. 'compress' must be "gzip" (the default), "compress", "bzip2", or None.
Both "tar" and the compression utility named by 'compress' must be on (compress will be deprecated in Python 3.2)
the default program search path, so this is probably Unix-specific.
'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 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" or ".Z").
Returns the output filename. Returns the output filename.
""" """
tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', None: '', 'compress': ''} 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 import tarfile # late import so Python build itself doesn't break
log.info('Creating tar archive') 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: if not dry_run:
tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress]) tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
try: try:
tar.add(base_dir) tar.add(base_dir, filter=_set_uid_gid)
finally: finally:
tar.close() tar.close()
@ -137,7 +190,7 @@ def check_archive_formats(formats):
return None return None
def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0, 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). """Create an archive file (eg. zip or tar).
'base_name' is the name of the file to create, minus any format-specific 'base_name' is the name of the file to create, minus any format-specific
@ -150,6 +203,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 ie. 'base_dir' will be the common prefix of all files and
directories in the archive. 'root_dir' and 'base_dir' both default directories in the archive. 'root_dir' and 'base_dir' both default
to the current directory. Returns the name of the archive file. 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() save_cwd = os.getcwd()
if root_dir is not None: if root_dir is not None:
@ -171,6 +227,11 @@ def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0,
func = format_info[0] func = format_info[0]
for arg, val in format_info[1]: for arg, val in format_info[1]:
kwargs[arg] = val kwargs[arg] = val
if format != 'zip':
kwargs['owner'] = owner
kwargs['group'] = group
filename = func(base_name, base_dir, **kwargs) filename = func(base_name, base_dir, **kwargs)
if root_dir is not None: if root_dir is not None:

View File

@ -367,9 +367,11 @@ class Command:
from distutils.spawn import spawn from distutils.spawn import spawn
spawn(cmd, search_path, dry_run=self.dry_run) 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,
return archive_util.make_archive(base_name, format, root_dir, base_dir, owner=None, group=None):
dry_run=self.dry_run) 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, def make_file(self, infiles, outfile, func, args,
exec_msg=None, skip_msg=None, level=1): exec_msg=None, skip_msg=None, level=1):

View File

@ -39,6 +39,12 @@ class bdist(Command):
"[default: dist]"), "[default: dist]"),
('skip-build', None, ('skip-build', None,
"skip rebuilding everything (for testing/debugging)"), "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'] boolean_options = ['skip-build']
@ -80,6 +86,8 @@ class bdist(Command):
self.formats = None self.formats = None
self.dist_dir = None self.dist_dir = None
self.skip_build = 0 self.skip_build = 0
self.group = None
self.owner = None
def finalize_options(self): def finalize_options(self):
# have to finalize 'plat_name' before 'bdist_base' # have to finalize 'plat_name' before 'bdist_base'
@ -125,6 +133,11 @@ class bdist(Command):
if cmd_name not in self.no_format_option: if cmd_name not in self.no_format_option:
sub_cmd.format = self.formats[i] 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 # If we're going to need to run this command again, tell it to
# keep its temporary files around so subsequent runs go faster. # keep its temporary files around so subsequent runs go faster.
if cmd_name in commands[i+1:]: if cmd_name in commands[i+1:]:

View File

@ -36,6 +36,12 @@ class bdist_dumb(Command):
('relative', None, ('relative', None,
"build the archive using relative paths" "build the archive using relative paths"
"(default: false)"), "(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'] boolean_options = ['keep-temp', 'skip-build', 'relative']
@ -52,6 +58,8 @@ class bdist_dumb(Command):
self.dist_dir = None self.dist_dir = None
self.skip_build = 0 self.skip_build = 0
self.relative = 0 self.relative = 0
self.owner = None
self.group = None
def finalize_options(self): def finalize_options(self):
if self.bdist_dir is None: if self.bdist_dir is None:
@ -109,7 +117,8 @@ class bdist_dumb(Command):
# Make the archive # Make the archive
filename = self.make_archive(pseudoinstall_root, 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(): if self.distribution.has_ext_modules():
pyversion = get_python_version() pyversion = get_python_version()
else: else:

View File

@ -75,6 +75,10 @@ class sdist(Command):
('medata-check', None, ('medata-check', None,
"Ensure that all required elements of meta-data " "Ensure that all required elements of meta-data "
"are supplied. Warn if any missing. [default]"), "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', boolean_options = ['use-defaults', 'prune',
@ -114,6 +118,8 @@ class sdist(Command):
self.archive_files = None self.archive_files = None
self.metadata_check = 1 self.metadata_check = 1
self.owner = None
self.group = None
def finalize_options(self): def finalize_options(self):
if self.manifest is None: if self.manifest is None:
@ -449,7 +455,8 @@ class sdist(Command):
self.formats.append(self.formats.pop(self.formats.index('tar'))) self.formats.append(self.formats.pop(self.formats.index('tar')))
for fmt in self.formats: 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) archive_files.append(file)
self.distribution.dist_files.append(('sdist', '', file)) self.distribution.dist_files.append(('sdist', '', file))

View File

@ -13,6 +13,13 @@ from distutils.spawn import find_executable, spawn
from distutils.tests import support from distutils.tests import support
from test.support import check_warnings from test.support import check_warnings
try:
import grp
import pwd
UID_GID_SUPPORT = True
except ImportError:
UID_GID_SUPPORT = False
try: try:
import zipfile import zipfile
ZIP_SUPPORT = True ZIP_SUPPORT = True
@ -30,7 +37,7 @@ class ArchiveUtilTestCase(support.TempdirManager,
support.LoggingSilencer, support.LoggingSilencer,
unittest.TestCase): unittest.TestCase):
@unittest.skipUnless(zlib, "Requires zlib") @unittest.skipUnless(zlib, "requires zlib")
def test_make_tarball(self): def test_make_tarball(self):
# creating something to tar # creating something to tar
tmpdir = self.mkdtemp() tmpdir = self.mkdtemp()
@ -41,7 +48,7 @@ class ArchiveUtilTestCase(support.TempdirManager,
tmpdir2 = self.mkdtemp() tmpdir2 = self.mkdtemp()
unittest.skipUnless(splitdrive(tmpdir)[0] == splitdrive(tmpdir2)[0], 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') base_name = os.path.join(tmpdir2, 'archive')
@ -202,6 +209,58 @@ class ArchiveUtilTestCase(support.TempdirManager,
base_name = os.path.join(tmpdir, 'archive') base_name = os.path.join(tmpdir, 'archive')
self.assertRaises(ValueError, make_archive, base_name, 'xxx') 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(): def test_suite():
return unittest.makeSuite(ArchiveUtilTestCase) return unittest.makeSuite(ArchiveUtilTestCase)

View File

@ -3,6 +3,7 @@ import os
import unittest import unittest
import shutil import shutil
import zipfile import zipfile
import tarfile
# zlib is not used here, but if it's not available # zlib is not used here, but if it's not available
# the tests that use zipfile may fail # the tests that use zipfile may fail
@ -11,6 +12,13 @@ try:
except ImportError: except ImportError:
zlib = None zlib = None
try:
import grp
import pwd
UID_GID_SUPPORT = True
except ImportError:
UID_GID_SUPPORT = False
from os.path import join from os.path import join
import sys import sys
import tempfile import tempfile
@ -288,6 +296,52 @@ class SDistTestCase(PyPIRCCommandTestCase):
cmd.formats = 'supazipa' cmd.formats = 'supazipa'
self.assertRaises(DistutilsOptionError, cmd.finalize_options) 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(): def test_suite():
return unittest.makeSuite(SDistTestCase) return unittest.makeSuite(SDistTestCase)

View File

@ -1080,6 +1080,9 @@ Core and Builtins
Library Library
------- -------
- Issue #6516: Added owner/group support when creating tar archives in
Distutils.
- Issue #6954: Fixed crash when using DISTUTILS_DEBUG flag in Distutils. - Issue #6954: Fixed crash when using DISTUTILS_DEBUG flag in Distutils.
- Issue #6163: Fixed HP-UX runtime library dir options in - Issue #6163: Fixed HP-UX runtime library dir options in