##// END OF EJS Templates
hghave: make bzr checks stricter...
timeless -
r29866:a5ce381a default
parent child Browse files
Show More
@@ -1,579 +1,583 b''
1 1 from __future__ import absolute_import
2 2
3 3 import errno
4 4 import os
5 5 import re
6 6 import socket
7 7 import stat
8 8 import subprocess
9 9 import sys
10 10 import tempfile
11 11
12 12 tempprefix = 'hg-hghave-'
13 13
14 14 checks = {
15 15 "true": (lambda: True, "yak shaving"),
16 16 "false": (lambda: False, "nail clipper"),
17 17 }
18 18
19 19 def check(name, desc):
20 20 """Registers a check function for a feature."""
21 21 def decorator(func):
22 22 checks[name] = (func, desc)
23 23 return func
24 24 return decorator
25 25
26 26 def checkvers(name, desc, vers):
27 27 """Registers a check function for each of a series of versions.
28 28
29 29 vers can be a list or an iterator"""
30 30 def decorator(func):
31 31 def funcv(v):
32 32 def f():
33 33 return func(v)
34 34 return f
35 35 for v in vers:
36 36 v = str(v)
37 37 f = funcv(v)
38 38 checks['%s%s' % (name, v.replace('.', ''))] = (f, desc % v)
39 39 return func
40 40 return decorator
41 41
42 42 def checkfeatures(features):
43 43 result = {
44 44 'error': [],
45 45 'missing': [],
46 46 'skipped': [],
47 47 }
48 48
49 49 for feature in features:
50 50 negate = feature.startswith('no-')
51 51 if negate:
52 52 feature = feature[3:]
53 53
54 54 if feature not in checks:
55 55 result['missing'].append(feature)
56 56 continue
57 57
58 58 check, desc = checks[feature]
59 59 try:
60 60 available = check()
61 61 except Exception:
62 62 result['error'].append('hghave check failed: %s' % feature)
63 63 continue
64 64
65 65 if not negate and not available:
66 66 result['skipped'].append('missing feature: %s' % desc)
67 67 elif negate and available:
68 68 result['skipped'].append('system supports %s' % desc)
69 69
70 70 return result
71 71
72 72 def require(features):
73 73 """Require that features are available, exiting if not."""
74 74 result = checkfeatures(features)
75 75
76 76 for missing in result['missing']:
77 77 sys.stderr.write('skipped: unknown feature: %s\n' % missing)
78 78 for msg in result['skipped']:
79 79 sys.stderr.write('skipped: %s\n' % msg)
80 80 for msg in result['error']:
81 81 sys.stderr.write('%s\n' % msg)
82 82
83 83 if result['missing']:
84 84 sys.exit(2)
85 85
86 86 if result['skipped'] or result['error']:
87 87 sys.exit(1)
88 88
89 89 def matchoutput(cmd, regexp, ignorestatus=False):
90 90 """Return the match object if cmd executes successfully and its output
91 91 is matched by the supplied regular expression.
92 92 """
93 93 r = re.compile(regexp)
94 94 try:
95 95 p = subprocess.Popen(
96 96 cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
97 97 except OSError as e:
98 98 if e.errno != errno.ENOENT:
99 99 raise
100 100 ret = -1
101 101 ret = p.wait()
102 102 s = p.stdout.read()
103 103 return (ignorestatus or not ret) and r.search(s)
104 104
105 105 @check("baz", "GNU Arch baz client")
106 106 def has_baz():
107 107 return matchoutput('baz --version 2>&1', br'baz Bazaar version')
108 108
109 109 @check("bzr", "Canonical's Bazaar client")
110 110 def has_bzr():
111 111 try:
112 112 import bzrlib
113 import bzrlib.bzrdir
114 import bzrlib.errors
115 import bzrlib.revision
116 import bzrlib.revisionspec.RevisionSpec
113 117 return bzrlib.__doc__ is not None
114 118 except ImportError:
115 119 return False
116 120
117 121 @checkvers("bzr", "Canonical's Bazaar client >= %s", (1.14,))
118 122 def has_bzr_range(v):
119 123 major, minor = v.split('.')[0:2]
120 124 try:
121 125 import bzrlib
122 126 return (bzrlib.__doc__ is not None
123 127 and bzrlib.version_info[:2] >= (int(major), int(minor)))
124 128 except ImportError:
125 129 return False
126 130
127 131 @check("chg", "running with chg")
128 132 def has_chg():
129 133 return 'CHGHG' in os.environ
130 134
131 135 @check("cvs", "cvs client/server")
132 136 def has_cvs():
133 137 re = br'Concurrent Versions System.*?server'
134 138 return matchoutput('cvs --version 2>&1', re) and not has_msys()
135 139
136 140 @check("cvs112", "cvs client/server 1.12.* (not cvsnt)")
137 141 def has_cvs112():
138 142 re = br'Concurrent Versions System \(CVS\) 1.12.*?server'
139 143 return matchoutput('cvs --version 2>&1', re) and not has_msys()
140 144
141 145 @check("cvsnt", "cvsnt client/server")
142 146 def has_cvsnt():
143 147 re = br'Concurrent Versions System \(CVSNT\) (\d+).(\d+).*\(client/server\)'
144 148 return matchoutput('cvsnt --version 2>&1', re)
145 149
146 150 @check("darcs", "darcs client")
147 151 def has_darcs():
148 152 return matchoutput('darcs --version', br'2\.[2-9]', True)
149 153
150 154 @check("mtn", "monotone client (>= 1.0)")
151 155 def has_mtn():
152 156 return matchoutput('mtn --version', br'monotone', True) and not matchoutput(
153 157 'mtn --version', br'monotone 0\.', True)
154 158
155 159 @check("eol-in-paths", "end-of-lines in paths")
156 160 def has_eol_in_paths():
157 161 try:
158 162 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r')
159 163 os.close(fd)
160 164 os.remove(path)
161 165 return True
162 166 except (IOError, OSError):
163 167 return False
164 168
165 169 @check("execbit", "executable bit")
166 170 def has_executablebit():
167 171 try:
168 172 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
169 173 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
170 174 try:
171 175 os.close(fh)
172 176 m = os.stat(fn).st_mode & 0o777
173 177 new_file_has_exec = m & EXECFLAGS
174 178 os.chmod(fn, m ^ EXECFLAGS)
175 179 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0o777) == m)
176 180 finally:
177 181 os.unlink(fn)
178 182 except (IOError, OSError):
179 183 # we don't care, the user probably won't be able to commit anyway
180 184 return False
181 185 return not (new_file_has_exec or exec_flags_cannot_flip)
182 186
183 187 @check("icasefs", "case insensitive file system")
184 188 def has_icasefs():
185 189 # Stolen from mercurial.util
186 190 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
187 191 os.close(fd)
188 192 try:
189 193 s1 = os.stat(path)
190 194 d, b = os.path.split(path)
191 195 p2 = os.path.join(d, b.upper())
192 196 if path == p2:
193 197 p2 = os.path.join(d, b.lower())
194 198 try:
195 199 s2 = os.stat(p2)
196 200 return s2 == s1
197 201 except OSError:
198 202 return False
199 203 finally:
200 204 os.remove(path)
201 205
202 206 @check("fifo", "named pipes")
203 207 def has_fifo():
204 208 if getattr(os, "mkfifo", None) is None:
205 209 return False
206 210 name = tempfile.mktemp(dir='.', prefix=tempprefix)
207 211 try:
208 212 os.mkfifo(name)
209 213 os.unlink(name)
210 214 return True
211 215 except OSError:
212 216 return False
213 217
214 218 @check("killdaemons", 'killdaemons.py support')
215 219 def has_killdaemons():
216 220 return True
217 221
218 222 @check("cacheable", "cacheable filesystem")
219 223 def has_cacheable_fs():
220 224 from mercurial import util
221 225
222 226 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
223 227 os.close(fd)
224 228 try:
225 229 return util.cachestat(path).cacheable()
226 230 finally:
227 231 os.remove(path)
228 232
229 233 @check("lsprof", "python lsprof module")
230 234 def has_lsprof():
231 235 try:
232 236 import _lsprof
233 237 _lsprof.Profiler # silence unused import warning
234 238 return True
235 239 except ImportError:
236 240 return False
237 241
238 242 def gethgversion():
239 243 m = matchoutput('hg --version --quiet 2>&1', br'(\d+)\.(\d+)')
240 244 if not m:
241 245 return (0, 0)
242 246 return (int(m.group(1)), int(m.group(2)))
243 247
244 248 @checkvers("hg", "Mercurial >= %s",
245 249 list([(1.0 * x) / 10 for x in range(9, 40)]))
246 250 def has_hg_range(v):
247 251 major, minor = v.split('.')[0:2]
248 252 return gethgversion() >= (int(major), int(minor))
249 253
250 254 @check("hg08", "Mercurial >= 0.8")
251 255 def has_hg08():
252 256 if checks["hg09"][0]():
253 257 return True
254 258 return matchoutput('hg help annotate 2>&1', '--date')
255 259
256 260 @check("hg07", "Mercurial >= 0.7")
257 261 def has_hg07():
258 262 if checks["hg08"][0]():
259 263 return True
260 264 return matchoutput('hg --version --quiet 2>&1', 'Mercurial Distributed SCM')
261 265
262 266 @check("hg06", "Mercurial >= 0.6")
263 267 def has_hg06():
264 268 if checks["hg07"][0]():
265 269 return True
266 270 return matchoutput('hg --version --quiet 2>&1', 'Mercurial version')
267 271
268 272 @check("gettext", "GNU Gettext (msgfmt)")
269 273 def has_gettext():
270 274 return matchoutput('msgfmt --version', br'GNU gettext-tools')
271 275
272 276 @check("git", "git command line client")
273 277 def has_git():
274 278 return matchoutput('git --version 2>&1', br'^git version')
275 279
276 280 @check("docutils", "Docutils text processing library")
277 281 def has_docutils():
278 282 try:
279 283 import docutils.core
280 284 docutils.core.publish_cmdline # silence unused import
281 285 return True
282 286 except ImportError:
283 287 return False
284 288
285 289 def getsvnversion():
286 290 m = matchoutput('svn --version --quiet 2>&1', br'^(\d+)\.(\d+)')
287 291 if not m:
288 292 return (0, 0)
289 293 return (int(m.group(1)), int(m.group(2)))
290 294
291 295 @checkvers("svn", "subversion client and admin tools >= %s", (1.3, 1.5))
292 296 def has_svn_range(v):
293 297 major, minor = v.split('.')[0:2]
294 298 return getsvnversion() >= (int(major), int(minor))
295 299
296 300 @check("svn", "subversion client and admin tools")
297 301 def has_svn():
298 302 return matchoutput('svn --version 2>&1', br'^svn, version') and \
299 303 matchoutput('svnadmin --version 2>&1', br'^svnadmin, version')
300 304
301 305 @check("svn-bindings", "subversion python bindings")
302 306 def has_svn_bindings():
303 307 try:
304 308 import svn.core
305 309 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
306 310 if version < (1, 4):
307 311 return False
308 312 return True
309 313 except ImportError:
310 314 return False
311 315
312 316 @check("p4", "Perforce server and client")
313 317 def has_p4():
314 318 return (matchoutput('p4 -V', br'Rev\. P4/') and
315 319 matchoutput('p4d -V', br'Rev\. P4D/'))
316 320
317 321 @check("symlink", "symbolic links")
318 322 def has_symlink():
319 323 if getattr(os, "symlink", None) is None:
320 324 return False
321 325 name = tempfile.mktemp(dir='.', prefix=tempprefix)
322 326 try:
323 327 os.symlink(".", name)
324 328 os.unlink(name)
325 329 return True
326 330 except (OSError, AttributeError):
327 331 return False
328 332
329 333 @check("hardlink", "hardlinks")
330 334 def has_hardlink():
331 335 from mercurial import util
332 336 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
333 337 os.close(fh)
334 338 name = tempfile.mktemp(dir='.', prefix=tempprefix)
335 339 try:
336 340 util.oslink(fn, name)
337 341 os.unlink(name)
338 342 return True
339 343 except OSError:
340 344 return False
341 345 finally:
342 346 os.unlink(fn)
343 347
344 348 @check("tla", "GNU Arch tla client")
345 349 def has_tla():
346 350 return matchoutput('tla --version 2>&1', br'The GNU Arch Revision')
347 351
348 352 @check("gpg", "gpg client")
349 353 def has_gpg():
350 354 return matchoutput('gpg --version 2>&1', br'GnuPG')
351 355
352 356 @check("gpg2", "gpg client v2")
353 357 def has_gpg2():
354 358 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.')
355 359
356 360 @check("unix-permissions", "unix-style permissions")
357 361 def has_unix_permissions():
358 362 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
359 363 try:
360 364 fname = os.path.join(d, 'foo')
361 365 for umask in (0o77, 0o07, 0o22):
362 366 os.umask(umask)
363 367 f = open(fname, 'w')
364 368 f.close()
365 369 mode = os.stat(fname).st_mode
366 370 os.unlink(fname)
367 371 if mode & 0o777 != ~umask & 0o666:
368 372 return False
369 373 return True
370 374 finally:
371 375 os.rmdir(d)
372 376
373 377 @check("unix-socket", "AF_UNIX socket family")
374 378 def has_unix_socket():
375 379 return getattr(socket, 'AF_UNIX', None) is not None
376 380
377 381 @check("root", "root permissions")
378 382 def has_root():
379 383 return getattr(os, 'geteuid', None) and os.geteuid() == 0
380 384
381 385 @check("pyflakes", "Pyflakes python linter")
382 386 def has_pyflakes():
383 387 return matchoutput("sh -c \"echo 'import re' 2>&1 | pyflakes\"",
384 388 br"<stdin>:1: 're' imported but unused",
385 389 True)
386 390
387 391 @check("pygments", "Pygments source highlighting library")
388 392 def has_pygments():
389 393 try:
390 394 import pygments
391 395 pygments.highlight # silence unused import warning
392 396 return True
393 397 except ImportError:
394 398 return False
395 399
396 400 @check("outer-repo", "outer repo")
397 401 def has_outer_repo():
398 402 # failing for other reasons than 'no repo' imply that there is a repo
399 403 return not matchoutput('hg root 2>&1',
400 404 br'abort: no repository found', True)
401 405
402 406 @check("ssl", "ssl module available")
403 407 def has_ssl():
404 408 try:
405 409 import ssl
406 410 ssl.CERT_NONE
407 411 return True
408 412 except ImportError:
409 413 return False
410 414
411 415 @check("sslcontext", "python >= 2.7.9 ssl")
412 416 def has_sslcontext():
413 417 try:
414 418 import ssl
415 419 ssl.SSLContext
416 420 return True
417 421 except (ImportError, AttributeError):
418 422 return False
419 423
420 424 @check("defaultcacerts", "can verify SSL certs by system's CA certs store")
421 425 def has_defaultcacerts():
422 426 from mercurial import sslutil, ui as uimod
423 427 ui = uimod.ui()
424 428 return sslutil._defaultcacerts(ui) or sslutil._canloaddefaultcerts
425 429
426 430 @check("defaultcacertsloaded", "detected presence of loaded system CA certs")
427 431 def has_defaultcacertsloaded():
428 432 import ssl
429 433 from mercurial import sslutil, ui as uimod
430 434
431 435 if not has_defaultcacerts():
432 436 return False
433 437 if not has_sslcontext():
434 438 return False
435 439
436 440 ui = uimod.ui()
437 441 cafile = sslutil._defaultcacerts(ui)
438 442 ctx = ssl.create_default_context()
439 443 if cafile:
440 444 ctx.load_verify_locations(cafile=cafile)
441 445 else:
442 446 ctx.load_default_certs()
443 447
444 448 return len(ctx.get_ca_certs()) > 0
445 449
446 450 @check("tls1.2", "TLS 1.2 protocol support")
447 451 def has_tls1_2():
448 452 from mercurial import sslutil
449 453 return 'tls1.2' in sslutil.supportedprotocols
450 454
451 455 @check("windows", "Windows")
452 456 def has_windows():
453 457 return os.name == 'nt'
454 458
455 459 @check("system-sh", "system() uses sh")
456 460 def has_system_sh():
457 461 return os.name != 'nt'
458 462
459 463 @check("serve", "platform and python can manage 'hg serve -d'")
460 464 def has_serve():
461 465 return os.name != 'nt' # gross approximation
462 466
463 467 @check("test-repo", "running tests from repository")
464 468 def has_test_repo():
465 469 t = os.environ["TESTDIR"]
466 470 return os.path.isdir(os.path.join(t, "..", ".hg"))
467 471
468 472 @check("tic", "terminfo compiler and curses module")
469 473 def has_tic():
470 474 try:
471 475 import curses
472 476 curses.COLOR_BLUE
473 477 return matchoutput('test -x "`which tic`"', br'')
474 478 except ImportError:
475 479 return False
476 480
477 481 @check("msys", "Windows with MSYS")
478 482 def has_msys():
479 483 return os.getenv('MSYSTEM')
480 484
481 485 @check("aix", "AIX")
482 486 def has_aix():
483 487 return sys.platform.startswith("aix")
484 488
485 489 @check("osx", "OS X")
486 490 def has_osx():
487 491 return sys.platform == 'darwin'
488 492
489 493 @check("osxpackaging", "OS X packaging tools")
490 494 def has_osxpackaging():
491 495 try:
492 496 return (matchoutput('pkgbuild', br'Usage: pkgbuild ', ignorestatus=1)
493 497 and matchoutput(
494 498 'productbuild', br'Usage: productbuild ',
495 499 ignorestatus=1)
496 500 and matchoutput('lsbom', br'Usage: lsbom', ignorestatus=1)
497 501 and matchoutput(
498 502 'xar --help', br'Usage: xar', ignorestatus=1))
499 503 except ImportError:
500 504 return False
501 505
502 506 @check("docker", "docker support")
503 507 def has_docker():
504 508 pat = br'A self-sufficient runtime for'
505 509 if matchoutput('docker --help', pat):
506 510 if 'linux' not in sys.platform:
507 511 # TODO: in theory we should be able to test docker-based
508 512 # package creation on non-linux using boot2docker, but in
509 513 # practice that requires extra coordination to make sure
510 514 # $TESTTEMP is going to be visible at the same path to the
511 515 # boot2docker VM. If we figure out how to verify that, we
512 516 # can use the following instead of just saying False:
513 517 # return 'DOCKER_HOST' in os.environ
514 518 return False
515 519
516 520 return True
517 521 return False
518 522
519 523 @check("debhelper", "debian packaging tools")
520 524 def has_debhelper():
521 525 dpkg = matchoutput('dpkg --version',
522 526 br"Debian `dpkg' package management program")
523 527 dh = matchoutput('dh --help',
524 528 br'dh is a part of debhelper.', ignorestatus=True)
525 529 dh_py2 = matchoutput('dh_python2 --help',
526 530 br'other supported Python versions')
527 531 return dpkg and dh and dh_py2
528 532
529 533 @check("absimport", "absolute_import in __future__")
530 534 def has_absimport():
531 535 import __future__
532 536 from mercurial import util
533 537 return util.safehasattr(__future__, "absolute_import")
534 538
535 539 @check("py27+", "running with Python 2.7+")
536 540 def has_python27ornewer():
537 541 return sys.version_info[0:2] >= (2, 7)
538 542
539 543 @check("py3k", "running with Python 3.x")
540 544 def has_py3k():
541 545 return 3 == sys.version_info[0]
542 546
543 547 @check("py3exe", "a Python 3.x interpreter is available")
544 548 def has_python3exe():
545 549 return 'PYTHON3' in os.environ
546 550
547 551 @check("py3pygments", "Pygments available on Python 3.x")
548 552 def has_py3pygments():
549 553 if has_py3k():
550 554 return has_pygments()
551 555 elif has_python3exe():
552 556 # just check exit status (ignoring output)
553 557 py3 = os.environ['PYTHON3']
554 558 return matchoutput('%s -c "import pygments"' % py3, br'')
555 559 return False
556 560
557 561 @check("pure", "running with pure Python code")
558 562 def has_pure():
559 563 return any([
560 564 os.environ.get("HGMODULEPOLICY") == "py",
561 565 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure",
562 566 ])
563 567
564 568 @check("slow", "allow slow tests")
565 569 def has_slow():
566 570 return os.environ.get('HGTEST_SLOW') == 'slow'
567 571
568 572 @check("hypothesis", "Hypothesis automated test generation")
569 573 def has_hypothesis():
570 574 try:
571 575 import hypothesis
572 576 hypothesis.given
573 577 return True
574 578 except ImportError:
575 579 return False
576 580
577 581 @check("unziplinks", "unzip(1) understands and extracts symlinks")
578 582 def unzip_understands_symlinks():
579 583 return matchoutput('unzip --help', br'Info-ZIP')
General Comments 0
You need to be logged in to leave comments. Login now