##// END OF EJS Templates
hghave: make black version regex work with newer versions of black...
Manuel Jacob -
r50162:f639982d stable
parent child Browse files
Show More
@@ -1,1172 +1,1172 b''
1 1 from __future__ import absolute_import, print_function
2 2
3 3 import distutils.version
4 4 import os
5 5 import re
6 6 import socket
7 7 import stat
8 8 import subprocess
9 9 import sys
10 10 import tempfile
11 11
12 12 tempprefix = 'hg-hghave-'
13 13
14 14 checks = {
15 15 "true": (lambda: True, "yak shaving"),
16 16 "false": (lambda: False, "nail clipper"),
17 17 "known-bad-output": (lambda: True, "use for currently known bad output"),
18 18 "missing-correct-output": (lambda: False, "use for missing good output"),
19 19 }
20 20
21 21 try:
22 22 import msvcrt
23 23
24 24 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
25 25 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
26 26 except ImportError:
27 27 pass
28 28
29 29 stdout = getattr(sys.stdout, 'buffer', sys.stdout)
30 30 stderr = getattr(sys.stderr, 'buffer', sys.stderr)
31 31
32 32 is_not_python2 = sys.version_info[0] >= 3
33 33 if is_not_python2:
34 34
35 35 def _sys2bytes(p):
36 36 if p is None:
37 37 return p
38 38 return p.encode('utf-8')
39 39
40 40 def _bytes2sys(p):
41 41 if p is None:
42 42 return p
43 43 return p.decode('utf-8')
44 44
45 45
46 46 else:
47 47
48 48 def _sys2bytes(p):
49 49 return p
50 50
51 51 _bytes2sys = _sys2bytes
52 52
53 53
54 54 def check(name, desc):
55 55 """Registers a check function for a feature."""
56 56
57 57 def decorator(func):
58 58 checks[name] = (func, desc)
59 59 return func
60 60
61 61 return decorator
62 62
63 63
64 64 def checkvers(name, desc, vers):
65 65 """Registers a check function for each of a series of versions.
66 66
67 67 vers can be a list or an iterator.
68 68
69 69 Produces a series of feature checks that have the form <name><vers> without
70 70 any punctuation (even if there's punctuation in 'vers'; i.e. this produces
71 71 'py38', not 'py3.8' or 'py-38')."""
72 72
73 73 def decorator(func):
74 74 def funcv(v):
75 75 def f():
76 76 return func(v)
77 77
78 78 return f
79 79
80 80 for v in vers:
81 81 v = str(v)
82 82 f = funcv(v)
83 83 checks['%s%s' % (name, v.replace('.', ''))] = (f, desc % v)
84 84 return func
85 85
86 86 return decorator
87 87
88 88
89 89 def checkfeatures(features):
90 90 result = {
91 91 'error': [],
92 92 'missing': [],
93 93 'skipped': [],
94 94 }
95 95
96 96 for feature in features:
97 97 negate = feature.startswith('no-')
98 98 if negate:
99 99 feature = feature[3:]
100 100
101 101 if feature not in checks:
102 102 result['missing'].append(feature)
103 103 continue
104 104
105 105 check, desc = checks[feature]
106 106 try:
107 107 available = check()
108 108 except Exception as e:
109 109 result['error'].append('hghave check %s failed: %r' % (feature, e))
110 110 continue
111 111
112 112 if not negate and not available:
113 113 result['skipped'].append('missing feature: %s' % desc)
114 114 elif negate and available:
115 115 result['skipped'].append('system supports %s' % desc)
116 116
117 117 return result
118 118
119 119
120 120 def require(features):
121 121 """Require that features are available, exiting if not."""
122 122 result = checkfeatures(features)
123 123
124 124 for missing in result['missing']:
125 125 stderr.write(
126 126 ('skipped: unknown feature: %s\n' % missing).encode('utf-8')
127 127 )
128 128 for msg in result['skipped']:
129 129 stderr.write(('skipped: %s\n' % msg).encode('utf-8'))
130 130 for msg in result['error']:
131 131 stderr.write(('%s\n' % msg).encode('utf-8'))
132 132
133 133 if result['missing']:
134 134 sys.exit(2)
135 135
136 136 if result['skipped'] or result['error']:
137 137 sys.exit(1)
138 138
139 139
140 140 def matchoutput(cmd, regexp, ignorestatus=False):
141 141 """Return the match object if cmd executes successfully and its output
142 142 is matched by the supplied regular expression.
143 143 """
144 144
145 145 # Tests on Windows have to fake USERPROFILE to point to the test area so
146 146 # that `~` is properly expanded on py3.8+. However, some tools like black
147 147 # make calls that need the real USERPROFILE in order to run `foo --version`.
148 148 env = os.environ
149 149 if os.name == 'nt':
150 150 env = os.environ.copy()
151 151 env['USERPROFILE'] = env['REALUSERPROFILE']
152 152
153 153 r = re.compile(regexp)
154 154 p = subprocess.Popen(
155 155 cmd,
156 156 shell=True,
157 157 stdout=subprocess.PIPE,
158 158 stderr=subprocess.STDOUT,
159 159 env=env,
160 160 )
161 161 s = p.communicate()[0]
162 162 ret = p.returncode
163 163 return (ignorestatus or not ret) and r.search(s)
164 164
165 165
166 166 @check("baz", "GNU Arch baz client")
167 167 def has_baz():
168 168 return matchoutput('baz --version 2>&1', br'baz Bazaar version')
169 169
170 170
171 171 @check("bzr", "Breezy library and executable version >= 3.1")
172 172 def has_bzr():
173 173 if not is_not_python2:
174 174 return False
175 175 try:
176 176 # Test the Breezy python lib
177 177 import breezy
178 178 import breezy.bzr.bzrdir
179 179 import breezy.errors
180 180 import breezy.revision
181 181 import breezy.revisionspec
182 182
183 183 breezy.revisionspec.RevisionSpec
184 184 if breezy.__doc__ is None or breezy.version_info[:2] < (3, 1):
185 185 return False
186 186 except (AttributeError, ImportError):
187 187 return False
188 188 # Test the executable
189 189 return matchoutput('brz --version 2>&1', br'Breezy \(brz\) ')
190 190
191 191
192 192 @check("chg", "running with chg")
193 193 def has_chg():
194 194 return 'CHG_INSTALLED_AS_HG' in os.environ
195 195
196 196
197 197 @check("rhg", "running with rhg as 'hg'")
198 198 def has_rhg():
199 199 return 'RHG_INSTALLED_AS_HG' in os.environ
200 200
201 201
202 202 @check("pyoxidizer", "running with pyoxidizer build as 'hg'")
203 203 def has_rhg():
204 204 return 'PYOXIDIZED_INSTALLED_AS_HG' in os.environ
205 205
206 206
207 207 @check("cvs", "cvs client/server")
208 208 def has_cvs():
209 209 re = br'Concurrent Versions System.*?server'
210 210 return matchoutput('cvs --version 2>&1', re) and not has_msys()
211 211
212 212
213 213 @check("cvs112", "cvs client/server 1.12.* (not cvsnt)")
214 214 def has_cvs112():
215 215 re = br'Concurrent Versions System \(CVS\) 1.12.*?server'
216 216 return matchoutput('cvs --version 2>&1', re) and not has_msys()
217 217
218 218
219 219 @check("cvsnt", "cvsnt client/server")
220 220 def has_cvsnt():
221 221 re = br'Concurrent Versions System \(CVSNT\) (\d+).(\d+).*\(client/server\)'
222 222 return matchoutput('cvsnt --version 2>&1', re)
223 223
224 224
225 225 @check("darcs", "darcs client")
226 226 def has_darcs():
227 227 return matchoutput('darcs --version', br'\b2\.([2-9]|\d{2})', True)
228 228
229 229
230 230 @check("mtn", "monotone client (>= 1.0)")
231 231 def has_mtn():
232 232 return matchoutput('mtn --version', br'monotone', True) and not matchoutput(
233 233 'mtn --version', br'monotone 0\.', True
234 234 )
235 235
236 236
237 237 @check("eol-in-paths", "end-of-lines in paths")
238 238 def has_eol_in_paths():
239 239 try:
240 240 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r')
241 241 os.close(fd)
242 242 os.remove(path)
243 243 return True
244 244 except (IOError, OSError):
245 245 return False
246 246
247 247
248 248 @check("execbit", "executable bit")
249 249 def has_executablebit():
250 250 try:
251 251 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
252 252 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
253 253 try:
254 254 os.close(fh)
255 255 m = os.stat(fn).st_mode & 0o777
256 256 new_file_has_exec = m & EXECFLAGS
257 257 os.chmod(fn, m ^ EXECFLAGS)
258 258 exec_flags_cannot_flip = (os.stat(fn).st_mode & 0o777) == m
259 259 finally:
260 260 os.unlink(fn)
261 261 except (IOError, OSError):
262 262 # we don't care, the user probably won't be able to commit anyway
263 263 return False
264 264 return not (new_file_has_exec or exec_flags_cannot_flip)
265 265
266 266
267 267 @check("suidbit", "setuid and setgid bit")
268 268 def has_suidbit():
269 269 if (
270 270 getattr(os, "statvfs", None) is None
271 271 or getattr(os, "ST_NOSUID", None) is None
272 272 ):
273 273 return False
274 274 return bool(os.statvfs('.').f_flag & os.ST_NOSUID)
275 275
276 276
277 277 @check("icasefs", "case insensitive file system")
278 278 def has_icasefs():
279 279 # Stolen from mercurial.util
280 280 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
281 281 os.close(fd)
282 282 try:
283 283 s1 = os.stat(path)
284 284 d, b = os.path.split(path)
285 285 p2 = os.path.join(d, b.upper())
286 286 if path == p2:
287 287 p2 = os.path.join(d, b.lower())
288 288 try:
289 289 s2 = os.stat(p2)
290 290 return s2 == s1
291 291 except OSError:
292 292 return False
293 293 finally:
294 294 os.remove(path)
295 295
296 296
297 297 @check("fifo", "named pipes")
298 298 def has_fifo():
299 299 if getattr(os, "mkfifo", None) is None:
300 300 return False
301 301 name = tempfile.mktemp(dir='.', prefix=tempprefix)
302 302 try:
303 303 os.mkfifo(name)
304 304 os.unlink(name)
305 305 return True
306 306 except OSError:
307 307 return False
308 308
309 309
310 310 @check("killdaemons", 'killdaemons.py support')
311 311 def has_killdaemons():
312 312 return True
313 313
314 314
315 315 @check("cacheable", "cacheable filesystem")
316 316 def has_cacheable_fs():
317 317 from mercurial import util
318 318
319 319 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
320 320 os.close(fd)
321 321 try:
322 322 return util.cachestat(path).cacheable()
323 323 finally:
324 324 os.remove(path)
325 325
326 326
327 327 @check("lsprof", "python lsprof module")
328 328 def has_lsprof():
329 329 try:
330 330 import _lsprof
331 331
332 332 _lsprof.Profiler # silence unused import warning
333 333 return True
334 334 except ImportError:
335 335 return False
336 336
337 337
338 338 def _gethgversion():
339 339 m = matchoutput('hg --version --quiet 2>&1', br'(\d+)\.(\d+)')
340 340 if not m:
341 341 return (0, 0)
342 342 return (int(m.group(1)), int(m.group(2)))
343 343
344 344
345 345 _hgversion = None
346 346
347 347
348 348 def gethgversion():
349 349 global _hgversion
350 350 if _hgversion is None:
351 351 _hgversion = _gethgversion()
352 352 return _hgversion
353 353
354 354
355 355 @checkvers(
356 356 "hg", "Mercurial >= %s", list([(1.0 * x) / 10 for x in range(9, 99)])
357 357 )
358 358 def has_hg_range(v):
359 359 major, minor = v.split('.')[0:2]
360 360 return gethgversion() >= (int(major), int(minor))
361 361
362 362
363 363 @check("rust", "Using the Rust extensions")
364 364 def has_rust():
365 365 """Check is the mercurial currently running is using some rust code"""
366 366 cmd = 'hg debuginstall --quiet 2>&1'
367 367 match = br'checking module policy \(([^)]+)\)'
368 368 policy = matchoutput(cmd, match)
369 369 if not policy:
370 370 return False
371 371 return b'rust' in policy.group(1)
372 372
373 373
374 374 @check("hg08", "Mercurial >= 0.8")
375 375 def has_hg08():
376 376 if checks["hg09"][0]():
377 377 return True
378 378 return matchoutput('hg help annotate 2>&1', '--date')
379 379
380 380
381 381 @check("hg07", "Mercurial >= 0.7")
382 382 def has_hg07():
383 383 if checks["hg08"][0]():
384 384 return True
385 385 return matchoutput('hg --version --quiet 2>&1', 'Mercurial Distributed SCM')
386 386
387 387
388 388 @check("hg06", "Mercurial >= 0.6")
389 389 def has_hg06():
390 390 if checks["hg07"][0]():
391 391 return True
392 392 return matchoutput('hg --version --quiet 2>&1', 'Mercurial version')
393 393
394 394
395 395 @check("gettext", "GNU Gettext (msgfmt)")
396 396 def has_gettext():
397 397 return matchoutput('msgfmt --version', br'GNU gettext-tools')
398 398
399 399
400 400 @check("git", "git command line client")
401 401 def has_git():
402 402 return matchoutput('git --version 2>&1', br'^git version')
403 403
404 404
405 405 def getgitversion():
406 406 m = matchoutput('git --version 2>&1', br'git version (\d+)\.(\d+)')
407 407 if not m:
408 408 return (0, 0)
409 409 return (int(m.group(1)), int(m.group(2)))
410 410
411 411
412 412 @check("pygit2", "pygit2 Python library")
413 413 def has_git():
414 414 try:
415 415 import pygit2
416 416
417 417 pygit2.Oid # silence unused import
418 418 return True
419 419 except ImportError:
420 420 return False
421 421
422 422
423 423 # https://github.com/git-lfs/lfs-test-server
424 424 @check("lfs-test-server", "git-lfs test server")
425 425 def has_lfsserver():
426 426 exe = 'lfs-test-server'
427 427 if has_windows():
428 428 exe = 'lfs-test-server.exe'
429 429 return any(
430 430 os.access(os.path.join(path, exe), os.X_OK)
431 431 for path in os.environ["PATH"].split(os.pathsep)
432 432 )
433 433
434 434
435 435 @checkvers("git", "git client (with ext::sh support) version >= %s", (1.9,))
436 436 def has_git_range(v):
437 437 major, minor = v.split('.')[0:2]
438 438 return getgitversion() >= (int(major), int(minor))
439 439
440 440
441 441 @check("docutils", "Docutils text processing library")
442 442 def has_docutils():
443 443 try:
444 444 import docutils.core
445 445
446 446 docutils.core.publish_cmdline # silence unused import
447 447 return True
448 448 except ImportError:
449 449 return False
450 450
451 451
452 452 def getsvnversion():
453 453 m = matchoutput('svn --version --quiet 2>&1', br'^(\d+)\.(\d+)')
454 454 if not m:
455 455 return (0, 0)
456 456 return (int(m.group(1)), int(m.group(2)))
457 457
458 458
459 459 @checkvers("svn", "subversion client and admin tools >= %s", (1.3, 1.5))
460 460 def has_svn_range(v):
461 461 major, minor = v.split('.')[0:2]
462 462 return getsvnversion() >= (int(major), int(minor))
463 463
464 464
465 465 @check("svn", "subversion client and admin tools")
466 466 def has_svn():
467 467 return matchoutput('svn --version 2>&1', br'^svn, version') and matchoutput(
468 468 'svnadmin --version 2>&1', br'^svnadmin, version'
469 469 )
470 470
471 471
472 472 @check("svn-bindings", "subversion python bindings")
473 473 def has_svn_bindings():
474 474 try:
475 475 import svn.core
476 476
477 477 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
478 478 if version < (1, 4):
479 479 return False
480 480 return True
481 481 except ImportError:
482 482 return False
483 483
484 484
485 485 @check("p4", "Perforce server and client")
486 486 def has_p4():
487 487 return matchoutput('p4 -V', br'Rev\. P4/') and matchoutput(
488 488 'p4d -V', br'Rev\. P4D/'
489 489 )
490 490
491 491
492 492 @check("symlink", "symbolic links")
493 493 def has_symlink():
494 494 # mercurial.windows.checklink() is a hard 'no' at the moment
495 495 if os.name == 'nt' or getattr(os, "symlink", None) is None:
496 496 return False
497 497 name = tempfile.mktemp(dir='.', prefix=tempprefix)
498 498 try:
499 499 os.symlink(".", name)
500 500 os.unlink(name)
501 501 return True
502 502 except (OSError, AttributeError):
503 503 return False
504 504
505 505
506 506 @check("hardlink", "hardlinks")
507 507 def has_hardlink():
508 508 from mercurial import util
509 509
510 510 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
511 511 os.close(fh)
512 512 name = tempfile.mktemp(dir='.', prefix=tempprefix)
513 513 try:
514 514 util.oslink(_sys2bytes(fn), _sys2bytes(name))
515 515 os.unlink(name)
516 516 return True
517 517 except OSError:
518 518 return False
519 519 finally:
520 520 os.unlink(fn)
521 521
522 522
523 523 @check("hardlink-whitelisted", "hardlinks on whitelisted filesystems")
524 524 def has_hardlink_whitelisted():
525 525 from mercurial import util
526 526
527 527 try:
528 528 fstype = util.getfstype(b'.')
529 529 except OSError:
530 530 return False
531 531 return fstype in util._hardlinkfswhitelist
532 532
533 533
534 534 @check("rmcwd", "can remove current working directory")
535 535 def has_rmcwd():
536 536 ocwd = os.getcwd()
537 537 temp = tempfile.mkdtemp(dir='.', prefix=tempprefix)
538 538 try:
539 539 os.chdir(temp)
540 540 # On Linux, 'rmdir .' isn't allowed, but the other names are okay.
541 541 # On Solaris and Windows, the cwd can't be removed by any names.
542 542 os.rmdir(os.getcwd())
543 543 return True
544 544 except OSError:
545 545 return False
546 546 finally:
547 547 os.chdir(ocwd)
548 548 # clean up temp dir on platforms where cwd can't be removed
549 549 try:
550 550 os.rmdir(temp)
551 551 except OSError:
552 552 pass
553 553
554 554
555 555 @check("tla", "GNU Arch tla client")
556 556 def has_tla():
557 557 return matchoutput('tla --version 2>&1', br'The GNU Arch Revision')
558 558
559 559
560 560 @check("gpg", "gpg client")
561 561 def has_gpg():
562 562 return matchoutput('gpg --version 2>&1', br'GnuPG')
563 563
564 564
565 565 @check("gpg2", "gpg client v2")
566 566 def has_gpg2():
567 567 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.')
568 568
569 569
570 570 @check("gpg21", "gpg client v2.1+")
571 571 def has_gpg21():
572 572 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.(?!0)')
573 573
574 574
575 575 @check("unix-permissions", "unix-style permissions")
576 576 def has_unix_permissions():
577 577 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
578 578 try:
579 579 fname = os.path.join(d, 'foo')
580 580 for umask in (0o77, 0o07, 0o22):
581 581 os.umask(umask)
582 582 f = open(fname, 'w')
583 583 f.close()
584 584 mode = os.stat(fname).st_mode
585 585 os.unlink(fname)
586 586 if mode & 0o777 != ~umask & 0o666:
587 587 return False
588 588 return True
589 589 finally:
590 590 os.rmdir(d)
591 591
592 592
593 593 @check("unix-socket", "AF_UNIX socket family")
594 594 def has_unix_socket():
595 595 return getattr(socket, 'AF_UNIX', None) is not None
596 596
597 597
598 598 @check("root", "root permissions")
599 599 def has_root():
600 600 return getattr(os, 'geteuid', None) and os.geteuid() == 0
601 601
602 602
603 603 @check("pyflakes", "Pyflakes python linter")
604 604 def has_pyflakes():
605 605 try:
606 606 import pyflakes
607 607
608 608 pyflakes.__version__
609 609 except ImportError:
610 610 return False
611 611 else:
612 612 return True
613 613
614 614
615 615 @check("pylint", "Pylint python linter")
616 616 def has_pylint():
617 617 return matchoutput("pylint --help", br"Usage:[ ]+pylint", True)
618 618
619 619
620 620 @check("clang-format", "clang-format C code formatter (>= 11)")
621 621 def has_clang_format():
622 622 m = matchoutput('clang-format --version', br'clang-format version (\d+)')
623 623 # style changed somewhere between 10.x and 11.x
624 624 if m:
625 625 return int(m.group(1)) >= 11
626 626 # Assist Googler contributors, they have a centrally-maintained version of
627 627 # clang-format that is generally very fresh, but unlike most builds (both
628 628 # official and unofficial), it does *not* include a version number.
629 629 return matchoutput(
630 630 'clang-format --version', br'clang-format .*google3-trunk \([0-9a-f]+\)'
631 631 )
632 632
633 633
634 634 @check("jshint", "JSHint static code analysis tool")
635 635 def has_jshint():
636 636 return matchoutput("jshint --version 2>&1", br"jshint v")
637 637
638 638
639 639 @check("pygments", "Pygments source highlighting library")
640 640 def has_pygments():
641 641 try:
642 642 import pygments
643 643
644 644 pygments.highlight # silence unused import warning
645 645 return True
646 646 except ImportError:
647 647 return False
648 648
649 649
650 650 @check("pygments25", "Pygments version >= 2.5")
651 651 def pygments25():
652 652 try:
653 653 import pygments
654 654
655 655 v = pygments.__version__
656 656 except ImportError:
657 657 return False
658 658
659 659 parts = v.split(".")
660 660 major = int(parts[0])
661 661 minor = int(parts[1])
662 662
663 663 return (major, minor) >= (2, 5)
664 664
665 665
666 666 @check("pygments211", "Pygments version >= 2.11")
667 667 def pygments211():
668 668 try:
669 669 import pygments
670 670
671 671 v = pygments.__version__
672 672 except ImportError:
673 673 return False
674 674
675 675 parts = v.split(".")
676 676 major = int(parts[0])
677 677 minor = int(parts[1])
678 678
679 679 return (major, minor) >= (2, 11)
680 680
681 681
682 682 @check("outer-repo", "outer repo")
683 683 def has_outer_repo():
684 684 # failing for other reasons than 'no repo' imply that there is a repo
685 685 return not matchoutput('hg root 2>&1', br'abort: no repository found', True)
686 686
687 687
688 688 @check("ssl", "ssl module available")
689 689 def has_ssl():
690 690 try:
691 691 import ssl
692 692
693 693 ssl.CERT_NONE
694 694 return True
695 695 except ImportError:
696 696 return False
697 697
698 698
699 699 @check("defaultcacertsloaded", "detected presence of loaded system CA certs")
700 700 def has_defaultcacertsloaded():
701 701 import ssl
702 702 from mercurial import sslutil, ui as uimod
703 703
704 704 ui = uimod.ui.load()
705 705 cafile = sslutil._defaultcacerts(ui)
706 706 ctx = ssl.create_default_context()
707 707 if cafile:
708 708 ctx.load_verify_locations(cafile=cafile)
709 709 else:
710 710 ctx.load_default_certs()
711 711
712 712 return len(ctx.get_ca_certs()) > 0
713 713
714 714
715 715 @check("tls1.2", "TLS 1.2 protocol support")
716 716 def has_tls1_2():
717 717 from mercurial import sslutil
718 718
719 719 return b'tls1.2' in sslutil.supportedprotocols
720 720
721 721
722 722 @check("windows", "Windows")
723 723 def has_windows():
724 724 return os.name == 'nt'
725 725
726 726
727 727 @check("system-sh", "system() uses sh")
728 728 def has_system_sh():
729 729 return os.name != 'nt'
730 730
731 731
732 732 @check("serve", "platform and python can manage 'hg serve -d'")
733 733 def has_serve():
734 734 return True
735 735
736 736
737 737 @check("setprocname", "whether osutil.setprocname is available or not")
738 738 def has_setprocname():
739 739 try:
740 740 from mercurial.utils import procutil
741 741
742 742 procutil.setprocname
743 743 return True
744 744 except AttributeError:
745 745 return False
746 746
747 747
748 748 @check("test-repo", "running tests from repository")
749 749 def has_test_repo():
750 750 t = os.environ["TESTDIR"]
751 751 return os.path.isdir(os.path.join(t, "..", ".hg"))
752 752
753 753
754 754 @check("network-io", "whether tests are allowed to access 3rd party services")
755 755 def has_test_repo():
756 756 t = os.environ.get("HGTESTS_ALLOW_NETIO")
757 757 return t == "1"
758 758
759 759
760 760 @check("curses", "terminfo compiler and curses module")
761 761 def has_curses():
762 762 try:
763 763 import curses
764 764
765 765 curses.COLOR_BLUE
766 766
767 767 # Windows doesn't have a `tic` executable, but the windows_curses
768 768 # package is sufficient to run the tests without it.
769 769 if os.name == 'nt':
770 770 return True
771 771
772 772 return has_tic()
773 773
774 774 except (ImportError, AttributeError):
775 775 return False
776 776
777 777
778 778 @check("tic", "terminfo compiler")
779 779 def has_tic():
780 780 return matchoutput('test -x "`which tic`"', br'')
781 781
782 782
783 783 @check("xz", "xz compression utility")
784 784 def has_xz():
785 785 # When Windows invokes a subprocess in shell mode, it uses `cmd.exe`, which
786 786 # only knows `where`, not `which`. So invoke MSYS shell explicitly.
787 787 return matchoutput("sh -c 'test -x \"`which xz`\"'", b'')
788 788
789 789
790 790 @check("msys", "Windows with MSYS")
791 791 def has_msys():
792 792 return os.getenv('MSYSTEM')
793 793
794 794
795 795 @check("aix", "AIX")
796 796 def has_aix():
797 797 return sys.platform.startswith("aix")
798 798
799 799
800 800 @check("osx", "OS X")
801 801 def has_osx():
802 802 return sys.platform == 'darwin'
803 803
804 804
805 805 @check("osxpackaging", "OS X packaging tools")
806 806 def has_osxpackaging():
807 807 try:
808 808 return (
809 809 matchoutput('pkgbuild', br'Usage: pkgbuild ', ignorestatus=1)
810 810 and matchoutput(
811 811 'productbuild', br'Usage: productbuild ', ignorestatus=1
812 812 )
813 813 and matchoutput('lsbom', br'Usage: lsbom', ignorestatus=1)
814 814 and matchoutput('xar --help', br'Usage: xar', ignorestatus=1)
815 815 )
816 816 except ImportError:
817 817 return False
818 818
819 819
820 820 @check('linuxormacos', 'Linux or MacOS')
821 821 def has_linuxormacos():
822 822 # This isn't a perfect test for MacOS. But it is sufficient for our needs.
823 823 return sys.platform.startswith(('linux', 'darwin'))
824 824
825 825
826 826 @check("docker", "docker support")
827 827 def has_docker():
828 828 pat = br'A self-sufficient runtime for'
829 829 if matchoutput('docker --help', pat):
830 830 if 'linux' not in sys.platform:
831 831 # TODO: in theory we should be able to test docker-based
832 832 # package creation on non-linux using boot2docker, but in
833 833 # practice that requires extra coordination to make sure
834 834 # $TESTTEMP is going to be visible at the same path to the
835 835 # boot2docker VM. If we figure out how to verify that, we
836 836 # can use the following instead of just saying False:
837 837 # return 'DOCKER_HOST' in os.environ
838 838 return False
839 839
840 840 return True
841 841 return False
842 842
843 843
844 844 @check("debhelper", "debian packaging tools")
845 845 def has_debhelper():
846 846 # Some versions of dpkg say `dpkg', some say 'dpkg' (` vs ' on the first
847 847 # quote), so just accept anything in that spot.
848 848 dpkg = matchoutput(
849 849 'dpkg --version', br"Debian .dpkg' package management program"
850 850 )
851 851 dh = matchoutput(
852 852 'dh --help', br'dh is a part of debhelper.', ignorestatus=True
853 853 )
854 854 dh_py2 = matchoutput(
855 855 'dh_python2 --help', br'other supported Python versions'
856 856 )
857 857 # debuild comes from the 'devscripts' package, though you might want
858 858 # the 'build-debs' package instead, which has a dependency on devscripts.
859 859 debuild = matchoutput(
860 860 'debuild --help', br'to run debian/rules with given parameter'
861 861 )
862 862 return dpkg and dh and dh_py2 and debuild
863 863
864 864
865 865 @check(
866 866 "debdeps", "debian build dependencies (run dpkg-checkbuilddeps in contrib/)"
867 867 )
868 868 def has_debdeps():
869 869 # just check exit status (ignoring output)
870 870 path = '%s/../contrib/packaging/debian/control' % os.environ['TESTDIR']
871 871 return matchoutput('dpkg-checkbuilddeps %s' % path, br'')
872 872
873 873
874 874 @check("demandimport", "demandimport enabled")
875 875 def has_demandimport():
876 876 # chg disables demandimport intentionally for performance wins.
877 877 return (not has_chg()) and os.environ.get('HGDEMANDIMPORT') != 'disable'
878 878
879 879
880 880 # Add "py27", "py35", ... as possible feature checks. Note that there's no
881 881 # punctuation here.
882 882 @checkvers("py", "Python >= %s", (2.7, 3.5, 3.6, 3.7, 3.8, 3.9))
883 883 def has_python_range(v):
884 884 major, minor = v.split('.')[0:2]
885 885 py_major, py_minor = sys.version_info.major, sys.version_info.minor
886 886
887 887 return (py_major, py_minor) >= (int(major), int(minor))
888 888
889 889
890 890 @check("py3", "running with Python 3.x")
891 891 def has_py3():
892 892 return 3 == sys.version_info[0]
893 893
894 894
895 895 @check("py3exe", "a Python 3.x interpreter is available")
896 896 def has_python3exe():
897 897 py = 'python3'
898 898 if os.name == 'nt':
899 899 py = 'py -3'
900 900 return matchoutput('%s -V' % py, br'^Python 3.(5|6|7|8|9)')
901 901
902 902
903 903 @check("pure", "running with pure Python code")
904 904 def has_pure():
905 905 return any(
906 906 [
907 907 os.environ.get("HGMODULEPOLICY") == "py",
908 908 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure",
909 909 ]
910 910 )
911 911
912 912
913 913 @check("slow", "allow slow tests (use --allow-slow-tests)")
914 914 def has_slow():
915 915 return os.environ.get('HGTEST_SLOW') == 'slow'
916 916
917 917
918 918 @check("hypothesis", "Hypothesis automated test generation")
919 919 def has_hypothesis():
920 920 try:
921 921 import hypothesis
922 922
923 923 hypothesis.given
924 924 return True
925 925 except ImportError:
926 926 return False
927 927
928 928
929 929 @check("unziplinks", "unzip(1) understands and extracts symlinks")
930 930 def unzip_understands_symlinks():
931 931 return matchoutput('unzip --help', br'Info-ZIP')
932 932
933 933
934 934 @check("zstd", "zstd Python module available")
935 935 def has_zstd():
936 936 try:
937 937 import mercurial.zstd
938 938
939 939 mercurial.zstd.__version__
940 940 return True
941 941 except ImportError:
942 942 return False
943 943
944 944
945 945 @check("devfull", "/dev/full special file")
946 946 def has_dev_full():
947 947 return os.path.exists('/dev/full')
948 948
949 949
950 950 @check("ensurepip", "ensurepip module")
951 951 def has_ensurepip():
952 952 try:
953 953 import ensurepip
954 954
955 955 ensurepip.bootstrap
956 956 return True
957 957 except ImportError:
958 958 return False
959 959
960 960
961 961 @check("virtualenv", "virtualenv support")
962 962 def has_virtualenv():
963 963 try:
964 964 import virtualenv
965 965
966 966 # --no-site-package became the default in 1.7 (Nov 2011), and the
967 967 # argument was removed in 20.0 (Feb 2020). Rather than make the
968 968 # script complicated, just ignore ancient versions.
969 969 return int(virtualenv.__version__.split('.')[0]) > 1
970 970 except (AttributeError, ImportError, IndexError):
971 971 return False
972 972
973 973
974 974 @check("fsmonitor", "running tests with fsmonitor")
975 975 def has_fsmonitor():
976 976 return 'HGFSMONITOR_TESTS' in os.environ
977 977
978 978
979 979 @check("fuzzywuzzy", "Fuzzy string matching library")
980 980 def has_fuzzywuzzy():
981 981 try:
982 982 import fuzzywuzzy
983 983
984 984 fuzzywuzzy.__version__
985 985 return True
986 986 except ImportError:
987 987 return False
988 988
989 989
990 990 @check("clang-libfuzzer", "clang new enough to include libfuzzer")
991 991 def has_clang_libfuzzer():
992 992 mat = matchoutput('clang --version', br'clang version (\d)')
993 993 if mat:
994 994 # libfuzzer is new in clang 6
995 995 return int(mat.group(1)) > 5
996 996 return False
997 997
998 998
999 999 @check("clang-6.0", "clang 6.0 with version suffix (libfuzzer included)")
1000 1000 def has_clang60():
1001 1001 return matchoutput('clang-6.0 --version', br'clang version 6\.')
1002 1002
1003 1003
1004 1004 @check("xdiff", "xdiff algorithm")
1005 1005 def has_xdiff():
1006 1006 try:
1007 1007 from mercurial import policy
1008 1008
1009 1009 bdiff = policy.importmod('bdiff')
1010 1010 return bdiff.xdiffblocks(b'', b'') == [(0, 0, 0, 0)]
1011 1011 except (ImportError, AttributeError):
1012 1012 return False
1013 1013
1014 1014
1015 1015 @check('extraextensions', 'whether tests are running with extra extensions')
1016 1016 def has_extraextensions():
1017 1017 return 'HGTESTEXTRAEXTENSIONS' in os.environ
1018 1018
1019 1019
1020 1020 def getrepofeatures():
1021 1021 """Obtain set of repository features in use.
1022 1022
1023 1023 HGREPOFEATURES can be used to define or remove features. It contains
1024 1024 a space-delimited list of feature strings. Strings beginning with ``-``
1025 1025 mean to remove.
1026 1026 """
1027 1027 # Default list provided by core.
1028 1028 features = {
1029 1029 'bundlerepo',
1030 1030 'revlogstore',
1031 1031 'fncache',
1032 1032 }
1033 1033
1034 1034 # Features that imply other features.
1035 1035 implies = {
1036 1036 'simplestore': ['-revlogstore', '-bundlerepo', '-fncache'],
1037 1037 }
1038 1038
1039 1039 for override in os.environ.get('HGREPOFEATURES', '').split(' '):
1040 1040 if not override:
1041 1041 continue
1042 1042
1043 1043 if override.startswith('-'):
1044 1044 if override[1:] in features:
1045 1045 features.remove(override[1:])
1046 1046 else:
1047 1047 features.add(override)
1048 1048
1049 1049 for imply in implies.get(override, []):
1050 1050 if imply.startswith('-'):
1051 1051 if imply[1:] in features:
1052 1052 features.remove(imply[1:])
1053 1053 else:
1054 1054 features.add(imply)
1055 1055
1056 1056 return features
1057 1057
1058 1058
1059 1059 @check('reporevlogstore', 'repository using the default revlog store')
1060 1060 def has_reporevlogstore():
1061 1061 return 'revlogstore' in getrepofeatures()
1062 1062
1063 1063
1064 1064 @check('reposimplestore', 'repository using simple storage extension')
1065 1065 def has_reposimplestore():
1066 1066 return 'simplestore' in getrepofeatures()
1067 1067
1068 1068
1069 1069 @check('repobundlerepo', 'whether we can open bundle files as repos')
1070 1070 def has_repobundlerepo():
1071 1071 return 'bundlerepo' in getrepofeatures()
1072 1072
1073 1073
1074 1074 @check('repofncache', 'repository has an fncache')
1075 1075 def has_repofncache():
1076 1076 return 'fncache' in getrepofeatures()
1077 1077
1078 1078
1079 1079 @check('dirstate-v2', 'using the v2 format of .hg/dirstate')
1080 1080 def has_dirstate_v2():
1081 1081 # Keep this logic in sync with `newreporequirements()` in `mercurial/localrepo.py`
1082 1082 return has_rust() and matchoutput(
1083 1083 'hg config format.exp-rc-dirstate-v2', b'(?i)1|yes|true|on|always'
1084 1084 )
1085 1085
1086 1086
1087 1087 @check('sqlite', 'sqlite3 module and matching cli is available')
1088 1088 def has_sqlite():
1089 1089 try:
1090 1090 import sqlite3
1091 1091
1092 1092 version = sqlite3.sqlite_version_info
1093 1093 except ImportError:
1094 1094 return False
1095 1095
1096 1096 if version < (3, 8, 3):
1097 1097 # WITH clause not supported
1098 1098 return False
1099 1099
1100 1100 return matchoutput('sqlite3 -version', br'^3\.\d+')
1101 1101
1102 1102
1103 1103 @check('vcr', 'vcr http mocking library (pytest-vcr)')
1104 1104 def has_vcr():
1105 1105 try:
1106 1106 import vcr
1107 1107
1108 1108 vcr.VCR
1109 1109 return True
1110 1110 except (ImportError, AttributeError):
1111 1111 pass
1112 1112 return False
1113 1113
1114 1114
1115 1115 @check('emacs', 'GNU Emacs')
1116 1116 def has_emacs():
1117 1117 # Our emacs lisp uses `with-eval-after-load` which is new in emacs
1118 1118 # 24.4, so we allow emacs 24.4, 24.5, and 25+ (24.5 was the last
1119 1119 # 24 release)
1120 1120 return matchoutput('emacs --version', b'GNU Emacs 2(4.4|4.5|5|6|7|8|9)')
1121 1121
1122 1122
1123 1123 @check('black', 'the black formatter for python (>= 20.8b1)')
1124 1124 def has_black():
1125 1125 blackcmd = 'black --version'
1126 version_regex = b'black, version ([0-9a-b.]+)'
1126 version_regex = b'black, (?:version )?([0-9a-b.]+)'
1127 1127 version = matchoutput(blackcmd, version_regex)
1128 1128 sv = distutils.version.StrictVersion
1129 1129 return version and sv(_bytes2sys(version.group(1))) >= sv('20.8b1')
1130 1130
1131 1131
1132 1132 @check('pytype', 'the pytype type checker')
1133 1133 def has_pytype():
1134 1134 pytypecmd = 'pytype --version'
1135 1135 version = matchoutput(pytypecmd, b'[0-9a-b.]+')
1136 1136 sv = distutils.version.StrictVersion
1137 1137 return version and sv(_bytes2sys(version.group(0))) >= sv('2019.10.17')
1138 1138
1139 1139
1140 1140 @check("rustfmt", "rustfmt tool at version nightly-2021-11-02")
1141 1141 def has_rustfmt():
1142 1142 # We use Nightly's rustfmt due to current unstable config options.
1143 1143 return matchoutput(
1144 1144 '`rustup which --toolchain nightly-2021-11-02 rustfmt` --version',
1145 1145 b'rustfmt',
1146 1146 )
1147 1147
1148 1148
1149 1149 @check("cargo", "cargo tool")
1150 1150 def has_cargo():
1151 1151 return matchoutput('`rustup which cargo` --version', b'cargo')
1152 1152
1153 1153
1154 1154 @check("lzma", "python lzma module")
1155 1155 def has_lzma():
1156 1156 try:
1157 1157 import _lzma
1158 1158
1159 1159 _lzma.FORMAT_XZ
1160 1160 return True
1161 1161 except ImportError:
1162 1162 return False
1163 1163
1164 1164
1165 1165 @check("bash", "bash shell")
1166 1166 def has_bash():
1167 1167 return matchoutput("bash -c 'echo hi'", b'^hi$')
1168 1168
1169 1169
1170 1170 @check("bigendian", "big-endian CPU")
1171 1171 def has_bigendian():
1172 1172 return sys.byteorder == 'big'
General Comments 0
You need to be logged in to leave comments. Login now