##// END OF EJS Templates
merge: with stable
Augie Fackler -
r49115:e2836d08 merge default
parent child Browse files
Show More
@@ -1,1134 +1,1141 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("icasefs", "case insensitive file system")
267 @check("icasefs", "case insensitive file system")
268 def has_icasefs():
268 def has_icasefs():
269 # Stolen from mercurial.util
269 # Stolen from mercurial.util
270 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
270 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
271 os.close(fd)
271 os.close(fd)
272 try:
272 try:
273 s1 = os.stat(path)
273 s1 = os.stat(path)
274 d, b = os.path.split(path)
274 d, b = os.path.split(path)
275 p2 = os.path.join(d, b.upper())
275 p2 = os.path.join(d, b.upper())
276 if path == p2:
276 if path == p2:
277 p2 = os.path.join(d, b.lower())
277 p2 = os.path.join(d, b.lower())
278 try:
278 try:
279 s2 = os.stat(p2)
279 s2 = os.stat(p2)
280 return s2 == s1
280 return s2 == s1
281 except OSError:
281 except OSError:
282 return False
282 return False
283 finally:
283 finally:
284 os.remove(path)
284 os.remove(path)
285
285
286
286
287 @check("fifo", "named pipes")
287 @check("fifo", "named pipes")
288 def has_fifo():
288 def has_fifo():
289 if getattr(os, "mkfifo", None) is None:
289 if getattr(os, "mkfifo", None) is None:
290 return False
290 return False
291 name = tempfile.mktemp(dir='.', prefix=tempprefix)
291 name = tempfile.mktemp(dir='.', prefix=tempprefix)
292 try:
292 try:
293 os.mkfifo(name)
293 os.mkfifo(name)
294 os.unlink(name)
294 os.unlink(name)
295 return True
295 return True
296 except OSError:
296 except OSError:
297 return False
297 return False
298
298
299
299
300 @check("killdaemons", 'killdaemons.py support')
300 @check("killdaemons", 'killdaemons.py support')
301 def has_killdaemons():
301 def has_killdaemons():
302 return True
302 return True
303
303
304
304
305 @check("cacheable", "cacheable filesystem")
305 @check("cacheable", "cacheable filesystem")
306 def has_cacheable_fs():
306 def has_cacheable_fs():
307 from mercurial import util
307 from mercurial import util
308
308
309 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
309 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
310 os.close(fd)
310 os.close(fd)
311 try:
311 try:
312 return util.cachestat(path).cacheable()
312 return util.cachestat(path).cacheable()
313 finally:
313 finally:
314 os.remove(path)
314 os.remove(path)
315
315
316
316
317 @check("lsprof", "python lsprof module")
317 @check("lsprof", "python lsprof module")
318 def has_lsprof():
318 def has_lsprof():
319 try:
319 try:
320 import _lsprof
320 import _lsprof
321
321
322 _lsprof.Profiler # silence unused import warning
322 _lsprof.Profiler # silence unused import warning
323 return True
323 return True
324 except ImportError:
324 except ImportError:
325 return False
325 return False
326
326
327
327
328 def _gethgversion():
328 def _gethgversion():
329 m = matchoutput('hg --version --quiet 2>&1', br'(\d+)\.(\d+)')
329 m = matchoutput('hg --version --quiet 2>&1', br'(\d+)\.(\d+)')
330 if not m:
330 if not m:
331 return (0, 0)
331 return (0, 0)
332 return (int(m.group(1)), int(m.group(2)))
332 return (int(m.group(1)), int(m.group(2)))
333
333
334
334
335 _hgversion = None
335 _hgversion = None
336
336
337
337
338 def gethgversion():
338 def gethgversion():
339 global _hgversion
339 global _hgversion
340 if _hgversion is None:
340 if _hgversion is None:
341 _hgversion = _gethgversion()
341 _hgversion = _gethgversion()
342 return _hgversion
342 return _hgversion
343
343
344
344
345 @checkvers(
345 @checkvers(
346 "hg", "Mercurial >= %s", list([(1.0 * x) / 10 for x in range(9, 99)])
346 "hg", "Mercurial >= %s", list([(1.0 * x) / 10 for x in range(9, 99)])
347 )
347 )
348 def has_hg_range(v):
348 def has_hg_range(v):
349 major, minor = v.split('.')[0:2]
349 major, minor = v.split('.')[0:2]
350 return gethgversion() >= (int(major), int(minor))
350 return gethgversion() >= (int(major), int(minor))
351
351
352
352
353 @check("rust", "Using the Rust extensions")
353 @check("rust", "Using the Rust extensions")
354 def has_rust():
354 def has_rust():
355 """Check is the mercurial currently running is using some rust code"""
355 """Check is the mercurial currently running is using some rust code"""
356 cmd = 'hg debuginstall --quiet 2>&1'
356 cmd = 'hg debuginstall --quiet 2>&1'
357 match = br'checking module policy \(([^)]+)\)'
357 match = br'checking module policy \(([^)]+)\)'
358 policy = matchoutput(cmd, match)
358 policy = matchoutput(cmd, match)
359 if not policy:
359 if not policy:
360 return False
360 return False
361 return b'rust' in policy.group(1)
361 return b'rust' in policy.group(1)
362
362
363
363
364 @check("hg08", "Mercurial >= 0.8")
364 @check("hg08", "Mercurial >= 0.8")
365 def has_hg08():
365 def has_hg08():
366 if checks["hg09"][0]():
366 if checks["hg09"][0]():
367 return True
367 return True
368 return matchoutput('hg help annotate 2>&1', '--date')
368 return matchoutput('hg help annotate 2>&1', '--date')
369
369
370
370
371 @check("hg07", "Mercurial >= 0.7")
371 @check("hg07", "Mercurial >= 0.7")
372 def has_hg07():
372 def has_hg07():
373 if checks["hg08"][0]():
373 if checks["hg08"][0]():
374 return True
374 return True
375 return matchoutput('hg --version --quiet 2>&1', 'Mercurial Distributed SCM')
375 return matchoutput('hg --version --quiet 2>&1', 'Mercurial Distributed SCM')
376
376
377
377
378 @check("hg06", "Mercurial >= 0.6")
378 @check("hg06", "Mercurial >= 0.6")
379 def has_hg06():
379 def has_hg06():
380 if checks["hg07"][0]():
380 if checks["hg07"][0]():
381 return True
381 return True
382 return matchoutput('hg --version --quiet 2>&1', 'Mercurial version')
382 return matchoutput('hg --version --quiet 2>&1', 'Mercurial version')
383
383
384
384
385 @check("gettext", "GNU Gettext (msgfmt)")
385 @check("gettext", "GNU Gettext (msgfmt)")
386 def has_gettext():
386 def has_gettext():
387 return matchoutput('msgfmt --version', br'GNU gettext-tools')
387 return matchoutput('msgfmt --version', br'GNU gettext-tools')
388
388
389
389
390 @check("git", "git command line client")
390 @check("git", "git command line client")
391 def has_git():
391 def has_git():
392 return matchoutput('git --version 2>&1', br'^git version')
392 return matchoutput('git --version 2>&1', br'^git version')
393
393
394
394
395 def getgitversion():
395 def getgitversion():
396 m = matchoutput('git --version 2>&1', br'git version (\d+)\.(\d+)')
396 m = matchoutput('git --version 2>&1', br'git version (\d+)\.(\d+)')
397 if not m:
397 if not m:
398 return (0, 0)
398 return (0, 0)
399 return (int(m.group(1)), int(m.group(2)))
399 return (int(m.group(1)), int(m.group(2)))
400
400
401
401
402 @check("pygit2", "pygit2 Python library")
402 @check("pygit2", "pygit2 Python library")
403 def has_git():
403 def has_git():
404 try:
404 try:
405 import pygit2
405 import pygit2
406
406
407 pygit2.Oid # silence unused import
407 pygit2.Oid # silence unused import
408 return True
408 return True
409 except ImportError:
409 except ImportError:
410 return False
410 return False
411
411
412
412
413 # https://github.com/git-lfs/lfs-test-server
413 # https://github.com/git-lfs/lfs-test-server
414 @check("lfs-test-server", "git-lfs test server")
414 @check("lfs-test-server", "git-lfs test server")
415 def has_lfsserver():
415 def has_lfsserver():
416 exe = 'lfs-test-server'
416 exe = 'lfs-test-server'
417 if has_windows():
417 if has_windows():
418 exe = 'lfs-test-server.exe'
418 exe = 'lfs-test-server.exe'
419 return any(
419 return any(
420 os.access(os.path.join(path, exe), os.X_OK)
420 os.access(os.path.join(path, exe), os.X_OK)
421 for path in os.environ["PATH"].split(os.pathsep)
421 for path in os.environ["PATH"].split(os.pathsep)
422 )
422 )
423
423
424
424
425 @checkvers("git", "git client (with ext::sh support) version >= %s", (1.9,))
425 @checkvers("git", "git client (with ext::sh support) version >= %s", (1.9,))
426 def has_git_range(v):
426 def has_git_range(v):
427 major, minor = v.split('.')[0:2]
427 major, minor = v.split('.')[0:2]
428 return getgitversion() >= (int(major), int(minor))
428 return getgitversion() >= (int(major), int(minor))
429
429
430
430
431 @check("docutils", "Docutils text processing library")
431 @check("docutils", "Docutils text processing library")
432 def has_docutils():
432 def has_docutils():
433 try:
433 try:
434 import docutils.core
434 import docutils.core
435
435
436 docutils.core.publish_cmdline # silence unused import
436 docutils.core.publish_cmdline # silence unused import
437 return True
437 return True
438 except ImportError:
438 except ImportError:
439 return False
439 return False
440
440
441
441
442 def getsvnversion():
442 def getsvnversion():
443 m = matchoutput('svn --version --quiet 2>&1', br'^(\d+)\.(\d+)')
443 m = matchoutput('svn --version --quiet 2>&1', br'^(\d+)\.(\d+)')
444 if not m:
444 if not m:
445 return (0, 0)
445 return (0, 0)
446 return (int(m.group(1)), int(m.group(2)))
446 return (int(m.group(1)), int(m.group(2)))
447
447
448
448
449 @checkvers("svn", "subversion client and admin tools >= %s", (1.3, 1.5))
449 @checkvers("svn", "subversion client and admin tools >= %s", (1.3, 1.5))
450 def has_svn_range(v):
450 def has_svn_range(v):
451 major, minor = v.split('.')[0:2]
451 major, minor = v.split('.')[0:2]
452 return getsvnversion() >= (int(major), int(minor))
452 return getsvnversion() >= (int(major), int(minor))
453
453
454
454
455 @check("svn", "subversion client and admin tools")
455 @check("svn", "subversion client and admin tools")
456 def has_svn():
456 def has_svn():
457 return matchoutput('svn --version 2>&1', br'^svn, version') and matchoutput(
457 return matchoutput('svn --version 2>&1', br'^svn, version') and matchoutput(
458 'svnadmin --version 2>&1', br'^svnadmin, version'
458 'svnadmin --version 2>&1', br'^svnadmin, version'
459 )
459 )
460
460
461
461
462 @check("svn-bindings", "subversion python bindings")
462 @check("svn-bindings", "subversion python bindings")
463 def has_svn_bindings():
463 def has_svn_bindings():
464 try:
464 try:
465 import svn.core
465 import svn.core
466
466
467 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
467 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
468 if version < (1, 4):
468 if version < (1, 4):
469 return False
469 return False
470 return True
470 return True
471 except ImportError:
471 except ImportError:
472 return False
472 return False
473
473
474
474
475 @check("p4", "Perforce server and client")
475 @check("p4", "Perforce server and client")
476 def has_p4():
476 def has_p4():
477 return matchoutput('p4 -V', br'Rev\. P4/') and matchoutput(
477 return matchoutput('p4 -V', br'Rev\. P4/') and matchoutput(
478 'p4d -V', br'Rev\. P4D/'
478 'p4d -V', br'Rev\. P4D/'
479 )
479 )
480
480
481
481
482 @check("symlink", "symbolic links")
482 @check("symlink", "symbolic links")
483 def has_symlink():
483 def has_symlink():
484 # mercurial.windows.checklink() is a hard 'no' at the moment
484 # mercurial.windows.checklink() is a hard 'no' at the moment
485 if os.name == 'nt' or getattr(os, "symlink", None) is None:
485 if os.name == 'nt' or getattr(os, "symlink", None) is None:
486 return False
486 return False
487 name = tempfile.mktemp(dir='.', prefix=tempprefix)
487 name = tempfile.mktemp(dir='.', prefix=tempprefix)
488 try:
488 try:
489 os.symlink(".", name)
489 os.symlink(".", name)
490 os.unlink(name)
490 os.unlink(name)
491 return True
491 return True
492 except (OSError, AttributeError):
492 except (OSError, AttributeError):
493 return False
493 return False
494
494
495
495
496 @check("hardlink", "hardlinks")
496 @check("hardlink", "hardlinks")
497 def has_hardlink():
497 def has_hardlink():
498 from mercurial import util
498 from mercurial import util
499
499
500 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
500 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
501 os.close(fh)
501 os.close(fh)
502 name = tempfile.mktemp(dir='.', prefix=tempprefix)
502 name = tempfile.mktemp(dir='.', prefix=tempprefix)
503 try:
503 try:
504 util.oslink(_sys2bytes(fn), _sys2bytes(name))
504 util.oslink(_sys2bytes(fn), _sys2bytes(name))
505 os.unlink(name)
505 os.unlink(name)
506 return True
506 return True
507 except OSError:
507 except OSError:
508 return False
508 return False
509 finally:
509 finally:
510 os.unlink(fn)
510 os.unlink(fn)
511
511
512
512
513 @check("hardlink-whitelisted", "hardlinks on whitelisted filesystems")
513 @check("hardlink-whitelisted", "hardlinks on whitelisted filesystems")
514 def has_hardlink_whitelisted():
514 def has_hardlink_whitelisted():
515 from mercurial import util
515 from mercurial import util
516
516
517 try:
517 try:
518 fstype = util.getfstype(b'.')
518 fstype = util.getfstype(b'.')
519 except OSError:
519 except OSError:
520 return False
520 return False
521 return fstype in util._hardlinkfswhitelist
521 return fstype in util._hardlinkfswhitelist
522
522
523
523
524 @check("rmcwd", "can remove current working directory")
524 @check("rmcwd", "can remove current working directory")
525 def has_rmcwd():
525 def has_rmcwd():
526 ocwd = os.getcwd()
526 ocwd = os.getcwd()
527 temp = tempfile.mkdtemp(dir='.', prefix=tempprefix)
527 temp = tempfile.mkdtemp(dir='.', prefix=tempprefix)
528 try:
528 try:
529 os.chdir(temp)
529 os.chdir(temp)
530 # On Linux, 'rmdir .' isn't allowed, but the other names are okay.
530 # On Linux, 'rmdir .' isn't allowed, but the other names are okay.
531 # On Solaris and Windows, the cwd can't be removed by any names.
531 # On Solaris and Windows, the cwd can't be removed by any names.
532 os.rmdir(os.getcwd())
532 os.rmdir(os.getcwd())
533 return True
533 return True
534 except OSError:
534 except OSError:
535 return False
535 return False
536 finally:
536 finally:
537 os.chdir(ocwd)
537 os.chdir(ocwd)
538 # clean up temp dir on platforms where cwd can't be removed
538 # clean up temp dir on platforms where cwd can't be removed
539 try:
539 try:
540 os.rmdir(temp)
540 os.rmdir(temp)
541 except OSError:
541 except OSError:
542 pass
542 pass
543
543
544
544
545 @check("tla", "GNU Arch tla client")
545 @check("tla", "GNU Arch tla client")
546 def has_tla():
546 def has_tla():
547 return matchoutput('tla --version 2>&1', br'The GNU Arch Revision')
547 return matchoutput('tla --version 2>&1', br'The GNU Arch Revision')
548
548
549
549
550 @check("gpg", "gpg client")
550 @check("gpg", "gpg client")
551 def has_gpg():
551 def has_gpg():
552 return matchoutput('gpg --version 2>&1', br'GnuPG')
552 return matchoutput('gpg --version 2>&1', br'GnuPG')
553
553
554
554
555 @check("gpg2", "gpg client v2")
555 @check("gpg2", "gpg client v2")
556 def has_gpg2():
556 def has_gpg2():
557 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.')
557 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.')
558
558
559
559
560 @check("gpg21", "gpg client v2.1+")
560 @check("gpg21", "gpg client v2.1+")
561 def has_gpg21():
561 def has_gpg21():
562 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.(?!0)')
562 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.(?!0)')
563
563
564
564
565 @check("unix-permissions", "unix-style permissions")
565 @check("unix-permissions", "unix-style permissions")
566 def has_unix_permissions():
566 def has_unix_permissions():
567 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
567 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
568 try:
568 try:
569 fname = os.path.join(d, 'foo')
569 fname = os.path.join(d, 'foo')
570 for umask in (0o77, 0o07, 0o22):
570 for umask in (0o77, 0o07, 0o22):
571 os.umask(umask)
571 os.umask(umask)
572 f = open(fname, 'w')
572 f = open(fname, 'w')
573 f.close()
573 f.close()
574 mode = os.stat(fname).st_mode
574 mode = os.stat(fname).st_mode
575 os.unlink(fname)
575 os.unlink(fname)
576 if mode & 0o777 != ~umask & 0o666:
576 if mode & 0o777 != ~umask & 0o666:
577 return False
577 return False
578 return True
578 return True
579 finally:
579 finally:
580 os.rmdir(d)
580 os.rmdir(d)
581
581
582
582
583 @check("unix-socket", "AF_UNIX socket family")
583 @check("unix-socket", "AF_UNIX socket family")
584 def has_unix_socket():
584 def has_unix_socket():
585 return getattr(socket, 'AF_UNIX', None) is not None
585 return getattr(socket, 'AF_UNIX', None) is not None
586
586
587
587
588 @check("root", "root permissions")
588 @check("root", "root permissions")
589 def has_root():
589 def has_root():
590 return getattr(os, 'geteuid', None) and os.geteuid() == 0
590 return getattr(os, 'geteuid', None) and os.geteuid() == 0
591
591
592
592
593 @check("pyflakes", "Pyflakes python linter")
593 @check("pyflakes", "Pyflakes python linter")
594 def has_pyflakes():
594 def has_pyflakes():
595 try:
595 try:
596 import pyflakes
596 import pyflakes
597
597
598 pyflakes.__version__
598 pyflakes.__version__
599 except ImportError:
599 except ImportError:
600 return False
600 return False
601 else:
601 else:
602 return True
602 return True
603
603
604
604
605 @check("pylint", "Pylint python linter")
605 @check("pylint", "Pylint python linter")
606 def has_pylint():
606 def has_pylint():
607 return matchoutput("pylint --help", br"Usage:[ ]+pylint", True)
607 return matchoutput("pylint --help", br"Usage:[ ]+pylint", True)
608
608
609
609
610 @check("clang-format", "clang-format C code formatter (>= 11)")
610 @check("clang-format", "clang-format C code formatter (>= 11)")
611 def has_clang_format():
611 def has_clang_format():
612 m = matchoutput('clang-format --version', br'clang-format version (\d+)')
612 m = matchoutput('clang-format --version', br'clang-format version (\d+)')
613 # style changed somewhere between 10.x and 11.x
613 # style changed somewhere between 10.x and 11.x
614 return m and int(m.group(1)) >= 11
614 if m:
615 return int(m.group(1)) >= 11
616 # Assist Googler contributors, they have a centrally-maintained version of
617 # clang-format that is generally very fresh, but unlike most builds (both
618 # official and unofficial), it does *not* include a version number.
619 return matchoutput(
620 'clang-format --version', br'clang-format .*google3-trunk \([0-9a-f]+\)'
621 )
615
622
616
623
617 @check("jshint", "JSHint static code analysis tool")
624 @check("jshint", "JSHint static code analysis tool")
618 def has_jshint():
625 def has_jshint():
619 return matchoutput("jshint --version 2>&1", br"jshint v")
626 return matchoutput("jshint --version 2>&1", br"jshint v")
620
627
621
628
622 @check("pygments", "Pygments source highlighting library")
629 @check("pygments", "Pygments source highlighting library")
623 def has_pygments():
630 def has_pygments():
624 try:
631 try:
625 import pygments
632 import pygments
626
633
627 pygments.highlight # silence unused import warning
634 pygments.highlight # silence unused import warning
628 return True
635 return True
629 except ImportError:
636 except ImportError:
630 return False
637 return False
631
638
632
639
633 @check("pygments25", "Pygments version >= 2.5")
640 @check("pygments25", "Pygments version >= 2.5")
634 def pygments25():
641 def pygments25():
635 try:
642 try:
636 import pygments
643 import pygments
637
644
638 v = pygments.__version__
645 v = pygments.__version__
639 except ImportError:
646 except ImportError:
640 return False
647 return False
641
648
642 parts = v.split(".")
649 parts = v.split(".")
643 major = int(parts[0])
650 major = int(parts[0])
644 minor = int(parts[1])
651 minor = int(parts[1])
645
652
646 return (major, minor) >= (2, 5)
653 return (major, minor) >= (2, 5)
647
654
648
655
649 @check("outer-repo", "outer repo")
656 @check("outer-repo", "outer repo")
650 def has_outer_repo():
657 def has_outer_repo():
651 # failing for other reasons than 'no repo' imply that there is a repo
658 # failing for other reasons than 'no repo' imply that there is a repo
652 return not matchoutput('hg root 2>&1', br'abort: no repository found', True)
659 return not matchoutput('hg root 2>&1', br'abort: no repository found', True)
653
660
654
661
655 @check("ssl", "ssl module available")
662 @check("ssl", "ssl module available")
656 def has_ssl():
663 def has_ssl():
657 try:
664 try:
658 import ssl
665 import ssl
659
666
660 ssl.CERT_NONE
667 ssl.CERT_NONE
661 return True
668 return True
662 except ImportError:
669 except ImportError:
663 return False
670 return False
664
671
665
672
666 @check("defaultcacertsloaded", "detected presence of loaded system CA certs")
673 @check("defaultcacertsloaded", "detected presence of loaded system CA certs")
667 def has_defaultcacertsloaded():
674 def has_defaultcacertsloaded():
668 import ssl
675 import ssl
669 from mercurial import sslutil, ui as uimod
676 from mercurial import sslutil, ui as uimod
670
677
671 ui = uimod.ui.load()
678 ui = uimod.ui.load()
672 cafile = sslutil._defaultcacerts(ui)
679 cafile = sslutil._defaultcacerts(ui)
673 ctx = ssl.create_default_context()
680 ctx = ssl.create_default_context()
674 if cafile:
681 if cafile:
675 ctx.load_verify_locations(cafile=cafile)
682 ctx.load_verify_locations(cafile=cafile)
676 else:
683 else:
677 ctx.load_default_certs()
684 ctx.load_default_certs()
678
685
679 return len(ctx.get_ca_certs()) > 0
686 return len(ctx.get_ca_certs()) > 0
680
687
681
688
682 @check("tls1.2", "TLS 1.2 protocol support")
689 @check("tls1.2", "TLS 1.2 protocol support")
683 def has_tls1_2():
690 def has_tls1_2():
684 from mercurial import sslutil
691 from mercurial import sslutil
685
692
686 return b'tls1.2' in sslutil.supportedprotocols
693 return b'tls1.2' in sslutil.supportedprotocols
687
694
688
695
689 @check("windows", "Windows")
696 @check("windows", "Windows")
690 def has_windows():
697 def has_windows():
691 return os.name == 'nt'
698 return os.name == 'nt'
692
699
693
700
694 @check("system-sh", "system() uses sh")
701 @check("system-sh", "system() uses sh")
695 def has_system_sh():
702 def has_system_sh():
696 return os.name != 'nt'
703 return os.name != 'nt'
697
704
698
705
699 @check("serve", "platform and python can manage 'hg serve -d'")
706 @check("serve", "platform and python can manage 'hg serve -d'")
700 def has_serve():
707 def has_serve():
701 return True
708 return True
702
709
703
710
704 @check("setprocname", "whether osutil.setprocname is available or not")
711 @check("setprocname", "whether osutil.setprocname is available or not")
705 def has_setprocname():
712 def has_setprocname():
706 try:
713 try:
707 from mercurial.utils import procutil
714 from mercurial.utils import procutil
708
715
709 procutil.setprocname
716 procutil.setprocname
710 return True
717 return True
711 except AttributeError:
718 except AttributeError:
712 return False
719 return False
713
720
714
721
715 @check("test-repo", "running tests from repository")
722 @check("test-repo", "running tests from repository")
716 def has_test_repo():
723 def has_test_repo():
717 t = os.environ["TESTDIR"]
724 t = os.environ["TESTDIR"]
718 return os.path.isdir(os.path.join(t, "..", ".hg"))
725 return os.path.isdir(os.path.join(t, "..", ".hg"))
719
726
720
727
721 @check("network-io", "whether tests are allowed to access 3rd party services")
728 @check("network-io", "whether tests are allowed to access 3rd party services")
722 def has_test_repo():
729 def has_test_repo():
723 t = os.environ.get("HGTESTS_ALLOW_NETIO")
730 t = os.environ.get("HGTESTS_ALLOW_NETIO")
724 return t == "1"
731 return t == "1"
725
732
726
733
727 @check("curses", "terminfo compiler and curses module")
734 @check("curses", "terminfo compiler and curses module")
728 def has_curses():
735 def has_curses():
729 try:
736 try:
730 import curses
737 import curses
731
738
732 curses.COLOR_BLUE
739 curses.COLOR_BLUE
733
740
734 # Windows doesn't have a `tic` executable, but the windows_curses
741 # Windows doesn't have a `tic` executable, but the windows_curses
735 # package is sufficient to run the tests without it.
742 # package is sufficient to run the tests without it.
736 if os.name == 'nt':
743 if os.name == 'nt':
737 return True
744 return True
738
745
739 return has_tic()
746 return has_tic()
740
747
741 except (ImportError, AttributeError):
748 except (ImportError, AttributeError):
742 return False
749 return False
743
750
744
751
745 @check("tic", "terminfo compiler")
752 @check("tic", "terminfo compiler")
746 def has_tic():
753 def has_tic():
747 return matchoutput('test -x "`which tic`"', br'')
754 return matchoutput('test -x "`which tic`"', br'')
748
755
749
756
750 @check("xz", "xz compression utility")
757 @check("xz", "xz compression utility")
751 def has_xz():
758 def has_xz():
752 # When Windows invokes a subprocess in shell mode, it uses `cmd.exe`, which
759 # When Windows invokes a subprocess in shell mode, it uses `cmd.exe`, which
753 # only knows `where`, not `which`. So invoke MSYS shell explicitly.
760 # only knows `where`, not `which`. So invoke MSYS shell explicitly.
754 return matchoutput("sh -c 'test -x \"`which xz`\"'", b'')
761 return matchoutput("sh -c 'test -x \"`which xz`\"'", b'')
755
762
756
763
757 @check("msys", "Windows with MSYS")
764 @check("msys", "Windows with MSYS")
758 def has_msys():
765 def has_msys():
759 return os.getenv('MSYSTEM')
766 return os.getenv('MSYSTEM')
760
767
761
768
762 @check("aix", "AIX")
769 @check("aix", "AIX")
763 def has_aix():
770 def has_aix():
764 return sys.platform.startswith("aix")
771 return sys.platform.startswith("aix")
765
772
766
773
767 @check("osx", "OS X")
774 @check("osx", "OS X")
768 def has_osx():
775 def has_osx():
769 return sys.platform == 'darwin'
776 return sys.platform == 'darwin'
770
777
771
778
772 @check("osxpackaging", "OS X packaging tools")
779 @check("osxpackaging", "OS X packaging tools")
773 def has_osxpackaging():
780 def has_osxpackaging():
774 try:
781 try:
775 return (
782 return (
776 matchoutput('pkgbuild', br'Usage: pkgbuild ', ignorestatus=1)
783 matchoutput('pkgbuild', br'Usage: pkgbuild ', ignorestatus=1)
777 and matchoutput(
784 and matchoutput(
778 'productbuild', br'Usage: productbuild ', ignorestatus=1
785 'productbuild', br'Usage: productbuild ', ignorestatus=1
779 )
786 )
780 and matchoutput('lsbom', br'Usage: lsbom', ignorestatus=1)
787 and matchoutput('lsbom', br'Usage: lsbom', ignorestatus=1)
781 and matchoutput('xar --help', br'Usage: xar', ignorestatus=1)
788 and matchoutput('xar --help', br'Usage: xar', ignorestatus=1)
782 )
789 )
783 except ImportError:
790 except ImportError:
784 return False
791 return False
785
792
786
793
787 @check('linuxormacos', 'Linux or MacOS')
794 @check('linuxormacos', 'Linux or MacOS')
788 def has_linuxormacos():
795 def has_linuxormacos():
789 # This isn't a perfect test for MacOS. But it is sufficient for our needs.
796 # This isn't a perfect test for MacOS. But it is sufficient for our needs.
790 return sys.platform.startswith(('linux', 'darwin'))
797 return sys.platform.startswith(('linux', 'darwin'))
791
798
792
799
793 @check("docker", "docker support")
800 @check("docker", "docker support")
794 def has_docker():
801 def has_docker():
795 pat = br'A self-sufficient runtime for'
802 pat = br'A self-sufficient runtime for'
796 if matchoutput('docker --help', pat):
803 if matchoutput('docker --help', pat):
797 if 'linux' not in sys.platform:
804 if 'linux' not in sys.platform:
798 # TODO: in theory we should be able to test docker-based
805 # TODO: in theory we should be able to test docker-based
799 # package creation on non-linux using boot2docker, but in
806 # package creation on non-linux using boot2docker, but in
800 # practice that requires extra coordination to make sure
807 # practice that requires extra coordination to make sure
801 # $TESTTEMP is going to be visible at the same path to the
808 # $TESTTEMP is going to be visible at the same path to the
802 # boot2docker VM. If we figure out how to verify that, we
809 # boot2docker VM. If we figure out how to verify that, we
803 # can use the following instead of just saying False:
810 # can use the following instead of just saying False:
804 # return 'DOCKER_HOST' in os.environ
811 # return 'DOCKER_HOST' in os.environ
805 return False
812 return False
806
813
807 return True
814 return True
808 return False
815 return False
809
816
810
817
811 @check("debhelper", "debian packaging tools")
818 @check("debhelper", "debian packaging tools")
812 def has_debhelper():
819 def has_debhelper():
813 # Some versions of dpkg say `dpkg', some say 'dpkg' (` vs ' on the first
820 # Some versions of dpkg say `dpkg', some say 'dpkg' (` vs ' on the first
814 # quote), so just accept anything in that spot.
821 # quote), so just accept anything in that spot.
815 dpkg = matchoutput(
822 dpkg = matchoutput(
816 'dpkg --version', br"Debian .dpkg' package management program"
823 'dpkg --version', br"Debian .dpkg' package management program"
817 )
824 )
818 dh = matchoutput(
825 dh = matchoutput(
819 'dh --help', br'dh is a part of debhelper.', ignorestatus=True
826 'dh --help', br'dh is a part of debhelper.', ignorestatus=True
820 )
827 )
821 dh_py2 = matchoutput(
828 dh_py2 = matchoutput(
822 'dh_python2 --help', br'other supported Python versions'
829 'dh_python2 --help', br'other supported Python versions'
823 )
830 )
824 # debuild comes from the 'devscripts' package, though you might want
831 # debuild comes from the 'devscripts' package, though you might want
825 # the 'build-debs' package instead, which has a dependency on devscripts.
832 # the 'build-debs' package instead, which has a dependency on devscripts.
826 debuild = matchoutput(
833 debuild = matchoutput(
827 'debuild --help', br'to run debian/rules with given parameter'
834 'debuild --help', br'to run debian/rules with given parameter'
828 )
835 )
829 return dpkg and dh and dh_py2 and debuild
836 return dpkg and dh and dh_py2 and debuild
830
837
831
838
832 @check(
839 @check(
833 "debdeps", "debian build dependencies (run dpkg-checkbuilddeps in contrib/)"
840 "debdeps", "debian build dependencies (run dpkg-checkbuilddeps in contrib/)"
834 )
841 )
835 def has_debdeps():
842 def has_debdeps():
836 # just check exit status (ignoring output)
843 # just check exit status (ignoring output)
837 path = '%s/../contrib/packaging/debian/control' % os.environ['TESTDIR']
844 path = '%s/../contrib/packaging/debian/control' % os.environ['TESTDIR']
838 return matchoutput('dpkg-checkbuilddeps %s' % path, br'')
845 return matchoutput('dpkg-checkbuilddeps %s' % path, br'')
839
846
840
847
841 @check("demandimport", "demandimport enabled")
848 @check("demandimport", "demandimport enabled")
842 def has_demandimport():
849 def has_demandimport():
843 # chg disables demandimport intentionally for performance wins.
850 # chg disables demandimport intentionally for performance wins.
844 return (not has_chg()) and os.environ.get('HGDEMANDIMPORT') != 'disable'
851 return (not has_chg()) and os.environ.get('HGDEMANDIMPORT') != 'disable'
845
852
846
853
847 # Add "py27", "py35", ... as possible feature checks. Note that there's no
854 # Add "py27", "py35", ... as possible feature checks. Note that there's no
848 # punctuation here.
855 # punctuation here.
849 @checkvers("py", "Python >= %s", (2.7, 3.5, 3.6, 3.7, 3.8, 3.9))
856 @checkvers("py", "Python >= %s", (2.7, 3.5, 3.6, 3.7, 3.8, 3.9))
850 def has_python_range(v):
857 def has_python_range(v):
851 major, minor = v.split('.')[0:2]
858 major, minor = v.split('.')[0:2]
852 py_major, py_minor = sys.version_info.major, sys.version_info.minor
859 py_major, py_minor = sys.version_info.major, sys.version_info.minor
853
860
854 return (py_major, py_minor) >= (int(major), int(minor))
861 return (py_major, py_minor) >= (int(major), int(minor))
855
862
856
863
857 @check("py3", "running with Python 3.x")
864 @check("py3", "running with Python 3.x")
858 def has_py3():
865 def has_py3():
859 return 3 == sys.version_info[0]
866 return 3 == sys.version_info[0]
860
867
861
868
862 @check("py3exe", "a Python 3.x interpreter is available")
869 @check("py3exe", "a Python 3.x interpreter is available")
863 def has_python3exe():
870 def has_python3exe():
864 py = 'python3'
871 py = 'python3'
865 if os.name == 'nt':
872 if os.name == 'nt':
866 py = 'py -3'
873 py = 'py -3'
867 return matchoutput('%s -V' % py, br'^Python 3.(5|6|7|8|9)')
874 return matchoutput('%s -V' % py, br'^Python 3.(5|6|7|8|9)')
868
875
869
876
870 @check("pure", "running with pure Python code")
877 @check("pure", "running with pure Python code")
871 def has_pure():
878 def has_pure():
872 return any(
879 return any(
873 [
880 [
874 os.environ.get("HGMODULEPOLICY") == "py",
881 os.environ.get("HGMODULEPOLICY") == "py",
875 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure",
882 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure",
876 ]
883 ]
877 )
884 )
878
885
879
886
880 @check("slow", "allow slow tests (use --allow-slow-tests)")
887 @check("slow", "allow slow tests (use --allow-slow-tests)")
881 def has_slow():
888 def has_slow():
882 return os.environ.get('HGTEST_SLOW') == 'slow'
889 return os.environ.get('HGTEST_SLOW') == 'slow'
883
890
884
891
885 @check("hypothesis", "Hypothesis automated test generation")
892 @check("hypothesis", "Hypothesis automated test generation")
886 def has_hypothesis():
893 def has_hypothesis():
887 try:
894 try:
888 import hypothesis
895 import hypothesis
889
896
890 hypothesis.given
897 hypothesis.given
891 return True
898 return True
892 except ImportError:
899 except ImportError:
893 return False
900 return False
894
901
895
902
896 @check("unziplinks", "unzip(1) understands and extracts symlinks")
903 @check("unziplinks", "unzip(1) understands and extracts symlinks")
897 def unzip_understands_symlinks():
904 def unzip_understands_symlinks():
898 return matchoutput('unzip --help', br'Info-ZIP')
905 return matchoutput('unzip --help', br'Info-ZIP')
899
906
900
907
901 @check("zstd", "zstd Python module available")
908 @check("zstd", "zstd Python module available")
902 def has_zstd():
909 def has_zstd():
903 try:
910 try:
904 import mercurial.zstd
911 import mercurial.zstd
905
912
906 mercurial.zstd.__version__
913 mercurial.zstd.__version__
907 return True
914 return True
908 except ImportError:
915 except ImportError:
909 return False
916 return False
910
917
911
918
912 @check("devfull", "/dev/full special file")
919 @check("devfull", "/dev/full special file")
913 def has_dev_full():
920 def has_dev_full():
914 return os.path.exists('/dev/full')
921 return os.path.exists('/dev/full')
915
922
916
923
917 @check("ensurepip", "ensurepip module")
924 @check("ensurepip", "ensurepip module")
918 def has_ensurepip():
925 def has_ensurepip():
919 try:
926 try:
920 import ensurepip
927 import ensurepip
921
928
922 ensurepip.bootstrap
929 ensurepip.bootstrap
923 return True
930 return True
924 except ImportError:
931 except ImportError:
925 return False
932 return False
926
933
927
934
928 @check("virtualenv", "virtualenv support")
935 @check("virtualenv", "virtualenv support")
929 def has_virtualenv():
936 def has_virtualenv():
930 try:
937 try:
931 import virtualenv
938 import virtualenv
932
939
933 # --no-site-package became the default in 1.7 (Nov 2011), and the
940 # --no-site-package became the default in 1.7 (Nov 2011), and the
934 # argument was removed in 20.0 (Feb 2020). Rather than make the
941 # argument was removed in 20.0 (Feb 2020). Rather than make the
935 # script complicated, just ignore ancient versions.
942 # script complicated, just ignore ancient versions.
936 return int(virtualenv.__version__.split('.')[0]) > 1
943 return int(virtualenv.__version__.split('.')[0]) > 1
937 except (AttributeError, ImportError, IndexError):
944 except (AttributeError, ImportError, IndexError):
938 return False
945 return False
939
946
940
947
941 @check("fsmonitor", "running tests with fsmonitor")
948 @check("fsmonitor", "running tests with fsmonitor")
942 def has_fsmonitor():
949 def has_fsmonitor():
943 return 'HGFSMONITOR_TESTS' in os.environ
950 return 'HGFSMONITOR_TESTS' in os.environ
944
951
945
952
946 @check("fuzzywuzzy", "Fuzzy string matching library")
953 @check("fuzzywuzzy", "Fuzzy string matching library")
947 def has_fuzzywuzzy():
954 def has_fuzzywuzzy():
948 try:
955 try:
949 import fuzzywuzzy
956 import fuzzywuzzy
950
957
951 fuzzywuzzy.__version__
958 fuzzywuzzy.__version__
952 return True
959 return True
953 except ImportError:
960 except ImportError:
954 return False
961 return False
955
962
956
963
957 @check("clang-libfuzzer", "clang new enough to include libfuzzer")
964 @check("clang-libfuzzer", "clang new enough to include libfuzzer")
958 def has_clang_libfuzzer():
965 def has_clang_libfuzzer():
959 mat = matchoutput('clang --version', br'clang version (\d)')
966 mat = matchoutput('clang --version', br'clang version (\d)')
960 if mat:
967 if mat:
961 # libfuzzer is new in clang 6
968 # libfuzzer is new in clang 6
962 return int(mat.group(1)) > 5
969 return int(mat.group(1)) > 5
963 return False
970 return False
964
971
965
972
966 @check("clang-6.0", "clang 6.0 with version suffix (libfuzzer included)")
973 @check("clang-6.0", "clang 6.0 with version suffix (libfuzzer included)")
967 def has_clang60():
974 def has_clang60():
968 return matchoutput('clang-6.0 --version', br'clang version 6\.')
975 return matchoutput('clang-6.0 --version', br'clang version 6\.')
969
976
970
977
971 @check("xdiff", "xdiff algorithm")
978 @check("xdiff", "xdiff algorithm")
972 def has_xdiff():
979 def has_xdiff():
973 try:
980 try:
974 from mercurial import policy
981 from mercurial import policy
975
982
976 bdiff = policy.importmod('bdiff')
983 bdiff = policy.importmod('bdiff')
977 return bdiff.xdiffblocks(b'', b'') == [(0, 0, 0, 0)]
984 return bdiff.xdiffblocks(b'', b'') == [(0, 0, 0, 0)]
978 except (ImportError, AttributeError):
985 except (ImportError, AttributeError):
979 return False
986 return False
980
987
981
988
982 @check('extraextensions', 'whether tests are running with extra extensions')
989 @check('extraextensions', 'whether tests are running with extra extensions')
983 def has_extraextensions():
990 def has_extraextensions():
984 return 'HGTESTEXTRAEXTENSIONS' in os.environ
991 return 'HGTESTEXTRAEXTENSIONS' in os.environ
985
992
986
993
987 def getrepofeatures():
994 def getrepofeatures():
988 """Obtain set of repository features in use.
995 """Obtain set of repository features in use.
989
996
990 HGREPOFEATURES can be used to define or remove features. It contains
997 HGREPOFEATURES can be used to define or remove features. It contains
991 a space-delimited list of feature strings. Strings beginning with ``-``
998 a space-delimited list of feature strings. Strings beginning with ``-``
992 mean to remove.
999 mean to remove.
993 """
1000 """
994 # Default list provided by core.
1001 # Default list provided by core.
995 features = {
1002 features = {
996 'bundlerepo',
1003 'bundlerepo',
997 'revlogstore',
1004 'revlogstore',
998 'fncache',
1005 'fncache',
999 }
1006 }
1000
1007
1001 # Features that imply other features.
1008 # Features that imply other features.
1002 implies = {
1009 implies = {
1003 'simplestore': ['-revlogstore', '-bundlerepo', '-fncache'],
1010 'simplestore': ['-revlogstore', '-bundlerepo', '-fncache'],
1004 }
1011 }
1005
1012
1006 for override in os.environ.get('HGREPOFEATURES', '').split(' '):
1013 for override in os.environ.get('HGREPOFEATURES', '').split(' '):
1007 if not override:
1014 if not override:
1008 continue
1015 continue
1009
1016
1010 if override.startswith('-'):
1017 if override.startswith('-'):
1011 if override[1:] in features:
1018 if override[1:] in features:
1012 features.remove(override[1:])
1019 features.remove(override[1:])
1013 else:
1020 else:
1014 features.add(override)
1021 features.add(override)
1015
1022
1016 for imply in implies.get(override, []):
1023 for imply in implies.get(override, []):
1017 if imply.startswith('-'):
1024 if imply.startswith('-'):
1018 if imply[1:] in features:
1025 if imply[1:] in features:
1019 features.remove(imply[1:])
1026 features.remove(imply[1:])
1020 else:
1027 else:
1021 features.add(imply)
1028 features.add(imply)
1022
1029
1023 return features
1030 return features
1024
1031
1025
1032
1026 @check('reporevlogstore', 'repository using the default revlog store')
1033 @check('reporevlogstore', 'repository using the default revlog store')
1027 def has_reporevlogstore():
1034 def has_reporevlogstore():
1028 return 'revlogstore' in getrepofeatures()
1035 return 'revlogstore' in getrepofeatures()
1029
1036
1030
1037
1031 @check('reposimplestore', 'repository using simple storage extension')
1038 @check('reposimplestore', 'repository using simple storage extension')
1032 def has_reposimplestore():
1039 def has_reposimplestore():
1033 return 'simplestore' in getrepofeatures()
1040 return 'simplestore' in getrepofeatures()
1034
1041
1035
1042
1036 @check('repobundlerepo', 'whether we can open bundle files as repos')
1043 @check('repobundlerepo', 'whether we can open bundle files as repos')
1037 def has_repobundlerepo():
1044 def has_repobundlerepo():
1038 return 'bundlerepo' in getrepofeatures()
1045 return 'bundlerepo' in getrepofeatures()
1039
1046
1040
1047
1041 @check('repofncache', 'repository has an fncache')
1048 @check('repofncache', 'repository has an fncache')
1042 def has_repofncache():
1049 def has_repofncache():
1043 return 'fncache' in getrepofeatures()
1050 return 'fncache' in getrepofeatures()
1044
1051
1045
1052
1046 @check('dirstate-v2', 'using the v2 format of .hg/dirstate')
1053 @check('dirstate-v2', 'using the v2 format of .hg/dirstate')
1047 def has_dirstate_v2():
1054 def has_dirstate_v2():
1048 # Keep this logic in sync with `newreporequirements()` in `mercurial/localrepo.py`
1055 # Keep this logic in sync with `newreporequirements()` in `mercurial/localrepo.py`
1049 return has_rust() and matchoutput(
1056 return has_rust() and matchoutput(
1050 'hg config format.exp-dirstate-v2', b'(?i)1|yes|true|on|always'
1057 'hg config format.exp-dirstate-v2', b'(?i)1|yes|true|on|always'
1051 )
1058 )
1052
1059
1053
1060
1054 @check('sqlite', 'sqlite3 module and matching cli is available')
1061 @check('sqlite', 'sqlite3 module and matching cli is available')
1055 def has_sqlite():
1062 def has_sqlite():
1056 try:
1063 try:
1057 import sqlite3
1064 import sqlite3
1058
1065
1059 version = sqlite3.sqlite_version_info
1066 version = sqlite3.sqlite_version_info
1060 except ImportError:
1067 except ImportError:
1061 return False
1068 return False
1062
1069
1063 if version < (3, 8, 3):
1070 if version < (3, 8, 3):
1064 # WITH clause not supported
1071 # WITH clause not supported
1065 return False
1072 return False
1066
1073
1067 return matchoutput('sqlite3 -version', br'^3\.\d+')
1074 return matchoutput('sqlite3 -version', br'^3\.\d+')
1068
1075
1069
1076
1070 @check('vcr', 'vcr http mocking library (pytest-vcr)')
1077 @check('vcr', 'vcr http mocking library (pytest-vcr)')
1071 def has_vcr():
1078 def has_vcr():
1072 try:
1079 try:
1073 import vcr
1080 import vcr
1074
1081
1075 vcr.VCR
1082 vcr.VCR
1076 return True
1083 return True
1077 except (ImportError, AttributeError):
1084 except (ImportError, AttributeError):
1078 pass
1085 pass
1079 return False
1086 return False
1080
1087
1081
1088
1082 @check('emacs', 'GNU Emacs')
1089 @check('emacs', 'GNU Emacs')
1083 def has_emacs():
1090 def has_emacs():
1084 # Our emacs lisp uses `with-eval-after-load` which is new in emacs
1091 # Our emacs lisp uses `with-eval-after-load` which is new in emacs
1085 # 24.4, so we allow emacs 24.4, 24.5, and 25+ (24.5 was the last
1092 # 24.4, so we allow emacs 24.4, 24.5, and 25+ (24.5 was the last
1086 # 24 release)
1093 # 24 release)
1087 return matchoutput('emacs --version', b'GNU Emacs 2(4.4|4.5|5|6|7|8|9)')
1094 return matchoutput('emacs --version', b'GNU Emacs 2(4.4|4.5|5|6|7|8|9)')
1088
1095
1089
1096
1090 @check('black', 'the black formatter for python (>= 20.8b1)')
1097 @check('black', 'the black formatter for python (>= 20.8b1)')
1091 def has_black():
1098 def has_black():
1092 blackcmd = 'black --version'
1099 blackcmd = 'black --version'
1093 version_regex = b'black, version ([0-9a-b.]+)'
1100 version_regex = b'black, version ([0-9a-b.]+)'
1094 version = matchoutput(blackcmd, version_regex)
1101 version = matchoutput(blackcmd, version_regex)
1095 sv = distutils.version.StrictVersion
1102 sv = distutils.version.StrictVersion
1096 return version and sv(_bytes2sys(version.group(1))) >= sv('20.8b1')
1103 return version and sv(_bytes2sys(version.group(1))) >= sv('20.8b1')
1097
1104
1098
1105
1099 @check('pytype', 'the pytype type checker')
1106 @check('pytype', 'the pytype type checker')
1100 def has_pytype():
1107 def has_pytype():
1101 pytypecmd = 'pytype --version'
1108 pytypecmd = 'pytype --version'
1102 version = matchoutput(pytypecmd, b'[0-9a-b.]+')
1109 version = matchoutput(pytypecmd, b'[0-9a-b.]+')
1103 sv = distutils.version.StrictVersion
1110 sv = distutils.version.StrictVersion
1104 return version and sv(_bytes2sys(version.group(0))) >= sv('2019.10.17')
1111 return version and sv(_bytes2sys(version.group(0))) >= sv('2019.10.17')
1105
1112
1106
1113
1107 @check("rustfmt", "rustfmt tool at version nightly-2020-10-04")
1114 @check("rustfmt", "rustfmt tool at version nightly-2020-10-04")
1108 def has_rustfmt():
1115 def has_rustfmt():
1109 # We use Nightly's rustfmt due to current unstable config options.
1116 # We use Nightly's rustfmt due to current unstable config options.
1110 return matchoutput(
1117 return matchoutput(
1111 '`rustup which --toolchain nightly-2020-10-04 rustfmt` --version',
1118 '`rustup which --toolchain nightly-2020-10-04 rustfmt` --version',
1112 b'rustfmt',
1119 b'rustfmt',
1113 )
1120 )
1114
1121
1115
1122
1116 @check("cargo", "cargo tool")
1123 @check("cargo", "cargo tool")
1117 def has_cargo():
1124 def has_cargo():
1118 return matchoutput('`rustup which cargo` --version', b'cargo')
1125 return matchoutput('`rustup which cargo` --version', b'cargo')
1119
1126
1120
1127
1121 @check("lzma", "python lzma module")
1128 @check("lzma", "python lzma module")
1122 def has_lzma():
1129 def has_lzma():
1123 try:
1130 try:
1124 import _lzma
1131 import _lzma
1125
1132
1126 _lzma.FORMAT_XZ
1133 _lzma.FORMAT_XZ
1127 return True
1134 return True
1128 except ImportError:
1135 except ImportError:
1129 return False
1136 return False
1130
1137
1131
1138
1132 @check("bash", "bash shell")
1139 @check("bash", "bash shell")
1133 def has_bash():
1140 def has_bash():
1134 return matchoutput("bash -c 'echo hi'", b'^hi$')
1141 return matchoutput("bash -c 'echo hi'", b'^hi$')
General Comments 0
You need to be logged in to leave comments. Login now