##// END OF EJS Templates
osutil: add darwin-only version of os.listdir using cffi
Maciej Fijalkowski -
r29600:7a157639 default
parent child Browse files
Show More
@@ -0,0 +1,101 b''
1 from __future__ import absolute_import
2
3 import cffi
4
5 ffi = cffi.FFI()
6 ffi.set_source("_osutil_cffi", """
7 #include <sys/attr.h>
8 #include <sys/vnode.h>
9 #include <unistd.h>
10 #include <fcntl.h>
11 #include <time.h>
12
13 typedef struct val_attrs {
14 uint32_t length;
15 attribute_set_t returned;
16 attrreference_t name_info;
17 fsobj_type_t obj_type;
18 struct timespec mtime;
19 uint32_t accessmask;
20 off_t datalength;
21 } __attribute__((aligned(4), packed)) val_attrs_t;
22 """, include_dirs=['mercurial'])
23 ffi.cdef('''
24
25 typedef uint32_t attrgroup_t;
26
27 typedef struct attrlist {
28 uint16_t bitmapcount; /* number of attr. bit sets in list */
29 uint16_t reserved; /* (to maintain 4-byte alignment) */
30 attrgroup_t commonattr; /* common attribute group */
31 attrgroup_t volattr; /* volume attribute group */
32 attrgroup_t dirattr; /* directory attribute group */
33 attrgroup_t fileattr; /* file attribute group */
34 attrgroup_t forkattr; /* fork attribute group */
35 ...;
36 };
37
38 typedef struct attribute_set {
39 ...;
40 } attribute_set_t;
41
42 typedef struct attrreference {
43 int attr_dataoffset;
44 int attr_length;
45 ...;
46 } attrreference_t;
47
48 typedef struct val_attrs {
49 uint32_t length;
50 attribute_set_t returned;
51 attrreference_t name_info;
52 uint32_t obj_type;
53 struct timespec mtime;
54 uint32_t accessmask;
55 int datalength;
56 ...;
57 } val_attrs_t;
58
59 /* the exact layout of the above struct will be figured out during build time */
60
61 typedef int ... time_t;
62 typedef int ... off_t;
63
64 typedef struct timespec {
65 time_t tv_sec;
66 ...;
67 };
68
69 int getattrlist(const char* path, struct attrlist * attrList, void * attrBuf,
70 size_t attrBufSize, unsigned int options);
71
72 int getattrlistbulk(int dirfd, struct attrlist * attrList, void * attrBuf,
73 size_t attrBufSize, uint64_t options);
74
75 #define ATTR_BIT_MAP_COUNT ...
76 #define ATTR_CMN_NAME ...
77 #define ATTR_CMN_OBJTYPE ...
78 #define ATTR_CMN_MODTIME ...
79 #define ATTR_CMN_ACCESSMASK ...
80 #define ATTR_CMN_ERROR ...
81 #define ATTR_CMN_RETURNED_ATTRS ...
82 #define ATTR_FILE_DATALENGTH ...
83
84 #define VREG ...
85 #define VDIR ...
86 #define VLNK ...
87 #define VBLK ...
88 #define VCHR ...
89 #define VFIFO ...
90 #define VSOCK ...
91
92 #define S_IFMT ...
93
94 int open(const char *path, int oflag, int perm);
95 int close(int);
96
97 #define O_RDONLY ...
98 ''')
99
100 if __name__ == '__main__':
101 ffi.compile()
@@ -1,268 +1,361 b''
1 1 # osutil.py - pure Python version of osutil.c
2 2 #
3 3 # Copyright 2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import ctypes
11 11 import ctypes.util
12 12 import os
13 13 import socket
14 14 import stat as statmod
15 15 import sys
16 16
17 from . import policy
18 modulepolicy = policy.policy
19 policynocffi = policy.policynocffi
20
17 21 def _mode_to_kind(mode):
18 22 if statmod.S_ISREG(mode):
19 23 return statmod.S_IFREG
20 24 if statmod.S_ISDIR(mode):
21 25 return statmod.S_IFDIR
22 26 if statmod.S_ISLNK(mode):
23 27 return statmod.S_IFLNK
24 28 if statmod.S_ISBLK(mode):
25 29 return statmod.S_IFBLK
26 30 if statmod.S_ISCHR(mode):
27 31 return statmod.S_IFCHR
28 32 if statmod.S_ISFIFO(mode):
29 33 return statmod.S_IFIFO
30 34 if statmod.S_ISSOCK(mode):
31 35 return statmod.S_IFSOCK
32 36 return mode
33 37
34 def listdir(path, stat=False, skip=None):
38 def listdirpure(path, stat=False, skip=None):
35 39 '''listdir(path, stat=False) -> list_of_tuples
36 40
37 41 Return a sorted list containing information about the entries
38 42 in the directory.
39 43
40 44 If stat is True, each element is a 3-tuple:
41 45
42 46 (name, type, stat object)
43 47
44 48 Otherwise, each element is a 2-tuple:
45 49
46 50 (name, type)
47 51 '''
48 52 result = []
49 53 prefix = path
50 54 if not prefix.endswith(os.sep):
51 55 prefix += os.sep
52 56 names = os.listdir(path)
53 57 names.sort()
54 58 for fn in names:
55 59 st = os.lstat(prefix + fn)
56 60 if fn == skip and statmod.S_ISDIR(st.st_mode):
57 61 return []
58 62 if stat:
59 63 result.append((fn, _mode_to_kind(st.st_mode), st))
60 64 else:
61 65 result.append((fn, _mode_to_kind(st.st_mode)))
62 66 return result
63 67
68 ffi = None
69 if modulepolicy not in policynocffi and sys.platform == 'darwin':
70 try:
71 from _osutil_cffi import ffi, lib
72 except ImportError:
73 if modulepolicy == 'cffi': # strict cffi import
74 raise
75
76 if sys.platform == 'darwin' and ffi is not None:
77 listdir_batch_size = 4096
78 # tweakable number, only affects performance, which chunks
79 # of bytes do we get back from getattrlistbulk
80
81 attrkinds = [None] * 20 # we need the max no for enum VXXX, 20 is plenty
82
83 attrkinds[lib.VREG] = statmod.S_IFREG
84 attrkinds[lib.VDIR] = statmod.S_IFDIR
85 attrkinds[lib.VLNK] = statmod.S_IFLNK
86 attrkinds[lib.VBLK] = statmod.S_IFBLK
87 attrkinds[lib.VCHR] = statmod.S_IFCHR
88 attrkinds[lib.VFIFO] = statmod.S_IFIFO
89 attrkinds[lib.VSOCK] = statmod.S_IFSOCK
90
91 class stat_res(object):
92 def __init__(self, st_mode, st_mtime, st_size):
93 self.st_mode = st_mode
94 self.st_mtime = st_mtime
95 self.st_size = st_size
96
97 tv_sec_ofs = ffi.offsetof("struct timespec", "tv_sec")
98 buf = ffi.new("char[]", listdir_batch_size)
99
100 def listdirinternal(dfd, req, stat, skip):
101 ret = []
102 while True:
103 r = lib.getattrlistbulk(dfd, req, buf, listdir_batch_size, 0)
104 if r == 0:
105 break
106 if r == -1:
107 raise OSError(ffi.errno, os.strerror(ffi.errno))
108 cur = ffi.cast("val_attrs_t*", buf)
109 for i in range(r):
110 lgt = cur.length
111 assert lgt == ffi.cast('uint32_t*', cur)[0]
112 ofs = cur.name_info.attr_dataoffset
113 str_lgt = cur.name_info.attr_length
114 base_ofs = ffi.offsetof('val_attrs_t', 'name_info')
115 name = str(ffi.buffer(ffi.cast("char*", cur) + base_ofs + ofs,
116 str_lgt - 1))
117 tp = attrkinds[cur.obj_type]
118 if name == "." or name == "..":
119 continue
120 if skip == name and tp == statmod.S_ISDIR:
121 return []
122 if stat:
123 mtime = cur.time.tv_sec
124 mode = (cur.accessmask & ~lib.S_IFMT)| tp
125 ret.append((name, tp, stat_res(st_mode=mode, st_mtime=mtime,
126 st_size=cur.datalength)))
127 else:
128 ret.append((name, tp))
129 cur += lgt
130 return ret
131
132 def listdir(path, stat=False, skip=None):
133 req = ffi.new("struct attrlist*")
134 req.bitmapcount = lib.ATTR_BIT_MAP_COUNT
135 req.commonattr = (lib.ATTR_CMN_RETURNED_ATTRS |
136 lib.ATTR_CMN_NAME |
137 lib.ATTR_CMN_OBJTYPE |
138 lib.ATTR_CMN_ACCESSMASK |
139 lib.ATTR_CMN_MODTIME)
140 req.fileattr = lib.ATTR_FILE_DATALENGTH
141 dfd = lib.open(path, lib.O_RDONLY, 0)
142 if dfd == -1:
143 raise OSError(ffi.errno, os.strerror(ffi.errno))
144
145 try:
146 ret = listdirinternal(dfd, req, stat, skip)
147 finally:
148 try:
149 lib.close(dfd)
150 except BaseException:
151 pass # we ignore all the errors from closing, not
152 # much we can do about that
153 return ret
154 else:
155 listdir = listdirpure
156
64 157 if os.name != 'nt':
65 158 posixfile = open
66 159
67 160 _SCM_RIGHTS = 0x01
68 161 _socklen_t = ctypes.c_uint
69 162
70 163 if sys.platform == 'linux2':
71 164 # socket.h says "the type should be socklen_t but the definition of
72 165 # the kernel is incompatible with this."
73 166 _cmsg_len_t = ctypes.c_size_t
74 167 _msg_controllen_t = ctypes.c_size_t
75 168 _msg_iovlen_t = ctypes.c_size_t
76 169 else:
77 170 _cmsg_len_t = _socklen_t
78 171 _msg_controllen_t = _socklen_t
79 172 _msg_iovlen_t = ctypes.c_int
80 173
81 174 class _iovec(ctypes.Structure):
82 175 _fields_ = [
83 176 ('iov_base', ctypes.c_void_p),
84 177 ('iov_len', ctypes.c_size_t),
85 178 ]
86 179
87 180 class _msghdr(ctypes.Structure):
88 181 _fields_ = [
89 182 ('msg_name', ctypes.c_void_p),
90 183 ('msg_namelen', _socklen_t),
91 184 ('msg_iov', ctypes.POINTER(_iovec)),
92 185 ('msg_iovlen', _msg_iovlen_t),
93 186 ('msg_control', ctypes.c_void_p),
94 187 ('msg_controllen', _msg_controllen_t),
95 188 ('msg_flags', ctypes.c_int),
96 189 ]
97 190
98 191 class _cmsghdr(ctypes.Structure):
99 192 _fields_ = [
100 193 ('cmsg_len', _cmsg_len_t),
101 194 ('cmsg_level', ctypes.c_int),
102 195 ('cmsg_type', ctypes.c_int),
103 196 ('cmsg_data', ctypes.c_ubyte * 0),
104 197 ]
105 198
106 199 _libc = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True)
107 200 _recvmsg = getattr(_libc, 'recvmsg', None)
108 201 if _recvmsg:
109 202 _recvmsg.restype = getattr(ctypes, 'c_ssize_t', ctypes.c_long)
110 203 _recvmsg.argtypes = (ctypes.c_int, ctypes.POINTER(_msghdr),
111 204 ctypes.c_int)
112 205 else:
113 206 # recvmsg isn't always provided by libc; such systems are unsupported
114 207 def _recvmsg(sockfd, msg, flags):
115 208 raise NotImplementedError('unsupported platform')
116 209
117 210 def _CMSG_FIRSTHDR(msgh):
118 211 if msgh.msg_controllen < ctypes.sizeof(_cmsghdr):
119 212 return
120 213 cmsgptr = ctypes.cast(msgh.msg_control, ctypes.POINTER(_cmsghdr))
121 214 return cmsgptr.contents
122 215
123 216 # The pure version is less portable than the native version because the
124 217 # handling of socket ancillary data heavily depends on C preprocessor.
125 218 # Also, some length fields are wrongly typed in Linux kernel.
126 219 def recvfds(sockfd):
127 220 """receive list of file descriptors via socket"""
128 221 dummy = (ctypes.c_ubyte * 1)()
129 222 iov = _iovec(ctypes.cast(dummy, ctypes.c_void_p), ctypes.sizeof(dummy))
130 223 cbuf = ctypes.create_string_buffer(256)
131 224 msgh = _msghdr(None, 0,
132 225 ctypes.pointer(iov), 1,
133 226 ctypes.cast(cbuf, ctypes.c_void_p), ctypes.sizeof(cbuf),
134 227 0)
135 228 r = _recvmsg(sockfd, ctypes.byref(msgh), 0)
136 229 if r < 0:
137 230 e = ctypes.get_errno()
138 231 raise OSError(e, os.strerror(e))
139 232 # assumes that the first cmsg has fds because it isn't easy to write
140 233 # portable CMSG_NXTHDR() with ctypes.
141 234 cmsg = _CMSG_FIRSTHDR(msgh)
142 235 if not cmsg:
143 236 return []
144 237 if (cmsg.cmsg_level != socket.SOL_SOCKET or
145 238 cmsg.cmsg_type != _SCM_RIGHTS):
146 239 return []
147 240 rfds = ctypes.cast(cmsg.cmsg_data, ctypes.POINTER(ctypes.c_int))
148 241 rfdscount = ((cmsg.cmsg_len - _cmsghdr.cmsg_data.offset) /
149 242 ctypes.sizeof(ctypes.c_int))
150 243 return [rfds[i] for i in xrange(rfdscount)]
151 244
152 245 else:
153 246 import msvcrt
154 247
155 248 _kernel32 = ctypes.windll.kernel32
156 249
157 250 _DWORD = ctypes.c_ulong
158 251 _LPCSTR = _LPSTR = ctypes.c_char_p
159 252 _HANDLE = ctypes.c_void_p
160 253
161 254 _INVALID_HANDLE_VALUE = _HANDLE(-1).value
162 255
163 256 # CreateFile
164 257 _FILE_SHARE_READ = 0x00000001
165 258 _FILE_SHARE_WRITE = 0x00000002
166 259 _FILE_SHARE_DELETE = 0x00000004
167 260
168 261 _CREATE_ALWAYS = 2
169 262 _OPEN_EXISTING = 3
170 263 _OPEN_ALWAYS = 4
171 264
172 265 _GENERIC_READ = 0x80000000
173 266 _GENERIC_WRITE = 0x40000000
174 267
175 268 _FILE_ATTRIBUTE_NORMAL = 0x80
176 269
177 270 # open_osfhandle flags
178 271 _O_RDONLY = 0x0000
179 272 _O_RDWR = 0x0002
180 273 _O_APPEND = 0x0008
181 274
182 275 _O_TEXT = 0x4000
183 276 _O_BINARY = 0x8000
184 277
185 278 # types of parameters of C functions used (required by pypy)
186 279
187 280 _kernel32.CreateFileA.argtypes = [_LPCSTR, _DWORD, _DWORD, ctypes.c_void_p,
188 281 _DWORD, _DWORD, _HANDLE]
189 282 _kernel32.CreateFileA.restype = _HANDLE
190 283
191 284 def _raiseioerror(name):
192 285 err = ctypes.WinError()
193 286 raise IOError(err.errno, '%s: %s' % (name, err.strerror))
194 287
195 288 class posixfile(object):
196 289 '''a file object aiming for POSIX-like semantics
197 290
198 291 CPython's open() returns a file that was opened *without* setting the
199 292 _FILE_SHARE_DELETE flag, which causes rename and unlink to abort.
200 293 This even happens if any hardlinked copy of the file is in open state.
201 294 We set _FILE_SHARE_DELETE here, so files opened with posixfile can be
202 295 renamed and deleted while they are held open.
203 296 Note that if a file opened with posixfile is unlinked, the file
204 297 remains but cannot be opened again or be recreated under the same name,
205 298 until all reading processes have closed the file.'''
206 299
207 300 def __init__(self, name, mode='r', bufsize=-1):
208 301 if 'b' in mode:
209 302 flags = _O_BINARY
210 303 else:
211 304 flags = _O_TEXT
212 305
213 306 m0 = mode[0]
214 307 if m0 == 'r' and '+' not in mode:
215 308 flags |= _O_RDONLY
216 309 access = _GENERIC_READ
217 310 else:
218 311 # work around http://support.microsoft.com/kb/899149 and
219 312 # set _O_RDWR for 'w' and 'a', even if mode has no '+'
220 313 flags |= _O_RDWR
221 314 access = _GENERIC_READ | _GENERIC_WRITE
222 315
223 316 if m0 == 'r':
224 317 creation = _OPEN_EXISTING
225 318 elif m0 == 'w':
226 319 creation = _CREATE_ALWAYS
227 320 elif m0 == 'a':
228 321 creation = _OPEN_ALWAYS
229 322 flags |= _O_APPEND
230 323 else:
231 324 raise ValueError("invalid mode: %s" % mode)
232 325
233 326 fh = _kernel32.CreateFileA(name, access,
234 327 _FILE_SHARE_READ | _FILE_SHARE_WRITE | _FILE_SHARE_DELETE,
235 328 None, creation, _FILE_ATTRIBUTE_NORMAL, None)
236 329 if fh == _INVALID_HANDLE_VALUE:
237 330 _raiseioerror(name)
238 331
239 332 fd = msvcrt.open_osfhandle(fh, flags)
240 333 if fd == -1:
241 334 _kernel32.CloseHandle(fh)
242 335 _raiseioerror(name)
243 336
244 337 f = os.fdopen(fd, mode, bufsize)
245 338 # unfortunately, f.name is '<fdopen>' at this point -- so we store
246 339 # the name on this wrapper. We cannot just assign to f.name,
247 340 # because that attribute is read-only.
248 341 object.__setattr__(self, 'name', name)
249 342 object.__setattr__(self, '_file', f)
250 343
251 344 def __iter__(self):
252 345 return self._file
253 346
254 347 def __getattr__(self, name):
255 348 return getattr(self._file, name)
256 349
257 350 def __setattr__(self, name, value):
258 351 '''mimics the read-only attributes of Python file objects
259 352 by raising 'TypeError: readonly attribute' if someone tries:
260 353 f = posixfile('foo.txt')
261 354 f.name = 'bla' '''
262 355 return self._file.__setattr__(name, value)
263 356
264 357 def __enter__(self):
265 358 return self._file.__enter__()
266 359
267 360 def __exit__(self, exc_type, exc_value, exc_tb):
268 361 return self._file.__exit__(exc_type, exc_value, exc_tb)
@@ -1,713 +1,716 b''
1 1 #
2 2 # This is the mercurial setup script.
3 3 #
4 4 # 'python setup.py install', or
5 5 # 'python setup.py --help' for more options
6 6
7 7 import sys, platform
8 8 if getattr(sys, 'version_info', (0, 0, 0)) < (2, 6, 0, 'final'):
9 9 raise SystemExit("Mercurial requires Python 2.6 or later.")
10 10
11 11 if sys.version_info[0] >= 3:
12 12 printf = eval('print')
13 13 libdir_escape = 'unicode_escape'
14 14 else:
15 15 libdir_escape = 'string_escape'
16 16 def printf(*args, **kwargs):
17 17 f = kwargs.get('file', sys.stdout)
18 18 end = kwargs.get('end', '\n')
19 19 f.write(b' '.join(args) + end)
20 20
21 21 # Solaris Python packaging brain damage
22 22 try:
23 23 import hashlib
24 24 sha = hashlib.sha1()
25 25 except ImportError:
26 26 try:
27 27 import sha
28 28 sha.sha # silence unused import warning
29 29 except ImportError:
30 30 raise SystemExit(
31 31 "Couldn't import standard hashlib (incomplete Python install).")
32 32
33 33 try:
34 34 import zlib
35 35 zlib.compressobj # silence unused import warning
36 36 except ImportError:
37 37 raise SystemExit(
38 38 "Couldn't import standard zlib (incomplete Python install).")
39 39
40 40 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
41 41 isironpython = False
42 42 try:
43 43 isironpython = (platform.python_implementation()
44 44 .lower().find("ironpython") != -1)
45 45 except AttributeError:
46 46 pass
47 47
48 48 if isironpython:
49 49 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
50 50 else:
51 51 try:
52 52 import bz2
53 53 bz2.BZ2Compressor # silence unused import warning
54 54 except ImportError:
55 55 raise SystemExit(
56 56 "Couldn't import standard bz2 (incomplete Python install).")
57 57
58 58 ispypy = "PyPy" in sys.version
59 59
60 60 import ctypes
61 61 import os, stat, subprocess, time
62 62 import re
63 63 import shutil
64 64 import tempfile
65 65 from distutils import log
66 66 if 'FORCE_SETUPTOOLS' in os.environ:
67 67 from setuptools import setup
68 68 else:
69 69 from distutils.core import setup
70 70 from distutils.core import Command, Extension
71 71 from distutils.dist import Distribution
72 72 from distutils.command.build import build
73 73 from distutils.command.build_ext import build_ext
74 74 from distutils.command.build_py import build_py
75 75 from distutils.command.build_scripts import build_scripts
76 76 from distutils.command.install_lib import install_lib
77 77 from distutils.command.install_scripts import install_scripts
78 78 from distutils.spawn import spawn, find_executable
79 79 from distutils import file_util
80 80 from distutils.errors import (
81 81 CCompilerError,
82 82 DistutilsError,
83 83 DistutilsExecError,
84 84 )
85 85 from distutils.sysconfig import get_python_inc, get_config_var
86 86 from distutils.version import StrictVersion
87 87
88 88 scripts = ['hg']
89 89 if os.name == 'nt':
90 90 # We remove hg.bat if we are able to build hg.exe.
91 91 scripts.append('contrib/win32/hg.bat')
92 92
93 93 # simplified version of distutils.ccompiler.CCompiler.has_function
94 94 # that actually removes its temporary files.
95 95 def hasfunction(cc, funcname):
96 96 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
97 97 devnull = oldstderr = None
98 98 try:
99 99 fname = os.path.join(tmpdir, 'funcname.c')
100 100 f = open(fname, 'w')
101 101 f.write('int main(void) {\n')
102 102 f.write(' %s();\n' % funcname)
103 103 f.write('}\n')
104 104 f.close()
105 105 # Redirect stderr to /dev/null to hide any error messages
106 106 # from the compiler.
107 107 # This will have to be changed if we ever have to check
108 108 # for a function on Windows.
109 109 devnull = open('/dev/null', 'w')
110 110 oldstderr = os.dup(sys.stderr.fileno())
111 111 os.dup2(devnull.fileno(), sys.stderr.fileno())
112 112 objects = cc.compile([fname], output_dir=tmpdir)
113 113 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
114 114 return True
115 115 except Exception:
116 116 return False
117 117 finally:
118 118 if oldstderr is not None:
119 119 os.dup2(oldstderr, sys.stderr.fileno())
120 120 if devnull is not None:
121 121 devnull.close()
122 122 shutil.rmtree(tmpdir)
123 123
124 124 # py2exe needs to be installed to work
125 125 try:
126 126 import py2exe
127 127 py2exe.Distribution # silence unused import warning
128 128 py2exeloaded = True
129 129 # import py2exe's patched Distribution class
130 130 from distutils.core import Distribution
131 131 except ImportError:
132 132 py2exeloaded = False
133 133
134 134 def runcmd(cmd, env):
135 135 if (sys.platform == 'plan9'
136 136 and (sys.version_info[0] == 2 and sys.version_info[1] < 7)):
137 137 # subprocess kludge to work around issues in half-baked Python
138 138 # ports, notably bichued/python:
139 139 _, out, err = os.popen3(cmd)
140 140 return str(out), str(err)
141 141 else:
142 142 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
143 143 stderr=subprocess.PIPE, env=env)
144 144 out, err = p.communicate()
145 145 return out, err
146 146
147 147 def runhg(cmd, env):
148 148 out, err = runcmd(cmd, env)
149 149 # If root is executing setup.py, but the repository is owned by
150 150 # another user (as in "sudo python setup.py install") we will get
151 151 # trust warnings since the .hg/hgrc file is untrusted. That is
152 152 # fine, we don't want to load it anyway. Python may warn about
153 153 # a missing __init__.py in mercurial/locale, we also ignore that.
154 154 err = [e for e in err.splitlines()
155 155 if not e.startswith(b'not trusting file') \
156 156 and not e.startswith(b'warning: Not importing') \
157 157 and not e.startswith(b'obsolete feature not enabled')]
158 158 if err:
159 159 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
160 160 printf(b'\n'.join([b' ' + e for e in err]), file=sys.stderr)
161 161 return ''
162 162 return out
163 163
164 164 version = ''
165 165
166 166 # Execute hg out of this directory with a custom environment which takes care
167 167 # to not use any hgrc files and do no localization.
168 168 env = {'HGMODULEPOLICY': 'py',
169 169 'HGRCPATH': '',
170 170 'LANGUAGE': 'C'}
171 171 if 'LD_LIBRARY_PATH' in os.environ:
172 172 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
173 173 if 'SystemRoot' in os.environ:
174 174 # Copy SystemRoot into the custom environment for Python 2.6
175 175 # under Windows. Otherwise, the subprocess will fail with
176 176 # error 0xc0150004. See: http://bugs.python.org/issue3440
177 177 env['SystemRoot'] = os.environ['SystemRoot']
178 178
179 179 if os.path.isdir('.hg'):
180 180 cmd = [sys.executable, 'hg', 'log', '-r', '.', '--template', '{tags}\n']
181 181 numerictags = [t for t in runhg(cmd, env).split() if t[0].isdigit()]
182 182 hgid = runhg([sys.executable, 'hg', 'id', '-i'], env).strip()
183 183 if numerictags: # tag(s) found
184 184 version = numerictags[-1]
185 185 if hgid.endswith('+'): # propagate the dirty status to the tag
186 186 version += '+'
187 187 else: # no tag found
188 188 ltagcmd = [sys.executable, 'hg', 'parents', '--template',
189 189 '{latesttag}']
190 190 ltag = runhg(ltagcmd, env)
191 191 changessincecmd = [sys.executable, 'hg', 'log', '-T', 'x\n', '-r',
192 192 "only(.,'%s')" % ltag]
193 193 changessince = len(runhg(changessincecmd, env).splitlines())
194 194 version = '%s+%s-%s' % (ltag, changessince, hgid)
195 195 if version.endswith('+'):
196 196 version += time.strftime('%Y%m%d')
197 197 elif os.path.exists('.hg_archival.txt'):
198 198 kw = dict([[t.strip() for t in l.split(':', 1)]
199 199 for l in open('.hg_archival.txt')])
200 200 if 'tag' in kw:
201 201 version = kw['tag']
202 202 elif 'latesttag' in kw:
203 203 if 'changessincelatesttag' in kw:
204 204 version = '%(latesttag)s+%(changessincelatesttag)s-%(node).12s' % kw
205 205 else:
206 206 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
207 207 else:
208 208 version = kw.get('node', '')[:12]
209 209
210 210 if version:
211 211 with open("mercurial/__version__.py", "w") as f:
212 212 f.write('# this file is autogenerated by setup.py\n')
213 213 f.write('version = "%s"\n' % version)
214 214
215 215 try:
216 216 oldpolicy = os.environ.get('HGMODULEPOLICY', None)
217 217 os.environ['HGMODULEPOLICY'] = 'py'
218 218 from mercurial import __version__
219 219 version = __version__.version
220 220 except ImportError:
221 221 version = 'unknown'
222 222 finally:
223 223 if oldpolicy is None:
224 224 del os.environ['HGMODULEPOLICY']
225 225 else:
226 226 os.environ['HGMODULEPOLICY'] = oldpolicy
227 227
228 228 class hgbuild(build):
229 229 # Insert hgbuildmo first so that files in mercurial/locale/ are found
230 230 # when build_py is run next.
231 231 sub_commands = [('build_mo', None)] + build.sub_commands
232 232
233 233 class hgbuildmo(build):
234 234
235 235 description = "build translations (.mo files)"
236 236
237 237 def run(self):
238 238 if not find_executable('msgfmt'):
239 239 self.warn("could not find msgfmt executable, no translations "
240 240 "will be built")
241 241 return
242 242
243 243 podir = 'i18n'
244 244 if not os.path.isdir(podir):
245 245 self.warn("could not find %s/ directory" % podir)
246 246 return
247 247
248 248 join = os.path.join
249 249 for po in os.listdir(podir):
250 250 if not po.endswith('.po'):
251 251 continue
252 252 pofile = join(podir, po)
253 253 modir = join('locale', po[:-3], 'LC_MESSAGES')
254 254 mofile = join(modir, 'hg.mo')
255 255 mobuildfile = join('mercurial', mofile)
256 256 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
257 257 if sys.platform != 'sunos5':
258 258 # msgfmt on Solaris does not know about -c
259 259 cmd.append('-c')
260 260 self.mkpath(join('mercurial', modir))
261 261 self.make_file([pofile], mobuildfile, spawn, (cmd,))
262 262
263 263
264 264 class hgdist(Distribution):
265 265 pure = False
266 266 cffi = ispypy
267 267
268 268 global_options = Distribution.global_options + \
269 269 [('pure', None, "use pure (slow) Python "
270 270 "code instead of C extensions"),
271 271 ]
272 272
273 273 def has_ext_modules(self):
274 274 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
275 275 # too late for some cases
276 276 return not self.pure and Distribution.has_ext_modules(self)
277 277
278 278 class hgbuildext(build_ext):
279 279
280 280 def build_extension(self, ext):
281 281 try:
282 282 build_ext.build_extension(self, ext)
283 283 except CCompilerError:
284 284 if not getattr(ext, 'optional', False):
285 285 raise
286 286 log.warn("Failed to build optional extension '%s' (skipping)",
287 287 ext.name)
288 288
289 289 class hgbuildscripts(build_scripts):
290 290 def run(self):
291 291 if os.name != 'nt' or self.distribution.pure:
292 292 return build_scripts.run(self)
293 293
294 294 exebuilt = False
295 295 try:
296 296 self.run_command('build_hgexe')
297 297 exebuilt = True
298 298 except (DistutilsError, CCompilerError):
299 299 log.warn('failed to build optional hg.exe')
300 300
301 301 if exebuilt:
302 302 # Copying hg.exe to the scripts build directory ensures it is
303 303 # installed by the install_scripts command.
304 304 hgexecommand = self.get_finalized_command('build_hgexe')
305 305 dest = os.path.join(self.build_dir, 'hg.exe')
306 306 self.mkpath(self.build_dir)
307 307 self.copy_file(hgexecommand.hgexepath, dest)
308 308
309 309 # Remove hg.bat because it is redundant with hg.exe.
310 310 self.scripts.remove('contrib/win32/hg.bat')
311 311
312 312 return build_scripts.run(self)
313 313
314 314 class hgbuildpy(build_py):
315 315 def finalize_options(self):
316 316 build_py.finalize_options(self)
317 317
318 318 if self.distribution.pure:
319 319 self.distribution.ext_modules = []
320 320 elif self.distribution.cffi:
321 321 exts = []
322 322 # cffi modules go here
323 if sys.platform == 'darwin':
324 import setup_osutil_cffi
325 exts.append(setup_osutil_cffi.ffi.distutils_extension())
323 326 self.distribution.ext_modules = exts
324 327 else:
325 328 h = os.path.join(get_python_inc(), 'Python.h')
326 329 if not os.path.exists(h):
327 330 raise SystemExit('Python headers are required to build '
328 331 'Mercurial but weren\'t found in %s' % h)
329 332
330 333 def run(self):
331 334 if self.distribution.pure:
332 335 modulepolicy = 'py'
333 336 else:
334 337 modulepolicy = 'c'
335 338 with open("mercurial/__modulepolicy__.py", "w") as f:
336 339 f.write('# this file is autogenerated by setup.py\n')
337 340 f.write('modulepolicy = "%s"\n' % modulepolicy)
338 341
339 342 build_py.run(self)
340 343
341 344 class buildhgextindex(Command):
342 345 description = 'generate prebuilt index of hgext (for frozen package)'
343 346 user_options = []
344 347 _indexfilename = 'hgext/__index__.py'
345 348
346 349 def initialize_options(self):
347 350 pass
348 351
349 352 def finalize_options(self):
350 353 pass
351 354
352 355 def run(self):
353 356 if os.path.exists(self._indexfilename):
354 357 with open(self._indexfilename, 'w') as f:
355 358 f.write('# empty\n')
356 359
357 360 # here no extension enabled, disabled() lists up everything
358 361 code = ('import pprint; from mercurial import extensions; '
359 362 'pprint.pprint(extensions.disabled())')
360 363 out, err = runcmd([sys.executable, '-c', code], env)
361 364 if err:
362 365 raise DistutilsExecError(err)
363 366
364 367 with open(self._indexfilename, 'w') as f:
365 368 f.write('# this file is autogenerated by setup.py\n')
366 369 f.write('docs = ')
367 370 f.write(out)
368 371
369 372 class buildhgexe(build_ext):
370 373 description = 'compile hg.exe from mercurial/exewrapper.c'
371 374
372 375 def build_extensions(self):
373 376 if os.name != 'nt':
374 377 return
375 378 if isinstance(self.compiler, HackedMingw32CCompiler):
376 379 self.compiler.compiler_so = self.compiler.compiler # no -mdll
377 380 self.compiler.dll_libraries = [] # no -lmsrvc90
378 381
379 382 # Different Python installs can have different Python library
380 383 # names. e.g. the official CPython distribution uses pythonXY.dll
381 384 # and MinGW uses libpythonX.Y.dll.
382 385 _kernel32 = ctypes.windll.kernel32
383 386 _kernel32.GetModuleFileNameA.argtypes = [ctypes.c_void_p,
384 387 ctypes.c_void_p,
385 388 ctypes.c_ulong]
386 389 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
387 390 size = 1000
388 391 buf = ctypes.create_string_buffer(size + 1)
389 392 filelen = _kernel32.GetModuleFileNameA(sys.dllhandle, ctypes.byref(buf),
390 393 size)
391 394
392 395 if filelen > 0 and filelen != size:
393 396 dllbasename = os.path.basename(buf.value)
394 397 if not dllbasename.lower().endswith('.dll'):
395 398 raise SystemExit('Python DLL does not end with .dll: %s' %
396 399 dllbasename)
397 400 pythonlib = dllbasename[:-4]
398 401 else:
399 402 log.warn('could not determine Python DLL filename; '
400 403 'assuming pythonXY')
401 404
402 405 hv = sys.hexversion
403 406 pythonlib = 'python%d%d' % (hv >> 24, (hv >> 16) & 0xff)
404 407
405 408 log.info('using %s as Python library name' % pythonlib)
406 409 with open('mercurial/hgpythonlib.h', 'wb') as f:
407 410 f.write('/* this file is autogenerated by setup.py */\n')
408 411 f.write('#define HGPYTHONLIB "%s"\n' % pythonlib)
409 412 objects = self.compiler.compile(['mercurial/exewrapper.c'],
410 413 output_dir=self.build_temp)
411 414 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
412 415 target = os.path.join(dir, 'hg')
413 416 self.compiler.link_executable(objects, target,
414 417 libraries=[],
415 418 output_dir=self.build_temp)
416 419
417 420 @property
418 421 def hgexepath(self):
419 422 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
420 423 return os.path.join(self.build_temp, dir, 'hg.exe')
421 424
422 425 class hginstalllib(install_lib):
423 426 '''
424 427 This is a specialization of install_lib that replaces the copy_file used
425 428 there so that it supports setting the mode of files after copying them,
426 429 instead of just preserving the mode that the files originally had. If your
427 430 system has a umask of something like 027, preserving the permissions when
428 431 copying will lead to a broken install.
429 432
430 433 Note that just passing keep_permissions=False to copy_file would be
431 434 insufficient, as it might still be applying a umask.
432 435 '''
433 436
434 437 def run(self):
435 438 realcopyfile = file_util.copy_file
436 439 def copyfileandsetmode(*args, **kwargs):
437 440 src, dst = args[0], args[1]
438 441 dst, copied = realcopyfile(*args, **kwargs)
439 442 if copied:
440 443 st = os.stat(src)
441 444 # Persist executable bit (apply it to group and other if user
442 445 # has it)
443 446 if st[stat.ST_MODE] & stat.S_IXUSR:
444 447 setmode = int('0755', 8)
445 448 else:
446 449 setmode = int('0644', 8)
447 450 m = stat.S_IMODE(st[stat.ST_MODE])
448 451 m = (m & ~int('0777', 8)) | setmode
449 452 os.chmod(dst, m)
450 453 file_util.copy_file = copyfileandsetmode
451 454 try:
452 455 install_lib.run(self)
453 456 finally:
454 457 file_util.copy_file = realcopyfile
455 458
456 459 class hginstallscripts(install_scripts):
457 460 '''
458 461 This is a specialization of install_scripts that replaces the @LIBDIR@ with
459 462 the configured directory for modules. If possible, the path is made relative
460 463 to the directory for scripts.
461 464 '''
462 465
463 466 def initialize_options(self):
464 467 install_scripts.initialize_options(self)
465 468
466 469 self.install_lib = None
467 470
468 471 def finalize_options(self):
469 472 install_scripts.finalize_options(self)
470 473 self.set_undefined_options('install',
471 474 ('install_lib', 'install_lib'))
472 475
473 476 def run(self):
474 477 install_scripts.run(self)
475 478
476 479 # It only makes sense to replace @LIBDIR@ with the install path if
477 480 # the install path is known. For wheels, the logic below calculates
478 481 # the libdir to be "../..". This is because the internal layout of a
479 482 # wheel archive looks like:
480 483 #
481 484 # mercurial-3.6.1.data/scripts/hg
482 485 # mercurial/__init__.py
483 486 #
484 487 # When installing wheels, the subdirectories of the "<pkg>.data"
485 488 # directory are translated to system local paths and files therein
486 489 # are copied in place. The mercurial/* files are installed into the
487 490 # site-packages directory. However, the site-packages directory
488 491 # isn't known until wheel install time. This means we have no clue
489 492 # at wheel generation time what the installed site-packages directory
490 493 # will be. And, wheels don't appear to provide the ability to register
491 494 # custom code to run during wheel installation. This all means that
492 495 # we can't reliably set the libdir in wheels: the default behavior
493 496 # of looking in sys.path must do.
494 497
495 498 if (os.path.splitdrive(self.install_dir)[0] !=
496 499 os.path.splitdrive(self.install_lib)[0]):
497 500 # can't make relative paths from one drive to another, so use an
498 501 # absolute path instead
499 502 libdir = self.install_lib
500 503 else:
501 504 common = os.path.commonprefix((self.install_dir, self.install_lib))
502 505 rest = self.install_dir[len(common):]
503 506 uplevel = len([n for n in os.path.split(rest) if n])
504 507
505 508 libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common):]
506 509
507 510 for outfile in self.outfiles:
508 511 with open(outfile, 'rb') as fp:
509 512 data = fp.read()
510 513
511 514 # skip binary files
512 515 if b'\0' in data:
513 516 continue
514 517
515 518 # During local installs, the shebang will be rewritten to the final
516 519 # install path. During wheel packaging, the shebang has a special
517 520 # value.
518 521 if data.startswith(b'#!python'):
519 522 log.info('not rewriting @LIBDIR@ in %s because install path '
520 523 'not known' % outfile)
521 524 continue
522 525
523 526 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
524 527 with open(outfile, 'wb') as fp:
525 528 fp.write(data)
526 529
527 530 cmdclass = {'build': hgbuild,
528 531 'build_mo': hgbuildmo,
529 532 'build_ext': hgbuildext,
530 533 'build_py': hgbuildpy,
531 534 'build_scripts': hgbuildscripts,
532 535 'build_hgextindex': buildhgextindex,
533 536 'install_lib': hginstalllib,
534 537 'install_scripts': hginstallscripts,
535 538 'build_hgexe': buildhgexe,
536 539 }
537 540
538 541 packages = ['mercurial', 'mercurial.hgweb', 'mercurial.httpclient',
539 542 'mercurial.pure',
540 543 'hgext', 'hgext.convert', 'hgext.fsmonitor',
541 544 'hgext.fsmonitor.pywatchman', 'hgext.highlight',
542 545 'hgext.largefiles', 'hgext.zeroconf', 'hgext3rd']
543 546
544 547 common_depends = ['mercurial/bitmanipulation.h',
545 548 'mercurial/compat.h',
546 549 'mercurial/util.h']
547 550
548 551 osutil_ldflags = []
549 552
550 553 if sys.platform == 'darwin':
551 554 osutil_ldflags += ['-framework', 'ApplicationServices']
552 555
553 556 extmodules = [
554 557 Extension('mercurial.base85', ['mercurial/base85.c'],
555 558 depends=common_depends),
556 559 Extension('mercurial.bdiff', ['mercurial/bdiff.c',
557 560 'mercurial/bdiff_module.c'],
558 561 depends=common_depends + ['mercurial/bdiff.h']),
559 562 Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c'],
560 563 depends=common_depends),
561 564 Extension('mercurial.mpatch', ['mercurial/mpatch.c'],
562 565 depends=common_depends),
563 566 Extension('mercurial.parsers', ['mercurial/dirs.c',
564 567 'mercurial/manifest.c',
565 568 'mercurial/parsers.c',
566 569 'mercurial/pathencode.c'],
567 570 depends=common_depends),
568 571 Extension('mercurial.osutil', ['mercurial/osutil.c'],
569 572 extra_link_args=osutil_ldflags,
570 573 depends=common_depends),
571 574 Extension('hgext.fsmonitor.pywatchman.bser',
572 575 ['hgext/fsmonitor/pywatchman/bser.c']),
573 576 ]
574 577
575 578 try:
576 579 from distutils import cygwinccompiler
577 580
578 581 # the -mno-cygwin option has been deprecated for years
579 582 compiler = cygwinccompiler.Mingw32CCompiler
580 583
581 584 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
582 585 def __init__(self, *args, **kwargs):
583 586 compiler.__init__(self, *args, **kwargs)
584 587 for i in 'compiler compiler_so linker_exe linker_so'.split():
585 588 try:
586 589 getattr(self, i).remove('-mno-cygwin')
587 590 except ValueError:
588 591 pass
589 592
590 593 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
591 594 except ImportError:
592 595 # the cygwinccompiler package is not available on some Python
593 596 # distributions like the ones from the optware project for Synology
594 597 # DiskStation boxes
595 598 class HackedMingw32CCompiler(object):
596 599 pass
597 600
598 601 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
599 602 'help/*.txt',
600 603 'help/internals/*.txt',
601 604 'default.d/*.rc',
602 605 'dummycert.pem']}
603 606
604 607 def ordinarypath(p):
605 608 return p and p[0] != '.' and p[-1] != '~'
606 609
607 610 for root in ('templates',):
608 611 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
609 612 curdir = curdir.split(os.sep, 1)[1]
610 613 dirs[:] = filter(ordinarypath, dirs)
611 614 for f in filter(ordinarypath, files):
612 615 f = os.path.join(curdir, f)
613 616 packagedata['mercurial'].append(f)
614 617
615 618 datafiles = []
616 619 setupversion = version
617 620 extra = {}
618 621
619 622 if py2exeloaded:
620 623 extra['console'] = [
621 624 {'script':'hg',
622 625 'copyright':'Copyright (C) 2005-2016 Matt Mackall and others',
623 626 'product_version':version}]
624 627 # sub command of 'build' because 'py2exe' does not handle sub_commands
625 628 build.sub_commands.insert(0, ('build_hgextindex', None))
626 629 # put dlls in sub directory so that they won't pollute PATH
627 630 extra['zipfile'] = 'lib/library.zip'
628 631
629 632 if os.name == 'nt':
630 633 # Windows binary file versions for exe/dll files must have the
631 634 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
632 635 setupversion = version.split('+', 1)[0]
633 636
634 637 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
635 638 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[0].splitlines()
636 639 if version:
637 640 version = version[0]
638 641 if sys.version_info[0] == 3:
639 642 version = version.decode('utf-8')
640 643 xcode4 = (version.startswith('Xcode') and
641 644 StrictVersion(version.split()[1]) >= StrictVersion('4.0'))
642 645 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
643 646 else:
644 647 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
645 648 # installed, but instead with only command-line tools. Assume
646 649 # that only happens on >= Lion, thus no PPC support.
647 650 xcode4 = True
648 651 xcode51 = False
649 652
650 653 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
651 654 # distutils.sysconfig
652 655 if xcode4:
653 656 os.environ['ARCHFLAGS'] = ''
654 657
655 658 # XCode 5.1 changes clang such that it now fails to compile if the
656 659 # -mno-fused-madd flag is passed, but the version of Python shipped with
657 660 # OS X 10.9 Mavericks includes this flag. This causes problems in all
658 661 # C extension modules, and a bug has been filed upstream at
659 662 # http://bugs.python.org/issue21244. We also need to patch this here
660 663 # so Mercurial can continue to compile in the meantime.
661 664 if xcode51:
662 665 cflags = get_config_var('CFLAGS')
663 666 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
664 667 os.environ['CFLAGS'] = (
665 668 os.environ.get('CFLAGS', '') + ' -Qunused-arguments')
666 669
667 670 setup(name='mercurial',
668 671 version=setupversion,
669 672 author='Matt Mackall and many others',
670 673 author_email='mercurial@selenic.com',
671 674 url='https://mercurial-scm.org/',
672 675 download_url='https://mercurial-scm.org/release/',
673 676 description=('Fast scalable distributed SCM (revision control, version '
674 677 'control) system'),
675 678 long_description=('Mercurial is a distributed SCM tool written in Python.'
676 679 ' It is used by a number of large projects that require'
677 680 ' fast, reliable distributed revision control, such as '
678 681 'Mozilla.'),
679 682 license='GNU GPLv2 or any later version',
680 683 classifiers=[
681 684 'Development Status :: 6 - Mature',
682 685 'Environment :: Console',
683 686 'Intended Audience :: Developers',
684 687 'Intended Audience :: System Administrators',
685 688 'License :: OSI Approved :: GNU General Public License (GPL)',
686 689 'Natural Language :: Danish',
687 690 'Natural Language :: English',
688 691 'Natural Language :: German',
689 692 'Natural Language :: Italian',
690 693 'Natural Language :: Japanese',
691 694 'Natural Language :: Portuguese (Brazilian)',
692 695 'Operating System :: Microsoft :: Windows',
693 696 'Operating System :: OS Independent',
694 697 'Operating System :: POSIX',
695 698 'Programming Language :: C',
696 699 'Programming Language :: Python',
697 700 'Topic :: Software Development :: Version Control',
698 701 ],
699 702 scripts=scripts,
700 703 packages=packages,
701 704 ext_modules=extmodules,
702 705 data_files=datafiles,
703 706 package_data=packagedata,
704 707 cmdclass=cmdclass,
705 708 distclass=hgdist,
706 709 options={'py2exe': {'packages': ['hgext', 'email']},
707 710 'bdist_mpkg': {'zipdist': False,
708 711 'license': 'COPYING',
709 712 'readme': 'contrib/macosx/Readme.html',
710 713 'welcome': 'contrib/macosx/Welcome.html',
711 714 },
712 715 },
713 716 **extra)
General Comments 0
You need to be logged in to leave comments. Login now