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