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