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