ยปCore Development>Code coverage>Lib/distutils/command/sdist.py

Python code coverage for Lib/distutils/command/sdist.py

#countcontent
1n/a"""distutils.command.sdist
2n/a
3n/aImplements the Distutils 'sdist' command (create a source distribution)."""
4n/a
5n/aimport os
6n/aimport sys
7n/afrom glob import glob
8n/afrom warnings import warn
9n/a
10n/afrom distutils.core import Command
11n/afrom distutils import dir_util
12n/afrom distutils import file_util
13n/afrom distutils import archive_util
14n/afrom distutils.text_file import TextFile
15n/afrom distutils.filelist import FileList
16n/afrom distutils import log
17n/afrom distutils.util import convert_path
18n/afrom distutils.errors import DistutilsTemplateError, DistutilsOptionError
19n/a
20n/a
21n/adef show_formats():
22n/a """Print all possible values for the 'formats' option (used by
23n/a the "--help-formats" command-line option).
24n/a """
25n/a from distutils.fancy_getopt import FancyGetopt
26n/a from distutils.archive_util import ARCHIVE_FORMATS
27n/a formats = []
28n/a for format in ARCHIVE_FORMATS.keys():
29n/a formats.append(("formats=" + format, None,
30n/a ARCHIVE_FORMATS[format][2]))
31n/a formats.sort()
32n/a FancyGetopt(formats).print_help(
33n/a "List of available source distribution formats:")
34n/a
35n/a
36n/aclass sdist(Command):
37n/a
38n/a description = "create a source distribution (tarball, zip file, etc.)"
39n/a
40n/a def checking_metadata(self):
41n/a """Callable used for the check sub-command.
42n/a
43n/a Placed here so user_options can view it"""
44n/a return self.metadata_check
45n/a
46n/a user_options = [
47n/a ('template=', 't',
48n/a "name of manifest template file [default: MANIFEST.in]"),
49n/a ('manifest=', 'm',
50n/a "name of manifest file [default: MANIFEST]"),
51n/a ('use-defaults', None,
52n/a "include the default file set in the manifest "
53n/a "[default; disable with --no-defaults]"),
54n/a ('no-defaults', None,
55n/a "don't include the default file set"),
56n/a ('prune', None,
57n/a "specifically exclude files/directories that should not be "
58n/a "distributed (build tree, RCS/CVS dirs, etc.) "
59n/a "[default; disable with --no-prune]"),
60n/a ('no-prune', None,
61n/a "don't automatically exclude anything"),
62n/a ('manifest-only', 'o',
63n/a "just regenerate the manifest and then stop "
64n/a "(implies --force-manifest)"),
65n/a ('force-manifest', 'f',
66n/a "forcibly regenerate the manifest and carry on as usual. "
67n/a "Deprecated: now the manifest is always regenerated."),
68n/a ('formats=', None,
69n/a "formats for source distribution (comma-separated list)"),
70n/a ('keep-temp', 'k',
71n/a "keep the distribution tree around after creating " +
72n/a "archive file(s)"),
73n/a ('dist-dir=', 'd',
74n/a "directory to put the source distribution archive(s) in "
75n/a "[default: dist]"),
76n/a ('metadata-check', None,
77n/a "Ensure that all required elements of meta-data "
78n/a "are supplied. Warn if any missing. [default]"),
79n/a ('owner=', 'u',
80n/a "Owner name used when creating a tar file [default: current user]"),
81n/a ('group=', 'g',
82n/a "Group name used when creating a tar file [default: current group]"),
83n/a ]
84n/a
85n/a boolean_options = ['use-defaults', 'prune',
86n/a 'manifest-only', 'force-manifest',
87n/a 'keep-temp', 'metadata-check']
88n/a
89n/a help_options = [
90n/a ('help-formats', None,
91n/a "list available distribution formats", show_formats),
92n/a ]
93n/a
94n/a negative_opt = {'no-defaults': 'use-defaults',
95n/a 'no-prune': 'prune' }
96n/a
97n/a sub_commands = [('check', checking_metadata)]
98n/a
99n/a READMES = 'README', 'README.txt'
100n/a
101n/a def initialize_options(self):
102n/a # 'template' and 'manifest' are, respectively, the names of
103n/a # the manifest template and manifest file.
104n/a self.template = None
105n/a self.manifest = None
106n/a
107n/a # 'use_defaults': if true, we will include the default file set
108n/a # in the manifest
109n/a self.use_defaults = 1
110n/a self.prune = 1
111n/a
112n/a self.manifest_only = 0
113n/a self.force_manifest = 0
114n/a
115n/a self.formats = ['gztar']
116n/a self.keep_temp = 0
117n/a self.dist_dir = None
118n/a
119n/a self.archive_files = None
120n/a self.metadata_check = 1
121n/a self.owner = None
122n/a self.group = None
123n/a
124n/a def finalize_options(self):
125n/a if self.manifest is None:
126n/a self.manifest = "MANIFEST"
127n/a if self.template is None:
128n/a self.template = "MANIFEST.in"
129n/a
130n/a self.ensure_string_list('formats')
131n/a
132n/a bad_format = archive_util.check_archive_formats(self.formats)
133n/a if bad_format:
134n/a raise DistutilsOptionError(
135n/a "unknown archive format '%s'" % bad_format)
136n/a
137n/a if self.dist_dir is None:
138n/a self.dist_dir = "dist"
139n/a
140n/a def run(self):
141n/a # 'filelist' contains the list of files that will make up the
142n/a # manifest
143n/a self.filelist = FileList()
144n/a
145n/a # Run sub commands
146n/a for cmd_name in self.get_sub_commands():
147n/a self.run_command(cmd_name)
148n/a
149n/a # Do whatever it takes to get the list of files to process
150n/a # (process the manifest template, read an existing manifest,
151n/a # whatever). File list is accumulated in 'self.filelist'.
152n/a self.get_file_list()
153n/a
154n/a # If user just wanted us to regenerate the manifest, stop now.
155n/a if self.manifest_only:
156n/a return
157n/a
158n/a # Otherwise, go ahead and create the source distribution tarball,
159n/a # or zipfile, or whatever.
160n/a self.make_distribution()
161n/a
162n/a def check_metadata(self):
163n/a """Deprecated API."""
164n/a warn("distutils.command.sdist.check_metadata is deprecated, \
165n/a use the check command instead", PendingDeprecationWarning)
166n/a check = self.distribution.get_command_obj('check')
167n/a check.ensure_finalized()
168n/a check.run()
169n/a
170n/a def get_file_list(self):
171n/a """Figure out the list of files to include in the source
172n/a distribution, and put it in 'self.filelist'. This might involve
173n/a reading the manifest template (and writing the manifest), or just
174n/a reading the manifest, or just using the default file set -- it all
175n/a depends on the user's options.
176n/a """
177n/a # new behavior when using a template:
178n/a # the file list is recalculated every time because
179n/a # even if MANIFEST.in or setup.py are not changed
180n/a # the user might have added some files in the tree that
181n/a # need to be included.
182n/a #
183n/a # This makes --force the default and only behavior with templates.
184n/a template_exists = os.path.isfile(self.template)
185n/a if not template_exists and self._manifest_is_not_generated():
186n/a self.read_manifest()
187n/a self.filelist.sort()
188n/a self.filelist.remove_duplicates()
189n/a return
190n/a
191n/a if not template_exists:
192n/a self.warn(("manifest template '%s' does not exist " +
193n/a "(using default file list)") %
194n/a self.template)
195n/a self.filelist.findall()
196n/a
197n/a if self.use_defaults:
198n/a self.add_defaults()
199n/a
200n/a if template_exists:
201n/a self.read_template()
202n/a
203n/a if self.prune:
204n/a self.prune_file_list()
205n/a
206n/a self.filelist.sort()
207n/a self.filelist.remove_duplicates()
208n/a self.write_manifest()
209n/a
210n/a def add_defaults(self):
211n/a """Add all the default files to self.filelist:
212n/a - README or README.txt
213n/a - setup.py
214n/a - test/test*.py
215n/a - all pure Python modules mentioned in setup script
216n/a - all files pointed by package_data (build_py)
217n/a - all files defined in data_files.
218n/a - all files defined as scripts.
219n/a - all C sources listed as part of extensions or C libraries
220n/a in the setup script (doesn't catch C headers!)
221n/a Warns if (README or README.txt) or setup.py are missing; everything
222n/a else is optional.
223n/a """
224n/a self._add_defaults_standards()
225n/a self._add_defaults_optional()
226n/a self._add_defaults_python()
227n/a self._add_defaults_data_files()
228n/a self._add_defaults_ext()
229n/a self._add_defaults_c_libs()
230n/a self._add_defaults_scripts()
231n/a
232n/a @staticmethod
233n/a def _cs_path_exists(fspath):
234n/a """
235n/a Case-sensitive path existence check
236n/a
237n/a >>> sdist._cs_path_exists(__file__)
238n/a True
239n/a >>> sdist._cs_path_exists(__file__.upper())
240n/a False
241n/a """
242n/a if not os.path.exists(fspath):
243n/a return False
244n/a # make absolute so we always have a directory
245n/a abspath = os.path.abspath(fspath)
246n/a directory, filename = os.path.split(abspath)
247n/a return filename in os.listdir(directory)
248n/a
249n/a def _add_defaults_standards(self):
250n/a standards = [self.READMES, self.distribution.script_name]
251n/a for fn in standards:
252n/a if isinstance(fn, tuple):
253n/a alts = fn
254n/a got_it = False
255n/a for fn in alts:
256n/a if self._cs_path_exists(fn):
257n/a got_it = True
258n/a self.filelist.append(fn)
259n/a break
260n/a
261n/a if not got_it:
262n/a self.warn("standard file not found: should have one of " +
263n/a ', '.join(alts))
264n/a else:
265n/a if self._cs_path_exists(fn):
266n/a self.filelist.append(fn)
267n/a else:
268n/a self.warn("standard file '%s' not found" % fn)
269n/a
270n/a def _add_defaults_optional(self):
271n/a optional = ['test/test*.py', 'setup.cfg']
272n/a for pattern in optional:
273n/a files = filter(os.path.isfile, glob(pattern))
274n/a self.filelist.extend(files)
275n/a
276n/a def _add_defaults_python(self):
277n/a # build_py is used to get:
278n/a # - python modules
279n/a # - files defined in package_data
280n/a build_py = self.get_finalized_command('build_py')
281n/a
282n/a # getting python files
283n/a if self.distribution.has_pure_modules():
284n/a self.filelist.extend(build_py.get_source_files())
285n/a
286n/a # getting package_data files
287n/a # (computed in build_py.data_files by build_py.finalize_options)
288n/a for pkg, src_dir, build_dir, filenames in build_py.data_files:
289n/a for filename in filenames:
290n/a self.filelist.append(os.path.join(src_dir, filename))
291n/a
292n/a def _add_defaults_data_files(self):
293n/a # getting distribution.data_files
294n/a if self.distribution.has_data_files():
295n/a for item in self.distribution.data_files:
296n/a if isinstance(item, str):
297n/a # plain file
298n/a item = convert_path(item)
299n/a if os.path.isfile(item):
300n/a self.filelist.append(item)
301n/a else:
302n/a # a (dirname, filenames) tuple
303n/a dirname, filenames = item
304n/a for f in filenames:
305n/a f = convert_path(f)
306n/a if os.path.isfile(f):
307n/a self.filelist.append(f)
308n/a
309n/a def _add_defaults_ext(self):
310n/a if self.distribution.has_ext_modules():
311n/a build_ext = self.get_finalized_command('build_ext')
312n/a self.filelist.extend(build_ext.get_source_files())
313n/a
314n/a def _add_defaults_c_libs(self):
315n/a if self.distribution.has_c_libraries():
316n/a build_clib = self.get_finalized_command('build_clib')
317n/a self.filelist.extend(build_clib.get_source_files())
318n/a
319n/a def _add_defaults_scripts(self):
320n/a if self.distribution.has_scripts():
321n/a build_scripts = self.get_finalized_command('build_scripts')
322n/a self.filelist.extend(build_scripts.get_source_files())
323n/a
324n/a def read_template(self):
325n/a """Read and parse manifest template file named by self.template.
326n/a
327n/a (usually "MANIFEST.in") The parsing and processing is done by
328n/a 'self.filelist', which updates itself accordingly.
329n/a """
330n/a log.info("reading manifest template '%s'", self.template)
331n/a template = TextFile(self.template, strip_comments=1, skip_blanks=1,
332n/a join_lines=1, lstrip_ws=1, rstrip_ws=1,
333n/a collapse_join=1)
334n/a
335n/a try:
336n/a while True:
337n/a line = template.readline()
338n/a if line is None: # end of file
339n/a break
340n/a
341n/a try:
342n/a self.filelist.process_template_line(line)
343n/a # the call above can raise a DistutilsTemplateError for
344n/a # malformed lines, or a ValueError from the lower-level
345n/a # convert_path function
346n/a except (DistutilsTemplateError, ValueError) as msg:
347n/a self.warn("%s, line %d: %s" % (template.filename,
348n/a template.current_line,
349n/a msg))
350n/a finally:
351n/a template.close()
352n/a
353n/a def prune_file_list(self):
354n/a """Prune off branches that might slip into the file list as created
355n/a by 'read_template()', but really don't belong there:
356n/a * the build tree (typically "build")
357n/a * the release tree itself (only an issue if we ran "sdist"
358n/a previously with --keep-temp, or it aborted)
359n/a * any RCS, CVS, .svn, .hg, .git, .bzr, _darcs directories
360n/a """
361n/a build = self.get_finalized_command('build')
362n/a base_dir = self.distribution.get_fullname()
363n/a
364n/a self.filelist.exclude_pattern(None, prefix=build.build_base)
365n/a self.filelist.exclude_pattern(None, prefix=base_dir)
366n/a
367n/a if sys.platform == 'win32':
368n/a seps = r'/|\\'
369n/a else:
370n/a seps = '/'
371n/a
372n/a vcs_dirs = ['RCS', 'CVS', r'\.svn', r'\.hg', r'\.git', r'\.bzr',
373n/a '_darcs']
374n/a vcs_ptrn = r'(^|%s)(%s)(%s).*' % (seps, '|'.join(vcs_dirs), seps)
375n/a self.filelist.exclude_pattern(vcs_ptrn, is_regex=1)
376n/a
377n/a def write_manifest(self):
378n/a """Write the file list in 'self.filelist' (presumably as filled in
379n/a by 'add_defaults()' and 'read_template()') to the manifest file
380n/a named by 'self.manifest'.
381n/a """
382n/a if self._manifest_is_not_generated():
383n/a log.info("not writing to manually maintained "
384n/a "manifest file '%s'" % self.manifest)
385n/a return
386n/a
387n/a content = self.filelist.files[:]
388n/a content.insert(0, '# file GENERATED by distutils, do NOT edit')
389n/a self.execute(file_util.write_file, (self.manifest, content),
390n/a "writing manifest file '%s'" % self.manifest)
391n/a
392n/a def _manifest_is_not_generated(self):
393n/a # check for special comment used in 3.1.3 and higher
394n/a if not os.path.isfile(self.manifest):
395n/a return False
396n/a
397n/a fp = open(self.manifest)
398n/a try:
399n/a first_line = fp.readline()
400n/a finally:
401n/a fp.close()
402n/a return first_line != '# file GENERATED by distutils, do NOT edit\n'
403n/a
404n/a def read_manifest(self):
405n/a """Read the manifest file (named by 'self.manifest') and use it to
406n/a fill in 'self.filelist', the list of files to include in the source
407n/a distribution.
408n/a """
409n/a log.info("reading manifest file '%s'", self.manifest)
410n/a manifest = open(self.manifest)
411n/a for line in manifest:
412n/a # ignore comments and blank lines
413n/a line = line.strip()
414n/a if line.startswith('#') or not line:
415n/a continue
416n/a self.filelist.append(line)
417n/a manifest.close()
418n/a
419n/a def make_release_tree(self, base_dir, files):
420n/a """Create the directory tree that will become the source
421n/a distribution archive. All directories implied by the filenames in
422n/a 'files' are created under 'base_dir', and then we hard link or copy
423n/a (if hard linking is unavailable) those files into place.
424n/a Essentially, this duplicates the developer's source tree, but in a
425n/a directory named after the distribution, containing only the files
426n/a to be distributed.
427n/a """
428n/a # Create all the directories under 'base_dir' necessary to
429n/a # put 'files' there; the 'mkpath()' is just so we don't die
430n/a # if the manifest happens to be empty.
431n/a self.mkpath(base_dir)
432n/a dir_util.create_tree(base_dir, files, dry_run=self.dry_run)
433n/a
434n/a # And walk over the list of files, either making a hard link (if
435n/a # os.link exists) to each one that doesn't already exist in its
436n/a # corresponding location under 'base_dir', or copying each file
437n/a # that's out-of-date in 'base_dir'. (Usually, all files will be
438n/a # out-of-date, because by default we blow away 'base_dir' when
439n/a # we're done making the distribution archives.)
440n/a
441n/a if hasattr(os, 'link'): # can make hard links on this system
442n/a link = 'hard'
443n/a msg = "making hard links in %s..." % base_dir
444n/a else: # nope, have to copy
445n/a link = None
446n/a msg = "copying files to %s..." % base_dir
447n/a
448n/a if not files:
449n/a log.warn("no files to distribute -- empty manifest?")
450n/a else:
451n/a log.info(msg)
452n/a for file in files:
453n/a if not os.path.isfile(file):
454n/a log.warn("'%s' not a regular file -- skipping", file)
455n/a else:
456n/a dest = os.path.join(base_dir, file)
457n/a self.copy_file(file, dest, link=link)
458n/a
459n/a self.distribution.metadata.write_pkg_info(base_dir)
460n/a
461n/a def make_distribution(self):
462n/a """Create the source distribution(s). First, we create the release
463n/a tree with 'make_release_tree()'; then, we create all required
464n/a archive files (according to 'self.formats') from the release tree.
465n/a Finally, we clean up by blowing away the release tree (unless
466n/a 'self.keep_temp' is true). The list of archive files created is
467n/a stored so it can be retrieved later by 'get_archive_files()'.
468n/a """
469n/a # Don't warn about missing meta-data here -- should be (and is!)
470n/a # done elsewhere.
471n/a base_dir = self.distribution.get_fullname()
472n/a base_name = os.path.join(self.dist_dir, base_dir)
473n/a
474n/a self.make_release_tree(base_dir, self.filelist.files)
475n/a archive_files = [] # remember names of files we create
476n/a # tar archive must be created last to avoid overwrite and remove
477n/a if 'tar' in self.formats:
478n/a self.formats.append(self.formats.pop(self.formats.index('tar')))
479n/a
480n/a for fmt in self.formats:
481n/a file = self.make_archive(base_name, fmt, base_dir=base_dir,
482n/a owner=self.owner, group=self.group)
483n/a archive_files.append(file)
484n/a self.distribution.dist_files.append(('sdist', '', file))
485n/a
486n/a self.archive_files = archive_files
487n/a
488n/a if not self.keep_temp:
489n/a dir_util.remove_tree(base_dir, dry_run=self.dry_run)
490n/a
491n/a def get_archive_files(self):
492n/a """Return the list of archive files created when the command
493n/a was run, or None if the command hasn't run yet.
494n/a """
495n/a return self.archive_files