##// END OF EJS Templates
hghave: make black version regex work with newer versions of black...
Manuel Jacob -
r50162:f639982d stable
parent child Browse files
Show More
@@ -1,1172 +1,1172 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("pygments211", "Pygments version >= 2.11")
666 @check("pygments211", "Pygments version >= 2.11")
667 def pygments211():
667 def pygments211():
668 try:
668 try:
669 import pygments
669 import pygments
670
670
671 v = pygments.__version__
671 v = pygments.__version__
672 except ImportError:
672 except ImportError:
673 return False
673 return False
674
674
675 parts = v.split(".")
675 parts = v.split(".")
676 major = int(parts[0])
676 major = int(parts[0])
677 minor = int(parts[1])
677 minor = int(parts[1])
678
678
679 return (major, minor) >= (2, 11)
679 return (major, minor) >= (2, 11)
680
680
681
681
682 @check("outer-repo", "outer repo")
682 @check("outer-repo", "outer repo")
683 def has_outer_repo():
683 def has_outer_repo():
684 # failing for other reasons than 'no repo' imply that there is a repo
684 # failing for other reasons than 'no repo' imply that there is a repo
685 return not matchoutput('hg root 2>&1', br'abort: no repository found', True)
685 return not matchoutput('hg root 2>&1', br'abort: no repository found', True)
686
686
687
687
688 @check("ssl", "ssl module available")
688 @check("ssl", "ssl module available")
689 def has_ssl():
689 def has_ssl():
690 try:
690 try:
691 import ssl
691 import ssl
692
692
693 ssl.CERT_NONE
693 ssl.CERT_NONE
694 return True
694 return True
695 except ImportError:
695 except ImportError:
696 return False
696 return False
697
697
698
698
699 @check("defaultcacertsloaded", "detected presence of loaded system CA certs")
699 @check("defaultcacertsloaded", "detected presence of loaded system CA certs")
700 def has_defaultcacertsloaded():
700 def has_defaultcacertsloaded():
701 import ssl
701 import ssl
702 from mercurial import sslutil, ui as uimod
702 from mercurial import sslutil, ui as uimod
703
703
704 ui = uimod.ui.load()
704 ui = uimod.ui.load()
705 cafile = sslutil._defaultcacerts(ui)
705 cafile = sslutil._defaultcacerts(ui)
706 ctx = ssl.create_default_context()
706 ctx = ssl.create_default_context()
707 if cafile:
707 if cafile:
708 ctx.load_verify_locations(cafile=cafile)
708 ctx.load_verify_locations(cafile=cafile)
709 else:
709 else:
710 ctx.load_default_certs()
710 ctx.load_default_certs()
711
711
712 return len(ctx.get_ca_certs()) > 0
712 return len(ctx.get_ca_certs()) > 0
713
713
714
714
715 @check("tls1.2", "TLS 1.2 protocol support")
715 @check("tls1.2", "TLS 1.2 protocol support")
716 def has_tls1_2():
716 def has_tls1_2():
717 from mercurial import sslutil
717 from mercurial import sslutil
718
718
719 return b'tls1.2' in sslutil.supportedprotocols
719 return b'tls1.2' in sslutil.supportedprotocols
720
720
721
721
722 @check("windows", "Windows")
722 @check("windows", "Windows")
723 def has_windows():
723 def has_windows():
724 return os.name == 'nt'
724 return os.name == 'nt'
725
725
726
726
727 @check("system-sh", "system() uses sh")
727 @check("system-sh", "system() uses sh")
728 def has_system_sh():
728 def has_system_sh():
729 return os.name != 'nt'
729 return os.name != 'nt'
730
730
731
731
732 @check("serve", "platform and python can manage 'hg serve -d'")
732 @check("serve", "platform and python can manage 'hg serve -d'")
733 def has_serve():
733 def has_serve():
734 return True
734 return True
735
735
736
736
737 @check("setprocname", "whether osutil.setprocname is available or not")
737 @check("setprocname", "whether osutil.setprocname is available or not")
738 def has_setprocname():
738 def has_setprocname():
739 try:
739 try:
740 from mercurial.utils import procutil
740 from mercurial.utils import procutil
741
741
742 procutil.setprocname
742 procutil.setprocname
743 return True
743 return True
744 except AttributeError:
744 except AttributeError:
745 return False
745 return False
746
746
747
747
748 @check("test-repo", "running tests from repository")
748 @check("test-repo", "running tests from repository")
749 def has_test_repo():
749 def has_test_repo():
750 t = os.environ["TESTDIR"]
750 t = os.environ["TESTDIR"]
751 return os.path.isdir(os.path.join(t, "..", ".hg"))
751 return os.path.isdir(os.path.join(t, "..", ".hg"))
752
752
753
753
754 @check("network-io", "whether tests are allowed to access 3rd party services")
754 @check("network-io", "whether tests are allowed to access 3rd party services")
755 def has_test_repo():
755 def has_test_repo():
756 t = os.environ.get("HGTESTS_ALLOW_NETIO")
756 t = os.environ.get("HGTESTS_ALLOW_NETIO")
757 return t == "1"
757 return t == "1"
758
758
759
759
760 @check("curses", "terminfo compiler and curses module")
760 @check("curses", "terminfo compiler and curses module")
761 def has_curses():
761 def has_curses():
762 try:
762 try:
763 import curses
763 import curses
764
764
765 curses.COLOR_BLUE
765 curses.COLOR_BLUE
766
766
767 # Windows doesn't have a `tic` executable, but the windows_curses
767 # Windows doesn't have a `tic` executable, but the windows_curses
768 # package is sufficient to run the tests without it.
768 # package is sufficient to run the tests without it.
769 if os.name == 'nt':
769 if os.name == 'nt':
770 return True
770 return True
771
771
772 return has_tic()
772 return has_tic()
773
773
774 except (ImportError, AttributeError):
774 except (ImportError, AttributeError):
775 return False
775 return False
776
776
777
777
778 @check("tic", "terminfo compiler")
778 @check("tic", "terminfo compiler")
779 def has_tic():
779 def has_tic():
780 return matchoutput('test -x "`which tic`"', br'')
780 return matchoutput('test -x "`which tic`"', br'')
781
781
782
782
783 @check("xz", "xz compression utility")
783 @check("xz", "xz compression utility")
784 def has_xz():
784 def has_xz():
785 # When Windows invokes a subprocess in shell mode, it uses `cmd.exe`, which
785 # When Windows invokes a subprocess in shell mode, it uses `cmd.exe`, which
786 # only knows `where`, not `which`. So invoke MSYS shell explicitly.
786 # only knows `where`, not `which`. So invoke MSYS shell explicitly.
787 return matchoutput("sh -c 'test -x \"`which xz`\"'", b'')
787 return matchoutput("sh -c 'test -x \"`which xz`\"'", b'')
788
788
789
789
790 @check("msys", "Windows with MSYS")
790 @check("msys", "Windows with MSYS")
791 def has_msys():
791 def has_msys():
792 return os.getenv('MSYSTEM')
792 return os.getenv('MSYSTEM')
793
793
794
794
795 @check("aix", "AIX")
795 @check("aix", "AIX")
796 def has_aix():
796 def has_aix():
797 return sys.platform.startswith("aix")
797 return sys.platform.startswith("aix")
798
798
799
799
800 @check("osx", "OS X")
800 @check("osx", "OS X")
801 def has_osx():
801 def has_osx():
802 return sys.platform == 'darwin'
802 return sys.platform == 'darwin'
803
803
804
804
805 @check("osxpackaging", "OS X packaging tools")
805 @check("osxpackaging", "OS X packaging tools")
806 def has_osxpackaging():
806 def has_osxpackaging():
807 try:
807 try:
808 return (
808 return (
809 matchoutput('pkgbuild', br'Usage: pkgbuild ', ignorestatus=1)
809 matchoutput('pkgbuild', br'Usage: pkgbuild ', ignorestatus=1)
810 and matchoutput(
810 and matchoutput(
811 'productbuild', br'Usage: productbuild ', ignorestatus=1
811 'productbuild', br'Usage: productbuild ', ignorestatus=1
812 )
812 )
813 and matchoutput('lsbom', br'Usage: lsbom', ignorestatus=1)
813 and matchoutput('lsbom', br'Usage: lsbom', ignorestatus=1)
814 and matchoutput('xar --help', br'Usage: xar', ignorestatus=1)
814 and matchoutput('xar --help', br'Usage: xar', ignorestatus=1)
815 )
815 )
816 except ImportError:
816 except ImportError:
817 return False
817 return False
818
818
819
819
820 @check('linuxormacos', 'Linux or MacOS')
820 @check('linuxormacos', 'Linux or MacOS')
821 def has_linuxormacos():
821 def has_linuxormacos():
822 # This isn't a perfect test for MacOS. But it is sufficient for our needs.
822 # This isn't a perfect test for MacOS. But it is sufficient for our needs.
823 return sys.platform.startswith(('linux', 'darwin'))
823 return sys.platform.startswith(('linux', 'darwin'))
824
824
825
825
826 @check("docker", "docker support")
826 @check("docker", "docker support")
827 def has_docker():
827 def has_docker():
828 pat = br'A self-sufficient runtime for'
828 pat = br'A self-sufficient runtime for'
829 if matchoutput('docker --help', pat):
829 if matchoutput('docker --help', pat):
830 if 'linux' not in sys.platform:
830 if 'linux' not in sys.platform:
831 # TODO: in theory we should be able to test docker-based
831 # TODO: in theory we should be able to test docker-based
832 # package creation on non-linux using boot2docker, but in
832 # package creation on non-linux using boot2docker, but in
833 # practice that requires extra coordination to make sure
833 # practice that requires extra coordination to make sure
834 # $TESTTEMP is going to be visible at the same path to the
834 # $TESTTEMP is going to be visible at the same path to the
835 # boot2docker VM. If we figure out how to verify that, we
835 # boot2docker VM. If we figure out how to verify that, we
836 # can use the following instead of just saying False:
836 # can use the following instead of just saying False:
837 # return 'DOCKER_HOST' in os.environ
837 # return 'DOCKER_HOST' in os.environ
838 return False
838 return False
839
839
840 return True
840 return True
841 return False
841 return False
842
842
843
843
844 @check("debhelper", "debian packaging tools")
844 @check("debhelper", "debian packaging tools")
845 def has_debhelper():
845 def has_debhelper():
846 # Some versions of dpkg say `dpkg', some say 'dpkg' (` vs ' on the first
846 # Some versions of dpkg say `dpkg', some say 'dpkg' (` vs ' on the first
847 # quote), so just accept anything in that spot.
847 # quote), so just accept anything in that spot.
848 dpkg = matchoutput(
848 dpkg = matchoutput(
849 'dpkg --version', br"Debian .dpkg' package management program"
849 'dpkg --version', br"Debian .dpkg' package management program"
850 )
850 )
851 dh = matchoutput(
851 dh = matchoutput(
852 'dh --help', br'dh is a part of debhelper.', ignorestatus=True
852 'dh --help', br'dh is a part of debhelper.', ignorestatus=True
853 )
853 )
854 dh_py2 = matchoutput(
854 dh_py2 = matchoutput(
855 'dh_python2 --help', br'other supported Python versions'
855 'dh_python2 --help', br'other supported Python versions'
856 )
856 )
857 # debuild comes from the 'devscripts' package, though you might want
857 # debuild comes from the 'devscripts' package, though you might want
858 # the 'build-debs' package instead, which has a dependency on devscripts.
858 # the 'build-debs' package instead, which has a dependency on devscripts.
859 debuild = matchoutput(
859 debuild = matchoutput(
860 'debuild --help', br'to run debian/rules with given parameter'
860 'debuild --help', br'to run debian/rules with given parameter'
861 )
861 )
862 return dpkg and dh and dh_py2 and debuild
862 return dpkg and dh and dh_py2 and debuild
863
863
864
864
865 @check(
865 @check(
866 "debdeps", "debian build dependencies (run dpkg-checkbuilddeps in contrib/)"
866 "debdeps", "debian build dependencies (run dpkg-checkbuilddeps in contrib/)"
867 )
867 )
868 def has_debdeps():
868 def has_debdeps():
869 # just check exit status (ignoring output)
869 # just check exit status (ignoring output)
870 path = '%s/../contrib/packaging/debian/control' % os.environ['TESTDIR']
870 path = '%s/../contrib/packaging/debian/control' % os.environ['TESTDIR']
871 return matchoutput('dpkg-checkbuilddeps %s' % path, br'')
871 return matchoutput('dpkg-checkbuilddeps %s' % path, br'')
872
872
873
873
874 @check("demandimport", "demandimport enabled")
874 @check("demandimport", "demandimport enabled")
875 def has_demandimport():
875 def has_demandimport():
876 # chg disables demandimport intentionally for performance wins.
876 # chg disables demandimport intentionally for performance wins.
877 return (not has_chg()) and os.environ.get('HGDEMANDIMPORT') != 'disable'
877 return (not has_chg()) and os.environ.get('HGDEMANDIMPORT') != 'disable'
878
878
879
879
880 # Add "py27", "py35", ... as possible feature checks. Note that there's no
880 # Add "py27", "py35", ... as possible feature checks. Note that there's no
881 # punctuation here.
881 # punctuation here.
882 @checkvers("py", "Python >= %s", (2.7, 3.5, 3.6, 3.7, 3.8, 3.9))
882 @checkvers("py", "Python >= %s", (2.7, 3.5, 3.6, 3.7, 3.8, 3.9))
883 def has_python_range(v):
883 def has_python_range(v):
884 major, minor = v.split('.')[0:2]
884 major, minor = v.split('.')[0:2]
885 py_major, py_minor = sys.version_info.major, sys.version_info.minor
885 py_major, py_minor = sys.version_info.major, sys.version_info.minor
886
886
887 return (py_major, py_minor) >= (int(major), int(minor))
887 return (py_major, py_minor) >= (int(major), int(minor))
888
888
889
889
890 @check("py3", "running with Python 3.x")
890 @check("py3", "running with Python 3.x")
891 def has_py3():
891 def has_py3():
892 return 3 == sys.version_info[0]
892 return 3 == sys.version_info[0]
893
893
894
894
895 @check("py3exe", "a Python 3.x interpreter is available")
895 @check("py3exe", "a Python 3.x interpreter is available")
896 def has_python3exe():
896 def has_python3exe():
897 py = 'python3'
897 py = 'python3'
898 if os.name == 'nt':
898 if os.name == 'nt':
899 py = 'py -3'
899 py = 'py -3'
900 return matchoutput('%s -V' % py, br'^Python 3.(5|6|7|8|9)')
900 return matchoutput('%s -V' % py, br'^Python 3.(5|6|7|8|9)')
901
901
902
902
903 @check("pure", "running with pure Python code")
903 @check("pure", "running with pure Python code")
904 def has_pure():
904 def has_pure():
905 return any(
905 return any(
906 [
906 [
907 os.environ.get("HGMODULEPOLICY") == "py",
907 os.environ.get("HGMODULEPOLICY") == "py",
908 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure",
908 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure",
909 ]
909 ]
910 )
910 )
911
911
912
912
913 @check("slow", "allow slow tests (use --allow-slow-tests)")
913 @check("slow", "allow slow tests (use --allow-slow-tests)")
914 def has_slow():
914 def has_slow():
915 return os.environ.get('HGTEST_SLOW') == 'slow'
915 return os.environ.get('HGTEST_SLOW') == 'slow'
916
916
917
917
918 @check("hypothesis", "Hypothesis automated test generation")
918 @check("hypothesis", "Hypothesis automated test generation")
919 def has_hypothesis():
919 def has_hypothesis():
920 try:
920 try:
921 import hypothesis
921 import hypothesis
922
922
923 hypothesis.given
923 hypothesis.given
924 return True
924 return True
925 except ImportError:
925 except ImportError:
926 return False
926 return False
927
927
928
928
929 @check("unziplinks", "unzip(1) understands and extracts symlinks")
929 @check("unziplinks", "unzip(1) understands and extracts symlinks")
930 def unzip_understands_symlinks():
930 def unzip_understands_symlinks():
931 return matchoutput('unzip --help', br'Info-ZIP')
931 return matchoutput('unzip --help', br'Info-ZIP')
932
932
933
933
934 @check("zstd", "zstd Python module available")
934 @check("zstd", "zstd Python module available")
935 def has_zstd():
935 def has_zstd():
936 try:
936 try:
937 import mercurial.zstd
937 import mercurial.zstd
938
938
939 mercurial.zstd.__version__
939 mercurial.zstd.__version__
940 return True
940 return True
941 except ImportError:
941 except ImportError:
942 return False
942 return False
943
943
944
944
945 @check("devfull", "/dev/full special file")
945 @check("devfull", "/dev/full special file")
946 def has_dev_full():
946 def has_dev_full():
947 return os.path.exists('/dev/full')
947 return os.path.exists('/dev/full')
948
948
949
949
950 @check("ensurepip", "ensurepip module")
950 @check("ensurepip", "ensurepip module")
951 def has_ensurepip():
951 def has_ensurepip():
952 try:
952 try:
953 import ensurepip
953 import ensurepip
954
954
955 ensurepip.bootstrap
955 ensurepip.bootstrap
956 return True
956 return True
957 except ImportError:
957 except ImportError:
958 return False
958 return False
959
959
960
960
961 @check("virtualenv", "virtualenv support")
961 @check("virtualenv", "virtualenv support")
962 def has_virtualenv():
962 def has_virtualenv():
963 try:
963 try:
964 import virtualenv
964 import virtualenv
965
965
966 # --no-site-package became the default in 1.7 (Nov 2011), and the
966 # --no-site-package became the default in 1.7 (Nov 2011), and the
967 # argument was removed in 20.0 (Feb 2020). Rather than make the
967 # argument was removed in 20.0 (Feb 2020). Rather than make the
968 # script complicated, just ignore ancient versions.
968 # script complicated, just ignore ancient versions.
969 return int(virtualenv.__version__.split('.')[0]) > 1
969 return int(virtualenv.__version__.split('.')[0]) > 1
970 except (AttributeError, ImportError, IndexError):
970 except (AttributeError, ImportError, IndexError):
971 return False
971 return False
972
972
973
973
974 @check("fsmonitor", "running tests with fsmonitor")
974 @check("fsmonitor", "running tests with fsmonitor")
975 def has_fsmonitor():
975 def has_fsmonitor():
976 return 'HGFSMONITOR_TESTS' in os.environ
976 return 'HGFSMONITOR_TESTS' in os.environ
977
977
978
978
979 @check("fuzzywuzzy", "Fuzzy string matching library")
979 @check("fuzzywuzzy", "Fuzzy string matching library")
980 def has_fuzzywuzzy():
980 def has_fuzzywuzzy():
981 try:
981 try:
982 import fuzzywuzzy
982 import fuzzywuzzy
983
983
984 fuzzywuzzy.__version__
984 fuzzywuzzy.__version__
985 return True
985 return True
986 except ImportError:
986 except ImportError:
987 return False
987 return False
988
988
989
989
990 @check("clang-libfuzzer", "clang new enough to include libfuzzer")
990 @check("clang-libfuzzer", "clang new enough to include libfuzzer")
991 def has_clang_libfuzzer():
991 def has_clang_libfuzzer():
992 mat = matchoutput('clang --version', br'clang version (\d)')
992 mat = matchoutput('clang --version', br'clang version (\d)')
993 if mat:
993 if mat:
994 # libfuzzer is new in clang 6
994 # libfuzzer is new in clang 6
995 return int(mat.group(1)) > 5
995 return int(mat.group(1)) > 5
996 return False
996 return False
997
997
998
998
999 @check("clang-6.0", "clang 6.0 with version suffix (libfuzzer included)")
999 @check("clang-6.0", "clang 6.0 with version suffix (libfuzzer included)")
1000 def has_clang60():
1000 def has_clang60():
1001 return matchoutput('clang-6.0 --version', br'clang version 6\.')
1001 return matchoutput('clang-6.0 --version', br'clang version 6\.')
1002
1002
1003
1003
1004 @check("xdiff", "xdiff algorithm")
1004 @check("xdiff", "xdiff algorithm")
1005 def has_xdiff():
1005 def has_xdiff():
1006 try:
1006 try:
1007 from mercurial import policy
1007 from mercurial import policy
1008
1008
1009 bdiff = policy.importmod('bdiff')
1009 bdiff = policy.importmod('bdiff')
1010 return bdiff.xdiffblocks(b'', b'') == [(0, 0, 0, 0)]
1010 return bdiff.xdiffblocks(b'', b'') == [(0, 0, 0, 0)]
1011 except (ImportError, AttributeError):
1011 except (ImportError, AttributeError):
1012 return False
1012 return False
1013
1013
1014
1014
1015 @check('extraextensions', 'whether tests are running with extra extensions')
1015 @check('extraextensions', 'whether tests are running with extra extensions')
1016 def has_extraextensions():
1016 def has_extraextensions():
1017 return 'HGTESTEXTRAEXTENSIONS' in os.environ
1017 return 'HGTESTEXTRAEXTENSIONS' in os.environ
1018
1018
1019
1019
1020 def getrepofeatures():
1020 def getrepofeatures():
1021 """Obtain set of repository features in use.
1021 """Obtain set of repository features in use.
1022
1022
1023 HGREPOFEATURES can be used to define or remove features. It contains
1023 HGREPOFEATURES can be used to define or remove features. It contains
1024 a space-delimited list of feature strings. Strings beginning with ``-``
1024 a space-delimited list of feature strings. Strings beginning with ``-``
1025 mean to remove.
1025 mean to remove.
1026 """
1026 """
1027 # Default list provided by core.
1027 # Default list provided by core.
1028 features = {
1028 features = {
1029 'bundlerepo',
1029 'bundlerepo',
1030 'revlogstore',
1030 'revlogstore',
1031 'fncache',
1031 'fncache',
1032 }
1032 }
1033
1033
1034 # Features that imply other features.
1034 # Features that imply other features.
1035 implies = {
1035 implies = {
1036 'simplestore': ['-revlogstore', '-bundlerepo', '-fncache'],
1036 'simplestore': ['-revlogstore', '-bundlerepo', '-fncache'],
1037 }
1037 }
1038
1038
1039 for override in os.environ.get('HGREPOFEATURES', '').split(' '):
1039 for override in os.environ.get('HGREPOFEATURES', '').split(' '):
1040 if not override:
1040 if not override:
1041 continue
1041 continue
1042
1042
1043 if override.startswith('-'):
1043 if override.startswith('-'):
1044 if override[1:] in features:
1044 if override[1:] in features:
1045 features.remove(override[1:])
1045 features.remove(override[1:])
1046 else:
1046 else:
1047 features.add(override)
1047 features.add(override)
1048
1048
1049 for imply in implies.get(override, []):
1049 for imply in implies.get(override, []):
1050 if imply.startswith('-'):
1050 if imply.startswith('-'):
1051 if imply[1:] in features:
1051 if imply[1:] in features:
1052 features.remove(imply[1:])
1052 features.remove(imply[1:])
1053 else:
1053 else:
1054 features.add(imply)
1054 features.add(imply)
1055
1055
1056 return features
1056 return features
1057
1057
1058
1058
1059 @check('reporevlogstore', 'repository using the default revlog store')
1059 @check('reporevlogstore', 'repository using the default revlog store')
1060 def has_reporevlogstore():
1060 def has_reporevlogstore():
1061 return 'revlogstore' in getrepofeatures()
1061 return 'revlogstore' in getrepofeatures()
1062
1062
1063
1063
1064 @check('reposimplestore', 'repository using simple storage extension')
1064 @check('reposimplestore', 'repository using simple storage extension')
1065 def has_reposimplestore():
1065 def has_reposimplestore():
1066 return 'simplestore' in getrepofeatures()
1066 return 'simplestore' in getrepofeatures()
1067
1067
1068
1068
1069 @check('repobundlerepo', 'whether we can open bundle files as repos')
1069 @check('repobundlerepo', 'whether we can open bundle files as repos')
1070 def has_repobundlerepo():
1070 def has_repobundlerepo():
1071 return 'bundlerepo' in getrepofeatures()
1071 return 'bundlerepo' in getrepofeatures()
1072
1072
1073
1073
1074 @check('repofncache', 'repository has an fncache')
1074 @check('repofncache', 'repository has an fncache')
1075 def has_repofncache():
1075 def has_repofncache():
1076 return 'fncache' in getrepofeatures()
1076 return 'fncache' in getrepofeatures()
1077
1077
1078
1078
1079 @check('dirstate-v2', 'using the v2 format of .hg/dirstate')
1079 @check('dirstate-v2', 'using the v2 format of .hg/dirstate')
1080 def has_dirstate_v2():
1080 def has_dirstate_v2():
1081 # Keep this logic in sync with `newreporequirements()` in `mercurial/localrepo.py`
1081 # Keep this logic in sync with `newreporequirements()` in `mercurial/localrepo.py`
1082 return has_rust() and matchoutput(
1082 return has_rust() and matchoutput(
1083 'hg config format.exp-rc-dirstate-v2', b'(?i)1|yes|true|on|always'
1083 'hg config format.exp-rc-dirstate-v2', b'(?i)1|yes|true|on|always'
1084 )
1084 )
1085
1085
1086
1086
1087 @check('sqlite', 'sqlite3 module and matching cli is available')
1087 @check('sqlite', 'sqlite3 module and matching cli is available')
1088 def has_sqlite():
1088 def has_sqlite():
1089 try:
1089 try:
1090 import sqlite3
1090 import sqlite3
1091
1091
1092 version = sqlite3.sqlite_version_info
1092 version = sqlite3.sqlite_version_info
1093 except ImportError:
1093 except ImportError:
1094 return False
1094 return False
1095
1095
1096 if version < (3, 8, 3):
1096 if version < (3, 8, 3):
1097 # WITH clause not supported
1097 # WITH clause not supported
1098 return False
1098 return False
1099
1099
1100 return matchoutput('sqlite3 -version', br'^3\.\d+')
1100 return matchoutput('sqlite3 -version', br'^3\.\d+')
1101
1101
1102
1102
1103 @check('vcr', 'vcr http mocking library (pytest-vcr)')
1103 @check('vcr', 'vcr http mocking library (pytest-vcr)')
1104 def has_vcr():
1104 def has_vcr():
1105 try:
1105 try:
1106 import vcr
1106 import vcr
1107
1107
1108 vcr.VCR
1108 vcr.VCR
1109 return True
1109 return True
1110 except (ImportError, AttributeError):
1110 except (ImportError, AttributeError):
1111 pass
1111 pass
1112 return False
1112 return False
1113
1113
1114
1114
1115 @check('emacs', 'GNU Emacs')
1115 @check('emacs', 'GNU Emacs')
1116 def has_emacs():
1116 def has_emacs():
1117 # Our emacs lisp uses `with-eval-after-load` which is new in emacs
1117 # Our emacs lisp uses `with-eval-after-load` which is new in emacs
1118 # 24.4, so we allow emacs 24.4, 24.5, and 25+ (24.5 was the last
1118 # 24.4, so we allow emacs 24.4, 24.5, and 25+ (24.5 was the last
1119 # 24 release)
1119 # 24 release)
1120 return matchoutput('emacs --version', b'GNU Emacs 2(4.4|4.5|5|6|7|8|9)')
1120 return matchoutput('emacs --version', b'GNU Emacs 2(4.4|4.5|5|6|7|8|9)')
1121
1121
1122
1122
1123 @check('black', 'the black formatter for python (>= 20.8b1)')
1123 @check('black', 'the black formatter for python (>= 20.8b1)')
1124 def has_black():
1124 def has_black():
1125 blackcmd = 'black --version'
1125 blackcmd = 'black --version'
1126 version_regex = b'black, version ([0-9a-b.]+)'
1126 version_regex = b'black, (?:version )?([0-9a-b.]+)'
1127 version = matchoutput(blackcmd, version_regex)
1127 version = matchoutput(blackcmd, version_regex)
1128 sv = distutils.version.StrictVersion
1128 sv = distutils.version.StrictVersion
1129 return version and sv(_bytes2sys(version.group(1))) >= sv('20.8b1')
1129 return version and sv(_bytes2sys(version.group(1))) >= sv('20.8b1')
1130
1130
1131
1131
1132 @check('pytype', 'the pytype type checker')
1132 @check('pytype', 'the pytype type checker')
1133 def has_pytype():
1133 def has_pytype():
1134 pytypecmd = 'pytype --version'
1134 pytypecmd = 'pytype --version'
1135 version = matchoutput(pytypecmd, b'[0-9a-b.]+')
1135 version = matchoutput(pytypecmd, b'[0-9a-b.]+')
1136 sv = distutils.version.StrictVersion
1136 sv = distutils.version.StrictVersion
1137 return version and sv(_bytes2sys(version.group(0))) >= sv('2019.10.17')
1137 return version and sv(_bytes2sys(version.group(0))) >= sv('2019.10.17')
1138
1138
1139
1139
1140 @check("rustfmt", "rustfmt tool at version nightly-2021-11-02")
1140 @check("rustfmt", "rustfmt tool at version nightly-2021-11-02")
1141 def has_rustfmt():
1141 def has_rustfmt():
1142 # We use Nightly's rustfmt due to current unstable config options.
1142 # We use Nightly's rustfmt due to current unstable config options.
1143 return matchoutput(
1143 return matchoutput(
1144 '`rustup which --toolchain nightly-2021-11-02 rustfmt` --version',
1144 '`rustup which --toolchain nightly-2021-11-02 rustfmt` --version',
1145 b'rustfmt',
1145 b'rustfmt',
1146 )
1146 )
1147
1147
1148
1148
1149 @check("cargo", "cargo tool")
1149 @check("cargo", "cargo tool")
1150 def has_cargo():
1150 def has_cargo():
1151 return matchoutput('`rustup which cargo` --version', b'cargo')
1151 return matchoutput('`rustup which cargo` --version', b'cargo')
1152
1152
1153
1153
1154 @check("lzma", "python lzma module")
1154 @check("lzma", "python lzma module")
1155 def has_lzma():
1155 def has_lzma():
1156 try:
1156 try:
1157 import _lzma
1157 import _lzma
1158
1158
1159 _lzma.FORMAT_XZ
1159 _lzma.FORMAT_XZ
1160 return True
1160 return True
1161 except ImportError:
1161 except ImportError:
1162 return False
1162 return False
1163
1163
1164
1164
1165 @check("bash", "bash shell")
1165 @check("bash", "bash shell")
1166 def has_bash():
1166 def has_bash():
1167 return matchoutput("bash -c 'echo hi'", b'^hi$')
1167 return matchoutput("bash -c 'echo hi'", b'^hi$')
1168
1168
1169
1169
1170 @check("bigendian", "big-endian CPU")
1170 @check("bigendian", "big-endian CPU")
1171 def has_bigendian():
1171 def has_bigendian():
1172 return sys.byteorder == 'big'
1172 return sys.byteorder == 'big'
General Comments 0
You need to be logged in to leave comments. Login now