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