##// END OF EJS Templates
hghave: cache the result of gethgversion...
Julien Cristau -
r44760:e48a996d default
parent child Browse files
Show More
@@ -1,1041 +1,1051 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 _bytespath(p):
32 def _bytespath(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 _strpath(p):
37 def _strpath(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 _bytespath(p):
45 def _bytespath(p):
46 return p
46 return p
47
47
48 _strpath = _bytespath
48 _strpath = _bytespath
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
318
319
320 def gethgversion():
321 global _hgversion
322 if _hgversion is None:
323 _hgversion = _gethgversion()
324 return _hgversion
325
326
317 @checkvers(
327 @checkvers(
318 "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)])
319 )
329 )
320 def has_hg_range(v):
330 def has_hg_range(v):
321 major, minor = v.split('.')[0:2]
331 major, minor = v.split('.')[0:2]
322 return gethgversion() >= (int(major), int(minor))
332 return gethgversion() >= (int(major), int(minor))
323
333
324
334
325 @check("hg08", "Mercurial >= 0.8")
335 @check("hg08", "Mercurial >= 0.8")
326 def has_hg08():
336 def has_hg08():
327 if checks["hg09"][0]():
337 if checks["hg09"][0]():
328 return True
338 return True
329 return matchoutput('hg help annotate 2>&1', '--date')
339 return matchoutput('hg help annotate 2>&1', '--date')
330
340
331
341
332 @check("hg07", "Mercurial >= 0.7")
342 @check("hg07", "Mercurial >= 0.7")
333 def has_hg07():
343 def has_hg07():
334 if checks["hg08"][0]():
344 if checks["hg08"][0]():
335 return True
345 return True
336 return matchoutput('hg --version --quiet 2>&1', 'Mercurial Distributed SCM')
346 return matchoutput('hg --version --quiet 2>&1', 'Mercurial Distributed SCM')
337
347
338
348
339 @check("hg06", "Mercurial >= 0.6")
349 @check("hg06", "Mercurial >= 0.6")
340 def has_hg06():
350 def has_hg06():
341 if checks["hg07"][0]():
351 if checks["hg07"][0]():
342 return True
352 return True
343 return matchoutput('hg --version --quiet 2>&1', 'Mercurial version')
353 return matchoutput('hg --version --quiet 2>&1', 'Mercurial version')
344
354
345
355
346 @check("gettext", "GNU Gettext (msgfmt)")
356 @check("gettext", "GNU Gettext (msgfmt)")
347 def has_gettext():
357 def has_gettext():
348 return matchoutput('msgfmt --version', br'GNU gettext-tools')
358 return matchoutput('msgfmt --version', br'GNU gettext-tools')
349
359
350
360
351 @check("git", "git command line client")
361 @check("git", "git command line client")
352 def has_git():
362 def has_git():
353 return matchoutput('git --version 2>&1', br'^git version')
363 return matchoutput('git --version 2>&1', br'^git version')
354
364
355
365
356 def getgitversion():
366 def getgitversion():
357 m = matchoutput('git --version 2>&1', br'git version (\d+)\.(\d+)')
367 m = matchoutput('git --version 2>&1', br'git version (\d+)\.(\d+)')
358 if not m:
368 if not m:
359 return (0, 0)
369 return (0, 0)
360 return (int(m.group(1)), int(m.group(2)))
370 return (int(m.group(1)), int(m.group(2)))
361
371
362
372
363 # https://github.com/git-lfs/lfs-test-server
373 # https://github.com/git-lfs/lfs-test-server
364 @check("lfs-test-server", "git-lfs test server")
374 @check("lfs-test-server", "git-lfs test server")
365 def has_lfsserver():
375 def has_lfsserver():
366 exe = 'lfs-test-server'
376 exe = 'lfs-test-server'
367 if has_windows():
377 if has_windows():
368 exe = 'lfs-test-server.exe'
378 exe = 'lfs-test-server.exe'
369 return any(
379 return any(
370 os.access(os.path.join(path, exe), os.X_OK)
380 os.access(os.path.join(path, exe), os.X_OK)
371 for path in os.environ["PATH"].split(os.pathsep)
381 for path in os.environ["PATH"].split(os.pathsep)
372 )
382 )
373
383
374
384
375 @checkvers("git", "git client (with ext::sh support) version >= %s", (1.9,))
385 @checkvers("git", "git client (with ext::sh support) version >= %s", (1.9,))
376 def has_git_range(v):
386 def has_git_range(v):
377 major, minor = v.split('.')[0:2]
387 major, minor = v.split('.')[0:2]
378 return getgitversion() >= (int(major), int(minor))
388 return getgitversion() >= (int(major), int(minor))
379
389
380
390
381 @check("docutils", "Docutils text processing library")
391 @check("docutils", "Docutils text processing library")
382 def has_docutils():
392 def has_docutils():
383 try:
393 try:
384 import docutils.core
394 import docutils.core
385
395
386 docutils.core.publish_cmdline # silence unused import
396 docutils.core.publish_cmdline # silence unused import
387 return True
397 return True
388 except ImportError:
398 except ImportError:
389 return False
399 return False
390
400
391
401
392 def getsvnversion():
402 def getsvnversion():
393 m = matchoutput('svn --version --quiet 2>&1', br'^(\d+)\.(\d+)')
403 m = matchoutput('svn --version --quiet 2>&1', br'^(\d+)\.(\d+)')
394 if not m:
404 if not m:
395 return (0, 0)
405 return (0, 0)
396 return (int(m.group(1)), int(m.group(2)))
406 return (int(m.group(1)), int(m.group(2)))
397
407
398
408
399 @checkvers("svn", "subversion client and admin tools >= %s", (1.3, 1.5))
409 @checkvers("svn", "subversion client and admin tools >= %s", (1.3, 1.5))
400 def has_svn_range(v):
410 def has_svn_range(v):
401 major, minor = v.split('.')[0:2]
411 major, minor = v.split('.')[0:2]
402 return getsvnversion() >= (int(major), int(minor))
412 return getsvnversion() >= (int(major), int(minor))
403
413
404
414
405 @check("svn", "subversion client and admin tools")
415 @check("svn", "subversion client and admin tools")
406 def has_svn():
416 def has_svn():
407 return matchoutput('svn --version 2>&1', br'^svn, version') and matchoutput(
417 return matchoutput('svn --version 2>&1', br'^svn, version') and matchoutput(
408 'svnadmin --version 2>&1', br'^svnadmin, version'
418 'svnadmin --version 2>&1', br'^svnadmin, version'
409 )
419 )
410
420
411
421
412 @check("svn-bindings", "subversion python bindings")
422 @check("svn-bindings", "subversion python bindings")
413 def has_svn_bindings():
423 def has_svn_bindings():
414 try:
424 try:
415 import svn.core
425 import svn.core
416
426
417 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
427 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
418 if version < (1, 4):
428 if version < (1, 4):
419 return False
429 return False
420 return True
430 return True
421 except ImportError:
431 except ImportError:
422 return False
432 return False
423
433
424
434
425 @check("p4", "Perforce server and client")
435 @check("p4", "Perforce server and client")
426 def has_p4():
436 def has_p4():
427 return matchoutput('p4 -V', br'Rev\. P4/') and matchoutput(
437 return matchoutput('p4 -V', br'Rev\. P4/') and matchoutput(
428 'p4d -V', br'Rev\. P4D/'
438 'p4d -V', br'Rev\. P4D/'
429 )
439 )
430
440
431
441
432 @check("symlink", "symbolic links")
442 @check("symlink", "symbolic links")
433 def has_symlink():
443 def has_symlink():
434 # mercurial.windows.checklink() is a hard 'no' at the moment
444 # mercurial.windows.checklink() is a hard 'no' at the moment
435 if os.name == 'nt' or getattr(os, "symlink", None) is None:
445 if os.name == 'nt' or getattr(os, "symlink", None) is None:
436 return False
446 return False
437 name = tempfile.mktemp(dir='.', prefix=tempprefix)
447 name = tempfile.mktemp(dir='.', prefix=tempprefix)
438 try:
448 try:
439 os.symlink(".", name)
449 os.symlink(".", name)
440 os.unlink(name)
450 os.unlink(name)
441 return True
451 return True
442 except (OSError, AttributeError):
452 except (OSError, AttributeError):
443 return False
453 return False
444
454
445
455
446 @check("hardlink", "hardlinks")
456 @check("hardlink", "hardlinks")
447 def has_hardlink():
457 def has_hardlink():
448 from mercurial import util
458 from mercurial import util
449
459
450 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
460 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
451 os.close(fh)
461 os.close(fh)
452 name = tempfile.mktemp(dir='.', prefix=tempprefix)
462 name = tempfile.mktemp(dir='.', prefix=tempprefix)
453 try:
463 try:
454 util.oslink(_bytespath(fn), _bytespath(name))
464 util.oslink(_bytespath(fn), _bytespath(name))
455 os.unlink(name)
465 os.unlink(name)
456 return True
466 return True
457 except OSError:
467 except OSError:
458 return False
468 return False
459 finally:
469 finally:
460 os.unlink(fn)
470 os.unlink(fn)
461
471
462
472
463 @check("hardlink-whitelisted", "hardlinks on whitelisted filesystems")
473 @check("hardlink-whitelisted", "hardlinks on whitelisted filesystems")
464 def has_hardlink_whitelisted():
474 def has_hardlink_whitelisted():
465 from mercurial import util
475 from mercurial import util
466
476
467 try:
477 try:
468 fstype = util.getfstype(b'.')
478 fstype = util.getfstype(b'.')
469 except OSError:
479 except OSError:
470 return False
480 return False
471 return fstype in util._hardlinkfswhitelist
481 return fstype in util._hardlinkfswhitelist
472
482
473
483
474 @check("rmcwd", "can remove current working directory")
484 @check("rmcwd", "can remove current working directory")
475 def has_rmcwd():
485 def has_rmcwd():
476 ocwd = os.getcwd()
486 ocwd = os.getcwd()
477 temp = tempfile.mkdtemp(dir='.', prefix=tempprefix)
487 temp = tempfile.mkdtemp(dir='.', prefix=tempprefix)
478 try:
488 try:
479 os.chdir(temp)
489 os.chdir(temp)
480 # On Linux, 'rmdir .' isn't allowed, but the other names are okay.
490 # On Linux, 'rmdir .' isn't allowed, but the other names are okay.
481 # On Solaris and Windows, the cwd can't be removed by any names.
491 # On Solaris and Windows, the cwd can't be removed by any names.
482 os.rmdir(os.getcwd())
492 os.rmdir(os.getcwd())
483 return True
493 return True
484 except OSError:
494 except OSError:
485 return False
495 return False
486 finally:
496 finally:
487 os.chdir(ocwd)
497 os.chdir(ocwd)
488 # clean up temp dir on platforms where cwd can't be removed
498 # clean up temp dir on platforms where cwd can't be removed
489 try:
499 try:
490 os.rmdir(temp)
500 os.rmdir(temp)
491 except OSError:
501 except OSError:
492 pass
502 pass
493
503
494
504
495 @check("tla", "GNU Arch tla client")
505 @check("tla", "GNU Arch tla client")
496 def has_tla():
506 def has_tla():
497 return matchoutput('tla --version 2>&1', br'The GNU Arch Revision')
507 return matchoutput('tla --version 2>&1', br'The GNU Arch Revision')
498
508
499
509
500 @check("gpg", "gpg client")
510 @check("gpg", "gpg client")
501 def has_gpg():
511 def has_gpg():
502 return matchoutput('gpg --version 2>&1', br'GnuPG')
512 return matchoutput('gpg --version 2>&1', br'GnuPG')
503
513
504
514
505 @check("gpg2", "gpg client v2")
515 @check("gpg2", "gpg client v2")
506 def has_gpg2():
516 def has_gpg2():
507 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.')
517 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.')
508
518
509
519
510 @check("gpg21", "gpg client v2.1+")
520 @check("gpg21", "gpg client v2.1+")
511 def has_gpg21():
521 def has_gpg21():
512 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.(?!0)')
522 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.(?!0)')
513
523
514
524
515 @check("unix-permissions", "unix-style permissions")
525 @check("unix-permissions", "unix-style permissions")
516 def has_unix_permissions():
526 def has_unix_permissions():
517 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
527 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
518 try:
528 try:
519 fname = os.path.join(d, 'foo')
529 fname = os.path.join(d, 'foo')
520 for umask in (0o77, 0o07, 0o22):
530 for umask in (0o77, 0o07, 0o22):
521 os.umask(umask)
531 os.umask(umask)
522 f = open(fname, 'w')
532 f = open(fname, 'w')
523 f.close()
533 f.close()
524 mode = os.stat(fname).st_mode
534 mode = os.stat(fname).st_mode
525 os.unlink(fname)
535 os.unlink(fname)
526 if mode & 0o777 != ~umask & 0o666:
536 if mode & 0o777 != ~umask & 0o666:
527 return False
537 return False
528 return True
538 return True
529 finally:
539 finally:
530 os.rmdir(d)
540 os.rmdir(d)
531
541
532
542
533 @check("unix-socket", "AF_UNIX socket family")
543 @check("unix-socket", "AF_UNIX socket family")
534 def has_unix_socket():
544 def has_unix_socket():
535 return getattr(socket, 'AF_UNIX', None) is not None
545 return getattr(socket, 'AF_UNIX', None) is not None
536
546
537
547
538 @check("root", "root permissions")
548 @check("root", "root permissions")
539 def has_root():
549 def has_root():
540 return getattr(os, 'geteuid', None) and os.geteuid() == 0
550 return getattr(os, 'geteuid', None) and os.geteuid() == 0
541
551
542
552
543 @check("pyflakes", "Pyflakes python linter")
553 @check("pyflakes", "Pyflakes python linter")
544 def has_pyflakes():
554 def has_pyflakes():
545 return matchoutput(
555 return matchoutput(
546 "sh -c \"echo 'import re' 2>&1 | pyflakes\"",
556 "sh -c \"echo 'import re' 2>&1 | pyflakes\"",
547 br"<stdin>:1: 're' imported but unused",
557 br"<stdin>:1: 're' imported but unused",
548 True,
558 True,
549 )
559 )
550
560
551
561
552 @check("pylint", "Pylint python linter")
562 @check("pylint", "Pylint python linter")
553 def has_pylint():
563 def has_pylint():
554 return matchoutput("pylint --help", br"Usage: pylint", True)
564 return matchoutput("pylint --help", br"Usage: pylint", True)
555
565
556
566
557 @check("clang-format", "clang-format C code formatter")
567 @check("clang-format", "clang-format C code formatter")
558 def has_clang_format():
568 def has_clang_format():
559 m = matchoutput('clang-format --version', br'clang-format version (\d)')
569 m = matchoutput('clang-format --version', br'clang-format version (\d)')
560 # style changed somewhere between 4.x and 6.x
570 # style changed somewhere between 4.x and 6.x
561 return m and int(m.group(1)) >= 6
571 return m and int(m.group(1)) >= 6
562
572
563
573
564 @check("jshint", "JSHint static code analysis tool")
574 @check("jshint", "JSHint static code analysis tool")
565 def has_jshint():
575 def has_jshint():
566 return matchoutput("jshint --version 2>&1", br"jshint v")
576 return matchoutput("jshint --version 2>&1", br"jshint v")
567
577
568
578
569 @check("pygments", "Pygments source highlighting library")
579 @check("pygments", "Pygments source highlighting library")
570 def has_pygments():
580 def has_pygments():
571 try:
581 try:
572 import pygments
582 import pygments
573
583
574 pygments.highlight # silence unused import warning
584 pygments.highlight # silence unused import warning
575 return True
585 return True
576 except ImportError:
586 except ImportError:
577 return False
587 return False
578
588
579
589
580 @check("pygments25", "Pygments version >= 2.5")
590 @check("pygments25", "Pygments version >= 2.5")
581 def pygments25():
591 def pygments25():
582 try:
592 try:
583 import pygments
593 import pygments
584
594
585 v = pygments.__version__
595 v = pygments.__version__
586 except ImportError:
596 except ImportError:
587 return False
597 return False
588
598
589 parts = v.split(".")
599 parts = v.split(".")
590 major = int(parts[0])
600 major = int(parts[0])
591 minor = int(parts[1])
601 minor = int(parts[1])
592
602
593 return (major, minor) >= (2, 5)
603 return (major, minor) >= (2, 5)
594
604
595
605
596 @check("outer-repo", "outer repo")
606 @check("outer-repo", "outer repo")
597 def has_outer_repo():
607 def has_outer_repo():
598 # failing for other reasons than 'no repo' imply that there is a repo
608 # failing for other reasons than 'no repo' imply that there is a repo
599 return not matchoutput('hg root 2>&1', br'abort: no repository found', True)
609 return not matchoutput('hg root 2>&1', br'abort: no repository found', True)
600
610
601
611
602 @check("ssl", "ssl module available")
612 @check("ssl", "ssl module available")
603 def has_ssl():
613 def has_ssl():
604 try:
614 try:
605 import ssl
615 import ssl
606
616
607 ssl.CERT_NONE
617 ssl.CERT_NONE
608 return True
618 return True
609 except ImportError:
619 except ImportError:
610 return False
620 return False
611
621
612
622
613 @check("sslcontext", "python >= 2.7.9 ssl")
623 @check("sslcontext", "python >= 2.7.9 ssl")
614 def has_sslcontext():
624 def has_sslcontext():
615 try:
625 try:
616 import ssl
626 import ssl
617
627
618 ssl.SSLContext
628 ssl.SSLContext
619 return True
629 return True
620 except (ImportError, AttributeError):
630 except (ImportError, AttributeError):
621 return False
631 return False
622
632
623
633
624 @check("defaultcacerts", "can verify SSL certs by system's CA certs store")
634 @check("defaultcacerts", "can verify SSL certs by system's CA certs store")
625 def has_defaultcacerts():
635 def has_defaultcacerts():
626 from mercurial import sslutil, ui as uimod
636 from mercurial import sslutil, ui as uimod
627
637
628 ui = uimod.ui.load()
638 ui = uimod.ui.load()
629 return sslutil._defaultcacerts(ui) or sslutil._canloaddefaultcerts
639 return sslutil._defaultcacerts(ui) or sslutil._canloaddefaultcerts
630
640
631
641
632 @check("defaultcacertsloaded", "detected presence of loaded system CA certs")
642 @check("defaultcacertsloaded", "detected presence of loaded system CA certs")
633 def has_defaultcacertsloaded():
643 def has_defaultcacertsloaded():
634 import ssl
644 import ssl
635 from mercurial import sslutil, ui as uimod
645 from mercurial import sslutil, ui as uimod
636
646
637 if not has_defaultcacerts():
647 if not has_defaultcacerts():
638 return False
648 return False
639 if not has_sslcontext():
649 if not has_sslcontext():
640 return False
650 return False
641
651
642 ui = uimod.ui.load()
652 ui = uimod.ui.load()
643 cafile = sslutil._defaultcacerts(ui)
653 cafile = sslutil._defaultcacerts(ui)
644 ctx = ssl.create_default_context()
654 ctx = ssl.create_default_context()
645 if cafile:
655 if cafile:
646 ctx.load_verify_locations(cafile=cafile)
656 ctx.load_verify_locations(cafile=cafile)
647 else:
657 else:
648 ctx.load_default_certs()
658 ctx.load_default_certs()
649
659
650 return len(ctx.get_ca_certs()) > 0
660 return len(ctx.get_ca_certs()) > 0
651
661
652
662
653 @check("tls1.2", "TLS 1.2 protocol support")
663 @check("tls1.2", "TLS 1.2 protocol support")
654 def has_tls1_2():
664 def has_tls1_2():
655 from mercurial import sslutil
665 from mercurial import sslutil
656
666
657 return b'tls1.2' in sslutil.supportedprotocols
667 return b'tls1.2' in sslutil.supportedprotocols
658
668
659
669
660 @check("windows", "Windows")
670 @check("windows", "Windows")
661 def has_windows():
671 def has_windows():
662 return os.name == 'nt'
672 return os.name == 'nt'
663
673
664
674
665 @check("system-sh", "system() uses sh")
675 @check("system-sh", "system() uses sh")
666 def has_system_sh():
676 def has_system_sh():
667 return os.name != 'nt'
677 return os.name != 'nt'
668
678
669
679
670 @check("serve", "platform and python can manage 'hg serve -d'")
680 @check("serve", "platform and python can manage 'hg serve -d'")
671 def has_serve():
681 def has_serve():
672 return True
682 return True
673
683
674
684
675 @check("test-repo", "running tests from repository")
685 @check("test-repo", "running tests from repository")
676 def has_test_repo():
686 def has_test_repo():
677 t = os.environ["TESTDIR"]
687 t = os.environ["TESTDIR"]
678 return os.path.isdir(os.path.join(t, "..", ".hg"))
688 return os.path.isdir(os.path.join(t, "..", ".hg"))
679
689
680
690
681 @check("tic", "terminfo compiler and curses module")
691 @check("tic", "terminfo compiler and curses module")
682 def has_tic():
692 def has_tic():
683 try:
693 try:
684 import curses
694 import curses
685
695
686 curses.COLOR_BLUE
696 curses.COLOR_BLUE
687 return matchoutput('test -x "`which tic`"', br'')
697 return matchoutput('test -x "`which tic`"', br'')
688 except (ImportError, AttributeError):
698 except (ImportError, AttributeError):
689 return False
699 return False
690
700
691
701
692 @check("xz", "xz compression utility")
702 @check("xz", "xz compression utility")
693 def has_xz():
703 def has_xz():
694 # When Windows invokes a subprocess in shell mode, it uses `cmd.exe`, which
704 # When Windows invokes a subprocess in shell mode, it uses `cmd.exe`, which
695 # only knows `where`, not `which`. So invoke MSYS shell explicitly.
705 # only knows `where`, not `which`. So invoke MSYS shell explicitly.
696 return matchoutput("sh -c 'test -x \"`which xz`\"'", b'')
706 return matchoutput("sh -c 'test -x \"`which xz`\"'", b'')
697
707
698
708
699 @check("msys", "Windows with MSYS")
709 @check("msys", "Windows with MSYS")
700 def has_msys():
710 def has_msys():
701 return os.getenv('MSYSTEM')
711 return os.getenv('MSYSTEM')
702
712
703
713
704 @check("aix", "AIX")
714 @check("aix", "AIX")
705 def has_aix():
715 def has_aix():
706 return sys.platform.startswith("aix")
716 return sys.platform.startswith("aix")
707
717
708
718
709 @check("osx", "OS X")
719 @check("osx", "OS X")
710 def has_osx():
720 def has_osx():
711 return sys.platform == 'darwin'
721 return sys.platform == 'darwin'
712
722
713
723
714 @check("osxpackaging", "OS X packaging tools")
724 @check("osxpackaging", "OS X packaging tools")
715 def has_osxpackaging():
725 def has_osxpackaging():
716 try:
726 try:
717 return (
727 return (
718 matchoutput('pkgbuild', br'Usage: pkgbuild ', ignorestatus=1)
728 matchoutput('pkgbuild', br'Usage: pkgbuild ', ignorestatus=1)
719 and matchoutput(
729 and matchoutput(
720 'productbuild', br'Usage: productbuild ', ignorestatus=1
730 'productbuild', br'Usage: productbuild ', ignorestatus=1
721 )
731 )
722 and matchoutput('lsbom', br'Usage: lsbom', ignorestatus=1)
732 and matchoutput('lsbom', br'Usage: lsbom', ignorestatus=1)
723 and matchoutput('xar --help', br'Usage: xar', ignorestatus=1)
733 and matchoutput('xar --help', br'Usage: xar', ignorestatus=1)
724 )
734 )
725 except ImportError:
735 except ImportError:
726 return False
736 return False
727
737
728
738
729 @check('linuxormacos', 'Linux or MacOS')
739 @check('linuxormacos', 'Linux or MacOS')
730 def has_linuxormacos():
740 def has_linuxormacos():
731 # This isn't a perfect test for MacOS. But it is sufficient for our needs.
741 # This isn't a perfect test for MacOS. But it is sufficient for our needs.
732 return sys.platform.startswith(('linux', 'darwin'))
742 return sys.platform.startswith(('linux', 'darwin'))
733
743
734
744
735 @check("docker", "docker support")
745 @check("docker", "docker support")
736 def has_docker():
746 def has_docker():
737 pat = br'A self-sufficient runtime for'
747 pat = br'A self-sufficient runtime for'
738 if matchoutput('docker --help', pat):
748 if matchoutput('docker --help', pat):
739 if 'linux' not in sys.platform:
749 if 'linux' not in sys.platform:
740 # TODO: in theory we should be able to test docker-based
750 # TODO: in theory we should be able to test docker-based
741 # package creation on non-linux using boot2docker, but in
751 # package creation on non-linux using boot2docker, but in
742 # practice that requires extra coordination to make sure
752 # practice that requires extra coordination to make sure
743 # $TESTTEMP is going to be visible at the same path to the
753 # $TESTTEMP is going to be visible at the same path to the
744 # boot2docker VM. If we figure out how to verify that, we
754 # boot2docker VM. If we figure out how to verify that, we
745 # can use the following instead of just saying False:
755 # can use the following instead of just saying False:
746 # return 'DOCKER_HOST' in os.environ
756 # return 'DOCKER_HOST' in os.environ
747 return False
757 return False
748
758
749 return True
759 return True
750 return False
760 return False
751
761
752
762
753 @check("debhelper", "debian packaging tools")
763 @check("debhelper", "debian packaging tools")
754 def has_debhelper():
764 def has_debhelper():
755 # Some versions of dpkg say `dpkg', some say 'dpkg' (` vs ' on the first
765 # Some versions of dpkg say `dpkg', some say 'dpkg' (` vs ' on the first
756 # quote), so just accept anything in that spot.
766 # quote), so just accept anything in that spot.
757 dpkg = matchoutput(
767 dpkg = matchoutput(
758 'dpkg --version', br"Debian .dpkg' package management program"
768 'dpkg --version', br"Debian .dpkg' package management program"
759 )
769 )
760 dh = matchoutput(
770 dh = matchoutput(
761 'dh --help', br'dh is a part of debhelper.', ignorestatus=True
771 'dh --help', br'dh is a part of debhelper.', ignorestatus=True
762 )
772 )
763 dh_py2 = matchoutput(
773 dh_py2 = matchoutput(
764 'dh_python2 --help', br'other supported Python versions'
774 'dh_python2 --help', br'other supported Python versions'
765 )
775 )
766 # debuild comes from the 'devscripts' package, though you might want
776 # debuild comes from the 'devscripts' package, though you might want
767 # the 'build-debs' package instead, which has a dependency on devscripts.
777 # the 'build-debs' package instead, which has a dependency on devscripts.
768 debuild = matchoutput(
778 debuild = matchoutput(
769 'debuild --help', br'to run debian/rules with given parameter'
779 'debuild --help', br'to run debian/rules with given parameter'
770 )
780 )
771 return dpkg and dh and dh_py2 and debuild
781 return dpkg and dh and dh_py2 and debuild
772
782
773
783
774 @check(
784 @check(
775 "debdeps", "debian build dependencies (run dpkg-checkbuilddeps in contrib/)"
785 "debdeps", "debian build dependencies (run dpkg-checkbuilddeps in contrib/)"
776 )
786 )
777 def has_debdeps():
787 def has_debdeps():
778 # just check exit status (ignoring output)
788 # just check exit status (ignoring output)
779 path = '%s/../contrib/packaging/debian/control' % os.environ['TESTDIR']
789 path = '%s/../contrib/packaging/debian/control' % os.environ['TESTDIR']
780 return matchoutput('dpkg-checkbuilddeps %s' % path, br'')
790 return matchoutput('dpkg-checkbuilddeps %s' % path, br'')
781
791
782
792
783 @check("demandimport", "demandimport enabled")
793 @check("demandimport", "demandimport enabled")
784 def has_demandimport():
794 def has_demandimport():
785 # chg disables demandimport intentionally for performance wins.
795 # chg disables demandimport intentionally for performance wins.
786 return (not has_chg()) and os.environ.get('HGDEMANDIMPORT') != 'disable'
796 return (not has_chg()) and os.environ.get('HGDEMANDIMPORT') != 'disable'
787
797
788
798
789 # Add "py27", "py35", ... as possible feature checks. Note that there's no
799 # Add "py27", "py35", ... as possible feature checks. Note that there's no
790 # punctuation here.
800 # punctuation here.
791 @checkvers("py", "Python >= %s", (2.7, 3.5, 3.6, 3.7, 3.8, 3.9))
801 @checkvers("py", "Python >= %s", (2.7, 3.5, 3.6, 3.7, 3.8, 3.9))
792 def has_python_range(v):
802 def has_python_range(v):
793 major, minor = v.split('.')[0:2]
803 major, minor = v.split('.')[0:2]
794 py_major, py_minor = sys.version_info.major, sys.version_info.minor
804 py_major, py_minor = sys.version_info.major, sys.version_info.minor
795
805
796 return (py_major, py_minor) >= (int(major), int(minor))
806 return (py_major, py_minor) >= (int(major), int(minor))
797
807
798
808
799 @check("py3", "running with Python 3.x")
809 @check("py3", "running with Python 3.x")
800 def has_py3():
810 def has_py3():
801 return 3 == sys.version_info[0]
811 return 3 == sys.version_info[0]
802
812
803
813
804 @check("py3exe", "a Python 3.x interpreter is available")
814 @check("py3exe", "a Python 3.x interpreter is available")
805 def has_python3exe():
815 def has_python3exe():
806 return matchoutput('python3 -V', br'^Python 3.(5|6|7|8|9)')
816 return matchoutput('python3 -V', br'^Python 3.(5|6|7|8|9)')
807
817
808
818
809 @check("pure", "running with pure Python code")
819 @check("pure", "running with pure Python code")
810 def has_pure():
820 def has_pure():
811 return any(
821 return any(
812 [
822 [
813 os.environ.get("HGMODULEPOLICY") == "py",
823 os.environ.get("HGMODULEPOLICY") == "py",
814 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure",
824 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure",
815 ]
825 ]
816 )
826 )
817
827
818
828
819 @check("slow", "allow slow tests (use --allow-slow-tests)")
829 @check("slow", "allow slow tests (use --allow-slow-tests)")
820 def has_slow():
830 def has_slow():
821 return os.environ.get('HGTEST_SLOW') == 'slow'
831 return os.environ.get('HGTEST_SLOW') == 'slow'
822
832
823
833
824 @check("hypothesis", "Hypothesis automated test generation")
834 @check("hypothesis", "Hypothesis automated test generation")
825 def has_hypothesis():
835 def has_hypothesis():
826 try:
836 try:
827 import hypothesis
837 import hypothesis
828
838
829 hypothesis.given
839 hypothesis.given
830 return True
840 return True
831 except ImportError:
841 except ImportError:
832 return False
842 return False
833
843
834
844
835 @check("unziplinks", "unzip(1) understands and extracts symlinks")
845 @check("unziplinks", "unzip(1) understands and extracts symlinks")
836 def unzip_understands_symlinks():
846 def unzip_understands_symlinks():
837 return matchoutput('unzip --help', br'Info-ZIP')
847 return matchoutput('unzip --help', br'Info-ZIP')
838
848
839
849
840 @check("zstd", "zstd Python module available")
850 @check("zstd", "zstd Python module available")
841 def has_zstd():
851 def has_zstd():
842 try:
852 try:
843 import mercurial.zstd
853 import mercurial.zstd
844
854
845 mercurial.zstd.__version__
855 mercurial.zstd.__version__
846 return True
856 return True
847 except ImportError:
857 except ImportError:
848 return False
858 return False
849
859
850
860
851 @check("devfull", "/dev/full special file")
861 @check("devfull", "/dev/full special file")
852 def has_dev_full():
862 def has_dev_full():
853 return os.path.exists('/dev/full')
863 return os.path.exists('/dev/full')
854
864
855
865
856 @check("ensurepip", "ensurepip module")
866 @check("ensurepip", "ensurepip module")
857 def has_ensurepip():
867 def has_ensurepip():
858 try:
868 try:
859 import ensurepip
869 import ensurepip
860
870
861 ensurepip.bootstrap
871 ensurepip.bootstrap
862 return True
872 return True
863 except ImportError:
873 except ImportError:
864 return False
874 return False
865
875
866
876
867 @check("virtualenv", "Python virtualenv support")
877 @check("virtualenv", "Python virtualenv support")
868 def has_virtualenv():
878 def has_virtualenv():
869 try:
879 try:
870 import virtualenv
880 import virtualenv
871
881
872 virtualenv.ACTIVATE_SH
882 virtualenv.ACTIVATE_SH
873 return True
883 return True
874 except ImportError:
884 except ImportError:
875 return False
885 return False
876
886
877
887
878 @check("fsmonitor", "running tests with fsmonitor")
888 @check("fsmonitor", "running tests with fsmonitor")
879 def has_fsmonitor():
889 def has_fsmonitor():
880 return 'HGFSMONITOR_TESTS' in os.environ
890 return 'HGFSMONITOR_TESTS' in os.environ
881
891
882
892
883 @check("fuzzywuzzy", "Fuzzy string matching library")
893 @check("fuzzywuzzy", "Fuzzy string matching library")
884 def has_fuzzywuzzy():
894 def has_fuzzywuzzy():
885 try:
895 try:
886 import fuzzywuzzy
896 import fuzzywuzzy
887
897
888 fuzzywuzzy.__version__
898 fuzzywuzzy.__version__
889 return True
899 return True
890 except ImportError:
900 except ImportError:
891 return False
901 return False
892
902
893
903
894 @check("clang-libfuzzer", "clang new enough to include libfuzzer")
904 @check("clang-libfuzzer", "clang new enough to include libfuzzer")
895 def has_clang_libfuzzer():
905 def has_clang_libfuzzer():
896 mat = matchoutput('clang --version', br'clang version (\d)')
906 mat = matchoutput('clang --version', br'clang version (\d)')
897 if mat:
907 if mat:
898 # libfuzzer is new in clang 6
908 # libfuzzer is new in clang 6
899 return int(mat.group(1)) > 5
909 return int(mat.group(1)) > 5
900 return False
910 return False
901
911
902
912
903 @check("clang-6.0", "clang 6.0 with version suffix (libfuzzer included)")
913 @check("clang-6.0", "clang 6.0 with version suffix (libfuzzer included)")
904 def has_clang60():
914 def has_clang60():
905 return matchoutput('clang-6.0 --version', br'clang version 6\.')
915 return matchoutput('clang-6.0 --version', br'clang version 6\.')
906
916
907
917
908 @check("xdiff", "xdiff algorithm")
918 @check("xdiff", "xdiff algorithm")
909 def has_xdiff():
919 def has_xdiff():
910 try:
920 try:
911 from mercurial import policy
921 from mercurial import policy
912
922
913 bdiff = policy.importmod('bdiff')
923 bdiff = policy.importmod('bdiff')
914 return bdiff.xdiffblocks(b'', b'') == [(0, 0, 0, 0)]
924 return bdiff.xdiffblocks(b'', b'') == [(0, 0, 0, 0)]
915 except (ImportError, AttributeError):
925 except (ImportError, AttributeError):
916 return False
926 return False
917
927
918
928
919 @check('extraextensions', 'whether tests are running with extra extensions')
929 @check('extraextensions', 'whether tests are running with extra extensions')
920 def has_extraextensions():
930 def has_extraextensions():
921 return 'HGTESTEXTRAEXTENSIONS' in os.environ
931 return 'HGTESTEXTRAEXTENSIONS' in os.environ
922
932
923
933
924 def getrepofeatures():
934 def getrepofeatures():
925 """Obtain set of repository features in use.
935 """Obtain set of repository features in use.
926
936
927 HGREPOFEATURES can be used to define or remove features. It contains
937 HGREPOFEATURES can be used to define or remove features. It contains
928 a space-delimited list of feature strings. Strings beginning with ``-``
938 a space-delimited list of feature strings. Strings beginning with ``-``
929 mean to remove.
939 mean to remove.
930 """
940 """
931 # Default list provided by core.
941 # Default list provided by core.
932 features = {
942 features = {
933 'bundlerepo',
943 'bundlerepo',
934 'revlogstore',
944 'revlogstore',
935 'fncache',
945 'fncache',
936 }
946 }
937
947
938 # Features that imply other features.
948 # Features that imply other features.
939 implies = {
949 implies = {
940 'simplestore': ['-revlogstore', '-bundlerepo', '-fncache'],
950 'simplestore': ['-revlogstore', '-bundlerepo', '-fncache'],
941 }
951 }
942
952
943 for override in os.environ.get('HGREPOFEATURES', '').split(' '):
953 for override in os.environ.get('HGREPOFEATURES', '').split(' '):
944 if not override:
954 if not override:
945 continue
955 continue
946
956
947 if override.startswith('-'):
957 if override.startswith('-'):
948 if override[1:] in features:
958 if override[1:] in features:
949 features.remove(override[1:])
959 features.remove(override[1:])
950 else:
960 else:
951 features.add(override)
961 features.add(override)
952
962
953 for imply in implies.get(override, []):
963 for imply in implies.get(override, []):
954 if imply.startswith('-'):
964 if imply.startswith('-'):
955 if imply[1:] in features:
965 if imply[1:] in features:
956 features.remove(imply[1:])
966 features.remove(imply[1:])
957 else:
967 else:
958 features.add(imply)
968 features.add(imply)
959
969
960 return features
970 return features
961
971
962
972
963 @check('reporevlogstore', 'repository using the default revlog store')
973 @check('reporevlogstore', 'repository using the default revlog store')
964 def has_reporevlogstore():
974 def has_reporevlogstore():
965 return 'revlogstore' in getrepofeatures()
975 return 'revlogstore' in getrepofeatures()
966
976
967
977
968 @check('reposimplestore', 'repository using simple storage extension')
978 @check('reposimplestore', 'repository using simple storage extension')
969 def has_reposimplestore():
979 def has_reposimplestore():
970 return 'simplestore' in getrepofeatures()
980 return 'simplestore' in getrepofeatures()
971
981
972
982
973 @check('repobundlerepo', 'whether we can open bundle files as repos')
983 @check('repobundlerepo', 'whether we can open bundle files as repos')
974 def has_repobundlerepo():
984 def has_repobundlerepo():
975 return 'bundlerepo' in getrepofeatures()
985 return 'bundlerepo' in getrepofeatures()
976
986
977
987
978 @check('repofncache', 'repository has an fncache')
988 @check('repofncache', 'repository has an fncache')
979 def has_repofncache():
989 def has_repofncache():
980 return 'fncache' in getrepofeatures()
990 return 'fncache' in getrepofeatures()
981
991
982
992
983 @check('sqlite', 'sqlite3 module is available')
993 @check('sqlite', 'sqlite3 module is available')
984 def has_sqlite():
994 def has_sqlite():
985 try:
995 try:
986 import sqlite3
996 import sqlite3
987
997
988 version = sqlite3.sqlite_version_info
998 version = sqlite3.sqlite_version_info
989 except ImportError:
999 except ImportError:
990 return False
1000 return False
991
1001
992 if version < (3, 8, 3):
1002 if version < (3, 8, 3):
993 # WITH clause not supported
1003 # WITH clause not supported
994 return False
1004 return False
995
1005
996 return matchoutput('sqlite3 -version', br'^3\.\d+')
1006 return matchoutput('sqlite3 -version', br'^3\.\d+')
997
1007
998
1008
999 @check('vcr', 'vcr http mocking library')
1009 @check('vcr', 'vcr http mocking library')
1000 def has_vcr():
1010 def has_vcr():
1001 try:
1011 try:
1002 import vcr
1012 import vcr
1003
1013
1004 vcr.VCR
1014 vcr.VCR
1005 return True
1015 return True
1006 except (ImportError, AttributeError):
1016 except (ImportError, AttributeError):
1007 pass
1017 pass
1008 return False
1018 return False
1009
1019
1010
1020
1011 @check('emacs', 'GNU Emacs')
1021 @check('emacs', 'GNU Emacs')
1012 def has_emacs():
1022 def has_emacs():
1013 # Our emacs lisp uses `with-eval-after-load` which is new in emacs
1023 # Our emacs lisp uses `with-eval-after-load` which is new in emacs
1014 # 24.4, so we allow emacs 24.4, 24.5, and 25+ (24.5 was the last
1024 # 24.4, so we allow emacs 24.4, 24.5, and 25+ (24.5 was the last
1015 # 24 release)
1025 # 24 release)
1016 return matchoutput('emacs --version', b'GNU Emacs 2(4.4|4.5|5|6|7|8|9)')
1026 return matchoutput('emacs --version', b'GNU Emacs 2(4.4|4.5|5|6|7|8|9)')
1017
1027
1018
1028
1019 @check('black', 'the black formatter for python')
1029 @check('black', 'the black formatter for python')
1020 def has_black():
1030 def has_black():
1021 blackcmd = 'black --version'
1031 blackcmd = 'black --version'
1022 version_regex = b'black, version ([0-9a-b.]+)'
1032 version_regex = b'black, version ([0-9a-b.]+)'
1023 version = matchoutput(blackcmd, version_regex)
1033 version = matchoutput(blackcmd, version_regex)
1024 sv = distutils.version.StrictVersion
1034 sv = distutils.version.StrictVersion
1025 return version and sv(_strpath(version.group(1))) >= sv('19.10b0')
1035 return version and sv(_strpath(version.group(1))) >= sv('19.10b0')
1026
1036
1027
1037
1028 @check('pytype', 'the pytype type checker')
1038 @check('pytype', 'the pytype type checker')
1029 def has_pytype():
1039 def has_pytype():
1030 pytypecmd = 'pytype --version'
1040 pytypecmd = 'pytype --version'
1031 version = matchoutput(pytypecmd, b'[0-9a-b.]+')
1041 version = matchoutput(pytypecmd, b'[0-9a-b.]+')
1032 sv = distutils.version.StrictVersion
1042 sv = distutils.version.StrictVersion
1033 return version and sv(_strpath(version.group(0))) >= sv('2019.10.17')
1043 return version and sv(_strpath(version.group(0))) >= sv('2019.10.17')
1034
1044
1035
1045
1036 @check("rustfmt", "rustfmt tool")
1046 @check("rustfmt", "rustfmt tool")
1037 def has_rustfmt():
1047 def has_rustfmt():
1038 # We use Nightly's rustfmt due to current unstable config options.
1048 # We use Nightly's rustfmt due to current unstable config options.
1039 return matchoutput(
1049 return matchoutput(
1040 '`rustup which --toolchain nightly rustfmt` --version', b'rustfmt'
1050 '`rustup which --toolchain nightly rustfmt` --version', b'rustfmt'
1041 )
1051 )
General Comments 0
You need to be logged in to leave comments. Login now