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