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