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