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