blob: eabf5c7936aa10b2e59a5ebc9974278c354de70d [file] [log] [blame]
Greg Warda82122b2000-02-17 23:56:15 +00001"""distutils.command.sdist
2
3Implements the Distutils 'sdist' command (create a source distribution)."""
4
5# created 1999/09/22, Greg Ward
6
Greg Ward3ce77fd2000-03-02 01:49:45 +00007__revision__ = "$Id$"
Greg Warda82122b2000-02-17 23:56:15 +00008
9import sys, os, string, re
10import fnmatch
11from types import *
12from glob import glob
Greg Warda82122b2000-02-17 23:56:15 +000013from distutils.core import Command
Greg Wardf1fe1032000-06-08 00:14:18 +000014from distutils.util import \
15 convert_path, create_tree, remove_tree, newer, write_file, \
16 check_archive_formats, ARCHIVE_FORMATS
Greg Warda82122b2000-02-17 23:56:15 +000017from distutils.text_file import TextFile
Greg Ward6a9a5452000-04-22 03:11:55 +000018from distutils.errors import DistutilsExecError, DistutilsOptionError
Greg Warda82122b2000-02-17 23:56:15 +000019
20
Greg Ward1993f9a2000-02-18 00:13:53 +000021class sdist (Command):
Greg Warda82122b2000-02-17 23:56:15 +000022
23 description = "create a source distribution (tarball, zip file, etc.)"
24
Greg Wardbbeceea2000-02-18 00:25:39 +000025 user_options = [
26 ('template=', 't',
27 "name of manifest template file [default: MANIFEST.in]"),
28 ('manifest=', 'm',
29 "name of manifest file [default: MANIFEST]"),
30 ('use-defaults', None,
31 "include the default file set in the manifest "
32 "[default; disable with --no-defaults]"),
Greg Ward839d5322000-04-26 01:14:33 +000033 ('manifest-only', 'o',
Greg Wardbbeceea2000-02-18 00:25:39 +000034 "just regenerate the manifest and then stop"),
Greg Ward839d5322000-04-26 01:14:33 +000035 ('force-manifest', 'f',
Greg Wardbbeceea2000-02-18 00:25:39 +000036 "forcibly regenerate the manifest and carry on as usual"),
Greg Wardbbeceea2000-02-18 00:25:39 +000037 ('formats=', None,
Greg Ward9d17a7a2000-06-07 03:00:06 +000038 "formats for source distribution"),
Greg Wardbbeceea2000-02-18 00:25:39 +000039 ('keep-tree', 'k',
40 "keep the distribution tree around after creating " +
41 "archive file(s)"),
42 ]
Greg Wardf1fe1032000-06-08 00:14:18 +000043
44
45 # XXX ugh: this has to precede the 'help_options' list, because
46 # it is mentioned there -- also, this is not a method, even though
47 # it's defined in a class: double-ugh!
48 def show_formats ():
49 """Print all possible values for the 'formats' option -- used by
50 the "--help-formats" command-line option.
51 """
Greg Ward9d17a7a2000-06-07 03:00:06 +000052 from distutils.fancy_getopt import FancyGetopt
Greg Wardf1fe1032000-06-08 00:14:18 +000053 formats=[]
Greg Ward9d17a7a2000-06-07 03:00:06 +000054 for format in ARCHIVE_FORMATS.keys():
Greg Wardf1fe1032000-06-08 00:14:18 +000055 formats.append(("formats="+format,None,ARCHIVE_FORMATS[format][2]))
56 formats.sort()
57 pretty_printer = FancyGetopt(formats)
58 pretty_printer.print_help(
59 "List of available source distribution formats:")
Greg Ward9d17a7a2000-06-07 03:00:06 +000060
61 help_options = [
62 ('help-formats', None,
Greg Wardf1fe1032000-06-08 00:14:18 +000063 "lists available distribution formats", show_formats),
Greg Ward9d17a7a2000-06-07 03:00:06 +000064 ]
65
Greg Warda82122b2000-02-17 23:56:15 +000066 negative_opts = {'use-defaults': 'no-defaults'}
67
68 default_format = { 'posix': 'gztar',
69 'nt': 'zip' }
70
71 exclude_re = re.compile (r'\s*!\s*(\S+)') # for manifest lines
72
73
Greg Warde01149c2000-02-18 00:35:22 +000074 def initialize_options (self):
Greg Warda82122b2000-02-17 23:56:15 +000075 # 'template' and 'manifest' are, respectively, the names of
76 # the manifest template and manifest file.
77 self.template = None
78 self.manifest = None
79
80 # 'use_defaults': if true, we will include the default file set
81 # in the manifest
82 self.use_defaults = 1
83
84 self.manifest_only = 0
85 self.force_manifest = 0
86
87 self.formats = None
Greg Warda82122b2000-02-17 23:56:15 +000088 self.keep_tree = 0
89
Greg Wardd87eb732000-06-01 01:10:56 +000090 self.archive_files = None
91
Greg Warda82122b2000-02-17 23:56:15 +000092
Greg Warde01149c2000-02-18 00:35:22 +000093 def finalize_options (self):
Greg Warda82122b2000-02-17 23:56:15 +000094 if self.manifest is None:
95 self.manifest = "MANIFEST"
96 if self.template is None:
97 self.template = "MANIFEST.in"
98
Greg Ward62d5a572000-06-04 15:12:51 +000099 self.ensure_string_list('formats')
Greg Warda82122b2000-02-17 23:56:15 +0000100 if self.formats is None:
101 try:
102 self.formats = [self.default_format[os.name]]
103 except KeyError:
104 raise DistutilsPlatformError, \
Greg Ward578c10d2000-03-31 02:50:04 +0000105 "don't know how to create source distributions " + \
106 "on platform %s" % os.name
Greg Warda82122b2000-02-17 23:56:15 +0000107
Greg Ward6a9a5452000-04-22 03:11:55 +0000108 bad_format = check_archive_formats (self.formats)
109 if bad_format:
110 raise DistutilsOptionError, \
111 "unknown archive format '%s'" % bad_format
112
Greg Warda82122b2000-02-17 23:56:15 +0000113
114 def run (self):
115
116 # 'files' is the list of files that will make up the manifest
117 self.files = []
118
119 # Ensure that all required meta-data is given; warn if not (but
120 # don't die, it's not *that* serious!)
121 self.check_metadata ()
122
123 # Do whatever it takes to get the list of files to process
124 # (process the manifest template, read an existing manifest,
125 # whatever). File list is put into 'self.files'.
126 self.get_file_list ()
127
128 # If user just wanted us to regenerate the manifest, stop now.
129 if self.manifest_only:
130 return
131
132 # Otherwise, go ahead and create the source distribution tarball,
133 # or zipfile, or whatever.
134 self.make_distribution ()
135
136
137 def check_metadata (self):
138
Greg Ward535f2d92000-04-21 04:37:12 +0000139 metadata = self.distribution.metadata
Greg Warda82122b2000-02-17 23:56:15 +0000140
141 missing = []
142 for attr in ('name', 'version', 'url'):
Greg Ward535f2d92000-04-21 04:37:12 +0000143 if not (hasattr (metadata, attr) and getattr (metadata, attr)):
Greg Warda82122b2000-02-17 23:56:15 +0000144 missing.append (attr)
145
146 if missing:
147 self.warn ("missing required meta-data: " +
148 string.join (missing, ", "))
149
Greg Ward535f2d92000-04-21 04:37:12 +0000150 if metadata.author:
151 if not metadata.author_email:
Greg Warda82122b2000-02-17 23:56:15 +0000152 self.warn ("missing meta-data: if 'author' supplied, " +
153 "'author_email' must be supplied too")
Greg Ward535f2d92000-04-21 04:37:12 +0000154 elif metadata.maintainer:
155 if not metadata.maintainer_email:
Greg Warda82122b2000-02-17 23:56:15 +0000156 self.warn ("missing meta-data: if 'maintainer' supplied, " +
157 "'maintainer_email' must be supplied too")
158 else:
159 self.warn ("missing meta-data: either (author and author_email) " +
160 "or (maintainer and maintainer_email) " +
161 "must be supplied")
162
163 # check_metadata ()
164
165
166 def get_file_list (self):
167 """Figure out the list of files to include in the source
168 distribution, and put it in 'self.files'. This might
169 involve reading the manifest template (and writing the
170 manifest), or just reading the manifest, or just using
171 the default file set -- it all depends on the user's
172 options and the state of the filesystem."""
173
174
175 template_exists = os.path.isfile (self.template)
176 if template_exists:
177 template_newer = newer (self.template, self.manifest)
178
179 # Regenerate the manifest if necessary (or if explicitly told to)
180 if ((template_exists and template_newer) or
181 self.force_manifest or
182 self.manifest_only):
183
184 if not template_exists:
185 self.warn (("manifest template '%s' does not exist " +
186 "(using default file list)") %
187 self.template)
188
189 # Add default file set to 'files'
190 if self.use_defaults:
191 self.find_defaults ()
192
193 # Read manifest template if it exists
194 if template_exists:
195 self.read_template ()
196
197 # File list now complete -- sort it so that higher-level files
198 # come first
199 sortable_files = map (os.path.split, self.files)
200 sortable_files.sort ()
201 self.files = []
202 for sort_tuple in sortable_files:
203 self.files.append (apply (os.path.join, sort_tuple))
204
205 # Remove duplicates from the file list
206 for i in range (len(self.files)-1, 0, -1):
207 if self.files[i] == self.files[i-1]:
208 del self.files[i]
209
210 # And write complete file list (including default file set) to
211 # the manifest.
212 self.write_manifest ()
213
214 # Don't regenerate the manifest, just read it in.
215 else:
216 self.read_manifest ()
217
218 # get_file_list ()
219
220
221 def find_defaults (self):
222
223 standards = [('README', 'README.txt'), 'setup.py']
224 for fn in standards:
225 if type (fn) is TupleType:
226 alts = fn
Greg Ward48401122000-02-24 03:17:43 +0000227 got_it = 0
Greg Warda82122b2000-02-17 23:56:15 +0000228 for fn in alts:
229 if os.path.exists (fn):
230 got_it = 1
231 self.files.append (fn)
232 break
233
234 if not got_it:
235 self.warn ("standard file not found: should have one of " +
236 string.join (alts, ', '))
237 else:
238 if os.path.exists (fn):
239 self.files.append (fn)
240 else:
241 self.warn ("standard file '%s' not found" % fn)
242
243 optional = ['test/test*.py']
244 for pattern in optional:
245 files = filter (os.path.isfile, glob (pattern))
246 if files:
247 self.files.extend (files)
248
Greg Ward578c10d2000-03-31 02:50:04 +0000249 if self.distribution.has_pure_modules():
Greg Ward4fb29e52000-05-27 17:27:23 +0000250 build_py = self.get_finalized_command ('build_py')
Greg Warda82122b2000-02-17 23:56:15 +0000251 self.files.extend (build_py.get_source_files ())
252
Greg Ward578c10d2000-03-31 02:50:04 +0000253 if self.distribution.has_ext_modules():
Greg Ward4fb29e52000-05-27 17:27:23 +0000254 build_ext = self.get_finalized_command ('build_ext')
Greg Warda82122b2000-02-17 23:56:15 +0000255 self.files.extend (build_ext.get_source_files ())
256
Greg Ward60908f12000-04-09 03:51:40 +0000257 if self.distribution.has_c_libraries():
Greg Ward4fb29e52000-05-27 17:27:23 +0000258 build_clib = self.get_finalized_command ('build_clib')
Greg Ward60908f12000-04-09 03:51:40 +0000259 self.files.extend (build_clib.get_source_files ())
260
Greg Warda82122b2000-02-17 23:56:15 +0000261
Greg Warda82122b2000-02-17 23:56:15 +0000262 def search_dir (self, dir, pattern=None):
263 """Recursively find files under 'dir' matching 'pattern' (a string
264 containing a Unix-style glob pattern). If 'pattern' is None,
265 find all files under 'dir'. Return the list of found
266 filenames."""
267
268 allfiles = findall (dir)
269 if pattern is None:
270 return allfiles
271
272 pattern_re = translate_pattern (pattern)
273 files = []
274 for file in allfiles:
275 if pattern_re.match (os.path.basename (file)):
276 files.append (file)
277
278 return files
279
280 # search_dir ()
281
282
Greg Wardf8b9e202000-06-08 00:08:14 +0000283# def exclude_pattern (self, pattern):
284# """Remove filenames from 'self.files' that match 'pattern'."""
285# self.debug_print("exclude_pattern: pattern=%s" % pattern)
286# pattern_re = translate_pattern (pattern)
287# for i in range (len (self.files)-1, -1, -1):
288# if pattern_re.match (self.files[i]):
289# self.debug_print("removing %s" % self.files[i])
290# del self.files[i]
Greg Warda82122b2000-02-17 23:56:15 +0000291
292
293 def recursive_exclude_pattern (self, dir, pattern=None):
294 """Remove filenames from 'self.files' that are under 'dir'
295 and whose basenames match 'pattern'."""
296
Greg Wardf8b9e202000-06-08 00:08:14 +0000297 self.debug_print("recursive_exclude_pattern: dir=%s, pattern=%s" %
298 (dir, pattern))
Greg Warda82122b2000-02-17 23:56:15 +0000299 if pattern is None:
300 pattern_re = None
301 else:
302 pattern_re = translate_pattern (pattern)
303
304 for i in range (len (self.files)-1, -1, -1):
305 (cur_dir, cur_base) = os.path.split (self.files[i])
306 if (cur_dir == dir and
307 (pattern_re is None or pattern_re.match (cur_base))):
Greg Wardf8b9e202000-06-08 00:08:14 +0000308 self.debug_print("removing %s" % self.files[i])
Greg Warda82122b2000-02-17 23:56:15 +0000309 del self.files[i]
310
311
312 def read_template (self):
313 """Read and parse the manifest template file named by
314 'self.template' (usually "MANIFEST.in"). Process all file
315 specifications (include and exclude) in the manifest template
316 and add the resulting filenames to 'self.files'."""
317
318 assert self.files is not None and type (self.files) is ListType
Greg Wardf8b9e202000-06-08 00:08:14 +0000319 self.announce("reading manifest template '%s'" % self.template)
Greg Warda82122b2000-02-17 23:56:15 +0000320
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_ws=1)
328
329 all_files = findall ()
330
331 while 1:
332
333 line = template.readline()
334 if line is None: # end of file
335 break
336
337 words = string.split (line)
338 action = words[0]
339
340 # First, check that the right number of words are present
341 # for the given action (which is the first word)
342 if action in ('include','exclude',
343 'global-include','global-exclude'):
Greg Ward9d5afa92000-04-21 04:31:10 +0000344 if len (words) < 2:
Greg Warda82122b2000-02-17 23:56:15 +0000345 template.warn \
346 ("invalid manifest template line: " +
Greg Ward9d5afa92000-04-21 04:31:10 +0000347 "'%s' expects <pattern1> <pattern2> ..." %
Greg Warda82122b2000-02-17 23:56:15 +0000348 action)
349 continue
350
Greg Wardd8dfb4c2000-05-31 02:32:10 +0000351 pattern_list = map(convert_path, words[1:])
Greg Warda82122b2000-02-17 23:56:15 +0000352
353 elif action in ('recursive-include','recursive-exclude'):
Greg Ward9d5afa92000-04-21 04:31:10 +0000354 if len (words) < 3:
Greg Warda82122b2000-02-17 23:56:15 +0000355 template.warn \
356 ("invalid manifest template line: " +
Greg Ward9d5afa92000-04-21 04:31:10 +0000357 "'%s' expects <dir> <pattern1> <pattern2> ..." %
Greg Warda82122b2000-02-17 23:56:15 +0000358 action)
359 continue
360
Greg Wardd8dfb4c2000-05-31 02:32:10 +0000361 dir = convert_path(words[1])
362 pattern_list = map (convert_path, words[2:])
Greg Warda82122b2000-02-17 23:56:15 +0000363
364 elif action in ('graft','prune'):
365 if len (words) != 2:
366 template.warn \
367 ("invalid manifest template line: " +
368 "'%s' expects a single <dir_pattern>" %
369 action)
370 continue
371
Greg Wardd8dfb4c2000-05-31 02:32:10 +0000372 dir_pattern = convert_path (words[1])
Greg Warda82122b2000-02-17 23:56:15 +0000373
374 else:
375 template.warn ("invalid manifest template line: " +
376 "unknown action '%s'" % action)
377 continue
378
379 # OK, now we know that the action is valid and we have the
380 # right number of words on the line for that action -- so we
381 # can proceed with minimal error-checking. Also, we have
Greg Ward2b9e43f2000-04-14 00:49:30 +0000382 # defined either (pattern), (dir and pattern), or
383 # (dir_pattern) -- so we don't have to spend any time
384 # digging stuff up out of 'words'.
Greg Warda82122b2000-02-17 23:56:15 +0000385
386 if action == 'include':
Greg Wardf8b9e202000-06-08 00:08:14 +0000387 self.debug_print("include " + string.join(pattern_list))
Greg Ward9d5afa92000-04-21 04:31:10 +0000388 for pattern in pattern_list:
Greg Wardf8b9e202000-06-08 00:08:14 +0000389 files = self.select_pattern (all_files, pattern, anchor=1)
Greg Ward9d5afa92000-04-21 04:31:10 +0000390 if not files:
Greg Wardf8b9e202000-06-08 00:08:14 +0000391 template.warn ("no files found matching '%s'" %
392 pattern)
Greg Ward9d5afa92000-04-21 04:31:10 +0000393 else:
394 self.files.extend (files)
Greg Warda82122b2000-02-17 23:56:15 +0000395
396 elif action == 'exclude':
Greg Wardf8b9e202000-06-08 00:08:14 +0000397 self.debug_print("exclude " + string.join(pattern_list))
Greg Ward9d5afa92000-04-21 04:31:10 +0000398 for pattern in pattern_list:
Greg Wardf8b9e202000-06-08 00:08:14 +0000399 num = self.exclude_pattern (self.files, pattern, anchor=1)
Greg Ward9d5afa92000-04-21 04:31:10 +0000400 if num == 0:
401 template.warn (
402 "no previously-included files found matching '%s'"%
403 pattern)
Greg Warda82122b2000-02-17 23:56:15 +0000404
405 elif action == 'global-include':
Greg Wardf8b9e202000-06-08 00:08:14 +0000406 self.debug_print("global-include " + string.join(pattern_list))
Greg Ward9d5afa92000-04-21 04:31:10 +0000407 for pattern in pattern_list:
Greg Wardf8b9e202000-06-08 00:08:14 +0000408 files = self.select_pattern (all_files, pattern, anchor=0)
Greg Ward9d5afa92000-04-21 04:31:10 +0000409 if not files:
410 template.warn (("no files found matching '%s' " +
411 "anywhere in distribution") %
412 pattern)
413 else:
414 self.files.extend (files)
Greg Warda82122b2000-02-17 23:56:15 +0000415
416 elif action == 'global-exclude':
Greg Wardf8b9e202000-06-08 00:08:14 +0000417 self.debug_print("global-exclude " + string.join(pattern_list))
Greg Ward9d5afa92000-04-21 04:31:10 +0000418 for pattern in pattern_list:
Greg Wardf8b9e202000-06-08 00:08:14 +0000419 num = self.exclude_pattern (self.files, pattern, anchor=0)
Greg Ward9d5afa92000-04-21 04:31:10 +0000420 if num == 0:
421 template.warn \
422 (("no previously-included files matching '%s' " +
423 "found anywhere in distribution") %
424 pattern)
Greg Warda82122b2000-02-17 23:56:15 +0000425
426 elif action == 'recursive-include':
Greg Wardf8b9e202000-06-08 00:08:14 +0000427 self.debug_print("recursive-include %s %s" %
428 (dir, string.join(pattern_list)))
Greg Ward9d5afa92000-04-21 04:31:10 +0000429 for pattern in pattern_list:
Greg Wardf8b9e202000-06-08 00:08:14 +0000430 files = self.select_pattern (
431 all_files, pattern, prefix=dir)
Greg Ward9d5afa92000-04-21 04:31:10 +0000432 if not files:
433 template.warn (("no files found matching '%s' " +
434 "under directory '%s'") %
435 (pattern, dir))
436 else:
437 self.files.extend (files)
Greg Warda82122b2000-02-17 23:56:15 +0000438
439 elif action == 'recursive-exclude':
Greg Wardf8b9e202000-06-08 00:08:14 +0000440 self.debug_print("recursive-exclude %s %s" %
441 (dir, string.join(pattern_list)))
Greg Ward9d5afa92000-04-21 04:31:10 +0000442 for pattern in pattern_list:
Greg Wardf8b9e202000-06-08 00:08:14 +0000443 num = self.exclude_pattern(
444 self.files, pattern, prefix=dir)
Greg Ward9d5afa92000-04-21 04:31:10 +0000445 if num == 0:
446 template.warn \
447 (("no previously-included files matching '%s' " +
448 "found under directory '%s'") %
449 (pattern, dir))
Greg Warda82122b2000-02-17 23:56:15 +0000450
451 elif action == 'graft':
Greg Wardf8b9e202000-06-08 00:08:14 +0000452 self.debug_print("graft " + dir_pattern)
453 files = self.select_pattern(
454 all_files, None, prefix=dir_pattern)
Greg Warda82122b2000-02-17 23:56:15 +0000455 if not files:
456 template.warn ("no directories found matching '%s'" %
457 dir_pattern)
458 else:
459 self.files.extend (files)
460
461 elif action == 'prune':
Greg Wardf8b9e202000-06-08 00:08:14 +0000462 self.debug_print("prune " + dir_pattern)
463 num = self.exclude_pattern(
464 self.files, None, prefix=dir_pattern)
Greg Warda82122b2000-02-17 23:56:15 +0000465 if num == 0:
466 template.warn \
467 (("no previously-included directories found " +
468 "matching '%s'") %
469 dir_pattern)
470 else:
471 raise RuntimeError, \
472 "this cannot happen: invalid action '%s'" % action
473
474 # while loop over lines of template file
475
Greg Warde5b267c2000-05-27 03:03:23 +0000476 # Prune away the build and source distribution directories
Greg Ward4fb29e52000-05-27 17:27:23 +0000477 build = self.get_finalized_command ('build')
Greg Wardf8b9e202000-06-08 00:08:14 +0000478 self.exclude_pattern (self.files, None, prefix=build.build_base)
Greg Warde5b267c2000-05-27 03:03:23 +0000479
480 base_dir = self.distribution.get_fullname()
Greg Wardf8b9e202000-06-08 00:08:14 +0000481 self.exclude_pattern (self.files, None, prefix=base_dir)
Greg Warde5b267c2000-05-27 03:03:23 +0000482
Greg Warda82122b2000-02-17 23:56:15 +0000483 # read_template ()
484
485
Greg Wardf8b9e202000-06-08 00:08:14 +0000486 def select_pattern (self, files, pattern, anchor=1, prefix=None):
487 """Select strings (presumably filenames) from 'files' that match
488 'pattern', a Unix-style wildcard (glob) pattern. Patterns are not
489 quite the same as implemented by the 'fnmatch' module: '*' and '?'
490 match non-special characters, where "special" is platform-dependent:
491 slash on Unix, colon, slash, and backslash on DOS/Windows, and colon on
492 Mac OS.
493
494 If 'anchor' is true (the default), then the pattern match is more
495 stringent: "*.py" will match "foo.py" but not "foo/bar.py". If
496 'anchor' is false, both of these will match.
497
498 If 'prefix' is supplied, then only filenames starting with 'prefix'
499 (itself a pattern) and ending with 'pattern', with anything in between
500 them, will match. 'anchor' is ignored in this case.
501
502 Return the list of matching strings, possibly empty.
503 """
504 matches = []
505 pattern_re = translate_pattern (pattern, anchor, prefix)
506 self.debug_print("select_pattern: applying regex r'%s'" %
507 pattern_re.pattern)
508 for name in files:
509 if pattern_re.search (name):
510 matches.append (name)
511 self.debug_print(" adding " + name)
512
513 return matches
514
515 # select_pattern ()
516
517
518 def exclude_pattern (self, files, pattern, anchor=1, prefix=None):
519 """Remove strings (presumably filenames) from 'files' that match
520 'pattern'. 'pattern', 'anchor', 'and 'prefix' are the same
521 as for 'select_pattern()', above. The list 'files' is modified
522 in place.
523 """
524 pattern_re = translate_pattern (pattern, anchor, prefix)
525 self.debug_print("exclude_pattern: applying regex r'%s'" %
526 pattern_re.pattern)
527 for i in range (len(files)-1, -1, -1):
528 if pattern_re.search (files[i]):
529 self.debug_print(" removing " + files[i])
530 del files[i]
531
532 # exclude_pattern ()
533
534
Greg Warda82122b2000-02-17 23:56:15 +0000535 def write_manifest (self):
536 """Write the file list in 'self.files' (presumably as filled in
537 by 'find_defaults()' and 'read_template()') to the manifest file
538 named by 'self.manifest'."""
539
Greg Ward1b8e1d42000-04-26 01:12:40 +0000540 self.execute(write_file,
541 (self.manifest, self.files),
Greg Wardf8b9e202000-06-08 00:08:14 +0000542 "writing manifest file '%s'" % self.manifest)
Greg Warda82122b2000-02-17 23:56:15 +0000543
544 # write_manifest ()
545
546
547 def read_manifest (self):
548 """Read the manifest file (named by 'self.manifest') and use
549 it to fill in 'self.files', the list of files to include
550 in the source distribution."""
551
Greg Wardf8b9e202000-06-08 00:08:14 +0000552 self.announce("reading manifest file '%s'" % self.manifest)
Greg Warda82122b2000-02-17 23:56:15 +0000553 manifest = open (self.manifest)
554 while 1:
555 line = manifest.readline ()
556 if line == '': # end of file
557 break
558 if line[-1] == '\n':
559 line = line[0:-1]
560 self.files.append (line)
561
562 # read_manifest ()
563
564
Greg Warda82122b2000-02-17 23:56:15 +0000565 def make_release_tree (self, base_dir, files):
566
Greg Ward578c10d2000-03-31 02:50:04 +0000567 # Create all the directories under 'base_dir' necessary to
568 # put 'files' there.
569 create_tree (base_dir, files,
570 verbose=self.verbose, dry_run=self.dry_run)
Greg Warda82122b2000-02-17 23:56:15 +0000571
572 # And walk over the list of files, either making a hard link (if
573 # os.link exists) to each one that doesn't already exist in its
574 # corresponding location under 'base_dir', or copying each file
575 # that's out-of-date in 'base_dir'. (Usually, all files will be
576 # out-of-date, because by default we blow away 'base_dir' when
577 # we're done making the distribution archives.)
578
Greg Ward578c10d2000-03-31 02:50:04 +0000579 if hasattr (os, 'link'): # can make hard links on this system
580 link = 'hard'
Greg Warda82122b2000-02-17 23:56:15 +0000581 msg = "making hard links in %s..." % base_dir
Greg Ward578c10d2000-03-31 02:50:04 +0000582 else: # nope, have to copy
583 link = None
Greg Warda82122b2000-02-17 23:56:15 +0000584 msg = "copying files to %s..." % base_dir
585
586 self.announce (msg)
587 for file in files:
588 dest = os.path.join (base_dir, file)
Greg Ward578c10d2000-03-31 02:50:04 +0000589 self.copy_file (file, dest, link=link)
Greg Warda82122b2000-02-17 23:56:15 +0000590
591 # make_release_tree ()
592
593
Greg Warda82122b2000-02-17 23:56:15 +0000594 def make_distribution (self):
595
Greg Ward578c10d2000-03-31 02:50:04 +0000596 # Don't warn about missing meta-data here -- should be (and is!)
597 # done elsewhere.
Greg Ward0ae7f762000-04-22 02:51:25 +0000598 base_dir = self.distribution.get_fullname()
Greg Warda82122b2000-02-17 23:56:15 +0000599
600 # Remove any files that match "base_dir" from the fileset -- we
601 # don't want to go distributing the distribution inside itself!
Greg Wardf8b9e202000-06-08 00:08:14 +0000602 self.exclude_pattern (self.files, base_dir + "*")
Greg Warda82122b2000-02-17 23:56:15 +0000603
604 self.make_release_tree (base_dir, self.files)
Greg Wardd87eb732000-06-01 01:10:56 +0000605 archive_files = [] # remember names of files we create
Greg Warda82122b2000-02-17 23:56:15 +0000606 for fmt in self.formats:
Greg Wardd87eb732000-06-01 01:10:56 +0000607 file = self.make_archive (base_dir, fmt, base_dir=base_dir)
608 archive_files.append(file)
609
610 self.archive_files = archive_files
Greg Warda82122b2000-02-17 23:56:15 +0000611
612 if not self.keep_tree:
Greg Ward2dc139c2000-03-18 15:43:42 +0000613 remove_tree (base_dir, self.verbose, self.dry_run)
Greg Warda82122b2000-02-17 23:56:15 +0000614
Greg Wardd87eb732000-06-01 01:10:56 +0000615 def get_archive_files (self):
616 """Return the list of archive files created when the command
617 was run, or None if the command hasn't run yet.
618 """
619 return self.archive_files
620
Greg Wardfcd974e2000-05-25 01:10:04 +0000621# class sdist
Greg Warda82122b2000-02-17 23:56:15 +0000622
623
624# ----------------------------------------------------------------------
625# Utility functions
626
627def findall (dir = os.curdir):
628 """Find all files under 'dir' and return the list of full
629 filenames (relative to 'dir')."""
630
631 list = []
632 stack = [dir]
633 pop = stack.pop
634 push = stack.append
635
636 while stack:
637 dir = pop()
638 names = os.listdir (dir)
639
640 for name in names:
641 if dir != os.curdir: # avoid the dreaded "./" syndrome
642 fullname = os.path.join (dir, name)
643 else:
644 fullname = name
645 list.append (fullname)
646 if os.path.isdir (fullname) and not os.path.islink(fullname):
647 push (fullname)
648
649 return list
650
651
Greg Warda82122b2000-02-17 23:56:15 +0000652def glob_to_re (pattern):
653 """Translate a shell-like glob pattern to a regular expression;
654 return a string containing the regex. Differs from
655 'fnmatch.translate()' in that '*' does not match "special
656 characters" (which are platform-specific)."""
657 pattern_re = fnmatch.translate (pattern)
658
659 # '?' and '*' in the glob pattern become '.' and '.*' in the RE, which
660 # IMHO is wrong -- '?' and '*' aren't supposed to match slash in Unix,
661 # and by extension they shouldn't match such "special characters" under
662 # any OS. So change all non-escaped dots in the RE to match any
663 # character except the special characters.
664 # XXX currently the "special characters" are just slash -- i.e. this is
665 # Unix-only.
666 pattern_re = re.sub (r'(^|[^\\])\.', r'\1[^/]', pattern_re)
667 return pattern_re
668
669# glob_to_re ()
670
671
672def translate_pattern (pattern, anchor=1, prefix=None):
673 """Translate a shell-like wildcard pattern to a compiled regular
674 expression. Return the compiled regex."""
675
676 if pattern:
677 pattern_re = glob_to_re (pattern)
678 else:
679 pattern_re = ''
680
681 if prefix is not None:
682 prefix_re = (glob_to_re (prefix))[0:-1] # ditch trailing $
683 pattern_re = "^" + os.path.join (prefix_re, ".*" + pattern_re)
684 else: # no prefix -- respect anchor flag
685 if anchor:
686 pattern_re = "^" + pattern_re
687
688 return re.compile (pattern_re)
689
690# translate_pattern ()