##// END OF EJS Templates
hghave: use util.getfstype
Yuya Nishihara -
r31674:b33e352c default
parent child Browse files
Show More
@@ -1,633 +1,633 b''
1 from __future__ import absolute_import
1 from __future__ import absolute_import
2
2
3 import errno
3 import errno
4 import os
4 import os
5 import re
5 import re
6 import socket
6 import socket
7 import stat
7 import stat
8 import subprocess
8 import subprocess
9 import sys
9 import sys
10 import tempfile
10 import tempfile
11
11
12 tempprefix = 'hg-hghave-'
12 tempprefix = 'hg-hghave-'
13
13
14 checks = {
14 checks = {
15 "true": (lambda: True, "yak shaving"),
15 "true": (lambda: True, "yak shaving"),
16 "false": (lambda: False, "nail clipper"),
16 "false": (lambda: False, "nail clipper"),
17 }
17 }
18
18
19 def check(name, desc):
19 def check(name, desc):
20 """Registers a check function for a feature."""
20 """Registers a check function for a feature."""
21 def decorator(func):
21 def decorator(func):
22 checks[name] = (func, desc)
22 checks[name] = (func, desc)
23 return func
23 return func
24 return decorator
24 return decorator
25
25
26 def checkvers(name, desc, vers):
26 def checkvers(name, desc, vers):
27 """Registers a check function for each of a series of versions.
27 """Registers a check function for each of a series of versions.
28
28
29 vers can be a list or an iterator"""
29 vers can be a list or an iterator"""
30 def decorator(func):
30 def decorator(func):
31 def funcv(v):
31 def funcv(v):
32 def f():
32 def f():
33 return func(v)
33 return func(v)
34 return f
34 return f
35 for v in vers:
35 for v in vers:
36 v = str(v)
36 v = str(v)
37 f = funcv(v)
37 f = funcv(v)
38 checks['%s%s' % (name, v.replace('.', ''))] = (f, desc % v)
38 checks['%s%s' % (name, v.replace('.', ''))] = (f, desc % v)
39 return func
39 return func
40 return decorator
40 return decorator
41
41
42 def checkfeatures(features):
42 def checkfeatures(features):
43 result = {
43 result = {
44 'error': [],
44 'error': [],
45 'missing': [],
45 'missing': [],
46 'skipped': [],
46 'skipped': [],
47 }
47 }
48
48
49 for feature in features:
49 for feature in features:
50 negate = feature.startswith('no-')
50 negate = feature.startswith('no-')
51 if negate:
51 if negate:
52 feature = feature[3:]
52 feature = feature[3:]
53
53
54 if feature not in checks:
54 if feature not in checks:
55 result['missing'].append(feature)
55 result['missing'].append(feature)
56 continue
56 continue
57
57
58 check, desc = checks[feature]
58 check, desc = checks[feature]
59 try:
59 try:
60 available = check()
60 available = check()
61 except Exception:
61 except Exception:
62 result['error'].append('hghave check failed: %s' % feature)
62 result['error'].append('hghave check failed: %s' % feature)
63 continue
63 continue
64
64
65 if not negate and not available:
65 if not negate and not available:
66 result['skipped'].append('missing feature: %s' % desc)
66 result['skipped'].append('missing feature: %s' % desc)
67 elif negate and available:
67 elif negate and available:
68 result['skipped'].append('system supports %s' % desc)
68 result['skipped'].append('system supports %s' % desc)
69
69
70 return result
70 return result
71
71
72 def require(features):
72 def require(features):
73 """Require that features are available, exiting if not."""
73 """Require that features are available, exiting if not."""
74 result = checkfeatures(features)
74 result = checkfeatures(features)
75
75
76 for missing in result['missing']:
76 for missing in result['missing']:
77 sys.stderr.write('skipped: unknown feature: %s\n' % missing)
77 sys.stderr.write('skipped: unknown feature: %s\n' % missing)
78 for msg in result['skipped']:
78 for msg in result['skipped']:
79 sys.stderr.write('skipped: %s\n' % msg)
79 sys.stderr.write('skipped: %s\n' % msg)
80 for msg in result['error']:
80 for msg in result['error']:
81 sys.stderr.write('%s\n' % msg)
81 sys.stderr.write('%s\n' % msg)
82
82
83 if result['missing']:
83 if result['missing']:
84 sys.exit(2)
84 sys.exit(2)
85
85
86 if result['skipped'] or result['error']:
86 if result['skipped'] or result['error']:
87 sys.exit(1)
87 sys.exit(1)
88
88
89 def matchoutput(cmd, regexp, ignorestatus=False):
89 def matchoutput(cmd, regexp, ignorestatus=False):
90 """Return the match object if cmd executes successfully and its output
90 """Return the match object if cmd executes successfully and its output
91 is matched by the supplied regular expression.
91 is matched by the supplied regular expression.
92 """
92 """
93 r = re.compile(regexp)
93 r = re.compile(regexp)
94 try:
94 try:
95 p = subprocess.Popen(
95 p = subprocess.Popen(
96 cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
96 cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
97 except OSError as e:
97 except OSError as e:
98 if e.errno != errno.ENOENT:
98 if e.errno != errno.ENOENT:
99 raise
99 raise
100 ret = -1
100 ret = -1
101 ret = p.wait()
101 ret = p.wait()
102 s = p.stdout.read()
102 s = p.stdout.read()
103 return (ignorestatus or not ret) and r.search(s)
103 return (ignorestatus or not ret) and r.search(s)
104
104
105 @check("baz", "GNU Arch baz client")
105 @check("baz", "GNU Arch baz client")
106 def has_baz():
106 def has_baz():
107 return matchoutput('baz --version 2>&1', br'baz Bazaar version')
107 return matchoutput('baz --version 2>&1', br'baz Bazaar version')
108
108
109 @check("bzr", "Canonical's Bazaar client")
109 @check("bzr", "Canonical's Bazaar client")
110 def has_bzr():
110 def has_bzr():
111 try:
111 try:
112 import bzrlib
112 import bzrlib
113 import bzrlib.bzrdir
113 import bzrlib.bzrdir
114 import bzrlib.errors
114 import bzrlib.errors
115 import bzrlib.revision
115 import bzrlib.revision
116 import bzrlib.revisionspec
116 import bzrlib.revisionspec
117 bzrlib.revisionspec.RevisionSpec
117 bzrlib.revisionspec.RevisionSpec
118 return bzrlib.__doc__ is not None
118 return bzrlib.__doc__ is not None
119 except (AttributeError, ImportError):
119 except (AttributeError, ImportError):
120 return False
120 return False
121
121
122 @checkvers("bzr", "Canonical's Bazaar client >= %s", (1.14,))
122 @checkvers("bzr", "Canonical's Bazaar client >= %s", (1.14,))
123 def has_bzr_range(v):
123 def has_bzr_range(v):
124 major, minor = v.split('.')[0:2]
124 major, minor = v.split('.')[0:2]
125 try:
125 try:
126 import bzrlib
126 import bzrlib
127 return (bzrlib.__doc__ is not None
127 return (bzrlib.__doc__ is not None
128 and bzrlib.version_info[:2] >= (int(major), int(minor)))
128 and bzrlib.version_info[:2] >= (int(major), int(minor)))
129 except ImportError:
129 except ImportError:
130 return False
130 return False
131
131
132 @check("chg", "running with chg")
132 @check("chg", "running with chg")
133 def has_chg():
133 def has_chg():
134 return 'CHGHG' in os.environ
134 return 'CHGHG' in os.environ
135
135
136 @check("cvs", "cvs client/server")
136 @check("cvs", "cvs client/server")
137 def has_cvs():
137 def has_cvs():
138 re = br'Concurrent Versions System.*?server'
138 re = br'Concurrent Versions System.*?server'
139 return matchoutput('cvs --version 2>&1', re) and not has_msys()
139 return matchoutput('cvs --version 2>&1', re) and not has_msys()
140
140
141 @check("cvs112", "cvs client/server 1.12.* (not cvsnt)")
141 @check("cvs112", "cvs client/server 1.12.* (not cvsnt)")
142 def has_cvs112():
142 def has_cvs112():
143 re = br'Concurrent Versions System \(CVS\) 1.12.*?server'
143 re = br'Concurrent Versions System \(CVS\) 1.12.*?server'
144 return matchoutput('cvs --version 2>&1', re) and not has_msys()
144 return matchoutput('cvs --version 2>&1', re) and not has_msys()
145
145
146 @check("cvsnt", "cvsnt client/server")
146 @check("cvsnt", "cvsnt client/server")
147 def has_cvsnt():
147 def has_cvsnt():
148 re = br'Concurrent Versions System \(CVSNT\) (\d+).(\d+).*\(client/server\)'
148 re = br'Concurrent Versions System \(CVSNT\) (\d+).(\d+).*\(client/server\)'
149 return matchoutput('cvsnt --version 2>&1', re)
149 return matchoutput('cvsnt --version 2>&1', re)
150
150
151 @check("darcs", "darcs client")
151 @check("darcs", "darcs client")
152 def has_darcs():
152 def has_darcs():
153 return matchoutput('darcs --version', br'\b2\.([2-9]|\d{2})', True)
153 return matchoutput('darcs --version', br'\b2\.([2-9]|\d{2})', True)
154
154
155 @check("mtn", "monotone client (>= 1.0)")
155 @check("mtn", "monotone client (>= 1.0)")
156 def has_mtn():
156 def has_mtn():
157 return matchoutput('mtn --version', br'monotone', True) and not matchoutput(
157 return matchoutput('mtn --version', br'monotone', True) and not matchoutput(
158 'mtn --version', br'monotone 0\.', True)
158 'mtn --version', br'monotone 0\.', True)
159
159
160 @check("eol-in-paths", "end-of-lines in paths")
160 @check("eol-in-paths", "end-of-lines in paths")
161 def has_eol_in_paths():
161 def has_eol_in_paths():
162 try:
162 try:
163 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r')
163 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r')
164 os.close(fd)
164 os.close(fd)
165 os.remove(path)
165 os.remove(path)
166 return True
166 return True
167 except (IOError, OSError):
167 except (IOError, OSError):
168 return False
168 return False
169
169
170 @check("execbit", "executable bit")
170 @check("execbit", "executable bit")
171 def has_executablebit():
171 def has_executablebit():
172 try:
172 try:
173 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
173 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
174 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
174 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
175 try:
175 try:
176 os.close(fh)
176 os.close(fh)
177 m = os.stat(fn).st_mode & 0o777
177 m = os.stat(fn).st_mode & 0o777
178 new_file_has_exec = m & EXECFLAGS
178 new_file_has_exec = m & EXECFLAGS
179 os.chmod(fn, m ^ EXECFLAGS)
179 os.chmod(fn, m ^ EXECFLAGS)
180 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0o777) == m)
180 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0o777) == m)
181 finally:
181 finally:
182 os.unlink(fn)
182 os.unlink(fn)
183 except (IOError, OSError):
183 except (IOError, OSError):
184 # we don't care, the user probably won't be able to commit anyway
184 # we don't care, the user probably won't be able to commit anyway
185 return False
185 return False
186 return not (new_file_has_exec or exec_flags_cannot_flip)
186 return not (new_file_has_exec or exec_flags_cannot_flip)
187
187
188 @check("icasefs", "case insensitive file system")
188 @check("icasefs", "case insensitive file system")
189 def has_icasefs():
189 def has_icasefs():
190 # Stolen from mercurial.util
190 # Stolen from mercurial.util
191 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
191 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
192 os.close(fd)
192 os.close(fd)
193 try:
193 try:
194 s1 = os.stat(path)
194 s1 = os.stat(path)
195 d, b = os.path.split(path)
195 d, b = os.path.split(path)
196 p2 = os.path.join(d, b.upper())
196 p2 = os.path.join(d, b.upper())
197 if path == p2:
197 if path == p2:
198 p2 = os.path.join(d, b.lower())
198 p2 = os.path.join(d, b.lower())
199 try:
199 try:
200 s2 = os.stat(p2)
200 s2 = os.stat(p2)
201 return s2 == s1
201 return s2 == s1
202 except OSError:
202 except OSError:
203 return False
203 return False
204 finally:
204 finally:
205 os.remove(path)
205 os.remove(path)
206
206
207 @check("fifo", "named pipes")
207 @check("fifo", "named pipes")
208 def has_fifo():
208 def has_fifo():
209 if getattr(os, "mkfifo", None) is None:
209 if getattr(os, "mkfifo", None) is None:
210 return False
210 return False
211 name = tempfile.mktemp(dir='.', prefix=tempprefix)
211 name = tempfile.mktemp(dir='.', prefix=tempprefix)
212 try:
212 try:
213 os.mkfifo(name)
213 os.mkfifo(name)
214 os.unlink(name)
214 os.unlink(name)
215 return True
215 return True
216 except OSError:
216 except OSError:
217 return False
217 return False
218
218
219 @check("killdaemons", 'killdaemons.py support')
219 @check("killdaemons", 'killdaemons.py support')
220 def has_killdaemons():
220 def has_killdaemons():
221 return True
221 return True
222
222
223 @check("cacheable", "cacheable filesystem")
223 @check("cacheable", "cacheable filesystem")
224 def has_cacheable_fs():
224 def has_cacheable_fs():
225 from mercurial import util
225 from mercurial import util
226
226
227 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
227 fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
228 os.close(fd)
228 os.close(fd)
229 try:
229 try:
230 return util.cachestat(path).cacheable()
230 return util.cachestat(path).cacheable()
231 finally:
231 finally:
232 os.remove(path)
232 os.remove(path)
233
233
234 @check("lsprof", "python lsprof module")
234 @check("lsprof", "python lsprof module")
235 def has_lsprof():
235 def has_lsprof():
236 try:
236 try:
237 import _lsprof
237 import _lsprof
238 _lsprof.Profiler # silence unused import warning
238 _lsprof.Profiler # silence unused import warning
239 return True
239 return True
240 except ImportError:
240 except ImportError:
241 return False
241 return False
242
242
243 def gethgversion():
243 def gethgversion():
244 m = matchoutput('hg --version --quiet 2>&1', br'(\d+)\.(\d+)')
244 m = matchoutput('hg --version --quiet 2>&1', br'(\d+)\.(\d+)')
245 if not m:
245 if not m:
246 return (0, 0)
246 return (0, 0)
247 return (int(m.group(1)), int(m.group(2)))
247 return (int(m.group(1)), int(m.group(2)))
248
248
249 @checkvers("hg", "Mercurial >= %s",
249 @checkvers("hg", "Mercurial >= %s",
250 list([(1.0 * x) / 10 for x in range(9, 40)]))
250 list([(1.0 * x) / 10 for x in range(9, 40)]))
251 def has_hg_range(v):
251 def has_hg_range(v):
252 major, minor = v.split('.')[0:2]
252 major, minor = v.split('.')[0:2]
253 return gethgversion() >= (int(major), int(minor))
253 return gethgversion() >= (int(major), int(minor))
254
254
255 @check("hg08", "Mercurial >= 0.8")
255 @check("hg08", "Mercurial >= 0.8")
256 def has_hg08():
256 def has_hg08():
257 if checks["hg09"][0]():
257 if checks["hg09"][0]():
258 return True
258 return True
259 return matchoutput('hg help annotate 2>&1', '--date')
259 return matchoutput('hg help annotate 2>&1', '--date')
260
260
261 @check("hg07", "Mercurial >= 0.7")
261 @check("hg07", "Mercurial >= 0.7")
262 def has_hg07():
262 def has_hg07():
263 if checks["hg08"][0]():
263 if checks["hg08"][0]():
264 return True
264 return True
265 return matchoutput('hg --version --quiet 2>&1', 'Mercurial Distributed SCM')
265 return matchoutput('hg --version --quiet 2>&1', 'Mercurial Distributed SCM')
266
266
267 @check("hg06", "Mercurial >= 0.6")
267 @check("hg06", "Mercurial >= 0.6")
268 def has_hg06():
268 def has_hg06():
269 if checks["hg07"][0]():
269 if checks["hg07"][0]():
270 return True
270 return True
271 return matchoutput('hg --version --quiet 2>&1', 'Mercurial version')
271 return matchoutput('hg --version --quiet 2>&1', 'Mercurial version')
272
272
273 @check("gettext", "GNU Gettext (msgfmt)")
273 @check("gettext", "GNU Gettext (msgfmt)")
274 def has_gettext():
274 def has_gettext():
275 return matchoutput('msgfmt --version', br'GNU gettext-tools')
275 return matchoutput('msgfmt --version', br'GNU gettext-tools')
276
276
277 @check("git", "git command line client")
277 @check("git", "git command line client")
278 def has_git():
278 def has_git():
279 return matchoutput('git --version 2>&1', br'^git version')
279 return matchoutput('git --version 2>&1', br'^git version')
280
280
281 @check("docutils", "Docutils text processing library")
281 @check("docutils", "Docutils text processing library")
282 def has_docutils():
282 def has_docutils():
283 try:
283 try:
284 import docutils.core
284 import docutils.core
285 docutils.core.publish_cmdline # silence unused import
285 docutils.core.publish_cmdline # silence unused import
286 return True
286 return True
287 except ImportError:
287 except ImportError:
288 return False
288 return False
289
289
290 def getsvnversion():
290 def getsvnversion():
291 m = matchoutput('svn --version --quiet 2>&1', br'^(\d+)\.(\d+)')
291 m = matchoutput('svn --version --quiet 2>&1', br'^(\d+)\.(\d+)')
292 if not m:
292 if not m:
293 return (0, 0)
293 return (0, 0)
294 return (int(m.group(1)), int(m.group(2)))
294 return (int(m.group(1)), int(m.group(2)))
295
295
296 @checkvers("svn", "subversion client and admin tools >= %s", (1.3, 1.5))
296 @checkvers("svn", "subversion client and admin tools >= %s", (1.3, 1.5))
297 def has_svn_range(v):
297 def has_svn_range(v):
298 major, minor = v.split('.')[0:2]
298 major, minor = v.split('.')[0:2]
299 return getsvnversion() >= (int(major), int(minor))
299 return getsvnversion() >= (int(major), int(minor))
300
300
301 @check("svn", "subversion client and admin tools")
301 @check("svn", "subversion client and admin tools")
302 def has_svn():
302 def has_svn():
303 return matchoutput('svn --version 2>&1', br'^svn, version') and \
303 return matchoutput('svn --version 2>&1', br'^svn, version') and \
304 matchoutput('svnadmin --version 2>&1', br'^svnadmin, version')
304 matchoutput('svnadmin --version 2>&1', br'^svnadmin, version')
305
305
306 @check("svn-bindings", "subversion python bindings")
306 @check("svn-bindings", "subversion python bindings")
307 def has_svn_bindings():
307 def has_svn_bindings():
308 try:
308 try:
309 import svn.core
309 import svn.core
310 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
310 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
311 if version < (1, 4):
311 if version < (1, 4):
312 return False
312 return False
313 return True
313 return True
314 except ImportError:
314 except ImportError:
315 return False
315 return False
316
316
317 @check("p4", "Perforce server and client")
317 @check("p4", "Perforce server and client")
318 def has_p4():
318 def has_p4():
319 return (matchoutput('p4 -V', br'Rev\. P4/') and
319 return (matchoutput('p4 -V', br'Rev\. P4/') and
320 matchoutput('p4d -V', br'Rev\. P4D/'))
320 matchoutput('p4d -V', br'Rev\. P4D/'))
321
321
322 @check("symlink", "symbolic links")
322 @check("symlink", "symbolic links")
323 def has_symlink():
323 def has_symlink():
324 if getattr(os, "symlink", None) is None:
324 if getattr(os, "symlink", None) is None:
325 return False
325 return False
326 name = tempfile.mktemp(dir='.', prefix=tempprefix)
326 name = tempfile.mktemp(dir='.', prefix=tempprefix)
327 try:
327 try:
328 os.symlink(".", name)
328 os.symlink(".", name)
329 os.unlink(name)
329 os.unlink(name)
330 return True
330 return True
331 except (OSError, AttributeError):
331 except (OSError, AttributeError):
332 return False
332 return False
333
333
334 @check("hardlink", "hardlinks")
334 @check("hardlink", "hardlinks")
335 def has_hardlink():
335 def has_hardlink():
336 from mercurial import util
336 from mercurial import util
337 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
337 fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
338 os.close(fh)
338 os.close(fh)
339 name = tempfile.mktemp(dir='.', prefix=tempprefix)
339 name = tempfile.mktemp(dir='.', prefix=tempprefix)
340 try:
340 try:
341 util.oslink(fn, name)
341 util.oslink(fn, name)
342 os.unlink(name)
342 os.unlink(name)
343 return True
343 return True
344 except OSError:
344 except OSError:
345 return False
345 return False
346 finally:
346 finally:
347 os.unlink(fn)
347 os.unlink(fn)
348
348
349 @check("hardlink-whitelisted", "hardlinks on whitelisted filesystems")
349 @check("hardlink-whitelisted", "hardlinks on whitelisted filesystems")
350 def has_hardlink_whitelisted():
350 def has_hardlink_whitelisted():
351 from mercurial import osutil, util
351 from mercurial import util
352 fstype = getattr(osutil, 'getfstype', lambda x: None)('.')
352 fstype = util.getfstype('.')
353 return fstype in util._hardlinkfswhitelist
353 return fstype in util._hardlinkfswhitelist
354
354
355 @check("rmcwd", "can remove current working directory")
355 @check("rmcwd", "can remove current working directory")
356 def has_rmcwd():
356 def has_rmcwd():
357 ocwd = os.getcwd()
357 ocwd = os.getcwd()
358 temp = tempfile.mkdtemp(dir='.', prefix=tempprefix)
358 temp = tempfile.mkdtemp(dir='.', prefix=tempprefix)
359 try:
359 try:
360 os.chdir(temp)
360 os.chdir(temp)
361 # On Linux, 'rmdir .' isn't allowed, but the other names are okay.
361 # On Linux, 'rmdir .' isn't allowed, but the other names are okay.
362 # On Solaris and Windows, the cwd can't be removed by any names.
362 # On Solaris and Windows, the cwd can't be removed by any names.
363 os.rmdir(os.getcwd())
363 os.rmdir(os.getcwd())
364 return True
364 return True
365 except OSError:
365 except OSError:
366 return False
366 return False
367 finally:
367 finally:
368 os.chdir(ocwd)
368 os.chdir(ocwd)
369 # clean up temp dir on platforms where cwd can't be removed
369 # clean up temp dir on platforms where cwd can't be removed
370 try:
370 try:
371 os.rmdir(temp)
371 os.rmdir(temp)
372 except OSError:
372 except OSError:
373 pass
373 pass
374
374
375 @check("tla", "GNU Arch tla client")
375 @check("tla", "GNU Arch tla client")
376 def has_tla():
376 def has_tla():
377 return matchoutput('tla --version 2>&1', br'The GNU Arch Revision')
377 return matchoutput('tla --version 2>&1', br'The GNU Arch Revision')
378
378
379 @check("gpg", "gpg client")
379 @check("gpg", "gpg client")
380 def has_gpg():
380 def has_gpg():
381 return matchoutput('gpg --version 2>&1', br'GnuPG')
381 return matchoutput('gpg --version 2>&1', br'GnuPG')
382
382
383 @check("gpg2", "gpg client v2")
383 @check("gpg2", "gpg client v2")
384 def has_gpg2():
384 def has_gpg2():
385 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.')
385 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.')
386
386
387 @check("gpg21", "gpg client v2.1+")
387 @check("gpg21", "gpg client v2.1+")
388 def has_gpg21():
388 def has_gpg21():
389 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.(?!0)')
389 return matchoutput('gpg --version 2>&1', br'GnuPG[^0-9]+2\.(?!0)')
390
390
391 @check("unix-permissions", "unix-style permissions")
391 @check("unix-permissions", "unix-style permissions")
392 def has_unix_permissions():
392 def has_unix_permissions():
393 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
393 d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
394 try:
394 try:
395 fname = os.path.join(d, 'foo')
395 fname = os.path.join(d, 'foo')
396 for umask in (0o77, 0o07, 0o22):
396 for umask in (0o77, 0o07, 0o22):
397 os.umask(umask)
397 os.umask(umask)
398 f = open(fname, 'w')
398 f = open(fname, 'w')
399 f.close()
399 f.close()
400 mode = os.stat(fname).st_mode
400 mode = os.stat(fname).st_mode
401 os.unlink(fname)
401 os.unlink(fname)
402 if mode & 0o777 != ~umask & 0o666:
402 if mode & 0o777 != ~umask & 0o666:
403 return False
403 return False
404 return True
404 return True
405 finally:
405 finally:
406 os.rmdir(d)
406 os.rmdir(d)
407
407
408 @check("unix-socket", "AF_UNIX socket family")
408 @check("unix-socket", "AF_UNIX socket family")
409 def has_unix_socket():
409 def has_unix_socket():
410 return getattr(socket, 'AF_UNIX', None) is not None
410 return getattr(socket, 'AF_UNIX', None) is not None
411
411
412 @check("root", "root permissions")
412 @check("root", "root permissions")
413 def has_root():
413 def has_root():
414 return getattr(os, 'geteuid', None) and os.geteuid() == 0
414 return getattr(os, 'geteuid', None) and os.geteuid() == 0
415
415
416 @check("pyflakes", "Pyflakes python linter")
416 @check("pyflakes", "Pyflakes python linter")
417 def has_pyflakes():
417 def has_pyflakes():
418 return matchoutput("sh -c \"echo 'import re' 2>&1 | pyflakes\"",
418 return matchoutput("sh -c \"echo 'import re' 2>&1 | pyflakes\"",
419 br"<stdin>:1: 're' imported but unused",
419 br"<stdin>:1: 're' imported but unused",
420 True)
420 True)
421
421
422 @check("pylint", "Pylint python linter")
422 @check("pylint", "Pylint python linter")
423 def has_pylint():
423 def has_pylint():
424 return matchoutput("pylint --help",
424 return matchoutput("pylint --help",
425 br"Usage: pylint",
425 br"Usage: pylint",
426 True)
426 True)
427
427
428 @check("pygments", "Pygments source highlighting library")
428 @check("pygments", "Pygments source highlighting library")
429 def has_pygments():
429 def has_pygments():
430 try:
430 try:
431 import pygments
431 import pygments
432 pygments.highlight # silence unused import warning
432 pygments.highlight # silence unused import warning
433 return True
433 return True
434 except ImportError:
434 except ImportError:
435 return False
435 return False
436
436
437 @check("outer-repo", "outer repo")
437 @check("outer-repo", "outer repo")
438 def has_outer_repo():
438 def has_outer_repo():
439 # failing for other reasons than 'no repo' imply that there is a repo
439 # failing for other reasons than 'no repo' imply that there is a repo
440 return not matchoutput('hg root 2>&1',
440 return not matchoutput('hg root 2>&1',
441 br'abort: no repository found', True)
441 br'abort: no repository found', True)
442
442
443 @check("ssl", "ssl module available")
443 @check("ssl", "ssl module available")
444 def has_ssl():
444 def has_ssl():
445 try:
445 try:
446 import ssl
446 import ssl
447 ssl.CERT_NONE
447 ssl.CERT_NONE
448 return True
448 return True
449 except ImportError:
449 except ImportError:
450 return False
450 return False
451
451
452 @check("sslcontext", "python >= 2.7.9 ssl")
452 @check("sslcontext", "python >= 2.7.9 ssl")
453 def has_sslcontext():
453 def has_sslcontext():
454 try:
454 try:
455 import ssl
455 import ssl
456 ssl.SSLContext
456 ssl.SSLContext
457 return True
457 return True
458 except (ImportError, AttributeError):
458 except (ImportError, AttributeError):
459 return False
459 return False
460
460
461 @check("defaultcacerts", "can verify SSL certs by system's CA certs store")
461 @check("defaultcacerts", "can verify SSL certs by system's CA certs store")
462 def has_defaultcacerts():
462 def has_defaultcacerts():
463 from mercurial import sslutil, ui as uimod
463 from mercurial import sslutil, ui as uimod
464 ui = uimod.ui.load()
464 ui = uimod.ui.load()
465 return sslutil._defaultcacerts(ui) or sslutil._canloaddefaultcerts
465 return sslutil._defaultcacerts(ui) or sslutil._canloaddefaultcerts
466
466
467 @check("defaultcacertsloaded", "detected presence of loaded system CA certs")
467 @check("defaultcacertsloaded", "detected presence of loaded system CA certs")
468 def has_defaultcacertsloaded():
468 def has_defaultcacertsloaded():
469 import ssl
469 import ssl
470 from mercurial import sslutil, ui as uimod
470 from mercurial import sslutil, ui as uimod
471
471
472 if not has_defaultcacerts():
472 if not has_defaultcacerts():
473 return False
473 return False
474 if not has_sslcontext():
474 if not has_sslcontext():
475 return False
475 return False
476
476
477 ui = uimod.ui.load()
477 ui = uimod.ui.load()
478 cafile = sslutil._defaultcacerts(ui)
478 cafile = sslutil._defaultcacerts(ui)
479 ctx = ssl.create_default_context()
479 ctx = ssl.create_default_context()
480 if cafile:
480 if cafile:
481 ctx.load_verify_locations(cafile=cafile)
481 ctx.load_verify_locations(cafile=cafile)
482 else:
482 else:
483 ctx.load_default_certs()
483 ctx.load_default_certs()
484
484
485 return len(ctx.get_ca_certs()) > 0
485 return len(ctx.get_ca_certs()) > 0
486
486
487 @check("tls1.2", "TLS 1.2 protocol support")
487 @check("tls1.2", "TLS 1.2 protocol support")
488 def has_tls1_2():
488 def has_tls1_2():
489 from mercurial import sslutil
489 from mercurial import sslutil
490 return 'tls1.2' in sslutil.supportedprotocols
490 return 'tls1.2' in sslutil.supportedprotocols
491
491
492 @check("windows", "Windows")
492 @check("windows", "Windows")
493 def has_windows():
493 def has_windows():
494 return os.name == 'nt'
494 return os.name == 'nt'
495
495
496 @check("system-sh", "system() uses sh")
496 @check("system-sh", "system() uses sh")
497 def has_system_sh():
497 def has_system_sh():
498 return os.name != 'nt'
498 return os.name != 'nt'
499
499
500 @check("serve", "platform and python can manage 'hg serve -d'")
500 @check("serve", "platform and python can manage 'hg serve -d'")
501 def has_serve():
501 def has_serve():
502 return os.name != 'nt' # gross approximation
502 return os.name != 'nt' # gross approximation
503
503
504 @check("test-repo", "running tests from repository")
504 @check("test-repo", "running tests from repository")
505 def has_test_repo():
505 def has_test_repo():
506 t = os.environ["TESTDIR"]
506 t = os.environ["TESTDIR"]
507 return os.path.isdir(os.path.join(t, "..", ".hg"))
507 return os.path.isdir(os.path.join(t, "..", ".hg"))
508
508
509 @check("tic", "terminfo compiler and curses module")
509 @check("tic", "terminfo compiler and curses module")
510 def has_tic():
510 def has_tic():
511 try:
511 try:
512 import curses
512 import curses
513 curses.COLOR_BLUE
513 curses.COLOR_BLUE
514 return matchoutput('test -x "`which tic`"', br'')
514 return matchoutput('test -x "`which tic`"', br'')
515 except ImportError:
515 except ImportError:
516 return False
516 return False
517
517
518 @check("msys", "Windows with MSYS")
518 @check("msys", "Windows with MSYS")
519 def has_msys():
519 def has_msys():
520 return os.getenv('MSYSTEM')
520 return os.getenv('MSYSTEM')
521
521
522 @check("aix", "AIX")
522 @check("aix", "AIX")
523 def has_aix():
523 def has_aix():
524 return sys.platform.startswith("aix")
524 return sys.platform.startswith("aix")
525
525
526 @check("osx", "OS X")
526 @check("osx", "OS X")
527 def has_osx():
527 def has_osx():
528 return sys.platform == 'darwin'
528 return sys.platform == 'darwin'
529
529
530 @check("osxpackaging", "OS X packaging tools")
530 @check("osxpackaging", "OS X packaging tools")
531 def has_osxpackaging():
531 def has_osxpackaging():
532 try:
532 try:
533 return (matchoutput('pkgbuild', br'Usage: pkgbuild ', ignorestatus=1)
533 return (matchoutput('pkgbuild', br'Usage: pkgbuild ', ignorestatus=1)
534 and matchoutput(
534 and matchoutput(
535 'productbuild', br'Usage: productbuild ',
535 'productbuild', br'Usage: productbuild ',
536 ignorestatus=1)
536 ignorestatus=1)
537 and matchoutput('lsbom', br'Usage: lsbom', ignorestatus=1)
537 and matchoutput('lsbom', br'Usage: lsbom', ignorestatus=1)
538 and matchoutput(
538 and matchoutput(
539 'xar --help', br'Usage: xar', ignorestatus=1))
539 'xar --help', br'Usage: xar', ignorestatus=1))
540 except ImportError:
540 except ImportError:
541 return False
541 return False
542
542
543 @check("docker", "docker support")
543 @check("docker", "docker support")
544 def has_docker():
544 def has_docker():
545 pat = br'A self-sufficient runtime for'
545 pat = br'A self-sufficient runtime for'
546 if matchoutput('docker --help', pat):
546 if matchoutput('docker --help', pat):
547 if 'linux' not in sys.platform:
547 if 'linux' not in sys.platform:
548 # TODO: in theory we should be able to test docker-based
548 # TODO: in theory we should be able to test docker-based
549 # package creation on non-linux using boot2docker, but in
549 # package creation on non-linux using boot2docker, but in
550 # practice that requires extra coordination to make sure
550 # practice that requires extra coordination to make sure
551 # $TESTTEMP is going to be visible at the same path to the
551 # $TESTTEMP is going to be visible at the same path to the
552 # boot2docker VM. If we figure out how to verify that, we
552 # boot2docker VM. If we figure out how to verify that, we
553 # can use the following instead of just saying False:
553 # can use the following instead of just saying False:
554 # return 'DOCKER_HOST' in os.environ
554 # return 'DOCKER_HOST' in os.environ
555 return False
555 return False
556
556
557 return True
557 return True
558 return False
558 return False
559
559
560 @check("debhelper", "debian packaging tools")
560 @check("debhelper", "debian packaging tools")
561 def has_debhelper():
561 def has_debhelper():
562 dpkg = matchoutput('dpkg --version',
562 dpkg = matchoutput('dpkg --version',
563 br"Debian `dpkg' package management program")
563 br"Debian `dpkg' package management program")
564 dh = matchoutput('dh --help',
564 dh = matchoutput('dh --help',
565 br'dh is a part of debhelper.', ignorestatus=True)
565 br'dh is a part of debhelper.', ignorestatus=True)
566 dh_py2 = matchoutput('dh_python2 --help',
566 dh_py2 = matchoutput('dh_python2 --help',
567 br'other supported Python versions')
567 br'other supported Python versions')
568 return dpkg and dh and dh_py2
568 return dpkg and dh and dh_py2
569
569
570 @check("demandimport", "demandimport enabled")
570 @check("demandimport", "demandimport enabled")
571 def has_demandimport():
571 def has_demandimport():
572 return os.environ.get('HGDEMANDIMPORT') != 'disable'
572 return os.environ.get('HGDEMANDIMPORT') != 'disable'
573
573
574 @check("absimport", "absolute_import in __future__")
574 @check("absimport", "absolute_import in __future__")
575 def has_absimport():
575 def has_absimport():
576 import __future__
576 import __future__
577 from mercurial import util
577 from mercurial import util
578 return util.safehasattr(__future__, "absolute_import")
578 return util.safehasattr(__future__, "absolute_import")
579
579
580 @check("py27+", "running with Python 2.7+")
580 @check("py27+", "running with Python 2.7+")
581 def has_python27ornewer():
581 def has_python27ornewer():
582 return sys.version_info[0:2] >= (2, 7)
582 return sys.version_info[0:2] >= (2, 7)
583
583
584 @check("py3k", "running with Python 3.x")
584 @check("py3k", "running with Python 3.x")
585 def has_py3k():
585 def has_py3k():
586 return 3 == sys.version_info[0]
586 return 3 == sys.version_info[0]
587
587
588 @check("py3exe", "a Python 3.x interpreter is available")
588 @check("py3exe", "a Python 3.x interpreter is available")
589 def has_python3exe():
589 def has_python3exe():
590 return 'PYTHON3' in os.environ
590 return 'PYTHON3' in os.environ
591
591
592 @check("py3pygments", "Pygments available on Python 3.x")
592 @check("py3pygments", "Pygments available on Python 3.x")
593 def has_py3pygments():
593 def has_py3pygments():
594 if has_py3k():
594 if has_py3k():
595 return has_pygments()
595 return has_pygments()
596 elif has_python3exe():
596 elif has_python3exe():
597 # just check exit status (ignoring output)
597 # just check exit status (ignoring output)
598 py3 = os.environ['PYTHON3']
598 py3 = os.environ['PYTHON3']
599 return matchoutput('%s -c "import pygments"' % py3, br'')
599 return matchoutput('%s -c "import pygments"' % py3, br'')
600 return False
600 return False
601
601
602 @check("pure", "running with pure Python code")
602 @check("pure", "running with pure Python code")
603 def has_pure():
603 def has_pure():
604 return any([
604 return any([
605 os.environ.get("HGMODULEPOLICY") == "py",
605 os.environ.get("HGMODULEPOLICY") == "py",
606 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure",
606 os.environ.get("HGTEST_RUN_TESTS_PURE") == "--pure",
607 ])
607 ])
608
608
609 @check("slow", "allow slow tests")
609 @check("slow", "allow slow tests")
610 def has_slow():
610 def has_slow():
611 return os.environ.get('HGTEST_SLOW') == 'slow'
611 return os.environ.get('HGTEST_SLOW') == 'slow'
612
612
613 @check("hypothesis", "Hypothesis automated test generation")
613 @check("hypothesis", "Hypothesis automated test generation")
614 def has_hypothesis():
614 def has_hypothesis():
615 try:
615 try:
616 import hypothesis
616 import hypothesis
617 hypothesis.given
617 hypothesis.given
618 return True
618 return True
619 except ImportError:
619 except ImportError:
620 return False
620 return False
621
621
622 @check("unziplinks", "unzip(1) understands and extracts symlinks")
622 @check("unziplinks", "unzip(1) understands and extracts symlinks")
623 def unzip_understands_symlinks():
623 def unzip_understands_symlinks():
624 return matchoutput('unzip --help', br'Info-ZIP')
624 return matchoutput('unzip --help', br'Info-ZIP')
625
625
626 @check("zstd", "zstd Python module available")
626 @check("zstd", "zstd Python module available")
627 def has_zstd():
627 def has_zstd():
628 try:
628 try:
629 import mercurial.zstd
629 import mercurial.zstd
630 mercurial.zstd.__version__
630 mercurial.zstd.__version__
631 return True
631 return True
632 except ImportError:
632 except ImportError:
633 return False
633 return False
General Comments 0
You need to be logged in to leave comments. Login now