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