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