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