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