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