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