blob: 7dbd40138ab937a966ee52a899b6bd8efc199737 [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 Ward578c10d2000-03-31 02:50:04 +000014from distutils.util import \
15 newer, remove_tree, make_tarball, make_zipfile, create_tree
Greg Warda82122b2000-02-17 23:56:15 +000016from distutils.text_file import TextFile
17from distutils.errors import DistutilsExecError
18
19
Greg Ward1993f9a2000-02-18 00:13:53 +000020class sdist (Command):
Greg Warda82122b2000-02-17 23:56:15 +000021
22 description = "create a source distribution (tarball, zip file, etc.)"
23
Greg Wardbbeceea2000-02-18 00:25:39 +000024 user_options = [
25 ('template=', 't',
26 "name of manifest template file [default: MANIFEST.in]"),
27 ('manifest=', 'm',
28 "name of manifest file [default: MANIFEST]"),
29 ('use-defaults', None,
30 "include the default file set in the manifest "
31 "[default; disable with --no-defaults]"),
32 ('manifest-only', None,
33 "just regenerate the manifest and then stop"),
34 ('force-manifest', None,
35 "forcibly regenerate the manifest and carry on as usual"),
36
37 ('formats=', None,
38 "formats for source distribution (tar, ztar, gztar, or zip)"),
39 ('list-only', 'l',
40 "just list files that would be distributed"),
41 ('keep-tree', 'k',
42 "keep the distribution tree around after creating " +
43 "archive file(s)"),
44 ]
Greg Warda82122b2000-02-17 23:56:15 +000045 negative_opts = {'use-defaults': 'no-defaults'}
46
47 default_format = { 'posix': 'gztar',
48 'nt': 'zip' }
49
50 exclude_re = re.compile (r'\s*!\s*(\S+)') # for manifest lines
51
52
Greg Warde01149c2000-02-18 00:35:22 +000053 def initialize_options (self):
Greg Warda82122b2000-02-17 23:56:15 +000054 # 'template' and 'manifest' are, respectively, the names of
55 # the manifest template and manifest file.
56 self.template = None
57 self.manifest = None
58
59 # 'use_defaults': if true, we will include the default file set
60 # in the manifest
61 self.use_defaults = 1
62
63 self.manifest_only = 0
64 self.force_manifest = 0
65
66 self.formats = None
67 self.list_only = 0
68 self.keep_tree = 0
69
70
Greg Warde01149c2000-02-18 00:35:22 +000071 def finalize_options (self):
Greg Warda82122b2000-02-17 23:56:15 +000072 if self.manifest is None:
73 self.manifest = "MANIFEST"
74 if self.template is None:
75 self.template = "MANIFEST.in"
76
77 if self.formats is None:
78 try:
79 self.formats = [self.default_format[os.name]]
80 except KeyError:
81 raise DistutilsPlatformError, \
Greg Ward578c10d2000-03-31 02:50:04 +000082 "don't know how to create source distributions " + \
83 "on platform %s" % os.name
Greg Warda82122b2000-02-17 23:56:15 +000084 elif type (self.formats) is StringType:
85 self.formats = string.split (self.formats, ',')
86
87
88 def run (self):
89
90 # 'files' is the list of files that will make up the manifest
91 self.files = []
92
93 # Ensure that all required meta-data is given; warn if not (but
94 # don't die, it's not *that* serious!)
95 self.check_metadata ()
96
97 # Do whatever it takes to get the list of files to process
98 # (process the manifest template, read an existing manifest,
99 # whatever). File list is put into 'self.files'.
100 self.get_file_list ()
101
102 # If user just wanted us to regenerate the manifest, stop now.
103 if self.manifest_only:
104 return
105
106 # Otherwise, go ahead and create the source distribution tarball,
107 # or zipfile, or whatever.
108 self.make_distribution ()
109
110
111 def check_metadata (self):
112
113 dist = self.distribution
114
115 missing = []
116 for attr in ('name', 'version', 'url'):
117 if not (hasattr (dist, attr) and getattr (dist, attr)):
118 missing.append (attr)
119
120 if missing:
121 self.warn ("missing required meta-data: " +
122 string.join (missing, ", "))
123
124 if dist.author:
125 if not dist.author_email:
126 self.warn ("missing meta-data: if 'author' supplied, " +
127 "'author_email' must be supplied too")
128 elif dist.maintainer:
129 if not dist.maintainer_email:
130 self.warn ("missing meta-data: if 'maintainer' supplied, " +
131 "'maintainer_email' must be supplied too")
132 else:
133 self.warn ("missing meta-data: either (author and author_email) " +
134 "or (maintainer and maintainer_email) " +
135 "must be supplied")
136
137 # check_metadata ()
138
139
140 def get_file_list (self):
141 """Figure out the list of files to include in the source
142 distribution, and put it in 'self.files'. This might
143 involve reading the manifest template (and writing the
144 manifest), or just reading the manifest, or just using
145 the default file set -- it all depends on the user's
146 options and the state of the filesystem."""
147
148
149 template_exists = os.path.isfile (self.template)
150 if template_exists:
151 template_newer = newer (self.template, self.manifest)
152
153 # Regenerate the manifest if necessary (or if explicitly told to)
154 if ((template_exists and template_newer) or
155 self.force_manifest or
156 self.manifest_only):
157
158 if not template_exists:
159 self.warn (("manifest template '%s' does not exist " +
160 "(using default file list)") %
161 self.template)
162
163 # Add default file set to 'files'
164 if self.use_defaults:
165 self.find_defaults ()
166
167 # Read manifest template if it exists
168 if template_exists:
169 self.read_template ()
170
171 # File list now complete -- sort it so that higher-level files
172 # come first
173 sortable_files = map (os.path.split, self.files)
174 sortable_files.sort ()
175 self.files = []
176 for sort_tuple in sortable_files:
177 self.files.append (apply (os.path.join, sort_tuple))
178
179 # Remove duplicates from the file list
180 for i in range (len(self.files)-1, 0, -1):
181 if self.files[i] == self.files[i-1]:
182 del self.files[i]
183
184 # And write complete file list (including default file set) to
185 # the manifest.
186 self.write_manifest ()
187
188 # Don't regenerate the manifest, just read it in.
189 else:
190 self.read_manifest ()
191
192 # get_file_list ()
193
194
195 def find_defaults (self):
196
197 standards = [('README', 'README.txt'), 'setup.py']
198 for fn in standards:
199 if type (fn) is TupleType:
200 alts = fn
Greg Ward48401122000-02-24 03:17:43 +0000201 got_it = 0
Greg Warda82122b2000-02-17 23:56:15 +0000202 for fn in alts:
203 if os.path.exists (fn):
204 got_it = 1
205 self.files.append (fn)
206 break
207
208 if not got_it:
209 self.warn ("standard file not found: should have one of " +
210 string.join (alts, ', '))
211 else:
212 if os.path.exists (fn):
213 self.files.append (fn)
214 else:
215 self.warn ("standard file '%s' not found" % fn)
216
217 optional = ['test/test*.py']
218 for pattern in optional:
219 files = filter (os.path.isfile, glob (pattern))
220 if files:
221 self.files.extend (files)
222
Greg Ward578c10d2000-03-31 02:50:04 +0000223 if self.distribution.has_pure_modules():
Greg Warda82122b2000-02-17 23:56:15 +0000224 build_py = self.find_peer ('build_py')
Greg Warda82122b2000-02-17 23:56:15 +0000225 self.files.extend (build_py.get_source_files ())
226
Greg Ward578c10d2000-03-31 02:50:04 +0000227 if self.distribution.has_ext_modules():
Greg Warda82122b2000-02-17 23:56:15 +0000228 build_ext = self.find_peer ('build_ext')
Greg Warda82122b2000-02-17 23:56:15 +0000229 self.files.extend (build_ext.get_source_files ())
230
231
Greg Warda82122b2000-02-17 23:56:15 +0000232 def search_dir (self, dir, pattern=None):
233 """Recursively find files under 'dir' matching 'pattern' (a string
234 containing a Unix-style glob pattern). If 'pattern' is None,
235 find all files under 'dir'. Return the list of found
236 filenames."""
237
238 allfiles = findall (dir)
239 if pattern is None:
240 return allfiles
241
242 pattern_re = translate_pattern (pattern)
243 files = []
244 for file in allfiles:
245 if pattern_re.match (os.path.basename (file)):
246 files.append (file)
247
248 return files
249
250 # search_dir ()
251
252
253 def exclude_pattern (self, pattern):
254 """Remove filenames from 'self.files' that match 'pattern'."""
255 print "exclude_pattern: pattern=%s" % pattern
256 pattern_re = translate_pattern (pattern)
257 for i in range (len (self.files)-1, -1, -1):
258 if pattern_re.match (self.files[i]):
259 print "removing %s" % self.files[i]
260 del self.files[i]
261
262
263 def recursive_exclude_pattern (self, dir, pattern=None):
264 """Remove filenames from 'self.files' that are under 'dir'
265 and whose basenames match 'pattern'."""
266
267 print "recursive_exclude_pattern: dir=%s, pattern=%s" % (dir, pattern)
268 if pattern is None:
269 pattern_re = None
270 else:
271 pattern_re = translate_pattern (pattern)
272
273 for i in range (len (self.files)-1, -1, -1):
274 (cur_dir, cur_base) = os.path.split (self.files[i])
275 if (cur_dir == dir and
276 (pattern_re is None or pattern_re.match (cur_base))):
277 print "removing %s" % self.files[i]
278 del self.files[i]
279
280
281 def read_template (self):
282 """Read and parse the manifest template file named by
283 'self.template' (usually "MANIFEST.in"). Process all file
284 specifications (include and exclude) in the manifest template
285 and add the resulting filenames to 'self.files'."""
286
287 assert self.files is not None and type (self.files) is ListType
288
289 template = TextFile (self.template,
290 strip_comments=1,
291 skip_blanks=1,
292 join_lines=1,
293 lstrip_ws=1,
294 rstrip_ws=1,
295 collapse_ws=1)
296
297 all_files = findall ()
298
299 while 1:
300
301 line = template.readline()
302 if line is None: # end of file
303 break
304
305 words = string.split (line)
306 action = words[0]
307
308 # First, check that the right number of words are present
309 # for the given action (which is the first word)
310 if action in ('include','exclude',
311 'global-include','global-exclude'):
312 if len (words) != 2:
313 template.warn \
314 ("invalid manifest template line: " +
315 "'%s' expects a single <pattern>" %
316 action)
317 continue
318
319 pattern = words[1]
320
321 elif action in ('recursive-include','recursive-exclude'):
322 if len (words) != 3:
323 template.warn \
324 ("invalid manifest template line: " +
325 "'%s' expects <dir> <pattern>" %
326 action)
327 continue
328
329 (dir, pattern) = words[1:3]
330
331 elif action in ('graft','prune'):
332 if len (words) != 2:
333 template.warn \
334 ("invalid manifest template line: " +
335 "'%s' expects a single <dir_pattern>" %
336 action)
337 continue
338
339 dir_pattern = words[1]
340
341 else:
342 template.warn ("invalid manifest template line: " +
343 "unknown action '%s'" % action)
344 continue
345
346 # OK, now we know that the action is valid and we have the
347 # right number of words on the line for that action -- so we
348 # can proceed with minimal error-checking. Also, we have
349 # defined either 'patter', 'dir' and 'pattern', or
350 # 'dir_pattern' -- so we don't have to spend any time digging
351 # stuff up out of 'words'.
352
353 if action == 'include':
354 print "include", pattern
355 files = select_pattern (all_files, pattern, anchor=1)
356 if not files:
357 template.warn ("no files found matching '%s'" % pattern)
358 else:
359 self.files.extend (files)
360
361 elif action == 'exclude':
362 print "exclude", pattern
363 num = exclude_pattern (self.files, pattern, anchor=1)
364 if num == 0:
365 template.warn \
366 ("no previously-included files found matching '%s'" %
367 pattern)
368
369 elif action == 'global-include':
370 print "global-include", pattern
371 files = select_pattern (all_files, pattern, anchor=0)
372 if not files:
373 template.warn (("no files found matching '%s' " +
374 "anywhere in distribution") %
375 pattern)
376 else:
377 self.files.extend (files)
378
379 elif action == 'global-exclude':
380 print "global-exclude", pattern
381 num = exclude_pattern (self.files, pattern, anchor=0)
382 if num == 0:
383 template.warn \
384 (("no previously-included files matching '%s' " +
385 "found anywhere in distribution") %
386 pattern)
387
388 elif action == 'recursive-include':
389 print "recursive-include", dir, pattern
390 files = select_pattern (all_files, pattern, prefix=dir)
391 if not files:
392 template.warn (("no files found matching '%s' " +
393 "under directory '%s'") %
394 (pattern, dir))
395 else:
396 self.files.extend (files)
397
398 elif action == 'recursive-exclude':
399 print "recursive-exclude", dir, pattern
400 num = exclude_pattern (self.files, pattern, prefix=dir)
401 if num == 0:
402 template.warn \
403 (("no previously-included files matching '%s' " +
404 "found under directory '%s'") %
405 (pattern, dir))
406
407 elif action == 'graft':
408 print "graft", dir_pattern
409 files = select_pattern (all_files, None, prefix=dir_pattern)
410 if not files:
411 template.warn ("no directories found matching '%s'" %
412 dir_pattern)
413 else:
414 self.files.extend (files)
415
416 elif action == 'prune':
417 print "prune", dir_pattern
418 num = exclude_pattern (self.files, None, prefix=dir_pattern)
419 if num == 0:
420 template.warn \
421 (("no previously-included directories found " +
422 "matching '%s'") %
423 dir_pattern)
424 else:
425 raise RuntimeError, \
426 "this cannot happen: invalid action '%s'" % action
427
428 # while loop over lines of template file
429
430 # read_template ()
431
432
433 def write_manifest (self):
434 """Write the file list in 'self.files' (presumably as filled in
435 by 'find_defaults()' and 'read_template()') to the manifest file
436 named by 'self.manifest'."""
437
438 manifest = open (self.manifest, "w")
439 for fn in self.files:
440 manifest.write (fn + '\n')
441 manifest.close ()
442
443 # write_manifest ()
444
445
446 def read_manifest (self):
447 """Read the manifest file (named by 'self.manifest') and use
448 it to fill in 'self.files', the list of files to include
449 in the source distribution."""
450
451 manifest = open (self.manifest)
452 while 1:
453 line = manifest.readline ()
454 if line == '': # end of file
455 break
456 if line[-1] == '\n':
457 line = line[0:-1]
458 self.files.append (line)
459
460 # read_manifest ()
461
462
463
464 def make_release_tree (self, base_dir, files):
465
Greg Ward578c10d2000-03-31 02:50:04 +0000466 # Create all the directories under 'base_dir' necessary to
467 # put 'files' there.
468 create_tree (base_dir, files,
469 verbose=self.verbose, dry_run=self.dry_run)
Greg Warda82122b2000-02-17 23:56:15 +0000470
471 # And walk over the list of files, either making a hard link (if
472 # os.link exists) to each one that doesn't already exist in its
473 # corresponding location under 'base_dir', or copying each file
474 # that's out-of-date in 'base_dir'. (Usually, all files will be
475 # out-of-date, because by default we blow away 'base_dir' when
476 # we're done making the distribution archives.)
477
Greg Ward578c10d2000-03-31 02:50:04 +0000478 if hasattr (os, 'link'): # can make hard links on this system
479 link = 'hard'
Greg Warda82122b2000-02-17 23:56:15 +0000480 msg = "making hard links in %s..." % base_dir
Greg Ward578c10d2000-03-31 02:50:04 +0000481 else: # nope, have to copy
482 link = None
Greg Warda82122b2000-02-17 23:56:15 +0000483 msg = "copying files to %s..." % base_dir
484
485 self.announce (msg)
486 for file in files:
487 dest = os.path.join (base_dir, file)
Greg Ward578c10d2000-03-31 02:50:04 +0000488 self.copy_file (file, dest, link=link)
Greg Warda82122b2000-02-17 23:56:15 +0000489
490 # make_release_tree ()
491
492
Greg Warda82122b2000-02-17 23:56:15 +0000493 def make_distribution (self):
494
Greg Ward578c10d2000-03-31 02:50:04 +0000495 # Don't warn about missing meta-data here -- should be (and is!)
496 # done elsewhere.
497 base_dir = self.distribution.get_full_name()
Greg Warda82122b2000-02-17 23:56:15 +0000498
499 # Remove any files that match "base_dir" from the fileset -- we
500 # don't want to go distributing the distribution inside itself!
501 self.exclude_pattern (base_dir + "*")
502
503 self.make_release_tree (base_dir, self.files)
504 for fmt in self.formats:
Greg Ward578c10d2000-03-31 02:50:04 +0000505 self.make_archive (base_dir, fmt, base_dir=base_dir)
Greg Warda82122b2000-02-17 23:56:15 +0000506
507 if not self.keep_tree:
Greg Ward2dc139c2000-03-18 15:43:42 +0000508 remove_tree (base_dir, self.verbose, self.dry_run)
Greg Warda82122b2000-02-17 23:56:15 +0000509
510# class Dist
511
512
513# ----------------------------------------------------------------------
514# Utility functions
515
516def findall (dir = os.curdir):
517 """Find all files under 'dir' and return the list of full
518 filenames (relative to 'dir')."""
519
520 list = []
521 stack = [dir]
522 pop = stack.pop
523 push = stack.append
524
525 while stack:
526 dir = pop()
527 names = os.listdir (dir)
528
529 for name in names:
530 if dir != os.curdir: # avoid the dreaded "./" syndrome
531 fullname = os.path.join (dir, name)
532 else:
533 fullname = name
534 list.append (fullname)
535 if os.path.isdir (fullname) and not os.path.islink(fullname):
536 push (fullname)
537
538 return list
539
540
541def select_pattern (files, pattern, anchor=1, prefix=None):
542 """Select strings (presumably filenames) from 'files' that match
543 'pattern', a Unix-style wildcard (glob) pattern. Patterns are not
544 quite the same as implemented by the 'fnmatch' module: '*' and '?'
545 match non-special characters, where "special" is platform-dependent:
546 slash on Unix, colon, slash, and backslash on DOS/Windows, and colon
547 on Mac OS.
548
549 If 'anchor' is true (the default), then the pattern match is more
550 stringent: "*.py" will match "foo.py" but not "foo/bar.py". If
551 'anchor' is false, both of these will match.
552
553 If 'prefix' is supplied, then only filenames starting with 'prefix'
554 (itself a pattern) and ending with 'pattern', with anything in
555 between them, will match. 'anchor' is ignored in this case.
556
557 Return the list of matching strings, possibly empty."""
558
559 matches = []
560 pattern_re = translate_pattern (pattern, anchor, prefix)
561 print "select_pattern: applying re %s" % pattern_re.pattern
562 for name in files:
563 if pattern_re.search (name):
564 matches.append (name)
565 print " adding", name
566
567 return matches
568
569# select_pattern ()
570
571
572def exclude_pattern (files, pattern, anchor=1, prefix=None):
573
574 pattern_re = translate_pattern (pattern, anchor, prefix)
575 print "exclude_pattern: applying re %s" % pattern_re.pattern
576 for i in range (len(files)-1, -1, -1):
577 if pattern_re.search (files[i]):
578 print " removing", files[i]
579 del files[i]
580
581# exclude_pattern ()
582
583
584def glob_to_re (pattern):
585 """Translate a shell-like glob pattern to a regular expression;
586 return a string containing the regex. Differs from
587 'fnmatch.translate()' in that '*' does not match "special
588 characters" (which are platform-specific)."""
589 pattern_re = fnmatch.translate (pattern)
590
591 # '?' and '*' in the glob pattern become '.' and '.*' in the RE, which
592 # IMHO is wrong -- '?' and '*' aren't supposed to match slash in Unix,
593 # and by extension they shouldn't match such "special characters" under
594 # any OS. So change all non-escaped dots in the RE to match any
595 # character except the special characters.
596 # XXX currently the "special characters" are just slash -- i.e. this is
597 # Unix-only.
598 pattern_re = re.sub (r'(^|[^\\])\.', r'\1[^/]', pattern_re)
599 return pattern_re
600
601# glob_to_re ()
602
603
604def translate_pattern (pattern, anchor=1, prefix=None):
605 """Translate a shell-like wildcard pattern to a compiled regular
606 expression. Return the compiled regex."""
607
608 if pattern:
609 pattern_re = glob_to_re (pattern)
610 else:
611 pattern_re = ''
612
613 if prefix is not None:
614 prefix_re = (glob_to_re (prefix))[0:-1] # ditch trailing $
615 pattern_re = "^" + os.path.join (prefix_re, ".*" + pattern_re)
616 else: # no prefix -- respect anchor flag
617 if anchor:
618 pattern_re = "^" + pattern_re
619
620 return re.compile (pattern_re)
621
622# translate_pattern ()