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