diff options
-rw-r--r-- | Lib/distutils/filelist.py | 20 | ||||
-rw-r--r-- | Lib/distutils/tests/test_filelist.py | 113 | ||||
-rw-r--r-- | Lib/distutils/tests/test_sdist.py | 14 | ||||
-rw-r--r-- | Misc/NEWS | 5 |
4 files changed, 98 insertions, 54 deletions
diff --git a/Lib/distutils/filelist.py b/Lib/distutils/filelist.py index 5540b39a38..2f1c457ea0 100644 --- a/Lib/distutils/filelist.py +++ b/Lib/distutils/filelist.py @@ -210,6 +210,7 @@ class FileList: Return 1 if files are found. """ + # XXX docstring lying about what the special chars are? files_found = 0 pattern_re = translate_pattern(pattern, anchor, prefix, is_regex) self.debug_print("include_pattern: applying regex r'%s'" % @@ -297,11 +298,14 @@ def glob_to_re(pattern): # IMHO is wrong -- '?' and '*' aren't supposed to match slash in Unix, # and by extension they shouldn't match such "special characters" under # any OS. So change all non-escaped dots in the RE to match any - # character except the special characters. - # XXX currently the "special characters" are just slash -- i.e. this is - # Unix-only. - pattern_re = re.sub(r'((?<!\\)(\\\\)*)\.', r'\1[^/]', pattern_re) - + # character except the special characters (currently: just os.sep). + sep = os.sep + if os.sep == '\\': + # we're using a regex to manipulate a regex, so we need + # to escape the backslash twice + sep = r'\\\\' + escaped = r'\1[^%s]' % sep + pattern_re = re.sub(r'((?<!\\)(\\\\)*)\.', escaped, pattern_re) return pattern_re @@ -328,8 +332,10 @@ def translate_pattern(pattern, anchor=1, prefix=None, is_regex=0): # ditch end of pattern character empty_pattern = glob_to_re('') prefix_re = glob_to_re(prefix)[:-len(empty_pattern)] - # paths should always use / in manifest templates - pattern_re = "^%s/.*%s" % (prefix_re, pattern_re) + sep = os.sep + if os.sep == '\\': + sep = r'\\' + pattern_re = "^" + sep.join((prefix_re, ".*" + pattern_re)) else: # no prefix -- respect anchor flag if anchor: pattern_re = "^" + pattern_re diff --git a/Lib/distutils/tests/test_filelist.py b/Lib/distutils/tests/test_filelist.py index d86ea44491..69b88f2df2 100644 --- a/Lib/distutils/tests/test_filelist.py +++ b/Lib/distutils/tests/test_filelist.py @@ -1,4 +1,5 @@ """Tests for distutils.filelist.""" +import os import re import unittest from distutils import debug @@ -14,6 +15,7 @@ include ok include xo exclude xo include foo.tmp +include buildout.cfg global-include *.x global-include *.txt global-exclude *.tmp @@ -24,6 +26,11 @@ prune dir3 """ +def make_local_path(s): + """Converts '/' in a string to os.sep""" + return s.replace('/', os.sep) + + class FileListTestCase(support.LoggingSilencer, unittest.TestCase): @@ -36,41 +43,60 @@ class FileListTestCase(support.LoggingSilencer, self.clear_logs() def test_glob_to_re(self): - # simple cases - self.assertEqual(glob_to_re('foo*'), 'foo[^/]*\\Z(?ms)') - self.assertEqual(glob_to_re('foo?'), 'foo[^/]\\Z(?ms)') - self.assertEqual(glob_to_re('foo??'), 'foo[^/][^/]\\Z(?ms)') - - # special cases - self.assertEqual(glob_to_re(r'foo\\*'), r'foo\\\\[^/]*\Z(?ms)') - self.assertEqual(glob_to_re(r'foo\\\*'), r'foo\\\\\\[^/]*\Z(?ms)') - self.assertEqual(glob_to_re('foo????'), r'foo[^/][^/][^/][^/]\Z(?ms)') - self.assertEqual(glob_to_re(r'foo\\??'), r'foo\\\\[^/][^/]\Z(?ms)') + sep = os.sep + if os.sep == '\\': + sep = re.escape(os.sep) + + for glob, regex in ( + # simple cases + ('foo*', r'foo[^%(sep)s]*\Z(?ms)'), + ('foo?', r'foo[^%(sep)s]\Z(?ms)'), + ('foo??', r'foo[^%(sep)s][^%(sep)s]\Z(?ms)'), + # special cases + (r'foo\\*', r'foo\\\\[^%(sep)s]*\Z(?ms)'), + (r'foo\\\*', r'foo\\\\\\[^%(sep)s]*\Z(?ms)'), + ('foo????', r'foo[^%(sep)s][^%(sep)s][^%(sep)s][^%(sep)s]\Z(?ms)'), + (r'foo\\??', r'foo\\\\[^%(sep)s][^%(sep)s]\Z(?ms)')): + regex = regex % {'sep': sep} + self.assertEqual(glob_to_re(glob), regex) def test_process_template_line(self): # testing all MANIFEST.in template patterns file_list = FileList() + l = make_local_path # simulated file list file_list.allfiles = ['foo.tmp', 'ok', 'xo', 'four.txt', - 'global/one.txt', - 'global/two.txt', - 'global/files.x', - 'global/here.tmp', - 'f/o/f.oo', - 'dir/graft-one', - 'dir/dir2/graft2', - 'dir3/ok', - 'dir3/sub/ok.txt', - ] + 'buildout.cfg', + # filelist does not filter out VCS directories, + # it's sdist that does + l('.hg/last-message.txt'), + l('global/one.txt'), + l('global/two.txt'), + l('global/files.x'), + l('global/here.tmp'), + l('f/o/f.oo'), + l('dir/graft-one'), + l('dir/dir2/graft2'), + l('dir3/ok'), + l('dir3/sub/ok.txt'), + ] for line in MANIFEST_IN.split('\n'): if line.strip() == '': continue file_list.process_template_line(line) - wanted = ['ok', 'four.txt', 'global/one.txt', 'global/two.txt', - 'f/o/f.oo', 'dir/graft-one', 'dir/dir2/graft2'] + wanted = ['ok', + 'buildout.cfg', + 'four.txt', + l('.hg/last-message.txt'), + l('global/one.txt'), + l('global/two.txt'), + l('f/o/f.oo'), + l('dir/graft-one'), + l('dir/dir2/graft2'), + ] self.assertEqual(file_list.files, wanted) @@ -158,6 +184,7 @@ class FileListTestCase(support.LoggingSilencer, self.assertEqual(file_list.allfiles, ['a.py', 'b.txt']) def test_process_template(self): + l = make_local_path # invalid lines file_list = FileList() for action in ('include', 'exclude', 'global-include', @@ -168,7 +195,7 @@ class FileListTestCase(support.LoggingSilencer, # include file_list = FileList() - file_list.set_allfiles(['a.py', 'b.txt', 'd/c.py']) + file_list.set_allfiles(['a.py', 'b.txt', l('d/c.py')]) file_list.process_template_line('include *.py') self.assertEqual(file_list.files, ['a.py']) @@ -180,31 +207,31 @@ class FileListTestCase(support.LoggingSilencer, # exclude file_list = FileList() - file_list.files = ['a.py', 'b.txt', 'd/c.py'] + file_list.files = ['a.py', 'b.txt', l('d/c.py')] file_list.process_template_line('exclude *.py') - self.assertEqual(file_list.files, ['b.txt', 'd/c.py']) + self.assertEqual(file_list.files, ['b.txt', l('d/c.py')]) self.assertNoWarnings() file_list.process_template_line('exclude *.rb') - self.assertEqual(file_list.files, ['b.txt', 'd/c.py']) + self.assertEqual(file_list.files, ['b.txt', l('d/c.py')]) self.assertWarnings() # global-include file_list = FileList() - file_list.set_allfiles(['a.py', 'b.txt', 'd/c.py']) + file_list.set_allfiles(['a.py', 'b.txt', l('d/c.py')]) file_list.process_template_line('global-include *.py') - self.assertEqual(file_list.files, ['a.py', 'd/c.py']) + self.assertEqual(file_list.files, ['a.py', l('d/c.py')]) self.assertNoWarnings() file_list.process_template_line('global-include *.rb') - self.assertEqual(file_list.files, ['a.py', 'd/c.py']) + self.assertEqual(file_list.files, ['a.py', l('d/c.py')]) self.assertWarnings() # global-exclude file_list = FileList() - file_list.files = ['a.py', 'b.txt', 'd/c.py'] + file_list.files = ['a.py', 'b.txt', l('d/c.py')] file_list.process_template_line('global-exclude *.py') self.assertEqual(file_list.files, ['b.txt']) @@ -216,50 +243,52 @@ class FileListTestCase(support.LoggingSilencer, # recursive-include file_list = FileList() - file_list.set_allfiles(['a.py', 'd/b.py', 'd/c.txt', 'd/d/e.py']) + file_list.set_allfiles(['a.py', l('d/b.py'), l('d/c.txt'), + l('d/d/e.py')]) file_list.process_template_line('recursive-include d *.py') - self.assertEqual(file_list.files, ['d/b.py', 'd/d/e.py']) + self.assertEqual(file_list.files, [l('d/b.py'), l('d/d/e.py')]) self.assertNoWarnings() file_list.process_template_line('recursive-include e *.py') - self.assertEqual(file_list.files, ['d/b.py', 'd/d/e.py']) + self.assertEqual(file_list.files, [l('d/b.py'), l('d/d/e.py')]) self.assertWarnings() # recursive-exclude file_list = FileList() - file_list.files = ['a.py', 'd/b.py', 'd/c.txt', 'd/d/e.py'] + file_list.files = ['a.py', l('d/b.py'), l('d/c.txt'), l('d/d/e.py')] file_list.process_template_line('recursive-exclude d *.py') - self.assertEqual(file_list.files, ['a.py', 'd/c.txt']) + self.assertEqual(file_list.files, ['a.py', l('d/c.txt')]) self.assertNoWarnings() file_list.process_template_line('recursive-exclude e *.py') - self.assertEqual(file_list.files, ['a.py', 'd/c.txt']) + self.assertEqual(file_list.files, ['a.py', l('d/c.txt')]) self.assertWarnings() # graft file_list = FileList() - file_list.set_allfiles(['a.py', 'd/b.py', 'd/d/e.py', 'f/f.py']) + file_list.set_allfiles(['a.py', l('d/b.py'), l('d/d/e.py'), + l('f/f.py')]) file_list.process_template_line('graft d') - self.assertEqual(file_list.files, ['d/b.py', 'd/d/e.py']) + self.assertEqual(file_list.files, [l('d/b.py'), l('d/d/e.py')]) self.assertNoWarnings() file_list.process_template_line('graft e') - self.assertEqual(file_list.files, ['d/b.py', 'd/d/e.py']) + self.assertEqual(file_list.files, [l('d/b.py'), l('d/d/e.py')]) self.assertWarnings() # prune file_list = FileList() - file_list.files = ['a.py', 'd/b.py', 'd/d/e.py', 'f/f.py'] + file_list.files = ['a.py', l('d/b.py'), l('d/d/e.py'), l('f/f.py')] file_list.process_template_line('prune d') - self.assertEqual(file_list.files, ['a.py', 'f/f.py']) + self.assertEqual(file_list.files, ['a.py', l('f/f.py')]) self.assertNoWarnings() file_list.process_template_line('prune e') - self.assertEqual(file_list.files, ['a.py', 'f/f.py']) + self.assertEqual(file_list.files, ['a.py', l('f/f.py')]) self.assertWarnings() diff --git a/Lib/distutils/tests/test_sdist.py b/Lib/distutils/tests/test_sdist.py index cb7beb7b2c..9e422fca17 100644 --- a/Lib/distutils/tests/test_sdist.py +++ b/Lib/distutils/tests/test_sdist.py @@ -42,6 +42,7 @@ setup(name='fake') MANIFEST = """\ # file GENERATED by distutils, do NOT edit README +buildout.cfg inroot.txt setup.py data%(sep)sdata.dt @@ -150,7 +151,7 @@ class SDistTestCase(PyPIRCCommandTestCase): dist_folder = join(self.tmp_dir, 'dist') result = os.listdir(dist_folder) result.sort() - self.assertEqual(result, ['fake-1.0.tar', 'fake-1.0.tar.gz'] ) + self.assertEqual(result, ['fake-1.0.tar', 'fake-1.0.tar.gz']) os.remove(join(dist_folder, 'fake-1.0.tar')) os.remove(join(dist_folder, 'fake-1.0.tar.gz')) @@ -209,11 +210,18 @@ class SDistTestCase(PyPIRCCommandTestCase): self.write_file((data_dir, 'data.dt'), '#') some_dir = join(self.tmp_dir, 'some') os.mkdir(some_dir) + # make sure VCS directories are pruned (#14004) + hg_dir = join(self.tmp_dir, '.hg') + os.mkdir(hg_dir) + self.write_file((hg_dir, 'last-message.txt'), '#') + # a buggy regex used to prevent this from working on windows (#6884) + self.write_file((self.tmp_dir, 'buildout.cfg'), '#') self.write_file((self.tmp_dir, 'inroot.txt'), '#') self.write_file((some_dir, 'file.txt'), '#') self.write_file((some_dir, 'other_file.txt'), '#') dist.data_files = [('data', ['data/data.dt', + 'buildout.cfg', 'inroot.txt', 'notexisting']), 'some/file.txt', @@ -243,15 +251,15 @@ class SDistTestCase(PyPIRCCommandTestCase): zip_file.close() # making sure everything was added - self.assertEqual(len(content), 11) + self.assertEqual(len(content), 12) # checking the MANIFEST f = open(join(self.tmp_dir, 'MANIFEST')) try: manifest = f.read() - self.assertEqual(manifest, MANIFEST % {'sep': os.sep}) finally: f.close() + self.assertEqual(manifest, MANIFEST % {'sep': os.sep}) @unittest.skipUnless(zlib, "requires zlib") def test_metadata_check_option(self): @@ -98,6 +98,9 @@ Core and Builtins Library ------- +- Issue #6884: Fix long-standing bugs with MANIFEST.in parsing in distutils + on Windows. + - Issue #8033: sqlite3: Fix 64-bit integer handling in user functions on 32-bit architectures. Initial patch by Philippe Devalkeneer. @@ -250,8 +253,6 @@ Library - Issues #1745761, #755670, #13357, #12629, #1200313: HTMLParser now correctly handles non-valid attributes, including adjacent and unquoted attributes. -- Issue #13193: Fix distutils.filelist.FileList under Windows. - - Issue #13373: multiprocessing.Queue.get() could sometimes block indefinitely when called with a timeout. Patch by Arnaud Ysmal. |