15 "false": (lambda: False, "nail clipper"), |
15 "false": (lambda: False, "nail clipper"), |
16 } |
16 } |
17 |
17 |
18 try: |
18 try: |
19 import msvcrt |
19 import msvcrt |
|
20 |
20 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) |
21 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) |
21 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY) |
22 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY) |
22 except ImportError: |
23 except ImportError: |
23 pass |
24 pass |
24 |
25 |
25 stdout = getattr(sys.stdout, 'buffer', sys.stdout) |
26 stdout = getattr(sys.stdout, 'buffer', sys.stdout) |
26 stderr = getattr(sys.stderr, 'buffer', sys.stderr) |
27 stderr = getattr(sys.stderr, 'buffer', sys.stderr) |
27 |
28 |
28 if sys.version_info[0] >= 3: |
29 if sys.version_info[0] >= 3: |
|
30 |
29 def _bytespath(p): |
31 def _bytespath(p): |
30 if p is None: |
32 if p is None: |
31 return p |
33 return p |
32 return p.encode('utf-8') |
34 return p.encode('utf-8') |
33 |
35 |
34 def _strpath(p): |
36 def _strpath(p): |
35 if p is None: |
37 if p is None: |
36 return p |
38 return p |
37 return p.decode('utf-8') |
39 return p.decode('utf-8') |
|
40 |
|
41 |
38 else: |
42 else: |
|
43 |
39 def _bytespath(p): |
44 def _bytespath(p): |
40 return p |
45 return p |
41 |
46 |
42 _strpath = _bytespath |
47 _strpath = _bytespath |
43 |
48 |
|
49 |
44 def check(name, desc): |
50 def check(name, desc): |
45 """Registers a check function for a feature.""" |
51 """Registers a check function for a feature.""" |
|
52 |
46 def decorator(func): |
53 def decorator(func): |
47 checks[name] = (func, desc) |
54 checks[name] = (func, desc) |
48 return func |
55 return func |
|
56 |
49 return decorator |
57 return decorator |
|
58 |
50 |
59 |
51 def checkvers(name, desc, vers): |
60 def checkvers(name, desc, vers): |
52 """Registers a check function for each of a series of versions. |
61 """Registers a check function for each of a series of versions. |
53 |
62 |
54 vers can be a list or an iterator""" |
63 vers can be a list or an iterator. |
|
64 |
|
65 Produces a series of feature checks that have the form <name><vers> without |
|
66 any punctuation (even if there's punctuation in 'vers'; i.e. this produces |
|
67 'py38', not 'py3.8' or 'py-38').""" |
|
68 |
55 def decorator(func): |
69 def decorator(func): |
56 def funcv(v): |
70 def funcv(v): |
57 def f(): |
71 def f(): |
58 return func(v) |
72 return func(v) |
|
73 |
59 return f |
74 return f |
|
75 |
60 for v in vers: |
76 for v in vers: |
61 v = str(v) |
77 v = str(v) |
62 f = funcv(v) |
78 f = funcv(v) |
63 checks['%s%s' % (name, v.replace('.', ''))] = (f, desc % v) |
79 checks['%s%s' % (name, v.replace('.', ''))] = (f, desc % v) |
64 return func |
80 return func |
|
81 |
65 return decorator |
82 return decorator |
|
83 |
66 |
84 |
67 def checkfeatures(features): |
85 def checkfeatures(features): |
68 result = { |
86 result = { |
69 'error': [], |
87 'error': [], |
70 'missing': [], |
88 'missing': [], |
92 elif negate and available: |
110 elif negate and available: |
93 result['skipped'].append('system supports %s' % desc) |
111 result['skipped'].append('system supports %s' % desc) |
94 |
112 |
95 return result |
113 return result |
96 |
114 |
|
115 |
97 def require(features): |
116 def require(features): |
98 """Require that features are available, exiting if not.""" |
117 """Require that features are available, exiting if not.""" |
99 result = checkfeatures(features) |
118 result = checkfeatures(features) |
100 |
119 |
101 for missing in result['missing']: |
120 for missing in result['missing']: |
102 stderr.write(('skipped: unknown feature: %s\n' |
121 stderr.write( |
103 % missing).encode('utf-8')) |
122 ('skipped: unknown feature: %s\n' % missing).encode('utf-8') |
|
123 ) |
104 for msg in result['skipped']: |
124 for msg in result['skipped']: |
105 stderr.write(('skipped: %s\n' % msg).encode('utf-8')) |
125 stderr.write(('skipped: %s\n' % msg).encode('utf-8')) |
106 for msg in result['error']: |
126 for msg in result['error']: |
107 stderr.write(('%s\n' % msg).encode('utf-8')) |
127 stderr.write(('%s\n' % msg).encode('utf-8')) |
108 |
128 |
109 if result['missing']: |
129 if result['missing']: |
110 sys.exit(2) |
130 sys.exit(2) |
111 |
131 |
112 if result['skipped'] or result['error']: |
132 if result['skipped'] or result['error']: |
113 sys.exit(1) |
133 sys.exit(1) |
|
134 |
114 |
135 |
115 def matchoutput(cmd, regexp, ignorestatus=False): |
136 def matchoutput(cmd, regexp, ignorestatus=False): |
116 """Return the match object if cmd executes successfully and its output |
137 """Return the match object if cmd executes successfully and its output |
117 is matched by the supplied regular expression. |
138 is matched by the supplied regular expression. |
118 """ |
139 """ |
119 r = re.compile(regexp) |
140 r = re.compile(regexp) |
120 p = subprocess.Popen( |
141 p = subprocess.Popen( |
121 cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) |
142 cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT |
|
143 ) |
122 s = p.communicate()[0] |
144 s = p.communicate()[0] |
123 ret = p.returncode |
145 ret = p.returncode |
124 return (ignorestatus or not ret) and r.search(s) |
146 return (ignorestatus or not ret) and r.search(s) |
125 |
147 |
|
148 |
126 @check("baz", "GNU Arch baz client") |
149 @check("baz", "GNU Arch baz client") |
127 def has_baz(): |
150 def has_baz(): |
128 return matchoutput('baz --version 2>&1', br'baz Bazaar version') |
151 return matchoutput('baz --version 2>&1', br'baz Bazaar version') |
|
152 |
129 |
153 |
130 @check("bzr", "Canonical's Bazaar client") |
154 @check("bzr", "Canonical's Bazaar client") |
131 def has_bzr(): |
155 def has_bzr(): |
132 try: |
156 try: |
133 import bzrlib |
157 import bzrlib |
134 import bzrlib.bzrdir |
158 import bzrlib.bzrdir |
135 import bzrlib.errors |
159 import bzrlib.errors |
136 import bzrlib.revision |
160 import bzrlib.revision |
137 import bzrlib.revisionspec |
161 import bzrlib.revisionspec |
|
162 |
138 bzrlib.revisionspec.RevisionSpec |
163 bzrlib.revisionspec.RevisionSpec |
139 return bzrlib.__doc__ is not None |
164 return bzrlib.__doc__ is not None |
140 except (AttributeError, ImportError): |
165 except (AttributeError, ImportError): |
141 return False |
166 return False |
142 |
167 |
|
168 |
143 @checkvers("bzr", "Canonical's Bazaar client >= %s", (1.14,)) |
169 @checkvers("bzr", "Canonical's Bazaar client >= %s", (1.14,)) |
144 def has_bzr_range(v): |
170 def has_bzr_range(v): |
145 major, minor = v.split('rc')[0].split('.')[0:2] |
171 major, minor = v.split('rc')[0].split('.')[0:2] |
146 try: |
172 try: |
147 import bzrlib |
173 import bzrlib |
148 return (bzrlib.__doc__ is not None |
174 |
149 and bzrlib.version_info[:2] >= (int(major), int(minor))) |
175 return bzrlib.__doc__ is not None and bzrlib.version_info[:2] >= ( |
150 except ImportError: |
176 int(major), |
151 return False |
177 int(minor), |
|
178 ) |
|
179 except ImportError: |
|
180 return False |
|
181 |
152 |
182 |
153 @check("chg", "running with chg") |
183 @check("chg", "running with chg") |
154 def has_chg(): |
184 def has_chg(): |
155 return 'CHGHG' in os.environ |
185 return 'CHGHG' in os.environ |
|
186 |
156 |
187 |
157 @check("cvs", "cvs client/server") |
188 @check("cvs", "cvs client/server") |
158 def has_cvs(): |
189 def has_cvs(): |
159 re = br'Concurrent Versions System.*?server' |
190 re = br'Concurrent Versions System.*?server' |
160 return matchoutput('cvs --version 2>&1', re) and not has_msys() |
191 return matchoutput('cvs --version 2>&1', re) and not has_msys() |
161 |
192 |
|
193 |
162 @check("cvs112", "cvs client/server 1.12.* (not cvsnt)") |
194 @check("cvs112", "cvs client/server 1.12.* (not cvsnt)") |
163 def has_cvs112(): |
195 def has_cvs112(): |
164 re = br'Concurrent Versions System \(CVS\) 1.12.*?server' |
196 re = br'Concurrent Versions System \(CVS\) 1.12.*?server' |
165 return matchoutput('cvs --version 2>&1', re) and not has_msys() |
197 return matchoutput('cvs --version 2>&1', re) and not has_msys() |
166 |
198 |
|
199 |
167 @check("cvsnt", "cvsnt client/server") |
200 @check("cvsnt", "cvsnt client/server") |
168 def has_cvsnt(): |
201 def has_cvsnt(): |
169 re = br'Concurrent Versions System \(CVSNT\) (\d+).(\d+).*\(client/server\)' |
202 re = br'Concurrent Versions System \(CVSNT\) (\d+).(\d+).*\(client/server\)' |
170 return matchoutput('cvsnt --version 2>&1', re) |
203 return matchoutput('cvsnt --version 2>&1', re) |
171 |
204 |
|
205 |
172 @check("darcs", "darcs client") |
206 @check("darcs", "darcs client") |
173 def has_darcs(): |
207 def has_darcs(): |
174 return matchoutput('darcs --version', br'\b2\.([2-9]|\d{2})', True) |
208 return matchoutput('darcs --version', br'\b2\.([2-9]|\d{2})', True) |
175 |
209 |
|
210 |
176 @check("mtn", "monotone client (>= 1.0)") |
211 @check("mtn", "monotone client (>= 1.0)") |
177 def has_mtn(): |
212 def has_mtn(): |
178 return matchoutput('mtn --version', br'monotone', True) and not matchoutput( |
213 return matchoutput('mtn --version', br'monotone', True) and not matchoutput( |
179 'mtn --version', br'monotone 0\.', True) |
214 'mtn --version', br'monotone 0\.', True |
|
215 ) |
|
216 |
180 |
217 |
181 @check("eol-in-paths", "end-of-lines in paths") |
218 @check("eol-in-paths", "end-of-lines in paths") |
182 def has_eol_in_paths(): |
219 def has_eol_in_paths(): |
183 try: |
220 try: |
184 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r') |
221 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r') |
185 os.close(fd) |
222 os.close(fd) |
186 os.remove(path) |
223 os.remove(path) |
187 return True |
224 return True |
188 except (IOError, OSError): |
225 except (IOError, OSError): |
189 return False |
226 return False |
|
227 |
190 |
228 |
191 @check("execbit", "executable bit") |
229 @check("execbit", "executable bit") |
192 def has_executablebit(): |
230 def has_executablebit(): |
193 try: |
231 try: |
194 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH |
232 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH |
250 try: |
292 try: |
251 return util.cachestat(path).cacheable() |
293 return util.cachestat(path).cacheable() |
252 finally: |
294 finally: |
253 os.remove(path) |
295 os.remove(path) |
254 |
296 |
|
297 |
255 @check("lsprof", "python lsprof module") |
298 @check("lsprof", "python lsprof module") |
256 def has_lsprof(): |
299 def has_lsprof(): |
257 try: |
300 try: |
258 import _lsprof |
301 import _lsprof |
259 _lsprof.Profiler # silence unused import warning |
302 |
260 return True |
303 _lsprof.Profiler # silence unused import warning |
261 except ImportError: |
304 return True |
262 return False |
305 except ImportError: |
|
306 return False |
|
307 |
263 |
308 |
264 def gethgversion(): |
309 def gethgversion(): |
265 m = matchoutput('hg --version --quiet 2>&1', br'(\d+)\.(\d+)') |
310 m = matchoutput('hg --version --quiet 2>&1', br'(\d+)\.(\d+)') |
266 if not m: |
311 if not m: |
267 return (0, 0) |
312 return (0, 0) |
268 return (int(m.group(1)), int(m.group(2))) |
313 return (int(m.group(1)), int(m.group(2))) |
269 |
314 |
270 @checkvers("hg", "Mercurial >= %s", |
315 |
271 list([(1.0 * x) / 10 for x in range(9, 99)])) |
316 @checkvers( |
|
317 "hg", "Mercurial >= %s", list([(1.0 * x) / 10 for x in range(9, 99)]) |
|
318 ) |
272 def has_hg_range(v): |
319 def has_hg_range(v): |
273 major, minor = v.split('.')[0:2] |
320 major, minor = v.split('.')[0:2] |
274 return gethgversion() >= (int(major), int(minor)) |
321 return gethgversion() >= (int(major), int(minor)) |
275 |
322 |
|
323 |
276 @check("hg08", "Mercurial >= 0.8") |
324 @check("hg08", "Mercurial >= 0.8") |
277 def has_hg08(): |
325 def has_hg08(): |
278 if checks["hg09"][0](): |
326 if checks["hg09"][0](): |
279 return True |
327 return True |
280 return matchoutput('hg help annotate 2>&1', '--date') |
328 return matchoutput('hg help annotate 2>&1', '--date') |
281 |
329 |
|
330 |
282 @check("hg07", "Mercurial >= 0.7") |
331 @check("hg07", "Mercurial >= 0.7") |
283 def has_hg07(): |
332 def has_hg07(): |
284 if checks["hg08"][0](): |
333 if checks["hg08"][0](): |
285 return True |
334 return True |
286 return matchoutput('hg --version --quiet 2>&1', 'Mercurial Distributed SCM') |
335 return matchoutput('hg --version --quiet 2>&1', 'Mercurial Distributed SCM') |
287 |
336 |
|
337 |
288 @check("hg06", "Mercurial >= 0.6") |
338 @check("hg06", "Mercurial >= 0.6") |
289 def has_hg06(): |
339 def has_hg06(): |
290 if checks["hg07"][0](): |
340 if checks["hg07"][0](): |
291 return True |
341 return True |
292 return matchoutput('hg --version --quiet 2>&1', 'Mercurial version') |
342 return matchoutput('hg --version --quiet 2>&1', 'Mercurial version') |
293 |
343 |
|
344 |
294 @check("gettext", "GNU Gettext (msgfmt)") |
345 @check("gettext", "GNU Gettext (msgfmt)") |
295 def has_gettext(): |
346 def has_gettext(): |
296 return matchoutput('msgfmt --version', br'GNU gettext-tools') |
347 return matchoutput('msgfmt --version', br'GNU gettext-tools') |
297 |
348 |
|
349 |
298 @check("git", "git command line client") |
350 @check("git", "git command line client") |
299 def has_git(): |
351 def has_git(): |
300 return matchoutput('git --version 2>&1', br'^git version') |
352 return matchoutput('git --version 2>&1', br'^git version') |
|
353 |
301 |
354 |
302 def getgitversion(): |
355 def getgitversion(): |
303 m = matchoutput('git --version 2>&1', br'git version (\d+)\.(\d+)') |
356 m = matchoutput('git --version 2>&1', br'git version (\d+)\.(\d+)') |
304 if not m: |
357 if not m: |
305 return (0, 0) |
358 return (0, 0) |
306 return (int(m.group(1)), int(m.group(2))) |
359 return (int(m.group(1)), int(m.group(2))) |
|
360 |
307 |
361 |
308 # https://github.com/git-lfs/lfs-test-server |
362 # https://github.com/git-lfs/lfs-test-server |
309 @check("lfs-test-server", "git-lfs test server") |
363 @check("lfs-test-server", "git-lfs test server") |
310 def has_lfsserver(): |
364 def has_lfsserver(): |
311 exe = 'lfs-test-server' |
365 exe = 'lfs-test-server' |
314 return any( |
368 return any( |
315 os.access(os.path.join(path, exe), os.X_OK) |
369 os.access(os.path.join(path, exe), os.X_OK) |
316 for path in os.environ["PATH"].split(os.pathsep) |
370 for path in os.environ["PATH"].split(os.pathsep) |
317 ) |
371 ) |
318 |
372 |
|
373 |
319 @checkvers("git", "git client (with ext::sh support) version >= %s", (1.9,)) |
374 @checkvers("git", "git client (with ext::sh support) version >= %s", (1.9,)) |
320 def has_git_range(v): |
375 def has_git_range(v): |
321 major, minor = v.split('.')[0:2] |
376 major, minor = v.split('.')[0:2] |
322 return getgitversion() >= (int(major), int(minor)) |
377 return getgitversion() >= (int(major), int(minor)) |
323 |
378 |
|
379 |
324 @check("docutils", "Docutils text processing library") |
380 @check("docutils", "Docutils text processing library") |
325 def has_docutils(): |
381 def has_docutils(): |
326 try: |
382 try: |
327 import docutils.core |
383 import docutils.core |
328 docutils.core.publish_cmdline # silence unused import |
384 |
329 return True |
385 docutils.core.publish_cmdline # silence unused import |
330 except ImportError: |
386 return True |
331 return False |
387 except ImportError: |
|
388 return False |
|
389 |
332 |
390 |
333 def getsvnversion(): |
391 def getsvnversion(): |
334 m = matchoutput('svn --version --quiet 2>&1', br'^(\d+)\.(\d+)') |
392 m = matchoutput('svn --version --quiet 2>&1', br'^(\d+)\.(\d+)') |
335 if not m: |
393 if not m: |
336 return (0, 0) |
394 return (0, 0) |
337 return (int(m.group(1)), int(m.group(2))) |
395 return (int(m.group(1)), int(m.group(2))) |
338 |
396 |
|
397 |
339 @checkvers("svn", "subversion client and admin tools >= %s", (1.3, 1.5)) |
398 @checkvers("svn", "subversion client and admin tools >= %s", (1.3, 1.5)) |
340 def has_svn_range(v): |
399 def has_svn_range(v): |
341 major, minor = v.split('.')[0:2] |
400 major, minor = v.split('.')[0:2] |
342 return getsvnversion() >= (int(major), int(minor)) |
401 return getsvnversion() >= (int(major), int(minor)) |
343 |
402 |
|
403 |
344 @check("svn", "subversion client and admin tools") |
404 @check("svn", "subversion client and admin tools") |
345 def has_svn(): |
405 def has_svn(): |
346 return (matchoutput('svn --version 2>&1', br'^svn, version') and |
406 return matchoutput('svn --version 2>&1', br'^svn, version') and matchoutput( |
347 matchoutput('svnadmin --version 2>&1', br'^svnadmin, version')) |
407 'svnadmin --version 2>&1', br'^svnadmin, version' |
|
408 ) |
|
409 |
348 |
410 |
349 @check("svn-bindings", "subversion python bindings") |
411 @check("svn-bindings", "subversion python bindings") |
350 def has_svn_bindings(): |
412 def has_svn_bindings(): |
351 try: |
413 try: |
352 import svn.core |
414 import svn.core |
|
415 |
353 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR |
416 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR |
354 if version < (1, 4): |
417 if version < (1, 4): |
355 return False |
418 return False |
356 return True |
419 return True |
357 except ImportError: |
420 except ImportError: |
358 return False |
421 return False |
359 |
422 |
|
423 |
360 @check("p4", "Perforce server and client") |
424 @check("p4", "Perforce server and client") |
361 def has_p4(): |
425 def has_p4(): |
362 return (matchoutput('p4 -V', br'Rev\. P4/') and |
426 return matchoutput('p4 -V', br'Rev\. P4/') and matchoutput( |
363 matchoutput('p4d -V', br'Rev\. P4D/')) |
427 'p4d -V', br'Rev\. P4D/' |
|
428 ) |
|
429 |
364 |
430 |
365 @check("symlink", "symbolic links") |
431 @check("symlink", "symbolic links") |
366 def has_symlink(): |
432 def has_symlink(): |
367 if getattr(os, "symlink", None) is None: |
433 if getattr(os, "symlink", None) is None: |
368 return False |
434 return False |
449 return False |
525 return False |
450 return True |
526 return True |
451 finally: |
527 finally: |
452 os.rmdir(d) |
528 os.rmdir(d) |
453 |
529 |
|
530 |
454 @check("unix-socket", "AF_UNIX socket family") |
531 @check("unix-socket", "AF_UNIX socket family") |
455 def has_unix_socket(): |
532 def has_unix_socket(): |
456 return getattr(socket, 'AF_UNIX', None) is not None |
533 return getattr(socket, 'AF_UNIX', None) is not None |
457 |
534 |
|
535 |
458 @check("root", "root permissions") |
536 @check("root", "root permissions") |
459 def has_root(): |
537 def has_root(): |
460 return getattr(os, 'geteuid', None) and os.geteuid() == 0 |
538 return getattr(os, 'geteuid', None) and os.geteuid() == 0 |
461 |
539 |
|
540 |
462 @check("pyflakes", "Pyflakes python linter") |
541 @check("pyflakes", "Pyflakes python linter") |
463 def has_pyflakes(): |
542 def has_pyflakes(): |
464 return matchoutput("sh -c \"echo 'import re' 2>&1 | pyflakes\"", |
543 return matchoutput( |
465 br"<stdin>:1: 're' imported but unused", |
544 "sh -c \"echo 'import re' 2>&1 | pyflakes\"", |
466 True) |
545 br"<stdin>:1: 're' imported but unused", |
|
546 True, |
|
547 ) |
|
548 |
467 |
549 |
468 @check("pylint", "Pylint python linter") |
550 @check("pylint", "Pylint python linter") |
469 def has_pylint(): |
551 def has_pylint(): |
470 return matchoutput("pylint --help", |
552 return matchoutput("pylint --help", br"Usage: pylint", True) |
471 br"Usage: pylint", |
553 |
472 True) |
|
473 |
554 |
474 @check("clang-format", "clang-format C code formatter") |
555 @check("clang-format", "clang-format C code formatter") |
475 def has_clang_format(): |
556 def has_clang_format(): |
476 m = matchoutput('clang-format --version', br'clang-format version (\d)') |
557 m = matchoutput('clang-format --version', br'clang-format version (\d)') |
477 # style changed somewhere between 4.x and 6.x |
558 # style changed somewhere between 4.x and 6.x |
478 return m and int(m.group(1)) >= 6 |
559 return m and int(m.group(1)) >= 6 |
479 |
560 |
|
561 |
480 @check("jshint", "JSHint static code analysis tool") |
562 @check("jshint", "JSHint static code analysis tool") |
481 def has_jshint(): |
563 def has_jshint(): |
482 return matchoutput("jshint --version 2>&1", br"jshint v") |
564 return matchoutput("jshint --version 2>&1", br"jshint v") |
483 |
565 |
|
566 |
484 @check("pygments", "Pygments source highlighting library") |
567 @check("pygments", "Pygments source highlighting library") |
485 def has_pygments(): |
568 def has_pygments(): |
486 try: |
569 try: |
487 import pygments |
570 import pygments |
488 pygments.highlight # silence unused import warning |
571 |
489 return True |
572 pygments.highlight # silence unused import warning |
490 except ImportError: |
573 return True |
491 return False |
574 except ImportError: |
|
575 return False |
|
576 |
492 |
577 |
493 @check("outer-repo", "outer repo") |
578 @check("outer-repo", "outer repo") |
494 def has_outer_repo(): |
579 def has_outer_repo(): |
495 # failing for other reasons than 'no repo' imply that there is a repo |
580 # failing for other reasons than 'no repo' imply that there is a repo |
496 return not matchoutput('hg root 2>&1', |
581 return not matchoutput('hg root 2>&1', br'abort: no repository found', True) |
497 br'abort: no repository found', True) |
582 |
498 |
583 |
499 @check("ssl", "ssl module available") |
584 @check("ssl", "ssl module available") |
500 def has_ssl(): |
585 def has_ssl(): |
501 try: |
586 try: |
502 import ssl |
587 import ssl |
|
588 |
503 ssl.CERT_NONE |
589 ssl.CERT_NONE |
504 return True |
590 return True |
505 except ImportError: |
591 except ImportError: |
506 return False |
592 return False |
|
593 |
507 |
594 |
508 @check("sslcontext", "python >= 2.7.9 ssl") |
595 @check("sslcontext", "python >= 2.7.9 ssl") |
509 def has_sslcontext(): |
596 def has_sslcontext(): |
510 try: |
597 try: |
511 import ssl |
598 import ssl |
|
599 |
512 ssl.SSLContext |
600 ssl.SSLContext |
513 return True |
601 return True |
514 except (ImportError, AttributeError): |
602 except (ImportError, AttributeError): |
515 return False |
603 return False |
|
604 |
516 |
605 |
517 @check("defaultcacerts", "can verify SSL certs by system's CA certs store") |
606 @check("defaultcacerts", "can verify SSL certs by system's CA certs store") |
518 def has_defaultcacerts(): |
607 def has_defaultcacerts(): |
519 from mercurial import sslutil, ui as uimod |
608 from mercurial import sslutil, ui as uimod |
|
609 |
520 ui = uimod.ui.load() |
610 ui = uimod.ui.load() |
521 return sslutil._defaultcacerts(ui) or sslutil._canloaddefaultcerts |
611 return sslutil._defaultcacerts(ui) or sslutil._canloaddefaultcerts |
|
612 |
522 |
613 |
523 @check("defaultcacertsloaded", "detected presence of loaded system CA certs") |
614 @check("defaultcacertsloaded", "detected presence of loaded system CA certs") |
524 def has_defaultcacertsloaded(): |
615 def has_defaultcacertsloaded(): |
525 import ssl |
616 import ssl |
526 from mercurial import sslutil, ui as uimod |
617 from mercurial import sslutil, ui as uimod |
538 else: |
629 else: |
539 ctx.load_default_certs() |
630 ctx.load_default_certs() |
540 |
631 |
541 return len(ctx.get_ca_certs()) > 0 |
632 return len(ctx.get_ca_certs()) > 0 |
542 |
633 |
|
634 |
543 @check("tls1.2", "TLS 1.2 protocol support") |
635 @check("tls1.2", "TLS 1.2 protocol support") |
544 def has_tls1_2(): |
636 def has_tls1_2(): |
545 from mercurial import sslutil |
637 from mercurial import sslutil |
|
638 |
546 return b'tls1.2' in sslutil.supportedprotocols |
639 return b'tls1.2' in sslutil.supportedprotocols |
|
640 |
547 |
641 |
548 @check("windows", "Windows") |
642 @check("windows", "Windows") |
549 def has_windows(): |
643 def has_windows(): |
550 return os.name == 'nt' |
644 return os.name == 'nt' |
551 |
645 |
|
646 |
552 @check("system-sh", "system() uses sh") |
647 @check("system-sh", "system() uses sh") |
553 def has_system_sh(): |
648 def has_system_sh(): |
554 return os.name != 'nt' |
649 return os.name != 'nt' |
555 |
650 |
|
651 |
556 @check("serve", "platform and python can manage 'hg serve -d'") |
652 @check("serve", "platform and python can manage 'hg serve -d'") |
557 def has_serve(): |
653 def has_serve(): |
558 return True |
654 return True |
|
655 |
559 |
656 |
560 @check("test-repo", "running tests from repository") |
657 @check("test-repo", "running tests from repository") |
561 def has_test_repo(): |
658 def has_test_repo(): |
562 t = os.environ["TESTDIR"] |
659 t = os.environ["TESTDIR"] |
563 return os.path.isdir(os.path.join(t, "..", ".hg")) |
660 return os.path.isdir(os.path.join(t, "..", ".hg")) |
564 |
661 |
|
662 |
565 @check("tic", "terminfo compiler and curses module") |
663 @check("tic", "terminfo compiler and curses module") |
566 def has_tic(): |
664 def has_tic(): |
567 try: |
665 try: |
568 import curses |
666 import curses |
|
667 |
569 curses.COLOR_BLUE |
668 curses.COLOR_BLUE |
570 return matchoutput('test -x "`which tic`"', br'') |
669 return matchoutput('test -x "`which tic`"', br'') |
571 except ImportError: |
670 except ImportError: |
572 return False |
671 return False |
|
672 |
573 |
673 |
574 @check("msys", "Windows with MSYS") |
674 @check("msys", "Windows with MSYS") |
575 def has_msys(): |
675 def has_msys(): |
576 return os.getenv('MSYSTEM') |
676 return os.getenv('MSYSTEM') |
577 |
677 |
|
678 |
578 @check("aix", "AIX") |
679 @check("aix", "AIX") |
579 def has_aix(): |
680 def has_aix(): |
580 return sys.platform.startswith("aix") |
681 return sys.platform.startswith("aix") |
581 |
682 |
|
683 |
582 @check("osx", "OS X") |
684 @check("osx", "OS X") |
583 def has_osx(): |
685 def has_osx(): |
584 return sys.platform == 'darwin' |
686 return sys.platform == 'darwin' |
585 |
687 |
|
688 |
586 @check("osxpackaging", "OS X packaging tools") |
689 @check("osxpackaging", "OS X packaging tools") |
587 def has_osxpackaging(): |
690 def has_osxpackaging(): |
588 try: |
691 try: |
589 return (matchoutput('pkgbuild', br'Usage: pkgbuild ', ignorestatus=1) |
692 return ( |
590 and matchoutput( |
693 matchoutput('pkgbuild', br'Usage: pkgbuild ', ignorestatus=1) |
591 'productbuild', br'Usage: productbuild ', |
694 and matchoutput( |
592 ignorestatus=1) |
695 'productbuild', br'Usage: productbuild ', ignorestatus=1 |
593 and matchoutput('lsbom', br'Usage: lsbom', ignorestatus=1) |
696 ) |
594 and matchoutput( |
697 and matchoutput('lsbom', br'Usage: lsbom', ignorestatus=1) |
595 'xar --help', br'Usage: xar', ignorestatus=1)) |
698 and matchoutput('xar --help', br'Usage: xar', ignorestatus=1) |
596 except ImportError: |
699 ) |
597 return False |
700 except ImportError: |
|
701 return False |
|
702 |
598 |
703 |
599 @check('linuxormacos', 'Linux or MacOS') |
704 @check('linuxormacos', 'Linux or MacOS') |
600 def has_linuxormacos(): |
705 def has_linuxormacos(): |
601 # This isn't a perfect test for MacOS. But it is sufficient for our needs. |
706 # This isn't a perfect test for MacOS. But it is sufficient for our needs. |
602 return sys.platform.startswith(('linux', 'darwin')) |
707 return sys.platform.startswith(('linux', 'darwin')) |
|
708 |
603 |
709 |
604 @check("docker", "docker support") |
710 @check("docker", "docker support") |
605 def has_docker(): |
711 def has_docker(): |
606 pat = br'A self-sufficient runtime for' |
712 pat = br'A self-sufficient runtime for' |
607 if matchoutput('docker --help', pat): |
713 if matchoutput('docker --help', pat): |
616 return False |
722 return False |
617 |
723 |
618 return True |
724 return True |
619 return False |
725 return False |
620 |
726 |
|
727 |
621 @check("debhelper", "debian packaging tools") |
728 @check("debhelper", "debian packaging tools") |
622 def has_debhelper(): |
729 def has_debhelper(): |
623 # Some versions of dpkg say `dpkg', some say 'dpkg' (` vs ' on the first |
730 # Some versions of dpkg say `dpkg', some say 'dpkg' (` vs ' on the first |
624 # quote), so just accept anything in that spot. |
731 # quote), so just accept anything in that spot. |
625 dpkg = matchoutput('dpkg --version', |
732 dpkg = matchoutput( |
626 br"Debian .dpkg' package management program") |
733 'dpkg --version', br"Debian .dpkg' package management program" |
627 dh = matchoutput('dh --help', |
734 ) |
628 br'dh is a part of debhelper.', ignorestatus=True) |
735 dh = matchoutput( |
629 dh_py2 = matchoutput('dh_python2 --help', |
736 'dh --help', br'dh is a part of debhelper.', ignorestatus=True |
630 br'other supported Python versions') |
737 ) |
|
738 dh_py2 = matchoutput( |
|
739 'dh_python2 --help', br'other supported Python versions' |
|
740 ) |
631 # debuild comes from the 'devscripts' package, though you might want |
741 # debuild comes from the 'devscripts' package, though you might want |
632 # the 'build-debs' package instead, which has a dependency on devscripts. |
742 # the 'build-debs' package instead, which has a dependency on devscripts. |
633 debuild = matchoutput('debuild --help', |
743 debuild = matchoutput( |
634 br'to run debian/rules with given parameter') |
744 'debuild --help', br'to run debian/rules with given parameter' |
|
745 ) |
635 return dpkg and dh and dh_py2 and debuild |
746 return dpkg and dh and dh_py2 and debuild |
636 |
747 |
637 @check("debdeps", |
748 |
638 "debian build dependencies (run dpkg-checkbuilddeps in contrib/)") |
749 @check( |
|
750 "debdeps", "debian build dependencies (run dpkg-checkbuilddeps in contrib/)" |
|
751 ) |
639 def has_debdeps(): |
752 def has_debdeps(): |
640 # just check exit status (ignoring output) |
753 # just check exit status (ignoring output) |
641 path = '%s/../contrib/packaging/debian/control' % os.environ['TESTDIR'] |
754 path = '%s/../contrib/packaging/debian/control' % os.environ['TESTDIR'] |
642 return matchoutput('dpkg-checkbuilddeps %s' % path, br'') |
755 return matchoutput('dpkg-checkbuilddeps %s' % path, br'') |
643 |
756 |
|
757 |
644 @check("demandimport", "demandimport enabled") |
758 @check("demandimport", "demandimport enabled") |
645 def has_demandimport(): |
759 def has_demandimport(): |
646 # chg disables demandimport intentionally for performance wins. |
760 # chg disables demandimport intentionally for performance wins. |
647 return ((not has_chg()) and os.environ.get('HGDEMANDIMPORT') != 'disable') |
761 return (not has_chg()) and os.environ.get('HGDEMANDIMPORT') != 'disable' |
648 |
762 |
|
763 |
|
764 # Add "py27", "py35", ... as possible feature checks. Note that there's no |
|
765 # punctuation here. |
649 @checkvers("py", "Python >= %s", (2.7, 3.5, 3.6, 3.7, 3.8, 3.9)) |
766 @checkvers("py", "Python >= %s", (2.7, 3.5, 3.6, 3.7, 3.8, 3.9)) |
650 def has_python_range(v): |
767 def has_python_range(v): |
651 major, minor = v.split('.')[0:2] |
768 major, minor = v.split('.')[0:2] |
652 py_major, py_minor = sys.version_info.major, sys.version_info.minor |
769 py_major, py_minor = sys.version_info.major, sys.version_info.minor |
653 |
770 |
654 return (py_major, py_minor) >= (int(major), int(minor)) |
771 return (py_major, py_minor) >= (int(major), int(minor)) |
655 |
772 |
|
773 |
656 @check("py3", "running with Python 3.x") |
774 @check("py3", "running with Python 3.x") |
657 def has_py3(): |
775 def has_py3(): |
658 return 3 == sys.version_info[0] |
776 return 3 == sys.version_info[0] |
659 |
777 |
|
778 |
660 @check("py3exe", "a Python 3.x interpreter is available") |
779 @check("py3exe", "a Python 3.x interpreter is available") |
661 def has_python3exe(): |
780 def has_python3exe(): |
662 return matchoutput('python3 -V', br'^Python 3.(5|6|7|8|9)') |
781 return matchoutput('python3 -V', br'^Python 3.(5|6|7|8|9)') |
663 |
782 |
|
783 |
664 @check("pure", "running with pure Python code") |
784 @check("pure", "running with pure Python code") |
665 def has_pure(): |
785 def has_pure(): |
666 return any([ |
786 return any( |
667 os.environ.get("HGMODULEPOLICY") == "py", |
787 [ |
668 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure", |
788 os.environ.get("HGMODULEPOLICY") == "py", |
669 ]) |
789 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure", |
|
790 ] |
|
791 ) |
|
792 |
670 |
793 |
671 @check("slow", "allow slow tests (use --allow-slow-tests)") |
794 @check("slow", "allow slow tests (use --allow-slow-tests)") |
672 def has_slow(): |
795 def has_slow(): |
673 return os.environ.get('HGTEST_SLOW') == 'slow' |
796 return os.environ.get('HGTEST_SLOW') == 'slow' |
674 |
797 |
|
798 |
675 @check("hypothesis", "Hypothesis automated test generation") |
799 @check("hypothesis", "Hypothesis automated test generation") |
676 def has_hypothesis(): |
800 def has_hypothesis(): |
677 try: |
801 try: |
678 import hypothesis |
802 import hypothesis |
|
803 |
679 hypothesis.given |
804 hypothesis.given |
680 return True |
805 return True |
681 except ImportError: |
806 except ImportError: |
682 return False |
807 return False |
|
808 |
683 |
809 |
684 @check("unziplinks", "unzip(1) understands and extracts symlinks") |
810 @check("unziplinks", "unzip(1) understands and extracts symlinks") |
685 def unzip_understands_symlinks(): |
811 def unzip_understands_symlinks(): |
686 return matchoutput('unzip --help', br'Info-ZIP') |
812 return matchoutput('unzip --help', br'Info-ZIP') |
687 |
813 |
|
814 |
688 @check("zstd", "zstd Python module available") |
815 @check("zstd", "zstd Python module available") |
689 def has_zstd(): |
816 def has_zstd(): |
690 try: |
817 try: |
691 import mercurial.zstd |
818 import mercurial.zstd |
|
819 |
692 mercurial.zstd.__version__ |
820 mercurial.zstd.__version__ |
693 return True |
821 return True |
694 except ImportError: |
822 except ImportError: |
695 return False |
823 return False |
|
824 |
696 |
825 |
697 @check("devfull", "/dev/full special file") |
826 @check("devfull", "/dev/full special file") |
698 def has_dev_full(): |
827 def has_dev_full(): |
699 return os.path.exists('/dev/full') |
828 return os.path.exists('/dev/full') |
700 |
829 |
|
830 |
701 @check("virtualenv", "Python virtualenv support") |
831 @check("virtualenv", "Python virtualenv support") |
702 def has_virtualenv(): |
832 def has_virtualenv(): |
703 try: |
833 try: |
704 import virtualenv |
834 import virtualenv |
|
835 |
705 virtualenv.ACTIVATE_SH |
836 virtualenv.ACTIVATE_SH |
706 return True |
837 return True |
707 except ImportError: |
838 except ImportError: |
708 return False |
839 return False |
|
840 |
709 |
841 |
710 @check("fsmonitor", "running tests with fsmonitor") |
842 @check("fsmonitor", "running tests with fsmonitor") |
711 def has_fsmonitor(): |
843 def has_fsmonitor(): |
712 return 'HGFSMONITOR_TESTS' in os.environ |
844 return 'HGFSMONITOR_TESTS' in os.environ |
713 |
845 |
|
846 |
714 @check("fuzzywuzzy", "Fuzzy string matching library") |
847 @check("fuzzywuzzy", "Fuzzy string matching library") |
715 def has_fuzzywuzzy(): |
848 def has_fuzzywuzzy(): |
716 try: |
849 try: |
717 import fuzzywuzzy |
850 import fuzzywuzzy |
|
851 |
718 fuzzywuzzy.__version__ |
852 fuzzywuzzy.__version__ |
719 return True |
853 return True |
720 except ImportError: |
854 except ImportError: |
721 return False |
855 return False |
|
856 |
722 |
857 |
723 @check("clang-libfuzzer", "clang new enough to include libfuzzer") |
858 @check("clang-libfuzzer", "clang new enough to include libfuzzer") |
724 def has_clang_libfuzzer(): |
859 def has_clang_libfuzzer(): |
725 mat = matchoutput('clang --version', br'clang version (\d)') |
860 mat = matchoutput('clang --version', br'clang version (\d)') |
726 if mat: |
861 if mat: |
727 # libfuzzer is new in clang 6 |
862 # libfuzzer is new in clang 6 |
728 return int(mat.group(1)) > 5 |
863 return int(mat.group(1)) > 5 |
729 return False |
864 return False |
730 |
865 |
|
866 |
731 @check("clang-6.0", "clang 6.0 with version suffix (libfuzzer included)") |
867 @check("clang-6.0", "clang 6.0 with version suffix (libfuzzer included)") |
732 def has_clang60(): |
868 def has_clang60(): |
733 return matchoutput('clang-6.0 --version', br'clang version 6\.') |
869 return matchoutput('clang-6.0 --version', br'clang version 6\.') |
734 |
870 |
|
871 |
735 @check("xdiff", "xdiff algorithm") |
872 @check("xdiff", "xdiff algorithm") |
736 def has_xdiff(): |
873 def has_xdiff(): |
737 try: |
874 try: |
738 from mercurial import policy |
875 from mercurial import policy |
|
876 |
739 bdiff = policy.importmod('bdiff') |
877 bdiff = policy.importmod('bdiff') |
740 return bdiff.xdiffblocks(b'', b'') == [(0, 0, 0, 0)] |
878 return bdiff.xdiffblocks(b'', b'') == [(0, 0, 0, 0)] |
741 except (ImportError, AttributeError): |
879 except (ImportError, AttributeError): |
742 return False |
880 return False |
743 |
881 |
|
882 |
744 @check('extraextensions', 'whether tests are running with extra extensions') |
883 @check('extraextensions', 'whether tests are running with extra extensions') |
745 def has_extraextensions(): |
884 def has_extraextensions(): |
746 return 'HGTESTEXTRAEXTENSIONS' in os.environ |
885 return 'HGTESTEXTRAEXTENSIONS' in os.environ |
|
886 |
747 |
887 |
748 def getrepofeatures(): |
888 def getrepofeatures(): |
749 """Obtain set of repository features in use. |
889 """Obtain set of repository features in use. |
750 |
890 |
751 HGREPOFEATURES can be used to define or remove features. It contains |
891 HGREPOFEATURES can be used to define or remove features. It contains |
781 else: |
921 else: |
782 features.add(imply) |
922 features.add(imply) |
783 |
923 |
784 return features |
924 return features |
785 |
925 |
|
926 |
786 @check('reporevlogstore', 'repository using the default revlog store') |
927 @check('reporevlogstore', 'repository using the default revlog store') |
787 def has_reporevlogstore(): |
928 def has_reporevlogstore(): |
788 return 'revlogstore' in getrepofeatures() |
929 return 'revlogstore' in getrepofeatures() |
789 |
930 |
|
931 |
790 @check('reposimplestore', 'repository using simple storage extension') |
932 @check('reposimplestore', 'repository using simple storage extension') |
791 def has_reposimplestore(): |
933 def has_reposimplestore(): |
792 return 'simplestore' in getrepofeatures() |
934 return 'simplestore' in getrepofeatures() |
793 |
935 |
|
936 |
794 @check('repobundlerepo', 'whether we can open bundle files as repos') |
937 @check('repobundlerepo', 'whether we can open bundle files as repos') |
795 def has_repobundlerepo(): |
938 def has_repobundlerepo(): |
796 return 'bundlerepo' in getrepofeatures() |
939 return 'bundlerepo' in getrepofeatures() |
797 |
940 |
|
941 |
798 @check('repofncache', 'repository has an fncache') |
942 @check('repofncache', 'repository has an fncache') |
799 def has_repofncache(): |
943 def has_repofncache(): |
800 return 'fncache' in getrepofeatures() |
944 return 'fncache' in getrepofeatures() |
801 |
945 |
|
946 |
802 @check('sqlite', 'sqlite3 module is available') |
947 @check('sqlite', 'sqlite3 module is available') |
803 def has_sqlite(): |
948 def has_sqlite(): |
804 try: |
949 try: |
805 import sqlite3 |
950 import sqlite3 |
|
951 |
806 version = sqlite3.sqlite_version_info |
952 version = sqlite3.sqlite_version_info |
807 except ImportError: |
953 except ImportError: |
808 return False |
954 return False |
809 |
955 |
810 if version < (3, 8, 3): |
956 if version < (3, 8, 3): |
811 # WITH clause not supported |
957 # WITH clause not supported |
812 return False |
958 return False |
813 |
959 |
814 return matchoutput('sqlite3 -version', br'^3\.\d+') |
960 return matchoutput('sqlite3 -version', br'^3\.\d+') |
815 |
961 |
|
962 |
816 @check('vcr', 'vcr http mocking library') |
963 @check('vcr', 'vcr http mocking library') |
817 def has_vcr(): |
964 def has_vcr(): |
818 try: |
965 try: |
819 import vcr |
966 import vcr |
|
967 |
820 vcr.VCR |
968 vcr.VCR |
821 return True |
969 return True |
822 except (ImportError, AttributeError): |
970 except (ImportError, AttributeError): |
823 pass |
971 pass |
824 return False |
972 return False |
|
973 |
825 |
974 |
826 @check('emacs', 'GNU Emacs') |
975 @check('emacs', 'GNU Emacs') |
827 def has_emacs(): |
976 def has_emacs(): |
828 # Our emacs lisp uses `with-eval-after-load` which is new in emacs |
977 # Our emacs lisp uses `with-eval-after-load` which is new in emacs |
829 # 24.4, so we allow emacs 24.4, 24.5, and 25+ (24.5 was the last |
978 # 24.4, so we allow emacs 24.4, 24.5, and 25+ (24.5 was the last |
830 # 24 release) |
979 # 24 release) |
831 return matchoutput('emacs --version', b'GNU Emacs 2(4.4|4.5|5|6|7|8|9)') |
980 return matchoutput('emacs --version', b'GNU Emacs 2(4.4|4.5|5|6|7|8|9)') |
|
981 |
|
982 |
|
983 # @check('black', 'the black formatter for python') |
|
984 @check('grey', 'grey, the fork of the black formatter for python') |
|
985 def has_black(): |
|
986 # use that to actual black as soon as possible |
|
987 # blackcmd = 'black --version' |
|
988 blackcmd = 'python3 $RUNTESTDIR/../contrib/grey.py --version' |
|
989 # version_regex = b'black, version \d' |
|
990 version_regex = b'grey.py, version \d' |
|
991 return matchoutput(blackcmd, version_regex) |