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