1
1
#!/usr/bin/env python3
2
2
"""Check proposed changes for common issues."""
3
+ import re
3
4
import sys
5
+ import shutil
4
6
import os .path
5
7
import subprocess
6
8
import sysconfig
7
9
10
+ import reindent
11
+ import untabify
12
+
13
+
8
14
def get_python_source_dir ():
9
15
src_dir = sysconfig .get_config_var ('abs_srcdir' )
10
16
if not src_dir :
11
17
src_dir = sysconfig .get_config_var ('srcdir' )
12
18
return os .path .abspath (src_dir )
19
+
20
+
21
+ # Excluded directories which are copies of external libraries:
22
+ # don't check their coding style
23
+ EXCLUDE_DIRS = [
24
+ os .path .join ('Modules' , '_decimal' , 'libmpdec' ),
25
+ os .path .join ('Modules' , 'expat' ),
26
+ os .path .join ('Modules' , 'zlib' ),
27
+ ]
13
28
SRCDIR = get_python_source_dir ()
14
29
15
30
@@ -140,8 +155,62 @@ def changed_files(base_branch=None):
140
155
else :
141
156
sys .exit ('need a git checkout to get modified files' )
142
157
143
- # Normalize the path to be able to match using str.startswith()
144
- return list (map (os .path .normpath , filenames ))
158
+ filenames2 = []
159
+ for filename in filenames :
160
+ # Normalize the path to be able to match using .startswith()
161
+ filename = os .path .normpath (filename )
162
+ if any (filename .startswith (path ) for path in EXCLUDE_DIRS ):
163
+ # Exclude the file
164
+ continue
165
+ filenames2 .append (filename )
166
+
167
+ return filenames2
168
+
169
+
170
+ def report_modified_files (file_paths ):
171
+ count = len (file_paths )
172
+ if count == 0 :
173
+ return n_files_str (count )
174
+ else :
175
+ lines = [f"{ n_files_str (count )} :" ]
176
+ for path in file_paths :
177
+ lines .append (f" { path } " )
178
+ return "\n " .join (lines )
179
+
180
+
181
+ #: Python files that have tabs by design:
182
+ _PYTHON_FILES_WITH_TABS = frozenset ({
183
+ 'Tools/c-analyzer/cpython/_parser.py' ,
184
+ })
185
+
186
+
187
+ @status ("Fixing Python file whitespace" , info = report_modified_files )
188
+ def normalize_whitespace (file_paths ):
189
+ """Make sure that the whitespace for .py files have been normalized."""
190
+ reindent .makebackup = False # No need to create backups.
191
+ fixed = [
192
+ path for path in file_paths
193
+ if (
194
+ path .endswith ('.py' )
195
+ and path not in _PYTHON_FILES_WITH_TABS
196
+ and reindent .check (os .path .join (SRCDIR , path ))
197
+ )
198
+ ]
199
+ return fixed
200
+
201
+
202
+ @status ("Fixing C file whitespace" , info = report_modified_files )
203
+ def normalize_c_whitespace (file_paths ):
204
+ """Report if any C files """
205
+ fixed = []
206
+ for path in file_paths :
207
+ abspath = os .path .join (SRCDIR , path )
208
+ with open (abspath , 'r' ) as f :
209
+ if '\t ' not in f .read ():
210
+ continue
211
+ untabify .process (abspath , 8 , verbose = False )
212
+ fixed .append (path )
213
+ return fixed
145
214
146
215
147
216
@status ("Docs modified" , modal = True )
@@ -181,12 +250,38 @@ def regenerated_pyconfig_h_in(file_paths):
181
250
return "not needed"
182
251
183
252
253
+ def ci (pull_request ):
254
+ if pull_request == 'false' :
255
+ print ('Not a pull request; skipping' )
256
+ return
257
+ base_branch = get_base_branch ()
258
+ file_paths = changed_files (base_branch )
259
+ python_files = [fn for fn in file_paths if fn .endswith ('.py' )]
260
+ c_files = [fn for fn in file_paths if fn .endswith (('.c' , '.h' ))]
261
+ fixed = []
262
+ fixed .extend (normalize_whitespace (python_files ))
263
+ fixed .extend (normalize_c_whitespace (c_files ))
264
+ if not fixed :
265
+ print ('No whitespace issues found' )
266
+ else :
267
+ count = len (fixed )
268
+ print (f'Please fix the { n_files_str (count )} with whitespace issues' )
269
+ print ('(on Unix you can run `make patchcheck` to make the fixes)' )
270
+ sys .exit (1 )
271
+
272
+
184
273
def main ():
185
274
base_branch = get_base_branch ()
186
275
file_paths = changed_files (base_branch )
276
+ python_files = [fn for fn in file_paths if fn .endswith ('.py' )]
277
+ c_files = [fn for fn in file_paths if fn .endswith (('.c' , '.h' ))]
187
278
doc_files = [fn for fn in file_paths if fn .startswith ('Doc' ) and
188
279
fn .endswith (('.rst' , '.inc' ))]
189
280
misc_files = {p for p in file_paths if p .startswith ('Misc' )}
281
+ # PEP 8 whitespace rules enforcement.
282
+ normalize_whitespace (python_files )
283
+ # C rules enforcement.
284
+ normalize_c_whitespace (c_files )
190
285
# Docs updated.
191
286
docs_modified (doc_files )
192
287
# Misc/ACKS changed.
@@ -199,14 +294,19 @@ def main():
199
294
regenerated_pyconfig_h_in (file_paths )
200
295
201
296
# Test suite run and passed.
202
- has_c_files = any (fn for fn in file_paths if fn .endswith (('.c' , '.h' )))
203
- has_python_files = any (fn for fn in file_paths if fn .endswith ('.py' ))
204
- print ()
205
- if has_c_files :
206
- print ("Did you run the test suite and check for refleaks?" )
207
- elif has_python_files :
208
- print ("Did you run the test suite?" )
297
+ if python_files or c_files :
298
+ end = " and check for refleaks?" if c_files else "?"
299
+ print ()
300
+ print ("Did you run the test suite" + end )
209
301
210
302
211
303
if __name__ == '__main__' :
212
- main ()
304
+ import argparse
305
+ parser = argparse .ArgumentParser (description = __doc__ )
306
+ parser .add_argument ('--ci' ,
307
+ help = 'Perform pass/fail checks' )
308
+ args = parser .parse_args ()
309
+ if args .ci :
310
+ ci (args .ci )
311
+ else :
312
+ main ()
0 commit comments