##// END OF EJS Templates
hghave: add py312 and py313...
Manuel Jacob -
r52267:54a75576 default
parent child Browse files
Show More
@@ -1,1161 +1,1165 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 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 358 @checkvers(
359 359 "hg", "Mercurial >= %s", ['%d.%d' % divmod(x, 10) for x in range(9, 99)]
360 360 )
361 361 def has_hg_range(v):
362 362 major, minor = v.split('.')[0:2]
363 363 return gethgversion() >= (int(major), int(minor))
364 364
365 365
366 366 @check("rust", "Using the Rust extensions")
367 367 def has_rust():
368 368 """Check is the mercurial currently running is using some rust code"""
369 369 cmd = 'hg debuginstall --quiet 2>&1'
370 370 match = br'checking module policy \(([^)]+)\)'
371 371 policy = matchoutput(cmd, match)
372 372 if not policy:
373 373 return False
374 374 return b'rust' in policy.group(1)
375 375
376 376
377 377 @check("hg08", "Mercurial >= 0.8")
378 378 def has_hg08():
379 379 if checks["hg09"][0]():
380 380 return True
381 381 return matchoutput('hg help annotate 2>&1', '--date')
382 382
383 383
384 384 @check("hg07", "Mercurial >= 0.7")
385 385 def has_hg07():
386 386 if checks["hg08"][0]():
387 387 return True
388 388 return matchoutput('hg --version --quiet 2>&1', 'Mercurial Distributed SCM')
389 389
390 390
391 391 @check("hg06", "Mercurial >= 0.6")
392 392 def has_hg06():
393 393 if checks["hg07"][0]():
394 394 return True
395 395 return matchoutput('hg --version --quiet 2>&1', 'Mercurial version')
396 396
397 397
398 398 @check("gettext", "GNU Gettext (msgfmt)")
399 399 def has_gettext():
400 400 return matchoutput('msgfmt --version', br'GNU gettext-tools')
401 401
402 402
403 403 @check("git", "git command line client")
404 404 def has_git():
405 405 return matchoutput('git --version 2>&1', br'^git version')
406 406
407 407
408 408 def getgitversion():
409 409 m = matchoutput('git --version 2>&1', br'git version (\d+)\.(\d+)')
410 410 if not m:
411 411 return (0, 0)
412 412 return (int(m.group(1)), int(m.group(2)))
413 413
414 414
415 415 @check("pygit2", "pygit2 Python library")
416 416 def has_pygit2():
417 417 try:
418 418 import pygit2
419 419
420 420 pygit2.Oid # silence unused import
421 421 return True
422 422 except ImportError:
423 423 return False
424 424
425 425
426 426 # https://github.com/git-lfs/lfs-test-server
427 427 @check("lfs-test-server", "git-lfs test server")
428 428 def has_lfsserver():
429 429 exe = 'lfs-test-server'
430 430 if has_windows():
431 431 exe = 'lfs-test-server.exe'
432 432 return any(
433 433 os.access(os.path.join(path, exe), os.X_OK)
434 434 for path in os.environ["PATH"].split(os.pathsep)
435 435 )
436 436
437 437
438 438 @checkvers("git", "git client (with ext::sh support) version >= %s", ('1.9',))
439 439 def has_git_range(v):
440 440 major, minor = v.split('.')[0:2]
441 441 return getgitversion() >= (int(major), int(minor))
442 442
443 443
444 444 @check("docutils", "Docutils text processing library")
445 445 def has_docutils():
446 446 try:
447 447 import docutils.core
448 448
449 449 docutils.core.publish_cmdline # silence unused import
450 450 return True
451 451 except ImportError:
452 452 return False
453 453
454 454
455 455 def getsvnversion():
456 456 m = matchoutput('svn --version --quiet 2>&1', br'^(\d+)\.(\d+)')
457 457 if not m:
458 458 return (0, 0)
459 459 return (int(m.group(1)), int(m.group(2)))
460 460
461 461
462 462 @checkvers("svn", "subversion client and admin tools >= %s", ('1.3', '1.5'))
463 463 def has_svn_range(v):
464 464 major, minor = v.split('.')[0:2]
465 465 return getsvnversion() >= (int(major), int(minor))
466 466
467 467
468 468 @check("svn", "subversion client and admin tools")
469 469 def has_svn():
470 470 return matchoutput('svn --version 2>&1', br'^svn, version') and matchoutput(
471 471 'svnadmin --version 2>&1', br'^svnadmin, version'
472 472 )
473 473
474 474
475 475 @check("svn-bindings", "subversion python bindings")
476 476 def has_svn_bindings():
477 477 try:
478 478 import svn.core
479 479
480 480 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
481 481 if version < (1, 4):
482 482 return False
483 483 return True
484 484 except ImportError:
485 485 return False
486 486
487 487
488 488 @check("p4", "Perforce server and client")
489 489 def has_p4():
490 490 return matchoutput('p4 -V', br'Rev\. P4/') and matchoutput(
491 491 'p4d -V', br'Rev\. P4D/'
492 492 )
493 493
494 494
495 495 @check("symlink", "symbolic links")
496 496 def has_symlink():
497 497 # mercurial.windows.checklink() is a hard 'no' at the moment
498 498 if os.name == 'nt' or getattr(os, "symlink", None) is None:
499 499 return False
500 500 name = tempfile.mktemp(dir='.', prefix=tempprefix)
501 501 try:
502 502 os.symlink(".", name)
503 503 os.unlink(name)
504 504 return True
505 505 except (OSError, AttributeError):
506 506 return False
507 507
508 508
509 509 @check("hardlink", "hardlinks")
510 510 def has_hardlink():
511 511 from mercurial import util
512 512
513 513 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
514 514 os.close(fh)
515 515 name = tempfile.mktemp(dir='.', prefix=tempprefix)
516 516 try:
517 517 util.oslink(_sys2bytes(fn), _sys2bytes(name))
518 518 os.unlink(name)
519 519 return True
520 520 except OSError:
521 521 return False
522 522 finally:
523 523 os.unlink(fn)
524 524
525 525
526 526 @check("hardlink-whitelisted", "hardlinks on whitelisted filesystems")
527 527 def has_hardlink_whitelisted():
528 528 from mercurial import util
529 529
530 530 try:
531 531 fstype = util.getfstype(b'.')
532 532 except OSError:
533 533 return False
534 534 return fstype in util._hardlinkfswhitelist
535 535
536 536
537 537 @check("rmcwd", "can remove current working directory")
538 538 def has_rmcwd():
539 539 ocwd = os.getcwd()
540 540 temp = tempfile.mkdtemp(dir='.', prefix=tempprefix)
541 541 try:
542 542 os.chdir(temp)
543 543 # On Linux, 'rmdir .' isn't allowed, but the other names are okay.
544 544 # On Solaris and Windows, the cwd can't be removed by any names.
545 545 os.rmdir(os.getcwd())
546 546 return True
547 547 except OSError:
548 548 return False
549 549 finally:
550 550 os.chdir(ocwd)
551 551 # clean up temp dir on platforms where cwd can't be removed
552 552 try:
553 553 os.rmdir(temp)
554 554 except OSError:
555 555 pass
556 556
557 557
558 558 @check("tla", "GNU Arch tla client")
559 559 def has_tla():
560 560 return matchoutput('tla --version 2>&1', br'The GNU Arch Revision')
561 561
562 562
563 563 @check("gpg", "gpg client")
564 564 def has_gpg():
565 565 return matchoutput('gpg --version 2>&1', br'GnuPG')
566 566
567 567
568 568 @check("gpg2", "gpg client v2")
569 569 def has_gpg2():
570 570 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.')
571 571
572 572
573 573 @check("gpg21", "gpg client v2.1+")
574 574 def has_gpg21():
575 575 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.(?!0)')
576 576
577 577
578 578 @check("unix-permissions", "unix-style permissions")
579 579 def has_unix_permissions():
580 580 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
581 581 try:
582 582 fname = os.path.join(d, 'foo')
583 583 for umask in (0o77, 0o07, 0o22):
584 584 os.umask(umask)
585 585 f = open(fname, 'w')
586 586 f.close()
587 587 mode = os.stat(fname).st_mode
588 588 os.unlink(fname)
589 589 if mode & 0o777 != ~umask & 0o666:
590 590 return False
591 591 return True
592 592 finally:
593 593 os.rmdir(d)
594 594
595 595
596 596 @check("unix-socket", "AF_UNIX socket family")
597 597 def has_unix_socket():
598 598 return getattr(socket, 'AF_UNIX', None) is not None
599 599
600 600
601 601 @check("root", "root permissions")
602 602 def has_root():
603 603 return getattr(os, 'geteuid', None) and os.geteuid() == 0
604 604
605 605
606 606 @check("pyflakes", "Pyflakes python linter")
607 607 def has_pyflakes():
608 608 try:
609 609 import pyflakes
610 610
611 611 pyflakes.__version__
612 612 except ImportError:
613 613 return False
614 614 else:
615 615 return True
616 616
617 617
618 618 @check("pylint", "Pylint python linter")
619 619 def has_pylint():
620 620 return matchoutput("pylint --help", br"[Uu]sage:[ ]+pylint", True)
621 621
622 622
623 623 @check("clang-format", "clang-format C code formatter (>= 11)")
624 624 def has_clang_format():
625 625 m = matchoutput('clang-format --version', br'clang-format version (\d+)')
626 626 # style changed somewhere between 10.x and 11.x
627 627 if m:
628 628 return int(m.group(1)) >= 11
629 629 # Assist Googler contributors, they have a centrally-maintained version of
630 630 # clang-format that is generally very fresh, but unlike most builds (both
631 631 # official and unofficial), it does *not* include a version number.
632 632 return matchoutput(
633 633 'clang-format --version', br'clang-format .*google3-trunk \([0-9a-f]+\)'
634 634 )
635 635
636 636
637 637 @check("jshint", "JSHint static code analysis tool")
638 638 def has_jshint():
639 639 return matchoutput("jshint --version 2>&1", br"jshint v")
640 640
641 641
642 642 @check("pygments", "Pygments source highlighting library")
643 643 def has_pygments():
644 644 try:
645 645 import pygments
646 646
647 647 pygments.highlight # silence unused import warning
648 648 return True
649 649 except ImportError:
650 650 return False
651 651
652 652
653 653 def getpygmentsversion():
654 654 try:
655 655 import pygments
656 656
657 657 v = pygments.__version__
658 658
659 659 parts = v.split(".")
660 660 return (int(parts[0]), int(parts[1]))
661 661 except ImportError:
662 662 return (0, 0)
663 663
664 664
665 665 @checkvers("pygments", "Pygments version >= %s", ('2.5', '2.11', '2.14'))
666 666 def has_pygments_range(v):
667 667 major, minor = v.split('.')[0:2]
668 668 return getpygmentsversion() >= (int(major), int(minor))
669 669
670 670
671 671 @check("outer-repo", "outer repo")
672 672 def has_outer_repo():
673 673 # failing for other reasons than 'no repo' imply that there is a repo
674 674 return not matchoutput('hg root 2>&1', br'abort: no repository found', True)
675 675
676 676
677 677 @check("ssl", "ssl module available")
678 678 def has_ssl():
679 679 try:
680 680 import ssl
681 681
682 682 ssl.CERT_NONE
683 683 return True
684 684 except ImportError:
685 685 return False
686 686
687 687
688 688 @check("defaultcacertsloaded", "detected presence of loaded system CA certs")
689 689 def has_defaultcacertsloaded():
690 690 import ssl
691 691 from mercurial import sslutil, ui as uimod
692 692
693 693 ui = uimod.ui.load()
694 694 cafile = sslutil._defaultcacerts(ui)
695 695 ctx = ssl.create_default_context()
696 696 if cafile:
697 697 ctx.load_verify_locations(cafile=cafile)
698 698 else:
699 699 ctx.load_default_certs()
700 700
701 701 return len(ctx.get_ca_certs()) > 0
702 702
703 703
704 704 @check("tls1.2", "TLS 1.2 protocol support")
705 705 def has_tls1_2():
706 706 from mercurial import sslutil
707 707
708 708 return b'tls1.2' in sslutil.supportedprotocols
709 709
710 710
711 711 @check("windows", "Windows")
712 712 def has_windows():
713 713 return os.name == 'nt'
714 714
715 715
716 716 @check("system-sh", "system() uses sh")
717 717 def has_system_sh():
718 718 return os.name != 'nt'
719 719
720 720
721 721 @check("serve", "platform and python can manage 'hg serve -d'")
722 722 def has_serve():
723 723 return True
724 724
725 725
726 726 @check("setprocname", "whether osutil.setprocname is available or not")
727 727 def has_setprocname():
728 728 try:
729 729 from mercurial.utils import procutil
730 730
731 731 procutil.setprocname
732 732 return True
733 733 except AttributeError:
734 734 return False
735 735
736 736
737 737 @check("test-repo", "running tests from repository")
738 738 def has_test_repo():
739 739 t = os.environ["TESTDIR"]
740 740 return os.path.isdir(os.path.join(t, "..", ".hg"))
741 741
742 742
743 743 @check("network-io", "whether tests are allowed to access 3rd party services")
744 744 def has_network_io():
745 745 t = os.environ.get("HGTESTS_ALLOW_NETIO")
746 746 return t == "1"
747 747
748 748
749 749 @check("curses", "terminfo compiler and curses module")
750 750 def has_curses():
751 751 try:
752 752 import curses
753 753
754 754 curses.COLOR_BLUE
755 755
756 756 # Windows doesn't have a `tic` executable, but the windows_curses
757 757 # package is sufficient to run the tests without it.
758 758 if os.name == 'nt':
759 759 return True
760 760
761 761 return has_tic()
762 762
763 763 except (ImportError, AttributeError):
764 764 return False
765 765
766 766
767 767 @check("tic", "terminfo compiler")
768 768 def has_tic():
769 769 return matchoutput('test -x "`which tic`"', br'')
770 770
771 771
772 772 @check("xz", "xz compression utility")
773 773 def has_xz():
774 774 # When Windows invokes a subprocess in shell mode, it uses `cmd.exe`, which
775 775 # only knows `where`, not `which`. So invoke MSYS shell explicitly.
776 776 return matchoutput("sh -c 'test -x \"`which xz`\"'", b'')
777 777
778 778
779 779 @check("msys", "Windows with MSYS")
780 780 def has_msys():
781 781 return os.getenv('MSYSTEM')
782 782
783 783
784 784 @check("aix", "AIX")
785 785 def has_aix():
786 786 return sys.platform.startswith("aix")
787 787
788 788
789 789 @check("osx", "OS X")
790 790 def has_osx():
791 791 return sys.platform == 'darwin'
792 792
793 793
794 794 @check("osxpackaging", "OS X packaging tools")
795 795 def has_osxpackaging():
796 796 try:
797 797 return (
798 798 matchoutput('pkgbuild', br'Usage: pkgbuild ', ignorestatus=1)
799 799 and matchoutput(
800 800 'productbuild', br'Usage: productbuild ', ignorestatus=1
801 801 )
802 802 and matchoutput('lsbom', br'Usage: lsbom', ignorestatus=1)
803 803 and matchoutput('xar --help', br'Usage: xar', ignorestatus=1)
804 804 )
805 805 except ImportError:
806 806 return False
807 807
808 808
809 809 @check('linuxormacos', 'Linux or MacOS')
810 810 def has_linuxormacos():
811 811 # This isn't a perfect test for MacOS. But it is sufficient for our needs.
812 812 return sys.platform.startswith(('linux', 'darwin'))
813 813
814 814
815 815 @check("docker", "docker support")
816 816 def has_docker():
817 817 pat = br'A self-sufficient runtime for'
818 818 if matchoutput('docker --help', pat):
819 819 if 'linux' not in sys.platform:
820 820 # TODO: in theory we should be able to test docker-based
821 821 # package creation on non-linux using boot2docker, but in
822 822 # practice that requires extra coordination to make sure
823 823 # $TESTTEMP is going to be visible at the same path to the
824 824 # boot2docker VM. If we figure out how to verify that, we
825 825 # can use the following instead of just saying False:
826 826 # return 'DOCKER_HOST' in os.environ
827 827 return False
828 828
829 829 return True
830 830 return False
831 831
832 832
833 833 @check("debhelper", "debian packaging tools")
834 834 def has_debhelper():
835 835 # Some versions of dpkg say `dpkg', some say 'dpkg' (` vs ' on the first
836 836 # quote), so just accept anything in that spot.
837 837 dpkg = matchoutput(
838 838 'dpkg --version', br"Debian .dpkg' package management program"
839 839 )
840 840 dh = matchoutput(
841 841 'dh --help', br'dh is a part of debhelper.', ignorestatus=True
842 842 )
843 843 dh_py2 = matchoutput(
844 844 'dh_python2 --help', br'other supported Python versions'
845 845 )
846 846 # debuild comes from the 'devscripts' package, though you might want
847 847 # the 'build-debs' package instead, which has a dependency on devscripts.
848 848 debuild = matchoutput(
849 849 'debuild --help', br'to run debian/rules with given parameter'
850 850 )
851 851 return dpkg and dh and dh_py2 and debuild
852 852
853 853
854 854 @check(
855 855 "debdeps", "debian build dependencies (run dpkg-checkbuilddeps in contrib/)"
856 856 )
857 857 def has_debdeps():
858 858 # just check exit status (ignoring output)
859 859 path = '%s/../contrib/packaging/debian/control' % os.environ['TESTDIR']
860 860 return matchoutput('dpkg-checkbuilddeps %s' % path, br'')
861 861
862 862
863 863 @check("demandimport", "demandimport enabled")
864 864 def has_demandimport():
865 865 # chg disables demandimport intentionally for performance wins.
866 866 return (not has_chg()) and os.environ.get('HGDEMANDIMPORT') != 'disable'
867 867
868 868
869 869 # Add "py36", "py37", ... as possible feature checks. Note that there's no
870 870 # punctuation here.
871 @checkvers("py", "Python >= %s", ('3.6', '3.7', '3.8', '3.9', '3.10', '3.11'))
871 @checkvers(
872 "py",
873 "Python >= %s",
874 ('3.6', '3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13'),
875 )
872 876 def has_python_range(v):
873 877 major, minor = v.split('.')[0:2]
874 878 py_major, py_minor = sys.version_info.major, sys.version_info.minor
875 879
876 880 return (py_major, py_minor) >= (int(major), int(minor))
877 881
878 882
879 883 @check("py3", "running with Python 3.x")
880 884 def has_py3():
881 885 return 3 == sys.version_info[0]
882 886
883 887
884 888 @check("py3exe", "a Python 3.x interpreter is available")
885 889 def has_python3exe():
886 890 py = 'python3'
887 891 if os.name == 'nt':
888 892 py = 'py -3'
889 893 return matchoutput('%s -V' % py, br'^Python 3.(6|7|8|9|10|11)')
890 894
891 895
892 896 @check("pure", "running with pure Python code")
893 897 def has_pure():
894 898 return any(
895 899 [
896 900 os.environ.get("HGMODULEPOLICY") == "py",
897 901 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure",
898 902 ]
899 903 )
900 904
901 905
902 906 @check("slow", "allow slow tests (use --allow-slow-tests)")
903 907 def has_slow():
904 908 return os.environ.get('HGTEST_SLOW') == 'slow'
905 909
906 910
907 911 @check("hypothesis", "Hypothesis automated test generation")
908 912 def has_hypothesis():
909 913 try:
910 914 import hypothesis
911 915
912 916 hypothesis.given
913 917 return True
914 918 except ImportError:
915 919 return False
916 920
917 921
918 922 @check("unziplinks", "unzip(1) understands and extracts symlinks")
919 923 def unzip_understands_symlinks():
920 924 return matchoutput('unzip --help', br'Info-ZIP')
921 925
922 926
923 927 @check("zstd", "zstd Python module available")
924 928 def has_zstd():
925 929 try:
926 930 import mercurial.zstd
927 931
928 932 mercurial.zstd.__version__
929 933 return True
930 934 except ImportError:
931 935 return False
932 936
933 937
934 938 @check("devfull", "/dev/full special file")
935 939 def has_dev_full():
936 940 return os.path.exists('/dev/full')
937 941
938 942
939 943 @check("ensurepip", "ensurepip module")
940 944 def has_ensurepip():
941 945 try:
942 946 import ensurepip
943 947
944 948 ensurepip.bootstrap
945 949 return True
946 950 except ImportError:
947 951 return False
948 952
949 953
950 954 @check("virtualenv", "virtualenv support")
951 955 def has_virtualenv():
952 956 try:
953 957 import virtualenv
954 958
955 959 # --no-site-package became the default in 1.7 (Nov 2011), and the
956 960 # argument was removed in 20.0 (Feb 2020). Rather than make the
957 961 # script complicated, just ignore ancient versions.
958 962 return int(virtualenv.__version__.split('.')[0]) > 1
959 963 except (AttributeError, ImportError, IndexError):
960 964 return False
961 965
962 966
963 967 @check("fsmonitor", "running tests with fsmonitor")
964 968 def has_fsmonitor():
965 969 return 'HGFSMONITOR_TESTS' in os.environ
966 970
967 971
968 972 @check("fuzzywuzzy", "Fuzzy string matching library")
969 973 def has_fuzzywuzzy():
970 974 try:
971 975 import fuzzywuzzy
972 976
973 977 fuzzywuzzy.__version__
974 978 return True
975 979 except ImportError:
976 980 return False
977 981
978 982
979 983 @check("clang-libfuzzer", "clang new enough to include libfuzzer")
980 984 def has_clang_libfuzzer():
981 985 mat = matchoutput('clang --version', br'clang version (\d)')
982 986 if mat:
983 987 # libfuzzer is new in clang 6
984 988 return int(mat.group(1)) > 5
985 989 return False
986 990
987 991
988 992 @check("clang-6.0", "clang 6.0 with version suffix (libfuzzer included)")
989 993 def has_clang60():
990 994 return matchoutput('clang-6.0 --version', br'clang version 6\.')
991 995
992 996
993 997 @check("xdiff", "xdiff algorithm")
994 998 def has_xdiff():
995 999 try:
996 1000 from mercurial import policy
997 1001
998 1002 bdiff = policy.importmod('bdiff')
999 1003 return bdiff.xdiffblocks(b'', b'') == [(0, 0, 0, 0)]
1000 1004 except (ImportError, AttributeError):
1001 1005 return False
1002 1006
1003 1007
1004 1008 @check('extraextensions', 'whether tests are running with extra extensions')
1005 1009 def has_extraextensions():
1006 1010 return 'HGTESTEXTRAEXTENSIONS' in os.environ
1007 1011
1008 1012
1009 1013 def getrepofeatures():
1010 1014 """Obtain set of repository features in use.
1011 1015
1012 1016 HGREPOFEATURES can be used to define or remove features. It contains
1013 1017 a space-delimited list of feature strings. Strings beginning with ``-``
1014 1018 mean to remove.
1015 1019 """
1016 1020 # Default list provided by core.
1017 1021 features = {
1018 1022 'bundlerepo',
1019 1023 'revlogstore',
1020 1024 'fncache',
1021 1025 }
1022 1026
1023 1027 # Features that imply other features.
1024 1028 implies = {
1025 1029 'simplestore': ['-revlogstore', '-bundlerepo', '-fncache'],
1026 1030 }
1027 1031
1028 1032 for override in os.environ.get('HGREPOFEATURES', '').split(' '):
1029 1033 if not override:
1030 1034 continue
1031 1035
1032 1036 if override.startswith('-'):
1033 1037 if override[1:] in features:
1034 1038 features.remove(override[1:])
1035 1039 else:
1036 1040 features.add(override)
1037 1041
1038 1042 for imply in implies.get(override, []):
1039 1043 if imply.startswith('-'):
1040 1044 if imply[1:] in features:
1041 1045 features.remove(imply[1:])
1042 1046 else:
1043 1047 features.add(imply)
1044 1048
1045 1049 return features
1046 1050
1047 1051
1048 1052 @check('reporevlogstore', 'repository using the default revlog store')
1049 1053 def has_reporevlogstore():
1050 1054 return 'revlogstore' in getrepofeatures()
1051 1055
1052 1056
1053 1057 @check('reposimplestore', 'repository using simple storage extension')
1054 1058 def has_reposimplestore():
1055 1059 return 'simplestore' in getrepofeatures()
1056 1060
1057 1061
1058 1062 @check('repobundlerepo', 'whether we can open bundle files as repos')
1059 1063 def has_repobundlerepo():
1060 1064 return 'bundlerepo' in getrepofeatures()
1061 1065
1062 1066
1063 1067 @check('repofncache', 'repository has an fncache')
1064 1068 def has_repofncache():
1065 1069 return 'fncache' in getrepofeatures()
1066 1070
1067 1071
1068 1072 @check('dirstate-v2', 'using the v2 format of .hg/dirstate')
1069 1073 def has_dirstate_v2():
1070 1074 # Keep this logic in sync with `newreporequirements()` in `mercurial/localrepo.py`
1071 1075 return matchoutput(
1072 1076 'hg config format.use-dirstate-v2', b'(?i)1|yes|true|on|always'
1073 1077 )
1074 1078
1075 1079
1076 1080 @check('sqlite', 'sqlite3 module and matching cli is available')
1077 1081 def has_sqlite():
1078 1082 try:
1079 1083 import sqlite3
1080 1084
1081 1085 version = sqlite3.sqlite_version_info
1082 1086 except ImportError:
1083 1087 return False
1084 1088
1085 1089 if version < (3, 8, 3):
1086 1090 # WITH clause not supported
1087 1091 return False
1088 1092
1089 1093 return matchoutput('sqlite3 -version', br'^3\.\d+')
1090 1094
1091 1095
1092 1096 @check('vcr', 'vcr http mocking library (pytest-vcr)')
1093 1097 def has_vcr():
1094 1098 try:
1095 1099 import vcr
1096 1100
1097 1101 vcr.VCR
1098 1102 return True
1099 1103 except (ImportError, AttributeError):
1100 1104 pass
1101 1105 return False
1102 1106
1103 1107
1104 1108 @check('emacs', 'GNU Emacs')
1105 1109 def has_emacs():
1106 1110 # Our emacs lisp uses `with-eval-after-load` which is new in emacs
1107 1111 # 24.4, so we allow emacs 24.4, 24.5, and 25+ (24.5 was the last
1108 1112 # 24 release)
1109 1113 return matchoutput('emacs --version', b'GNU Emacs 2(4.4|4.5|5|6|7|8|9)')
1110 1114
1111 1115
1112 1116 @check('black', 'the black formatter for python (>= 20.8b1)')
1113 1117 def has_black():
1114 1118 blackcmd = 'black --version'
1115 1119 version_regex = b'black, (?:version )?([0-9a-b.]+)'
1116 1120 version = matchoutput(blackcmd, version_regex)
1117 1121 sv = distutils.version.StrictVersion
1118 1122 return version and sv(_bytes2sys(version.group(1))) >= sv('20.8b1')
1119 1123
1120 1124
1121 1125 @check('pytype', 'the pytype type checker')
1122 1126 def has_pytype():
1123 1127 pytypecmd = 'pytype --version'
1124 1128 version = matchoutput(pytypecmd, b'[0-9a-b.]+')
1125 1129 sv = distutils.version.StrictVersion
1126 1130 return version and sv(_bytes2sys(version.group(0))) >= sv('2019.10.17')
1127 1131
1128 1132
1129 1133 @check("rustfmt", "rustfmt tool at version nightly-2021-11-02")
1130 1134 def has_rustfmt():
1131 1135 # We use Nightly's rustfmt due to current unstable config options.
1132 1136 return matchoutput(
1133 1137 '`rustup which --toolchain nightly-2021-11-02 rustfmt` --version',
1134 1138 b'rustfmt',
1135 1139 )
1136 1140
1137 1141
1138 1142 @check("cargo", "cargo tool")
1139 1143 def has_cargo():
1140 1144 return matchoutput('`rustup which cargo` --version', b'cargo')
1141 1145
1142 1146
1143 1147 @check("lzma", "python lzma module")
1144 1148 def has_lzma():
1145 1149 try:
1146 1150 import _lzma
1147 1151
1148 1152 _lzma.FORMAT_XZ
1149 1153 return True
1150 1154 except ImportError:
1151 1155 return False
1152 1156
1153 1157
1154 1158 @check("bash", "bash shell")
1155 1159 def has_bash():
1156 1160 return matchoutput("bash -c 'echo hi'", b'^hi$')
1157 1161
1158 1162
1159 1163 @check("bigendian", "big-endian CPU")
1160 1164 def has_bigendian():
1161 1165 return sys.byteorder == 'big'
General Comments 0
You need to be logged in to leave comments. Login now