Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cpython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
cpython
Commits
3d6b023f
Commit
3d6b023f
authored
Feb 17, 2000
by
Greg Ward
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
The 'dist' command is dead -- long live the 'sdist' command!
parent
efd0694a
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
0 additions
and
558 deletions
+0
-558
Lib/distutils/command/dist.py
Lib/distutils/command/dist.py
+0
-558
No files found.
Lib/distutils/command/dist.py
deleted
100644 → 0
View file @
efd0694a
"""distutils.command.dist
Implements the Distutils 'dist' command (create a source distribution)."""
# created 1999/09/22, Greg Ward
__rcsid__
=
"$Id$"
import
sys
,
os
,
string
,
re
import
fnmatch
from
types
import
*
from
glob
import
glob
from
shutil
import
rmtree
from
distutils.core
import
Command
from
distutils.text_file
import
TextFile
from
distutils.errors
import
DistutilsExecError
# Possible modes of operation:
# - require an explicit manifest that lists every single file (presumably
# along with a way to auto-generate the manifest)
# - require an explicit manifest, but allow it to have globs or
# filename patterns of some kind (and also have auto-generation)
# - allow an explict manifest, but automatically augment it at runtime
# with the source files mentioned in 'packages', 'py_modules', and
# 'ext_modules' (and any other such things that might come along)
# I'm liking the third way. Possible gotchas:
# - redundant specification: 'packages' includes 'foo' and manifest
# includes 'foo/*.py'
# - obvious conflict: 'packages' includes 'foo' and manifest
# includes '! foo/*.py' (can't imagine why you'd want this)
# - subtle conflict: 'packages' includes 'foo' and manifest
# includes '! foo/bar.py' (this could well be desired: eg. exclude
# an experimental module from distribution)
# Syntax for the manifest file:
# - if a line is just a Unix-style glob by itself, it's a "simple include
# pattern": go find all files that match and add them to the list
# of files
# - if a line is a glob preceded by "!", then it's a "simple exclude
# pattern": go over the current list of files and exclude any that
# match the glob pattern
# - if a line consists of a directory name followed by zero or more
# glob patterns, then we'll recursively explore that directory tree
# - the glob patterns can be include (no punctuation) or exclude
# (prefixed by "!", no space)
# - if no patterns given or the first pattern is not an include pattern,
# then assume "*" -- ie. find everything (and then start applying
# the rest of the patterns)
# - the patterns are given in order of increasing precedence, ie.
# the *last* one to match a given file applies to it
#
# example (ignoring auto-augmentation!):
# distutils/*.py
# distutils/command/*.py
# ! distutils/bleeding_edge.py
# examples/*.py
# examples/README
#
# smarter way (that *will* include distutils/command/bleeding_edge.py!)
# distutils *.py
# ! distutils/bleeding_edge.py
# examples !*~ !*.py[co] (same as: examples * !*~ !*.py[co])
# test test_* *.txt !*~ !*.py[co]
# README
# setup.py
#
# The actual Distutils manifest (don't need to mention source files,
# README, setup.py -- they're automatically distributed!):
# examples !*~ !*.py[co]
# test !*~ !*.py[co]
# The algorithm that will make it work:
# files = stuff from 'packages', 'py_modules', 'ext_modules',
# plus README, setup.py, ... ?
# foreach pattern in manifest file:
# if simple-include-pattern: # "distutils/*.py"
# files.append (glob (pattern))
# elif simple-exclude-pattern: # "! distutils/foo*"
# xfiles = glob (pattern)
# remove all xfiles from files
# elif recursive-pattern: # "examples" (just a directory name)
# patterns = rest-of-words-on-line
# dir_files = list of all files under dir
# if patterns:
# if patterns[0] is an exclude-pattern:
# insert "*" at patterns[0]
# for file in dir_files:
# for dpattern in reverse (patterns):
# if file matches dpattern:
# if dpattern is an include-pattern:
# files.append (file)
# else:
# nothing, don't include it
# next file
# else:
# files.extend (dir_files) # ie. accept all of them
# Anyways, this is all implemented below -- BUT it is largely untested; I
# know it works for the simple case of distributing the Distutils, but
# haven't tried it on more complicated examples. Undoubtedly doing so will
# reveal bugs and cause delays, so I'm waiting until after I've released
# Distutils 0.1.
# Other things we need to look for in creating a source distribution:
# - make sure there's a README
# - make sure the distribution meta-info is supplied and non-empty
# (*must* have name, version, ((author and author_email) or
# (maintainer and maintainer_email)), url
#
# Frills:
# - make sure the setup script is called "setup.py"
# - make sure the README refers to "setup.py" (ie. has a line matching
# /^\s*python\s+setup\.py/)
# A crazy idea that conflicts with having/requiring 'version' in setup.py:
# - make sure there's a version number in the "main file" (main file
# is __init__.py of first package, or the first module if no packages,
# or the first extension module if no pure Python modules)
# - XXX how do we look for __version__ in an extension module?
# - XXX do we import and look for __version__? or just scan source for
# /^__version__\s*=\s*"[^"]+"/ ?
# - what about 'version_from' as an alternative to 'version' -- then
# we know just where to search for the version -- no guessing about
# what the "main file" is
class
Dist
(
Command
):
description
=
"create a source distribution (tarball, zip file, etc.)"
options
=
[(
'formats='
,
None
,
"formats for source distribution (tar, ztar, gztar, or zip)"
),
(
'manifest='
,
'm'
,
"name of manifest file"
),
(
'list-only'
,
'l'
,
"just list files that would be distributed"
),
(
'keep-tree'
,
'k'
,
"keep the distribution tree around after creating "
+
"archive file(s)"
),
]
default_format
=
{
'posix'
:
'gztar'
,
'nt'
:
'zip'
}
exclude_re
=
re
.
compile
(
r'\
s*!
\s*(\
S+)
') # for manifest lines
def set_default_options (self):
self.formats = None
self.manifest = None
self.list_only = 0
self.keep_tree = 0
def set_final_options (self):
if self.formats is None:
try:
self.formats = [self.default_format[os.name]]
except KeyError:
raise DistutilsPlatformError, \
"don'
t
know
how
to
build
source
distributions
on
" +
\
"
%
s
platform
" % os.name
elif type (self.formats) is StringType:
self.formats = string.split (self.formats, ',')
if self.manifest is None:
self.manifest = "
MANIFEST
"
def run (self):
self.check_metadata ()
self.files = []
self.find_defaults ()
self.read_manifest ()
if self.list_only:
for f in self.files:
print f
else:
self.make_distribution ()
def check_metadata (self):
dist = self.distribution
missing = []
for attr in ('name', 'version', 'url'):
if not (hasattr (dist, attr) and getattr (dist, attr)):
missing.append (attr)
if missing:
self.warn ("
missing
required
meta
-
data
:
" +
string.join (missing, "
,
"))
if dist.author:
if not dist.author_email:
self.warn ("
missing
meta
-
data
:
if
'author'
supplied
,
" +
"'author_email'
must
be
supplied
too
")
elif dist.maintainer:
if not dist.maintainer_email:
self.warn ("
missing
meta
-
data
:
if
'maintainer'
supplied
,
" +
"'maintainer_email'
must
be
supplied
too
")
else:
self.warn ("
missing
meta
-
data
:
either
(
author
and
author_email
)
" +
"
or
(
maintainer
and
maintainer_email
)
" +
"
must
be
supplied
")
# check_metadata ()
def find_defaults (self):
standards = [('README', 'README.txt'), 'setup.py']
for fn in standards:
if type (fn) is TupleType:
alts = fn
for fn in alts:
if os.path.exists (fn):
got_it = 1
self.files.append (fn)
break
if not got_it:
self.warn ("
standard
file
not
found
:
should
have
one
of
" +
string.join (alts, ', '))
else:
if os.path.exists (fn):
self.files.append (fn)
else:
self.warn ("
standard
file
%
s
not
found
" % fn)
optional = ['test/test*.py']
for pattern in optional:
files = filter (os.path.isfile, glob (pattern))
if files:
self.files.extend (files)
if self.distribution.packages or self.distribution.py_modules:
build_py = self.find_peer ('build_py')
build_py.ensure_ready ()
self.files.extend (build_py.get_source_files ())
if self.distribution.ext_modules:
build_ext = self.find_peer ('build_ext')
build_ext.ensure_ready ()
self.files.extend (build_ext.get_source_files ())
def open_manifest (self, filename):
return TextFile (filename,
strip_comments=1,
skip_blanks=1,
join_lines=1,
lstrip_ws=1,
rstrip_ws=1,
collapse_ws=1)
def search_dir (self, dir, patterns):
allfiles = findall (dir)
if patterns:
if patterns[0][0] == "
!
": # starts with an exclude spec?
patterns.insert (0, "
*
")# then accept anything that isn't
# explicitly excluded
act_patterns = [] # "
action
-
patterns
": (include,regexp)
# tuples where include is a boolean
for pattern in patterns:
if pattern[0] == '!':
act_patterns.append
\
((0, re.compile (fnmatch.translate (pattern[1:]))))
else:
act_patterns.append
\
((1, re.compile (fnmatch.translate (pattern))))
act_patterns.reverse()
files = []
for file in allfiles:
for (include,regexp) in act_patterns:
if regexp.search (file):
if include:
files.append (file)
break # continue to next file
else:
files = allfiles
return files
# search_dir ()
def exclude_files (self, pattern):
regexp = re.compile (fnmatch.translate (pattern))
for i in range (len (self.files)-1, -1, -1):
if regexp.search (self.files[i]):
del self.files[i]
def read_manifest (self):
# self.files had better already be defined (and hold the
# "
automatically
found
" files -- Python modules and extensions,
# README, setup script, ...)
assert self.files is not None
try:
manifest = self.open_manifest (self.manifest)
except IOError, exc:
if type (exc) is InstanceType and hasattr (exc, 'strerror'):
msg = "
could
not
open
MANIFEST
(
%
s
)
" %
\
string.lower (exc.strerror)
else:
msg = "
could
not
open
MANIFST
"
self.warn (msg + "
:
using
default
file
list
")
return
while 1:
pattern = manifest.readline()
if pattern is None: # end of file
break
# Cases:
# 1) simple-include: "
*
.
py
", "
foo
/*
.
py
", "
doc
/*
.
html
", "
FAQ
"
# 2) simple-exclude: same, prefaced by !
# 3) recursive: multi-word line, first word a directory
exclude = self.exclude_re.match (pattern)
if exclude:
pattern = exclude.group (1)
words = string.split (pattern)
assert words # must have something!
if os.name != 'posix':
words[0] = apply (os.path.join, string.split (words[0], '/'))
# First word is a directory, possibly with include/exclude
# patterns making up the rest of the line: it's a recursive
# pattern
if os.path.isdir (words[0]):
if exclude:
manifest.warn ("
exclude
(
!
)
doesn
't apply to " +
"whole directory trees")
continue
dir_files = self.search_dir (words[0], words[1:])
self.files.extend (dir_files)
# Multiple words in pattern: that'
s
a
no
-
no
unless
the
first
# word is a directory name
elif
len
(
words
)
>
1
:
manifest
.
warn
(
"can't have multiple words unless first word "
+
"('%s') is a directory name"
%
words
[
0
])
continue
# Single word, no bang: it's a "simple include pattern"
elif
not
exclude
:
matches
=
filter
(
os
.
path
.
isfile
,
glob
(
pattern
))
if
matches
:
self
.
files
.
extend
(
matches
)
else
:
manifest
.
warn
(
"no matches for '%s' found"
%
pattern
)
# Single word prefixed with a bang: it's a "simple exclude pattern"
else
:
if
self
.
exclude_files
(
pattern
)
==
0
:
manifest
.
warn
(
"no files excluded by '%s'"
%
pattern
)
# if/elif/.../else on 'pattern'
# loop over lines of 'manifest'
# read_manifest ()
def
make_release_tree
(
self
,
base_dir
,
files
):
# XXX this is Unix-specific
# First get the list of directories to create
need_dir
=
{}
for
file
in
files
:
need_dir
[
os
.
path
.
join
(
base_dir
,
os
.
path
.
dirname
(
file
))]
=
1
need_dirs
=
need_dir
.
keys
()
need_dirs
.
sort
()
# Now create them
for
dir
in
need_dirs
:
self
.
mkpath
(
dir
)
# And walk over the list of files, either making a hard link (if
# os.link exists) to each one that doesn't already exist in its
# corresponding location under 'base_dir', or copying each file
# that's out-of-date in 'base_dir'. (Usually, all files will be
# out-of-date, because by default we blow away 'base_dir' when
# we're done making the distribution archives.)
try
:
link
=
os
.
link
msg
=
"making hard links in %s..."
%
base_dir
except
AttributeError
:
link
=
0
msg
=
"copying files to %s..."
%
base_dir
self
.
announce
(
msg
)
for
file
in
files
:
dest
=
os
.
path
.
join
(
base_dir
,
file
)
if
link
:
if
not
os
.
path
.
exists
(
dest
):
self
.
execute
(
os
.
link
,
(
file
,
dest
),
"linking %s -> %s"
%
(
file
,
dest
))
else
:
self
.
copy_file
(
file
,
dest
)
# make_release_tree ()
def
nuke_release_tree
(
self
,
base_dir
):
try
:
self
.
execute
(
rmtree
,
(
base_dir
,),
"removing %s"
%
base_dir
)
except
(
IOError
,
OSError
),
exc
:
if
exc
.
filename
:
msg
=
"error removing %s: %s (%s)"
%
\
(
base_dir
,
exc
.
strerror
,
exc
.
filename
)
else
:
msg
=
"error removing %s: %s"
%
(
base_dir
,
exc
.
strerror
)
self
.
warn
(
msg
)
def
make_tarball
(
self
,
base_dir
,
compress
=
"gzip"
):
# XXX GNU tar 1.13 has a nifty option to add a prefix directory.
# It's pretty new, though, so we certainly can't require it --
# but it would be nice to take advantage of it to skip the
# "create a tree of hardlinks" step! (Would also be nice to
# detect GNU tar to use its 'z' option and save a step.)
if
compress
is
not
None
and
compress
not
in
(
'gzip'
,
'compress'
):
raise
ValueError
,
\
"if given, 'compress' must be 'gzip' or 'compress'"
archive_name
=
base_dir
+
".tar"
self
.
spawn
([
"tar"
,
"-cf"
,
archive_name
,
base_dir
])
if
compress
:
self
.
spawn
([
compress
,
archive_name
])
def
make_zipfile
(
self
,
base_dir
):
# This initially assumed the Unix 'zip' utility -- but
# apparently InfoZIP's zip.exe works the same under Windows, so
# no changes needed!
try
:
self
.
spawn
([
"zip"
,
"-r"
,
base_dir
+
".zip"
,
base_dir
])
except
DistutilsExecError
:
# XXX really should distinguish between "couldn't find
# external 'zip' command" and "zip failed" -- shouldn't try
# again in the latter case. (I think fixing this will
# require some cooperation from the spawn module -- perhaps
# a utility function to search the path, so we can fallback
# on zipfile.py without the failed spawn.)
try
:
import
zipfile
except
ImportError
:
raise
DistutilsExecError
,
\
(
"unable to create zip file '%s.zip': "
+
"could neither find a standalone zip utility nor "
+
"import the 'zipfile' module"
)
%
base_dir
z
=
zipfile
.
ZipFile
(
base_dir
+
".zip"
,
"wb"
,
compression
=
zipfile
.
ZIP_DEFLATED
)
def
visit
(
z
,
dirname
,
names
):
for
name
in
names
:
path
=
os
.
path
.
join
(
dirname
,
name
)
if
os
.
path
.
isfile
(
path
):
z
.
write
(
path
,
path
)
os
.
path
.
walk
(
base_dir
,
visit
,
z
)
z
.
close
()
def
make_distribution
(
self
):
# Don't warn about missing meta-data here -- should be done
# elsewhere.
name
=
self
.
distribution
.
name
or
"UNKNOWN"
version
=
self
.
distribution
.
version
if
version
:
base_dir
=
"%s-%s"
%
(
name
,
version
)
else
:
base_dir
=
name
# Remove any files that match "base_dir" from the fileset -- we
# don't want to go distributing the distribution inside itself!
self
.
exclude_files
(
base_dir
+
"*"
)
self
.
make_release_tree
(
base_dir
,
self
.
files
)
for
fmt
in
self
.
formats
:
if
fmt
==
'gztar'
:
self
.
make_tarball
(
base_dir
,
compress
=
'gzip'
)
elif
fmt
==
'ztar'
:
self
.
make_tarball
(
base_dir
,
compress
=
'compress'
)
elif
fmt
==
'tar'
:
self
.
make_tarball
(
base_dir
,
compress
=
None
)
elif
fmt
==
'zip'
:
self
.
make_zipfile
(
base_dir
)
if
not
self
.
keep_tree
:
self
.
nuke_release_tree
(
base_dir
)
# class Dist
# ----------------------------------------------------------------------
# Utility functions
def
findall
(
dir
=
os
.
curdir
):
"""Find all files under 'dir' and return the sorted list of full
filenames (relative to 'dir')."""
list
=
[]
stack
=
[
dir
]
pop
=
stack
.
pop
push
=
stack
.
append
while
stack
:
dir
=
pop
()
names
=
os
.
listdir
(
dir
)
for
name
in
names
:
fullname
=
os
.
path
.
join
(
dir
,
name
)
list
.
append
(
fullname
)
if
os
.
path
.
isdir
(
fullname
)
and
not
os
.
path
.
islink
(
fullname
):
push
(
fullname
)
list
.
sort
()
return
list
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment