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