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