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