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