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