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