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