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

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

#countcontent
1n/a"""Create a source distribution."""
2n/a
3n/aimport os
4n/aimport re
5n/aimport sys
6n/afrom io import StringIO
7n/afrom shutil import get_archive_formats, rmtree
8n/a
9n/afrom packaging import logger
10n/afrom packaging.util import resolve_name
11n/afrom packaging.errors import (PackagingPlatformError, PackagingOptionError,
12n/a PackagingModuleError, PackagingFileError)
13n/afrom packaging.command import get_command_names
14n/afrom packaging.command.cmd import Command
15n/afrom packaging.manifest import Manifest
16n/a
17n/a
18n/adef show_formats():
19n/a """Print all possible values for the 'formats' option (used by
20n/a the "--help-formats" command-line option).
21n/a """
22n/a from packaging.fancy_getopt import FancyGetopt
23n/a formats = sorted(('formats=' + name, None, desc)
24n/a for name, desc in get_archive_formats())
25n/a FancyGetopt(formats).print_help(
26n/a "List of available source distribution formats:")
27n/a
28n/a# a \ followed by some spaces + EOL
29n/a_COLLAPSE_PATTERN = re.compile('\\\w\n', re.M)
30n/a_COMMENTED_LINE = re.compile('^#.*\n$|^\w*\n$', re.M)
31n/a
32n/a
33n/aclass sdist(Command):
34n/a
35n/a description = "create a source distribution (tarball, zip file, etc.)"
36n/a
37n/a user_options = [
38n/a ('manifest=', 'm',
39n/a "name of manifest file [default: MANIFEST]"),
40n/a ('use-defaults', None,
41n/a "include the default file set in the manifest "
42n/a "[default; disable with --no-defaults]"),
43n/a ('no-defaults', None,
44n/a "don't include the default file set"),
45n/a ('prune', None,
46n/a "specifically exclude files/directories that should not be "
47n/a "distributed (build tree, RCS/CVS dirs, etc.) "
48n/a "[default; disable with --no-prune]"),
49n/a ('no-prune', None,
50n/a "don't automatically exclude anything"),
51n/a ('manifest-only', 'o',
52n/a "just regenerate the manifest and then stop "),
53n/a ('formats=', None,
54n/a "formats for source distribution (comma-separated list)"),
55n/a ('keep-temp', 'k',
56n/a "keep the distribution tree around after creating " +
57n/a "archive file(s)"),
58n/a ('dist-dir=', 'd',
59n/a "directory to put the source distribution archive(s) in "
60n/a "[default: dist]"),
61n/a ('check-metadata', None,
62n/a "Ensure that all required elements of metadata "
63n/a "are supplied. Warn if any missing. [default]"),
64n/a ('owner=', 'u',
65n/a "Owner name used when creating a tar file [default: current user]"),
66n/a ('group=', 'g',
67n/a "Group name used when creating a tar file [default: current group]"),
68n/a ('manifest-builders=', None,
69n/a "manifest builders (comma-separated list)"),
70n/a ]
71n/a
72n/a boolean_options = ['use-defaults', 'prune',
73n/a 'manifest-only', 'keep-temp', 'check-metadata']
74n/a
75n/a help_options = [
76n/a ('help-formats', None,
77n/a "list available distribution formats", show_formats),
78n/a ]
79n/a
80n/a negative_opt = {'no-defaults': 'use-defaults',
81n/a 'no-prune': 'prune'}
82n/a
83n/a default_format = {'posix': 'gztar',
84n/a 'nt': 'zip'}
85n/a
86n/a def initialize_options(self):
87n/a self.manifest = None
88n/a # 'use_defaults': if true, we will include the default file set
89n/a # in the manifest
90n/a self.use_defaults = True
91n/a self.prune = True
92n/a self.manifest_only = False
93n/a self.formats = None
94n/a self.keep_temp = False
95n/a self.dist_dir = None
96n/a
97n/a self.archive_files = None
98n/a self.metadata_check = True
99n/a self.owner = None
100n/a self.group = None
101n/a self.filelist = None
102n/a self.manifest_builders = None
103n/a
104n/a def _check_archive_formats(self, formats):
105n/a supported_formats = [name for name, desc in get_archive_formats()]
106n/a for format in formats:
107n/a if format not in supported_formats:
108n/a return format
109n/a return None
110n/a
111n/a def finalize_options(self):
112n/a if self.manifest is None:
113n/a self.manifest = "MANIFEST"
114n/a
115n/a self.ensure_string_list('formats')
116n/a if self.formats is None:
117n/a try:
118n/a self.formats = [self.default_format[os.name]]
119n/a except KeyError:
120n/a raise PackagingPlatformError("don't know how to create source "
121n/a "distributions on platform %s" % os.name)
122n/a
123n/a bad_format = self._check_archive_formats(self.formats)
124n/a if bad_format:
125n/a raise PackagingOptionError("unknown archive format '%s'" \
126n/a % bad_format)
127n/a
128n/a if self.dist_dir is None:
129n/a self.dist_dir = "dist"
130n/a
131n/a if self.filelist is None:
132n/a self.filelist = Manifest()
133n/a
134n/a if self.manifest_builders is None:
135n/a self.manifest_builders = []
136n/a else:
137n/a if isinstance(self.manifest_builders, str):
138n/a self.manifest_builders = self.manifest_builders.split(',')
139n/a builders = []
140n/a for builder in self.manifest_builders:
141n/a builder = builder.strip()
142n/a if builder == '':
143n/a continue
144n/a try:
145n/a builder = resolve_name(builder)
146n/a except ImportError as e:
147n/a raise PackagingModuleError(e)
148n/a
149n/a builders.append(builder)
150n/a
151n/a self.manifest_builders = builders
152n/a
153n/a def run(self):
154n/a # 'filelist' contains the list of files that will make up the
155n/a # manifest
156n/a self.filelist.clear()
157n/a
158n/a # Check the package metadata
159n/a if self.metadata_check:
160n/a self.run_command('check')
161n/a
162n/a # Do whatever it takes to get the list of files to process
163n/a # (process the manifest template, read an existing manifest,
164n/a # whatever). File list is accumulated in 'self.filelist'.
165n/a self.get_file_list()
166n/a
167n/a # If user just wanted us to regenerate the manifest, stop now.
168n/a if self.manifest_only:
169n/a return
170n/a
171n/a # Otherwise, go ahead and create the source distribution tarball,
172n/a # or zipfile, or whatever.
173n/a self.make_distribution()
174n/a
175n/a def get_file_list(self):
176n/a """Figure out the list of files to include in the source
177n/a distribution, and put it in 'self.filelist'. This might involve
178n/a reading the manifest template (and writing the manifest), or just
179n/a reading the manifest, or just using the default file set -- it all
180n/a depends on the user's options.
181n/a """
182n/a template_exists = len(self.distribution.extra_files) > 0
183n/a if not template_exists:
184n/a logger.warning('%s: using default file list',
185n/a self.get_command_name())
186n/a self.filelist.findall()
187n/a
188n/a if self.use_defaults:
189n/a self.add_defaults()
190n/a if template_exists:
191n/a template = '\n'.join(self.distribution.extra_files)
192n/a self.filelist.read_template(StringIO(template))
193n/a
194n/a # call manifest builders, if any.
195n/a for builder in self.manifest_builders:
196n/a builder(self.distribution, self.filelist)
197n/a
198n/a if self.prune:
199n/a self.prune_file_list()
200n/a
201n/a self.filelist.write(self.manifest)
202n/a
203n/a def add_defaults(self):
204n/a """Add all default files to self.filelist.
205n/a
206n/a In addition to the setup.cfg file, this will include all files returned
207n/a by the get_source_files of every registered command. This will find
208n/a Python modules and packages, data files listed in package_data_,
209n/a data_files and extra_files, scripts, C sources of extension modules or
210n/a C libraries (headers are missing).
211n/a """
212n/a if os.path.exists('setup.cfg'):
213n/a self.filelist.append('setup.cfg')
214n/a else:
215n/a logger.warning("%s: standard 'setup.cfg' file not found",
216n/a self.get_command_name())
217n/a
218n/a for cmd_name in get_command_names():
219n/a try:
220n/a cmd_obj = self.get_finalized_command(cmd_name)
221n/a except PackagingOptionError:
222n/a pass
223n/a else:
224n/a self.filelist.extend(cmd_obj.get_source_files())
225n/a
226n/a def prune_file_list(self):
227n/a """Prune off branches that might slip into the file list as created
228n/a by 'read_template()', but really don't belong there:
229n/a * the build tree (typically "build")
230n/a * the release tree itself (only an issue if we ran "sdist"
231n/a previously with --keep-temp, or it aborted)
232n/a * any RCS, CVS, .svn, .hg, .git, .bzr, _darcs directories
233n/a """
234n/a build = self.get_finalized_command('build')
235n/a base_dir = self.distribution.get_fullname()
236n/a
237n/a self.filelist.exclude_pattern(None, prefix=build.build_base)
238n/a self.filelist.exclude_pattern(None, prefix=base_dir)
239n/a
240n/a # pruning out vcs directories
241n/a # both separators are used under win32
242n/a if sys.platform == 'win32':
243n/a seps = r'/|\\'
244n/a else:
245n/a seps = '/'
246n/a
247n/a vcs_dirs = ['RCS', 'CVS', r'\.svn', r'\.hg', r'\.git', r'\.bzr',
248n/a '_darcs']
249n/a vcs_ptrn = r'(^|%s)(%s)(%s).*' % (seps, '|'.join(vcs_dirs), seps)
250n/a self.filelist.exclude_pattern(vcs_ptrn, is_regex=True)
251n/a
252n/a def make_release_tree(self, base_dir, files):
253n/a """Create the directory tree that will become the source
254n/a distribution archive. All directories implied by the filenames in
255n/a 'files' are created under 'base_dir', and then we hard link or copy
256n/a (if hard linking is unavailable) those files into place.
257n/a Essentially, this duplicates the developer's source tree, but in a
258n/a directory named after the distribution, containing only the files
259n/a to be distributed.
260n/a """
261n/a # Create all the directories under 'base_dir' necessary to
262n/a # put 'files' there; the 'mkpath()' is just so we don't die
263n/a # if the manifest happens to be empty.
264n/a self.mkpath(base_dir)
265n/a self.create_tree(base_dir, files, dry_run=self.dry_run)
266n/a
267n/a # And walk over the list of files, either making a hard link (if
268n/a # os.link exists) to each one that doesn't already exist in its
269n/a # corresponding location under 'base_dir', or copying each file
270n/a # that's out-of-date in 'base_dir'. (Usually, all files will be
271n/a # out-of-date, because by default we blow away 'base_dir' when
272n/a # we're done making the distribution archives.)
273n/a
274n/a if hasattr(os, 'link'): # can make hard links on this system
275n/a link = 'hard'
276n/a msg = "making hard links in %s..." % base_dir
277n/a else: # nope, have to copy
278n/a link = None
279n/a msg = "copying files to %s..." % base_dir
280n/a
281n/a if not files:
282n/a logger.warning("no files to distribute -- empty manifest?")
283n/a else:
284n/a logger.info(msg)
285n/a
286n/a for file in self.distribution.metadata.requires_files:
287n/a if file not in files:
288n/a msg = "'%s' must be included explicitly in 'extra_files'" \
289n/a % file
290n/a raise PackagingFileError(msg)
291n/a
292n/a for file in files:
293n/a if not os.path.isfile(file):
294n/a logger.warning("'%s' not a regular file -- skipping", file)
295n/a else:
296n/a dest = os.path.join(base_dir, file)
297n/a self.copy_file(file, dest, link=link)
298n/a
299n/a self.distribution.metadata.write(os.path.join(base_dir, 'PKG-INFO'))
300n/a
301n/a def make_distribution(self):
302n/a """Create the source distribution(s). First, we create the release
303n/a tree with 'make_release_tree()'; then, we create all required
304n/a archive files (according to 'self.formats') from the release tree.
305n/a Finally, we clean up by blowing away the release tree (unless
306n/a 'self.keep_temp' is true). The list of archive files created is
307n/a stored so it can be retrieved later by 'get_archive_files()'.
308n/a """
309n/a # Don't warn about missing metadata here -- should be (and is!)
310n/a # done elsewhere.
311n/a base_dir = self.distribution.get_fullname()
312n/a base_name = os.path.join(self.dist_dir, base_dir)
313n/a
314n/a self.make_release_tree(base_dir, self.filelist.files)
315n/a archive_files = [] # remember names of files we create
316n/a # tar archive must be created last to avoid overwrite and remove
317n/a if 'tar' in self.formats:
318n/a self.formats.append(self.formats.pop(self.formats.index('tar')))
319n/a
320n/a for fmt in self.formats:
321n/a file = self.make_archive(base_name, fmt, base_dir=base_dir,
322n/a owner=self.owner, group=self.group)
323n/a archive_files.append(file)
324n/a self.distribution.dist_files.append(('sdist', '', file))
325n/a
326n/a self.archive_files = archive_files
327n/a
328n/a if not self.keep_temp:
329n/a if self.dry_run:
330n/a logger.info('removing %s', base_dir)
331n/a else:
332n/a rmtree(base_dir)
333n/a
334n/a def get_archive_files(self):
335n/a """Return the list of archive files created when the command
336n/a was run, or None if the command hasn't run yet.
337n/a """
338n/a return self.archive_files
339n/a
340n/a def create_tree(self, base_dir, files, mode=0o777, dry_run=False):
341n/a need_dir = set()
342n/a for file in files:
343n/a need_dir.add(os.path.join(base_dir, os.path.dirname(file)))
344n/a
345n/a # Now create them
346n/a for dir in sorted(need_dir):
347n/a self.mkpath(dir, mode, dry_run=dry_run)