|
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 2.1. |
|
6 |
|
7 __revision__ = "$Id: sdist.py 38697 2005-03-23 18:54:36Z loewis $" |
|
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 if self.distribution.has_scripts(): |
|
308 build_scripts = self.get_finalized_command('build_scripts') |
|
309 self.filelist.extend(build_scripts.get_source_files()) |
|
310 |
|
311 # add_defaults () |
|
312 |
|
313 |
|
314 def read_template (self): |
|
315 """Read and parse manifest template file named by self.template. |
|
316 |
|
317 (usually "MANIFEST.in") The parsing and processing is done by |
|
318 'self.filelist', which updates itself accordingly. |
|
319 """ |
|
320 log.info("reading manifest template '%s'", self.template) |
|
321 template = TextFile(self.template, |
|
322 strip_comments=1, |
|
323 skip_blanks=1, |
|
324 join_lines=1, |
|
325 lstrip_ws=1, |
|
326 rstrip_ws=1, |
|
327 collapse_join=1) |
|
328 |
|
329 while 1: |
|
330 line = template.readline() |
|
331 if line is None: # end of file |
|
332 break |
|
333 |
|
334 try: |
|
335 self.filelist.process_template_line(line) |
|
336 except DistutilsTemplateError, msg: |
|
337 self.warn("%s, line %d: %s" % (template.filename, |
|
338 template.current_line, |
|
339 msg)) |
|
340 |
|
341 # read_template () |
|
342 |
|
343 |
|
344 def prune_file_list (self): |
|
345 """Prune off branches that might slip into the file list as created |
|
346 by 'read_template()', but really don't belong there: |
|
347 * the build tree (typically "build") |
|
348 * the release tree itself (only an issue if we ran "sdist" |
|
349 previously with --keep-temp, or it aborted) |
|
350 * any RCS, CVS and .svn directories |
|
351 """ |
|
352 build = self.get_finalized_command('build') |
|
353 base_dir = self.distribution.get_fullname() |
|
354 |
|
355 self.filelist.exclude_pattern(None, prefix=build.build_base) |
|
356 self.filelist.exclude_pattern(None, prefix=base_dir) |
|
357 self.filelist.exclude_pattern(r'/(RCS|CVS|\.svn)/.*', is_regex=1) |
|
358 |
|
359 |
|
360 def write_manifest (self): |
|
361 """Write the file list in 'self.filelist' (presumably as filled in |
|
362 by 'add_defaults()' and 'read_template()') to the manifest file |
|
363 named by 'self.manifest'. |
|
364 """ |
|
365 self.execute(file_util.write_file, |
|
366 (self.manifest, self.filelist.files), |
|
367 "writing manifest file '%s'" % self.manifest) |
|
368 |
|
369 # write_manifest () |
|
370 |
|
371 |
|
372 def read_manifest (self): |
|
373 """Read the manifest file (named by 'self.manifest') and use it to |
|
374 fill in 'self.filelist', the list of files to include in the source |
|
375 distribution. |
|
376 """ |
|
377 log.info("reading manifest file '%s'", self.manifest) |
|
378 manifest = open(self.manifest) |
|
379 while 1: |
|
380 line = manifest.readline() |
|
381 if line == '': # end of file |
|
382 break |
|
383 if line[-1] == '\n': |
|
384 line = line[0:-1] |
|
385 self.filelist.append(line) |
|
386 |
|
387 # read_manifest () |
|
388 |
|
389 |
|
390 def make_release_tree (self, base_dir, files): |
|
391 """Create the directory tree that will become the source |
|
392 distribution archive. All directories implied by the filenames in |
|
393 'files' are created under 'base_dir', and then we hard link or copy |
|
394 (if hard linking is unavailable) those files into place. |
|
395 Essentially, this duplicates the developer's source tree, but in a |
|
396 directory named after the distribution, containing only the files |
|
397 to be distributed. |
|
398 """ |
|
399 # Create all the directories under 'base_dir' necessary to |
|
400 # put 'files' there; the 'mkpath()' is just so we don't die |
|
401 # if the manifest happens to be empty. |
|
402 self.mkpath(base_dir) |
|
403 dir_util.create_tree(base_dir, files, dry_run=self.dry_run) |
|
404 |
|
405 # And walk over the list of files, either making a hard link (if |
|
406 # os.link exists) to each one that doesn't already exist in its |
|
407 # corresponding location under 'base_dir', or copying each file |
|
408 # that's out-of-date in 'base_dir'. (Usually, all files will be |
|
409 # out-of-date, because by default we blow away 'base_dir' when |
|
410 # we're done making the distribution archives.) |
|
411 |
|
412 if hasattr(os, 'link'): # can make hard links on this system |
|
413 link = 'hard' |
|
414 msg = "making hard links in %s..." % base_dir |
|
415 else: # nope, have to copy |
|
416 link = None |
|
417 msg = "copying files to %s..." % base_dir |
|
418 |
|
419 if not files: |
|
420 log.warn("no files to distribute -- empty manifest?") |
|
421 else: |
|
422 log.info(msg) |
|
423 for file in files: |
|
424 if not os.path.isfile(file): |
|
425 log.warn("'%s' not a regular file -- skipping" % file) |
|
426 else: |
|
427 dest = os.path.join(base_dir, file) |
|
428 self.copy_file(file, dest, link=link) |
|
429 |
|
430 self.distribution.metadata.write_pkg_info(base_dir) |
|
431 |
|
432 # make_release_tree () |
|
433 |
|
434 def make_distribution (self): |
|
435 """Create the source distribution(s). First, we create the release |
|
436 tree with 'make_release_tree()'; then, we create all required |
|
437 archive files (according to 'self.formats') from the release tree. |
|
438 Finally, we clean up by blowing away the release tree (unless |
|
439 'self.keep_temp' is true). The list of archive files created is |
|
440 stored so it can be retrieved later by 'get_archive_files()'. |
|
441 """ |
|
442 # Don't warn about missing meta-data here -- should be (and is!) |
|
443 # done elsewhere. |
|
444 base_dir = self.distribution.get_fullname() |
|
445 base_name = os.path.join(self.dist_dir, base_dir) |
|
446 |
|
447 self.make_release_tree(base_dir, self.filelist.files) |
|
448 archive_files = [] # remember names of files we create |
|
449 for fmt in self.formats: |
|
450 file = self.make_archive(base_name, fmt, base_dir=base_dir) |
|
451 archive_files.append(file) |
|
452 self.distribution.dist_files.append(('sdist', '', file)) |
|
453 |
|
454 self.archive_files = archive_files |
|
455 |
|
456 if not self.keep_temp: |
|
457 dir_util.remove_tree(base_dir, dry_run=self.dry_run) |
|
458 |
|
459 def get_archive_files (self): |
|
460 """Return the list of archive files created when the command |
|
461 was run, or None if the command hasn't run yet. |
|
462 """ |
|
463 return self.archive_files |
|
464 |
|
465 # class sdist |