##// END OF EJS Templates
hghave: replace has_svn13/has_svn15 with checkvers...
timeless -
r28759:2348ca49 default
parent child Browse files
Show More
@@ -1,481 +1,478
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', r'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 113 return bzrlib.__doc__ is not None
114 114 except ImportError:
115 115 return False
116 116
117 117 @check("bzr114", "Canonical's Bazaar client >= 1.14")
118 118 def has_bzr114():
119 119 try:
120 120 import bzrlib
121 121 return (bzrlib.__doc__ is not None
122 122 and bzrlib.version_info[:2] >= (1, 14))
123 123 except ImportError:
124 124 return False
125 125
126 126 @check("cvs", "cvs client/server")
127 127 def has_cvs():
128 128 re = r'Concurrent Versions System.*?server'
129 129 return matchoutput('cvs --version 2>&1', re) and not has_msys()
130 130
131 131 @check("cvs112", "cvs client/server 1.12.* (not cvsnt)")
132 132 def has_cvs112():
133 133 re = r'Concurrent Versions System \(CVS\) 1.12.*?server'
134 134 return matchoutput('cvs --version 2>&1', re) and not has_msys()
135 135
136 136 @check("darcs", "darcs client")
137 137 def has_darcs():
138 138 return matchoutput('darcs --version', r'2\.[2-9]', True)
139 139
140 140 @check("mtn", "monotone client (>= 1.0)")
141 141 def has_mtn():
142 142 return matchoutput('mtn --version', r'monotone', True) and not matchoutput(
143 143 'mtn --version', r'monotone 0\.', True)
144 144
145 145 @check("eol-in-paths", "end-of-lines in paths")
146 146 def has_eol_in_paths():
147 147 try:
148 148 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r')
149 149 os.close(fd)
150 150 os.remove(path)
151 151 return True
152 152 except (IOError, OSError):
153 153 return False
154 154
155 155 @check("execbit", "executable bit")
156 156 def has_executablebit():
157 157 try:
158 158 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
159 159 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
160 160 try:
161 161 os.close(fh)
162 162 m = os.stat(fn).st_mode & 0o777
163 163 new_file_has_exec = m & EXECFLAGS
164 164 os.chmod(fn, m ^ EXECFLAGS)
165 165 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0o777) == m)
166 166 finally:
167 167 os.unlink(fn)
168 168 except (IOError, OSError):
169 169 # we don't care, the user probably won't be able to commit anyway
170 170 return False
171 171 return not (new_file_has_exec or exec_flags_cannot_flip)
172 172
173 173 @check("icasefs", "case insensitive file system")
174 174 def has_icasefs():
175 175 # Stolen from mercurial.util
176 176 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
177 177 os.close(fd)
178 178 try:
179 179 s1 = os.stat(path)
180 180 d, b = os.path.split(path)
181 181 p2 = os.path.join(d, b.upper())
182 182 if path == p2:
183 183 p2 = os.path.join(d, b.lower())
184 184 try:
185 185 s2 = os.stat(p2)
186 186 return s2 == s1
187 187 except OSError:
188 188 return False
189 189 finally:
190 190 os.remove(path)
191 191
192 192 @check("fifo", "named pipes")
193 193 def has_fifo():
194 194 if getattr(os, "mkfifo", None) is None:
195 195 return False
196 196 name = tempfile.mktemp(dir='.', prefix=tempprefix)
197 197 try:
198 198 os.mkfifo(name)
199 199 os.unlink(name)
200 200 return True
201 201 except OSError:
202 202 return False
203 203
204 204 @check("killdaemons", 'killdaemons.py support')
205 205 def has_killdaemons():
206 206 return True
207 207
208 208 @check("cacheable", "cacheable filesystem")
209 209 def has_cacheable_fs():
210 210 from mercurial import util
211 211
212 212 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
213 213 os.close(fd)
214 214 try:
215 215 return util.cachestat(path).cacheable()
216 216 finally:
217 217 os.remove(path)
218 218
219 219 @check("lsprof", "python lsprof module")
220 220 def has_lsprof():
221 221 try:
222 222 import _lsprof
223 223 _lsprof.Profiler # silence unused import warning
224 224 return True
225 225 except ImportError:
226 226 return False
227 227
228 228 @check("gettext", "GNU Gettext (msgfmt)")
229 229 def has_gettext():
230 230 return matchoutput('msgfmt --version', 'GNU gettext-tools')
231 231
232 232 @check("git", "git command line client")
233 233 def has_git():
234 234 return matchoutput('git --version 2>&1', r'^git version')
235 235
236 236 @check("docutils", "Docutils text processing library")
237 237 def has_docutils():
238 238 try:
239 239 from docutils.core import publish_cmdline
240 240 publish_cmdline # silence unused import
241 241 return True
242 242 except ImportError:
243 243 return False
244 244
245 245 def getsvnversion():
246 246 m = matchoutput('svn --version --quiet 2>&1', r'^(\d+)\.(\d+)')
247 247 if not m:
248 248 return (0, 0)
249 249 return (int(m.group(1)), int(m.group(2)))
250 250
251 @check("svn15", "subversion client and admin tools >= 1.5")
252 def has_svn15():
253 return getsvnversion() >= (1, 5)
254
255 @check("svn13", "subversion client and admin tools >= 1.3")
256 def has_svn13():
257 return getsvnversion() >= (1, 3)
251 @checkvers("svn", "subversion client and admin tools >= %s", (1.3, 1.5))
252 def has_svn_range(v):
253 major, minor = v.split('.')[0:2]
254 return getsvnversion() >= (int(major), int(minor))
258 255
259 256 @check("svn", "subversion client and admin tools")
260 257 def has_svn():
261 258 return matchoutput('svn --version 2>&1', r'^svn, version') and \
262 259 matchoutput('svnadmin --version 2>&1', r'^svnadmin, version')
263 260
264 261 @check("svn-bindings", "subversion python bindings")
265 262 def has_svn_bindings():
266 263 try:
267 264 import svn.core
268 265 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
269 266 if version < (1, 4):
270 267 return False
271 268 return True
272 269 except ImportError:
273 270 return False
274 271
275 272 @check("p4", "Perforce server and client")
276 273 def has_p4():
277 274 return (matchoutput('p4 -V', r'Rev\. P4/') and
278 275 matchoutput('p4d -V', r'Rev\. P4D/'))
279 276
280 277 @check("symlink", "symbolic links")
281 278 def has_symlink():
282 279 if getattr(os, "symlink", None) is None:
283 280 return False
284 281 name = tempfile.mktemp(dir='.', prefix=tempprefix)
285 282 try:
286 283 os.symlink(".", name)
287 284 os.unlink(name)
288 285 return True
289 286 except (OSError, AttributeError):
290 287 return False
291 288
292 289 @check("hardlink", "hardlinks")
293 290 def has_hardlink():
294 291 from mercurial import util
295 292 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
296 293 os.close(fh)
297 294 name = tempfile.mktemp(dir='.', prefix=tempprefix)
298 295 try:
299 296 util.oslink(fn, name)
300 297 os.unlink(name)
301 298 return True
302 299 except OSError:
303 300 return False
304 301 finally:
305 302 os.unlink(fn)
306 303
307 304 @check("tla", "GNU Arch tla client")
308 305 def has_tla():
309 306 return matchoutput('tla --version 2>&1', r'The GNU Arch Revision')
310 307
311 308 @check("gpg", "gpg client")
312 309 def has_gpg():
313 310 return matchoutput('gpg --version 2>&1', r'GnuPG')
314 311
315 312 @check("unix-permissions", "unix-style permissions")
316 313 def has_unix_permissions():
317 314 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
318 315 try:
319 316 fname = os.path.join(d, 'foo')
320 317 for umask in (0o77, 0o07, 0o22):
321 318 os.umask(umask)
322 319 f = open(fname, 'w')
323 320 f.close()
324 321 mode = os.stat(fname).st_mode
325 322 os.unlink(fname)
326 323 if mode & 0o777 != ~umask & 0o666:
327 324 return False
328 325 return True
329 326 finally:
330 327 os.rmdir(d)
331 328
332 329 @check("unix-socket", "AF_UNIX socket family")
333 330 def has_unix_socket():
334 331 return getattr(socket, 'AF_UNIX', None) is not None
335 332
336 333 @check("root", "root permissions")
337 334 def has_root():
338 335 return getattr(os, 'geteuid', None) and os.geteuid() == 0
339 336
340 337 @check("pyflakes", "Pyflakes python linter")
341 338 def has_pyflakes():
342 339 return matchoutput("sh -c \"echo 'import re' 2>&1 | pyflakes\"",
343 340 r"<stdin>:1: 're' imported but unused",
344 341 True)
345 342
346 343 @check("pygments", "Pygments source highlighting library")
347 344 def has_pygments():
348 345 try:
349 346 import pygments
350 347 pygments.highlight # silence unused import warning
351 348 return True
352 349 except ImportError:
353 350 return False
354 351
355 352 @check("outer-repo", "outer repo")
356 353 def has_outer_repo():
357 354 # failing for other reasons than 'no repo' imply that there is a repo
358 355 return not matchoutput('hg root 2>&1',
359 356 r'abort: no repository found', True)
360 357
361 358 @check("ssl", "ssl module available")
362 359 def has_ssl():
363 360 try:
364 361 import ssl
365 362 ssl.CERT_NONE
366 363 return True
367 364 except ImportError:
368 365 return False
369 366
370 367 @check("sslcontext", "python >= 2.7.9 ssl")
371 368 def has_sslcontext():
372 369 try:
373 370 import ssl
374 371 ssl.SSLContext
375 372 return True
376 373 except (ImportError, AttributeError):
377 374 return False
378 375
379 376 @check("defaultcacerts", "can verify SSL certs by system's CA certs store")
380 377 def has_defaultcacerts():
381 378 from mercurial import sslutil
382 379 return sslutil._defaultcacerts() != '!'
383 380
384 381 @check("windows", "Windows")
385 382 def has_windows():
386 383 return os.name == 'nt'
387 384
388 385 @check("system-sh", "system() uses sh")
389 386 def has_system_sh():
390 387 return os.name != 'nt'
391 388
392 389 @check("serve", "platform and python can manage 'hg serve -d'")
393 390 def has_serve():
394 391 return os.name != 'nt' # gross approximation
395 392
396 393 @check("test-repo", "running tests from repository")
397 394 def has_test_repo():
398 395 t = os.environ["TESTDIR"]
399 396 return os.path.isdir(os.path.join(t, "..", ".hg"))
400 397
401 398 @check("tic", "terminfo compiler and curses module")
402 399 def has_tic():
403 400 try:
404 401 import curses
405 402 curses.COLOR_BLUE
406 403 return matchoutput('test -x "`which tic`"', '')
407 404 except ImportError:
408 405 return False
409 406
410 407 @check("msys", "Windows with MSYS")
411 408 def has_msys():
412 409 return os.getenv('MSYSTEM')
413 410
414 411 @check("aix", "AIX")
415 412 def has_aix():
416 413 return sys.platform.startswith("aix")
417 414
418 415 @check("osx", "OS X")
419 416 def has_osx():
420 417 return sys.platform == 'darwin'
421 418
422 419 @check("docker", "docker support")
423 420 def has_docker():
424 421 pat = r'A self-sufficient runtime for linux containers\.'
425 422 if matchoutput('docker --help', pat):
426 423 if 'linux' not in sys.platform:
427 424 # TODO: in theory we should be able to test docker-based
428 425 # package creation on non-linux using boot2docker, but in
429 426 # practice that requires extra coordination to make sure
430 427 # $TESTTEMP is going to be visible at the same path to the
431 428 # boot2docker VM. If we figure out how to verify that, we
432 429 # can use the following instead of just saying False:
433 430 # return 'DOCKER_HOST' in os.environ
434 431 return False
435 432
436 433 return True
437 434 return False
438 435
439 436 @check("debhelper", "debian packaging tools")
440 437 def has_debhelper():
441 438 dpkg = matchoutput('dpkg --version',
442 439 "Debian `dpkg' package management program")
443 440 dh = matchoutput('dh --help',
444 441 'dh is a part of debhelper.', ignorestatus=True)
445 442 dh_py2 = matchoutput('dh_python2 --help',
446 443 'other supported Python versions')
447 444 return dpkg and dh and dh_py2
448 445
449 446 @check("absimport", "absolute_import in __future__")
450 447 def has_absimport():
451 448 import __future__
452 449 from mercurial import util
453 450 return util.safehasattr(__future__, "absolute_import")
454 451
455 452 @check("py3k", "running with Python 3.x")
456 453 def has_py3k():
457 454 return 3 == sys.version_info[0]
458 455
459 456 @check("py3exe", "a Python 3.x interpreter is available")
460 457 def has_python3exe():
461 458 return 'PYTHON3' in os.environ
462 459
463 460 @check("pure", "running with pure Python code")
464 461 def has_pure():
465 462 return any([
466 463 os.environ.get("HGMODULEPOLICY") == "py",
467 464 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure",
468 465 ])
469 466
470 467 @check("slow", "allow slow tests")
471 468 def has_slow():
472 469 return os.environ.get('HGTEST_SLOW') == 'slow'
473 470
474 471 @check("hypothesis", "Hypothesis automated test generation")
475 472 def has_hypothesis():
476 473 try:
477 474 import hypothesis
478 475 hypothesis.given
479 476 return True
480 477 except ImportError:
481 478 return False
General Comments 0
You need to be logged in to leave comments. Login now