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