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