##// END OF EJS Templates
hghave: disallow symlinks on Windows...
Matt Harbison -
r43760:6792da44 default
parent child Browse files
Show More
@@ -1,1001 +1,1002 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 _bytespath(p):
33 33 if p is None:
34 34 return p
35 35 return p.encode('utf-8')
36 36
37 37 def _strpath(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 _bytespath(p):
46 46 return p
47 47
48 48 _strpath = _bytespath
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 @checkvers(
318 318 "hg", "Mercurial >= %s", list([(1.0 * x) / 10 for x in range(9, 99)])
319 319 )
320 320 def has_hg_range(v):
321 321 major, minor = v.split('.')[0:2]
322 322 return gethgversion() >= (int(major), int(minor))
323 323
324 324
325 325 @check("hg08", "Mercurial >= 0.8")
326 326 def has_hg08():
327 327 if checks["hg09"][0]():
328 328 return True
329 329 return matchoutput('hg help annotate 2>&1', '--date')
330 330
331 331
332 332 @check("hg07", "Mercurial >= 0.7")
333 333 def has_hg07():
334 334 if checks["hg08"][0]():
335 335 return True
336 336 return matchoutput('hg --version --quiet 2>&1', 'Mercurial Distributed SCM')
337 337
338 338
339 339 @check("hg06", "Mercurial >= 0.6")
340 340 def has_hg06():
341 341 if checks["hg07"][0]():
342 342 return True
343 343 return matchoutput('hg --version --quiet 2>&1', 'Mercurial version')
344 344
345 345
346 346 @check("gettext", "GNU Gettext (msgfmt)")
347 347 def has_gettext():
348 348 return matchoutput('msgfmt --version', br'GNU gettext-tools')
349 349
350 350
351 351 @check("git", "git command line client")
352 352 def has_git():
353 353 return matchoutput('git --version 2>&1', br'^git version')
354 354
355 355
356 356 def getgitversion():
357 357 m = matchoutput('git --version 2>&1', br'git version (\d+)\.(\d+)')
358 358 if not m:
359 359 return (0, 0)
360 360 return (int(m.group(1)), int(m.group(2)))
361 361
362 362
363 363 # https://github.com/git-lfs/lfs-test-server
364 364 @check("lfs-test-server", "git-lfs test server")
365 365 def has_lfsserver():
366 366 exe = 'lfs-test-server'
367 367 if has_windows():
368 368 exe = 'lfs-test-server.exe'
369 369 return any(
370 370 os.access(os.path.join(path, exe), os.X_OK)
371 371 for path in os.environ["PATH"].split(os.pathsep)
372 372 )
373 373
374 374
375 375 @checkvers("git", "git client (with ext::sh support) version >= %s", (1.9,))
376 376 def has_git_range(v):
377 377 major, minor = v.split('.')[0:2]
378 378 return getgitversion() >= (int(major), int(minor))
379 379
380 380
381 381 @check("docutils", "Docutils text processing library")
382 382 def has_docutils():
383 383 try:
384 384 import docutils.core
385 385
386 386 docutils.core.publish_cmdline # silence unused import
387 387 return True
388 388 except ImportError:
389 389 return False
390 390
391 391
392 392 def getsvnversion():
393 393 m = matchoutput('svn --version --quiet 2>&1', br'^(\d+)\.(\d+)')
394 394 if not m:
395 395 return (0, 0)
396 396 return (int(m.group(1)), int(m.group(2)))
397 397
398 398
399 399 @checkvers("svn", "subversion client and admin tools >= %s", (1.3, 1.5))
400 400 def has_svn_range(v):
401 401 major, minor = v.split('.')[0:2]
402 402 return getsvnversion() >= (int(major), int(minor))
403 403
404 404
405 405 @check("svn", "subversion client and admin tools")
406 406 def has_svn():
407 407 return matchoutput('svn --version 2>&1', br'^svn, version') and matchoutput(
408 408 'svnadmin --version 2>&1', br'^svnadmin, version'
409 409 )
410 410
411 411
412 412 @check("svn-bindings", "subversion python bindings")
413 413 def has_svn_bindings():
414 414 try:
415 415 import svn.core
416 416
417 417 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
418 418 if version < (1, 4):
419 419 return False
420 420 return True
421 421 except ImportError:
422 422 return False
423 423
424 424
425 425 @check("p4", "Perforce server and client")
426 426 def has_p4():
427 427 return matchoutput('p4 -V', br'Rev\. P4/') and matchoutput(
428 428 'p4d -V', br'Rev\. P4D/'
429 429 )
430 430
431 431
432 432 @check("symlink", "symbolic links")
433 433 def has_symlink():
434 if getattr(os, "symlink", None) is None:
434 # mercurial.windows.checklink() is a hard 'no' at the moment
435 if os.name == 'nt' or getattr(os, "symlink", None) is None:
435 436 return False
436 437 name = tempfile.mktemp(dir='.', prefix=tempprefix)
437 438 try:
438 439 os.symlink(".", name)
439 440 os.unlink(name)
440 441 return True
441 442 except (OSError, AttributeError):
442 443 return False
443 444
444 445
445 446 @check("hardlink", "hardlinks")
446 447 def has_hardlink():
447 448 from mercurial import util
448 449
449 450 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
450 451 os.close(fh)
451 452 name = tempfile.mktemp(dir='.', prefix=tempprefix)
452 453 try:
453 454 util.oslink(_bytespath(fn), _bytespath(name))
454 455 os.unlink(name)
455 456 return True
456 457 except OSError:
457 458 return False
458 459 finally:
459 460 os.unlink(fn)
460 461
461 462
462 463 @check("hardlink-whitelisted", "hardlinks on whitelisted filesystems")
463 464 def has_hardlink_whitelisted():
464 465 from mercurial import util
465 466
466 467 try:
467 468 fstype = util.getfstype(b'.')
468 469 except OSError:
469 470 return False
470 471 return fstype in util._hardlinkfswhitelist
471 472
472 473
473 474 @check("rmcwd", "can remove current working directory")
474 475 def has_rmcwd():
475 476 ocwd = os.getcwd()
476 477 temp = tempfile.mkdtemp(dir='.', prefix=tempprefix)
477 478 try:
478 479 os.chdir(temp)
479 480 # On Linux, 'rmdir .' isn't allowed, but the other names are okay.
480 481 # On Solaris and Windows, the cwd can't be removed by any names.
481 482 os.rmdir(os.getcwd())
482 483 return True
483 484 except OSError:
484 485 return False
485 486 finally:
486 487 os.chdir(ocwd)
487 488 # clean up temp dir on platforms where cwd can't be removed
488 489 try:
489 490 os.rmdir(temp)
490 491 except OSError:
491 492 pass
492 493
493 494
494 495 @check("tla", "GNU Arch tla client")
495 496 def has_tla():
496 497 return matchoutput('tla --version 2>&1', br'The GNU Arch Revision')
497 498
498 499
499 500 @check("gpg", "gpg client")
500 501 def has_gpg():
501 502 return matchoutput('gpg --version 2>&1', br'GnuPG')
502 503
503 504
504 505 @check("gpg2", "gpg client v2")
505 506 def has_gpg2():
506 507 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.')
507 508
508 509
509 510 @check("gpg21", "gpg client v2.1+")
510 511 def has_gpg21():
511 512 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.(?!0)')
512 513
513 514
514 515 @check("unix-permissions", "unix-style permissions")
515 516 def has_unix_permissions():
516 517 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
517 518 try:
518 519 fname = os.path.join(d, 'foo')
519 520 for umask in (0o77, 0o07, 0o22):
520 521 os.umask(umask)
521 522 f = open(fname, 'w')
522 523 f.close()
523 524 mode = os.stat(fname).st_mode
524 525 os.unlink(fname)
525 526 if mode & 0o777 != ~umask & 0o666:
526 527 return False
527 528 return True
528 529 finally:
529 530 os.rmdir(d)
530 531
531 532
532 533 @check("unix-socket", "AF_UNIX socket family")
533 534 def has_unix_socket():
534 535 return getattr(socket, 'AF_UNIX', None) is not None
535 536
536 537
537 538 @check("root", "root permissions")
538 539 def has_root():
539 540 return getattr(os, 'geteuid', None) and os.geteuid() == 0
540 541
541 542
542 543 @check("pyflakes", "Pyflakes python linter")
543 544 def has_pyflakes():
544 545 return matchoutput(
545 546 "sh -c \"echo 'import re' 2>&1 | pyflakes\"",
546 547 br"<stdin>:1: 're' imported but unused",
547 548 True,
548 549 )
549 550
550 551
551 552 @check("pylint", "Pylint python linter")
552 553 def has_pylint():
553 554 return matchoutput("pylint --help", br"Usage: pylint", True)
554 555
555 556
556 557 @check("clang-format", "clang-format C code formatter")
557 558 def has_clang_format():
558 559 m = matchoutput('clang-format --version', br'clang-format version (\d)')
559 560 # style changed somewhere between 4.x and 6.x
560 561 return m and int(m.group(1)) >= 6
561 562
562 563
563 564 @check("jshint", "JSHint static code analysis tool")
564 565 def has_jshint():
565 566 return matchoutput("jshint --version 2>&1", br"jshint v")
566 567
567 568
568 569 @check("pygments", "Pygments source highlighting library")
569 570 def has_pygments():
570 571 try:
571 572 import pygments
572 573
573 574 pygments.highlight # silence unused import warning
574 575 return True
575 576 except ImportError:
576 577 return False
577 578
578 579
579 580 @check("outer-repo", "outer repo")
580 581 def has_outer_repo():
581 582 # failing for other reasons than 'no repo' imply that there is a repo
582 583 return not matchoutput('hg root 2>&1', br'abort: no repository found', True)
583 584
584 585
585 586 @check("ssl", "ssl module available")
586 587 def has_ssl():
587 588 try:
588 589 import ssl
589 590
590 591 ssl.CERT_NONE
591 592 return True
592 593 except ImportError:
593 594 return False
594 595
595 596
596 597 @check("sslcontext", "python >= 2.7.9 ssl")
597 598 def has_sslcontext():
598 599 try:
599 600 import ssl
600 601
601 602 ssl.SSLContext
602 603 return True
603 604 except (ImportError, AttributeError):
604 605 return False
605 606
606 607
607 608 @check("defaultcacerts", "can verify SSL certs by system's CA certs store")
608 609 def has_defaultcacerts():
609 610 from mercurial import sslutil, ui as uimod
610 611
611 612 ui = uimod.ui.load()
612 613 return sslutil._defaultcacerts(ui) or sslutil._canloaddefaultcerts
613 614
614 615
615 616 @check("defaultcacertsloaded", "detected presence of loaded system CA certs")
616 617 def has_defaultcacertsloaded():
617 618 import ssl
618 619 from mercurial import sslutil, ui as uimod
619 620
620 621 if not has_defaultcacerts():
621 622 return False
622 623 if not has_sslcontext():
623 624 return False
624 625
625 626 ui = uimod.ui.load()
626 627 cafile = sslutil._defaultcacerts(ui)
627 628 ctx = ssl.create_default_context()
628 629 if cafile:
629 630 ctx.load_verify_locations(cafile=cafile)
630 631 else:
631 632 ctx.load_default_certs()
632 633
633 634 return len(ctx.get_ca_certs()) > 0
634 635
635 636
636 637 @check("tls1.2", "TLS 1.2 protocol support")
637 638 def has_tls1_2():
638 639 from mercurial import sslutil
639 640
640 641 return b'tls1.2' in sslutil.supportedprotocols
641 642
642 643
643 644 @check("windows", "Windows")
644 645 def has_windows():
645 646 return os.name == 'nt'
646 647
647 648
648 649 @check("system-sh", "system() uses sh")
649 650 def has_system_sh():
650 651 return os.name != 'nt'
651 652
652 653
653 654 @check("serve", "platform and python can manage 'hg serve -d'")
654 655 def has_serve():
655 656 return True
656 657
657 658
658 659 @check("test-repo", "running tests from repository")
659 660 def has_test_repo():
660 661 t = os.environ["TESTDIR"]
661 662 return os.path.isdir(os.path.join(t, "..", ".hg"))
662 663
663 664
664 665 @check("tic", "terminfo compiler and curses module")
665 666 def has_tic():
666 667 try:
667 668 import curses
668 669
669 670 curses.COLOR_BLUE
670 671 return matchoutput('test -x "`which tic`"', br'')
671 672 except ImportError:
672 673 return False
673 674
674 675
675 676 @check("msys", "Windows with MSYS")
676 677 def has_msys():
677 678 return os.getenv('MSYSTEM')
678 679
679 680
680 681 @check("aix", "AIX")
681 682 def has_aix():
682 683 return sys.platform.startswith("aix")
683 684
684 685
685 686 @check("osx", "OS X")
686 687 def has_osx():
687 688 return sys.platform == 'darwin'
688 689
689 690
690 691 @check("osxpackaging", "OS X packaging tools")
691 692 def has_osxpackaging():
692 693 try:
693 694 return (
694 695 matchoutput('pkgbuild', br'Usage: pkgbuild ', ignorestatus=1)
695 696 and matchoutput(
696 697 'productbuild', br'Usage: productbuild ', ignorestatus=1
697 698 )
698 699 and matchoutput('lsbom', br'Usage: lsbom', ignorestatus=1)
699 700 and matchoutput('xar --help', br'Usage: xar', ignorestatus=1)
700 701 )
701 702 except ImportError:
702 703 return False
703 704
704 705
705 706 @check('linuxormacos', 'Linux or MacOS')
706 707 def has_linuxormacos():
707 708 # This isn't a perfect test for MacOS. But it is sufficient for our needs.
708 709 return sys.platform.startswith(('linux', 'darwin'))
709 710
710 711
711 712 @check("docker", "docker support")
712 713 def has_docker():
713 714 pat = br'A self-sufficient runtime for'
714 715 if matchoutput('docker --help', pat):
715 716 if 'linux' not in sys.platform:
716 717 # TODO: in theory we should be able to test docker-based
717 718 # package creation on non-linux using boot2docker, but in
718 719 # practice that requires extra coordination to make sure
719 720 # $TESTTEMP is going to be visible at the same path to the
720 721 # boot2docker VM. If we figure out how to verify that, we
721 722 # can use the following instead of just saying False:
722 723 # return 'DOCKER_HOST' in os.environ
723 724 return False
724 725
725 726 return True
726 727 return False
727 728
728 729
729 730 @check("debhelper", "debian packaging tools")
730 731 def has_debhelper():
731 732 # Some versions of dpkg say `dpkg', some say 'dpkg' (` vs ' on the first
732 733 # quote), so just accept anything in that spot.
733 734 dpkg = matchoutput(
734 735 'dpkg --version', br"Debian .dpkg' package management program"
735 736 )
736 737 dh = matchoutput(
737 738 'dh --help', br'dh is a part of debhelper.', ignorestatus=True
738 739 )
739 740 dh_py2 = matchoutput(
740 741 'dh_python2 --help', br'other supported Python versions'
741 742 )
742 743 # debuild comes from the 'devscripts' package, though you might want
743 744 # the 'build-debs' package instead, which has a dependency on devscripts.
744 745 debuild = matchoutput(
745 746 'debuild --help', br'to run debian/rules with given parameter'
746 747 )
747 748 return dpkg and dh and dh_py2 and debuild
748 749
749 750
750 751 @check(
751 752 "debdeps", "debian build dependencies (run dpkg-checkbuilddeps in contrib/)"
752 753 )
753 754 def has_debdeps():
754 755 # just check exit status (ignoring output)
755 756 path = '%s/../contrib/packaging/debian/control' % os.environ['TESTDIR']
756 757 return matchoutput('dpkg-checkbuilddeps %s' % path, br'')
757 758
758 759
759 760 @check("demandimport", "demandimport enabled")
760 761 def has_demandimport():
761 762 # chg disables demandimport intentionally for performance wins.
762 763 return (not has_chg()) and os.environ.get('HGDEMANDIMPORT') != 'disable'
763 764
764 765
765 766 # Add "py27", "py35", ... as possible feature checks. Note that there's no
766 767 # punctuation here.
767 768 @checkvers("py", "Python >= %s", (2.7, 3.5, 3.6, 3.7, 3.8, 3.9))
768 769 def has_python_range(v):
769 770 major, minor = v.split('.')[0:2]
770 771 py_major, py_minor = sys.version_info.major, sys.version_info.minor
771 772
772 773 return (py_major, py_minor) >= (int(major), int(minor))
773 774
774 775
775 776 @check("py3", "running with Python 3.x")
776 777 def has_py3():
777 778 return 3 == sys.version_info[0]
778 779
779 780
780 781 @check("py3exe", "a Python 3.x interpreter is available")
781 782 def has_python3exe():
782 783 return matchoutput('python3 -V', br'^Python 3.(5|6|7|8|9)')
783 784
784 785
785 786 @check("pure", "running with pure Python code")
786 787 def has_pure():
787 788 return any(
788 789 [
789 790 os.environ.get("HGMODULEPOLICY") == "py",
790 791 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure",
791 792 ]
792 793 )
793 794
794 795
795 796 @check("slow", "allow slow tests (use --allow-slow-tests)")
796 797 def has_slow():
797 798 return os.environ.get('HGTEST_SLOW') == 'slow'
798 799
799 800
800 801 @check("hypothesis", "Hypothesis automated test generation")
801 802 def has_hypothesis():
802 803 try:
803 804 import hypothesis
804 805
805 806 hypothesis.given
806 807 return True
807 808 except ImportError:
808 809 return False
809 810
810 811
811 812 @check("unziplinks", "unzip(1) understands and extracts symlinks")
812 813 def unzip_understands_symlinks():
813 814 return matchoutput('unzip --help', br'Info-ZIP')
814 815
815 816
816 817 @check("zstd", "zstd Python module available")
817 818 def has_zstd():
818 819 try:
819 820 import mercurial.zstd
820 821
821 822 mercurial.zstd.__version__
822 823 return True
823 824 except ImportError:
824 825 return False
825 826
826 827
827 828 @check("devfull", "/dev/full special file")
828 829 def has_dev_full():
829 830 return os.path.exists('/dev/full')
830 831
831 832
832 833 @check("ensurepip", "ensurepip module")
833 834 def has_ensurepip():
834 835 try:
835 836 import ensurepip
836 837
837 838 ensurepip.bootstrap
838 839 return True
839 840 except ImportError:
840 841 return False
841 842
842 843
843 844 @check("virtualenv", "Python virtualenv support")
844 845 def has_virtualenv():
845 846 try:
846 847 import virtualenv
847 848
848 849 virtualenv.ACTIVATE_SH
849 850 return True
850 851 except ImportError:
851 852 return False
852 853
853 854
854 855 @check("fsmonitor", "running tests with fsmonitor")
855 856 def has_fsmonitor():
856 857 return 'HGFSMONITOR_TESTS' in os.environ
857 858
858 859
859 860 @check("fuzzywuzzy", "Fuzzy string matching library")
860 861 def has_fuzzywuzzy():
861 862 try:
862 863 import fuzzywuzzy
863 864
864 865 fuzzywuzzy.__version__
865 866 return True
866 867 except ImportError:
867 868 return False
868 869
869 870
870 871 @check("clang-libfuzzer", "clang new enough to include libfuzzer")
871 872 def has_clang_libfuzzer():
872 873 mat = matchoutput('clang --version', br'clang version (\d)')
873 874 if mat:
874 875 # libfuzzer is new in clang 6
875 876 return int(mat.group(1)) > 5
876 877 return False
877 878
878 879
879 880 @check("clang-6.0", "clang 6.0 with version suffix (libfuzzer included)")
880 881 def has_clang60():
881 882 return matchoutput('clang-6.0 --version', br'clang version 6\.')
882 883
883 884
884 885 @check("xdiff", "xdiff algorithm")
885 886 def has_xdiff():
886 887 try:
887 888 from mercurial import policy
888 889
889 890 bdiff = policy.importmod('bdiff')
890 891 return bdiff.xdiffblocks(b'', b'') == [(0, 0, 0, 0)]
891 892 except (ImportError, AttributeError):
892 893 return False
893 894
894 895
895 896 @check('extraextensions', 'whether tests are running with extra extensions')
896 897 def has_extraextensions():
897 898 return 'HGTESTEXTRAEXTENSIONS' in os.environ
898 899
899 900
900 901 def getrepofeatures():
901 902 """Obtain set of repository features in use.
902 903
903 904 HGREPOFEATURES can be used to define or remove features. It contains
904 905 a space-delimited list of feature strings. Strings beginning with ``-``
905 906 mean to remove.
906 907 """
907 908 # Default list provided by core.
908 909 features = {
909 910 'bundlerepo',
910 911 'revlogstore',
911 912 'fncache',
912 913 }
913 914
914 915 # Features that imply other features.
915 916 implies = {
916 917 'simplestore': ['-revlogstore', '-bundlerepo', '-fncache'],
917 918 }
918 919
919 920 for override in os.environ.get('HGREPOFEATURES', '').split(' '):
920 921 if not override:
921 922 continue
922 923
923 924 if override.startswith('-'):
924 925 if override[1:] in features:
925 926 features.remove(override[1:])
926 927 else:
927 928 features.add(override)
928 929
929 930 for imply in implies.get(override, []):
930 931 if imply.startswith('-'):
931 932 if imply[1:] in features:
932 933 features.remove(imply[1:])
933 934 else:
934 935 features.add(imply)
935 936
936 937 return features
937 938
938 939
939 940 @check('reporevlogstore', 'repository using the default revlog store')
940 941 def has_reporevlogstore():
941 942 return 'revlogstore' in getrepofeatures()
942 943
943 944
944 945 @check('reposimplestore', 'repository using simple storage extension')
945 946 def has_reposimplestore():
946 947 return 'simplestore' in getrepofeatures()
947 948
948 949
949 950 @check('repobundlerepo', 'whether we can open bundle files as repos')
950 951 def has_repobundlerepo():
951 952 return 'bundlerepo' in getrepofeatures()
952 953
953 954
954 955 @check('repofncache', 'repository has an fncache')
955 956 def has_repofncache():
956 957 return 'fncache' in getrepofeatures()
957 958
958 959
959 960 @check('sqlite', 'sqlite3 module is available')
960 961 def has_sqlite():
961 962 try:
962 963 import sqlite3
963 964
964 965 version = sqlite3.sqlite_version_info
965 966 except ImportError:
966 967 return False
967 968
968 969 if version < (3, 8, 3):
969 970 # WITH clause not supported
970 971 return False
971 972
972 973 return matchoutput('sqlite3 -version', br'^3\.\d+')
973 974
974 975
975 976 @check('vcr', 'vcr http mocking library')
976 977 def has_vcr():
977 978 try:
978 979 import vcr
979 980
980 981 vcr.VCR
981 982 return True
982 983 except (ImportError, AttributeError):
983 984 pass
984 985 return False
985 986
986 987
987 988 @check('emacs', 'GNU Emacs')
988 989 def has_emacs():
989 990 # Our emacs lisp uses `with-eval-after-load` which is new in emacs
990 991 # 24.4, so we allow emacs 24.4, 24.5, and 25+ (24.5 was the last
991 992 # 24 release)
992 993 return matchoutput('emacs --version', b'GNU Emacs 2(4.4|4.5|5|6|7|8|9)')
993 994
994 995
995 996 @check('black', 'the black formatter for python')
996 997 def has_black():
997 998 blackcmd = 'black --version'
998 999 version_regex = b'black, version ([0-9a-b.]+)'
999 1000 version = matchoutput(blackcmd, version_regex)
1000 1001 sv = distutils.version.StrictVersion
1001 1002 return version and sv(_strpath(version.group(1))) >= sv('19.10b0')
General Comments 0
You need to be logged in to leave comments. Login now