##// END OF EJS Templates
bdiff: implement cffi version of blocks
Maciej Fijalkowski -
r29833:a8933d99 default
parent child Browse files
Show More
@@ -0,0 +1,31
1 from __future__ import absolute_import
2
3 import cffi
4 import os
5
6 ffi = cffi.FFI()
7 ffi.set_source("_bdiff_cffi",
8 open(os.path.join(os.path.join(os.path.dirname(__file__), 'mercurial'),
9 'bdiff.c')).read(), include_dirs=['mercurial'])
10 ffi.cdef("""
11 struct bdiff_line {
12 int hash, n, e;
13 ssize_t len;
14 const char *l;
15 };
16
17 struct bdiff_hunk;
18 struct bdiff_hunk {
19 int a1, a2, b1, b2;
20 struct bdiff_hunk *next;
21 };
22
23 int bdiff_splitlines(const char *a, ssize_t len, struct bdiff_line **lr);
24 int bdiff_diff(struct bdiff_line *a, int an, struct bdiff_line *b, int bn,
25 struct bdiff_hunk *base);
26 void bdiff_freehunks(struct bdiff_hunk *l);
27 void free(void*);
28 """)
29
30 if __name__ == '__main__':
31 ffi.compile()
@@ -1,98 +1,136
1 1 # bdiff.py - Python implementation of bdiff.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 array
11 11 import difflib
12 12 import re
13 13 import struct
14 14
15 from . import policy
16 policynocffi = policy.policynocffi
17 modulepolicy = policy.policy
18
15 19 def splitnewlines(text):
16 20 '''like str.splitlines, but only split on newlines.'''
17 21 lines = [l + '\n' for l in text.split('\n')]
18 22 if lines:
19 23 if lines[-1] == '\n':
20 24 lines.pop()
21 25 else:
22 26 lines[-1] = lines[-1][:-1]
23 27 return lines
24 28
25 29 def _normalizeblocks(a, b, blocks):
26 30 prev = None
27 31 r = []
28 32 for curr in blocks:
29 33 if prev is None:
30 34 prev = curr
31 35 continue
32 36 shift = 0
33 37
34 38 a1, b1, l1 = prev
35 39 a1end = a1 + l1
36 40 b1end = b1 + l1
37 41
38 42 a2, b2, l2 = curr
39 43 a2end = a2 + l2
40 44 b2end = b2 + l2
41 45 if a1end == a2:
42 46 while (a1end + shift < a2end and
43 47 a[a1end + shift] == b[b1end + shift]):
44 48 shift += 1
45 49 elif b1end == b2:
46 50 while (b1end + shift < b2end and
47 51 a[a1end + shift] == b[b1end + shift]):
48 52 shift += 1
49 53 r.append((a1, b1, l1 + shift))
50 54 prev = a2 + shift, b2 + shift, l2 - shift
51 55 r.append(prev)
52 56 return r
53 57
54 58 def _tostring(c):
55 59 if type(c) is array.array:
56 60 # this copy overhead isn't ideal
57 61 return c.tostring()
58 62 return str(c)
59 63
60 64 def bdiff(a, b):
61 65 a = _tostring(a).splitlines(True)
62 66 b = _tostring(b).splitlines(True)
63 67
64 68 if not a:
65 69 s = "".join(b)
66 70 return s and (struct.pack(">lll", 0, 0, len(s)) + s)
67 71
68 72 bin = []
69 73 p = [0]
70 74 for i in a: p.append(p[-1] + len(i))
71 75
72 76 d = difflib.SequenceMatcher(None, a, b).get_matching_blocks()
73 77 d = _normalizeblocks(a, b, d)
74 78 la = 0
75 79 lb = 0
76 80 for am, bm, size in d:
77 81 s = "".join(b[lb:bm])
78 82 if am > la or s:
79 83 bin.append(struct.pack(">lll", p[la], p[am], len(s)) + s)
80 84 la = am + size
81 85 lb = bm + size
82 86
83 87 return "".join(bin)
84 88
85 89 def blocks(a, b):
86 90 an = splitnewlines(a)
87 91 bn = splitnewlines(b)
88 92 d = difflib.SequenceMatcher(None, an, bn).get_matching_blocks()
89 93 d = _normalizeblocks(an, bn, d)
90 94 return [(i, i + n, j, j + n) for (i, j, n) in d]
91 95
92 96 def fixws(text, allws):
93 97 if allws:
94 98 text = re.sub('[ \t\r]+', '', text)
95 99 else:
96 100 text = re.sub('[ \t\r]+', ' ', text)
97 101 text = text.replace(' \n', '\n')
98 102 return text
103
104 if modulepolicy not in policynocffi:
105 try:
106 from _bdiff_cffi import ffi, lib
107 except ImportError:
108 if modulepolicy == 'cffi': # strict cffi import
109 raise
110 else:
111 def blocks(sa, sb):
112 a = ffi.new("struct bdiff_line**")
113 b = ffi.new("struct bdiff_line**")
114 ac = ffi.new("char[]", sa)
115 bc = ffi.new("char[]", sb)
116 try:
117 an = lib.bdiff_splitlines(ac, len(sa), a)
118 bn = lib.bdiff_splitlines(bc, len(sb), b)
119 if not a[0] or not b[0]:
120 raise MemoryError
121 l = ffi.new("struct bdiff_hunk*")
122 count = lib.bdiff_diff(a[0], an, b[0], bn, l)
123 if count < 0:
124 raise MemoryError
125 rl = [None] * count
126 h = l.next
127 i = 0
128 while h:
129 rl[i] = (h.a1, h.a2, h.b1, h.b2)
130 h = h.next
131 i += 1
132 finally:
133 lib.free(a[0])
134 lib.free(b[0])
135 lib.bdiff_freehunks(l.next)
136 return rl
@@ -1,718 +1,720
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 import setup_mpatch_cffi
322 exts = [setup_mpatch_cffi.ffi.distutils_extension()]
322 import setup_bdiff_cffi
323 exts = [setup_mpatch_cffi.ffi.distutils_extension(),
324 setup_bdiff_cffi.ffi.distutils_extension()]
323 325 # cffi modules go here
324 326 if sys.platform == 'darwin':
325 327 import setup_osutil_cffi
326 328 exts.append(setup_osutil_cffi.ffi.distutils_extension())
327 329 self.distribution.ext_modules = exts
328 330 else:
329 331 h = os.path.join(get_python_inc(), 'Python.h')
330 332 if not os.path.exists(h):
331 333 raise SystemExit('Python headers are required to build '
332 334 'Mercurial but weren\'t found in %s' % h)
333 335
334 336 def run(self):
335 337 if self.distribution.pure:
336 338 modulepolicy = 'py'
337 339 else:
338 340 modulepolicy = 'c'
339 341 with open("mercurial/__modulepolicy__.py", "w") as f:
340 342 f.write('# this file is autogenerated by setup.py\n')
341 343 f.write('modulepolicy = "%s"\n' % modulepolicy)
342 344
343 345 build_py.run(self)
344 346
345 347 class buildhgextindex(Command):
346 348 description = 'generate prebuilt index of hgext (for frozen package)'
347 349 user_options = []
348 350 _indexfilename = 'hgext/__index__.py'
349 351
350 352 def initialize_options(self):
351 353 pass
352 354
353 355 def finalize_options(self):
354 356 pass
355 357
356 358 def run(self):
357 359 if os.path.exists(self._indexfilename):
358 360 with open(self._indexfilename, 'w') as f:
359 361 f.write('# empty\n')
360 362
361 363 # here no extension enabled, disabled() lists up everything
362 364 code = ('import pprint; from mercurial import extensions; '
363 365 'pprint.pprint(extensions.disabled())')
364 366 out, err = runcmd([sys.executable, '-c', code], env)
365 367 if err:
366 368 raise DistutilsExecError(err)
367 369
368 370 with open(self._indexfilename, 'w') as f:
369 371 f.write('# this file is autogenerated by setup.py\n')
370 372 f.write('docs = ')
371 373 f.write(out)
372 374
373 375 class buildhgexe(build_ext):
374 376 description = 'compile hg.exe from mercurial/exewrapper.c'
375 377
376 378 def build_extensions(self):
377 379 if os.name != 'nt':
378 380 return
379 381 if isinstance(self.compiler, HackedMingw32CCompiler):
380 382 self.compiler.compiler_so = self.compiler.compiler # no -mdll
381 383 self.compiler.dll_libraries = [] # no -lmsrvc90
382 384
383 385 # Different Python installs can have different Python library
384 386 # names. e.g. the official CPython distribution uses pythonXY.dll
385 387 # and MinGW uses libpythonX.Y.dll.
386 388 _kernel32 = ctypes.windll.kernel32
387 389 _kernel32.GetModuleFileNameA.argtypes = [ctypes.c_void_p,
388 390 ctypes.c_void_p,
389 391 ctypes.c_ulong]
390 392 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
391 393 size = 1000
392 394 buf = ctypes.create_string_buffer(size + 1)
393 395 filelen = _kernel32.GetModuleFileNameA(sys.dllhandle, ctypes.byref(buf),
394 396 size)
395 397
396 398 if filelen > 0 and filelen != size:
397 399 dllbasename = os.path.basename(buf.value)
398 400 if not dllbasename.lower().endswith('.dll'):
399 401 raise SystemExit('Python DLL does not end with .dll: %s' %
400 402 dllbasename)
401 403 pythonlib = dllbasename[:-4]
402 404 else:
403 405 log.warn('could not determine Python DLL filename; '
404 406 'assuming pythonXY')
405 407
406 408 hv = sys.hexversion
407 409 pythonlib = 'python%d%d' % (hv >> 24, (hv >> 16) & 0xff)
408 410
409 411 log.info('using %s as Python library name' % pythonlib)
410 412 with open('mercurial/hgpythonlib.h', 'wb') as f:
411 413 f.write('/* this file is autogenerated by setup.py */\n')
412 414 f.write('#define HGPYTHONLIB "%s"\n' % pythonlib)
413 415 objects = self.compiler.compile(['mercurial/exewrapper.c'],
414 416 output_dir=self.build_temp)
415 417 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
416 418 target = os.path.join(dir, 'hg')
417 419 self.compiler.link_executable(objects, target,
418 420 libraries=[],
419 421 output_dir=self.build_temp)
420 422
421 423 @property
422 424 def hgexepath(self):
423 425 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
424 426 return os.path.join(self.build_temp, dir, 'hg.exe')
425 427
426 428 class hginstalllib(install_lib):
427 429 '''
428 430 This is a specialization of install_lib that replaces the copy_file used
429 431 there so that it supports setting the mode of files after copying them,
430 432 instead of just preserving the mode that the files originally had. If your
431 433 system has a umask of something like 027, preserving the permissions when
432 434 copying will lead to a broken install.
433 435
434 436 Note that just passing keep_permissions=False to copy_file would be
435 437 insufficient, as it might still be applying a umask.
436 438 '''
437 439
438 440 def run(self):
439 441 realcopyfile = file_util.copy_file
440 442 def copyfileandsetmode(*args, **kwargs):
441 443 src, dst = args[0], args[1]
442 444 dst, copied = realcopyfile(*args, **kwargs)
443 445 if copied:
444 446 st = os.stat(src)
445 447 # Persist executable bit (apply it to group and other if user
446 448 # has it)
447 449 if st[stat.ST_MODE] & stat.S_IXUSR:
448 450 setmode = int('0755', 8)
449 451 else:
450 452 setmode = int('0644', 8)
451 453 m = stat.S_IMODE(st[stat.ST_MODE])
452 454 m = (m & ~int('0777', 8)) | setmode
453 455 os.chmod(dst, m)
454 456 file_util.copy_file = copyfileandsetmode
455 457 try:
456 458 install_lib.run(self)
457 459 finally:
458 460 file_util.copy_file = realcopyfile
459 461
460 462 class hginstallscripts(install_scripts):
461 463 '''
462 464 This is a specialization of install_scripts that replaces the @LIBDIR@ with
463 465 the configured directory for modules. If possible, the path is made relative
464 466 to the directory for scripts.
465 467 '''
466 468
467 469 def initialize_options(self):
468 470 install_scripts.initialize_options(self)
469 471
470 472 self.install_lib = None
471 473
472 474 def finalize_options(self):
473 475 install_scripts.finalize_options(self)
474 476 self.set_undefined_options('install',
475 477 ('install_lib', 'install_lib'))
476 478
477 479 def run(self):
478 480 install_scripts.run(self)
479 481
480 482 # It only makes sense to replace @LIBDIR@ with the install path if
481 483 # the install path is known. For wheels, the logic below calculates
482 484 # the libdir to be "../..". This is because the internal layout of a
483 485 # wheel archive looks like:
484 486 #
485 487 # mercurial-3.6.1.data/scripts/hg
486 488 # mercurial/__init__.py
487 489 #
488 490 # When installing wheels, the subdirectories of the "<pkg>.data"
489 491 # directory are translated to system local paths and files therein
490 492 # are copied in place. The mercurial/* files are installed into the
491 493 # site-packages directory. However, the site-packages directory
492 494 # isn't known until wheel install time. This means we have no clue
493 495 # at wheel generation time what the installed site-packages directory
494 496 # will be. And, wheels don't appear to provide the ability to register
495 497 # custom code to run during wheel installation. This all means that
496 498 # we can't reliably set the libdir in wheels: the default behavior
497 499 # of looking in sys.path must do.
498 500
499 501 if (os.path.splitdrive(self.install_dir)[0] !=
500 502 os.path.splitdrive(self.install_lib)[0]):
501 503 # can't make relative paths from one drive to another, so use an
502 504 # absolute path instead
503 505 libdir = self.install_lib
504 506 else:
505 507 common = os.path.commonprefix((self.install_dir, self.install_lib))
506 508 rest = self.install_dir[len(common):]
507 509 uplevel = len([n for n in os.path.split(rest) if n])
508 510
509 511 libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common):]
510 512
511 513 for outfile in self.outfiles:
512 514 with open(outfile, 'rb') as fp:
513 515 data = fp.read()
514 516
515 517 # skip binary files
516 518 if b'\0' in data:
517 519 continue
518 520
519 521 # During local installs, the shebang will be rewritten to the final
520 522 # install path. During wheel packaging, the shebang has a special
521 523 # value.
522 524 if data.startswith(b'#!python'):
523 525 log.info('not rewriting @LIBDIR@ in %s because install path '
524 526 'not known' % outfile)
525 527 continue
526 528
527 529 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
528 530 with open(outfile, 'wb') as fp:
529 531 fp.write(data)
530 532
531 533 cmdclass = {'build': hgbuild,
532 534 'build_mo': hgbuildmo,
533 535 'build_ext': hgbuildext,
534 536 'build_py': hgbuildpy,
535 537 'build_scripts': hgbuildscripts,
536 538 'build_hgextindex': buildhgextindex,
537 539 'install_lib': hginstalllib,
538 540 'install_scripts': hginstallscripts,
539 541 'build_hgexe': buildhgexe,
540 542 }
541 543
542 544 packages = ['mercurial', 'mercurial.hgweb', 'mercurial.httpclient',
543 545 'mercurial.pure',
544 546 'hgext', 'hgext.convert', 'hgext.fsmonitor',
545 547 'hgext.fsmonitor.pywatchman', 'hgext.highlight',
546 548 'hgext.largefiles', 'hgext.zeroconf', 'hgext3rd']
547 549
548 550 common_depends = ['mercurial/bitmanipulation.h',
549 551 'mercurial/compat.h',
550 552 'mercurial/util.h']
551 553
552 554 osutil_ldflags = []
553 555
554 556 if sys.platform == 'darwin':
555 557 osutil_ldflags += ['-framework', 'ApplicationServices']
556 558
557 559 extmodules = [
558 560 Extension('mercurial.base85', ['mercurial/base85.c'],
559 561 depends=common_depends),
560 562 Extension('mercurial.bdiff', ['mercurial/bdiff.c',
561 563 'mercurial/bdiff_module.c'],
562 564 depends=common_depends + ['mercurial/bdiff.h']),
563 565 Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c'],
564 566 depends=common_depends),
565 567 Extension('mercurial.mpatch', ['mercurial/mpatch.c',
566 568 'mercurial/mpatch_module.c'],
567 569 depends=common_depends),
568 570 Extension('mercurial.parsers', ['mercurial/dirs.c',
569 571 'mercurial/manifest.c',
570 572 'mercurial/parsers.c',
571 573 'mercurial/pathencode.c'],
572 574 depends=common_depends),
573 575 Extension('mercurial.osutil', ['mercurial/osutil.c'],
574 576 extra_link_args=osutil_ldflags,
575 577 depends=common_depends),
576 578 Extension('hgext.fsmonitor.pywatchman.bser',
577 579 ['hgext/fsmonitor/pywatchman/bser.c']),
578 580 ]
579 581
580 582 try:
581 583 from distutils import cygwinccompiler
582 584
583 585 # the -mno-cygwin option has been deprecated for years
584 586 compiler = cygwinccompiler.Mingw32CCompiler
585 587
586 588 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
587 589 def __init__(self, *args, **kwargs):
588 590 compiler.__init__(self, *args, **kwargs)
589 591 for i in 'compiler compiler_so linker_exe linker_so'.split():
590 592 try:
591 593 getattr(self, i).remove('-mno-cygwin')
592 594 except ValueError:
593 595 pass
594 596
595 597 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
596 598 except ImportError:
597 599 # the cygwinccompiler package is not available on some Python
598 600 # distributions like the ones from the optware project for Synology
599 601 # DiskStation boxes
600 602 class HackedMingw32CCompiler(object):
601 603 pass
602 604
603 605 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
604 606 'help/*.txt',
605 607 'help/internals/*.txt',
606 608 'default.d/*.rc',
607 609 'dummycert.pem']}
608 610
609 611 def ordinarypath(p):
610 612 return p and p[0] != '.' and p[-1] != '~'
611 613
612 614 for root in ('templates',):
613 615 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
614 616 curdir = curdir.split(os.sep, 1)[1]
615 617 dirs[:] = filter(ordinarypath, dirs)
616 618 for f in filter(ordinarypath, files):
617 619 f = os.path.join(curdir, f)
618 620 packagedata['mercurial'].append(f)
619 621
620 622 datafiles = []
621 623 setupversion = version
622 624 extra = {}
623 625
624 626 if py2exeloaded:
625 627 extra['console'] = [
626 628 {'script':'hg',
627 629 'copyright':'Copyright (C) 2005-2016 Matt Mackall and others',
628 630 'product_version':version}]
629 631 # sub command of 'build' because 'py2exe' does not handle sub_commands
630 632 build.sub_commands.insert(0, ('build_hgextindex', None))
631 633 # put dlls in sub directory so that they won't pollute PATH
632 634 extra['zipfile'] = 'lib/library.zip'
633 635
634 636 if os.name == 'nt':
635 637 # Windows binary file versions for exe/dll files must have the
636 638 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
637 639 setupversion = version.split('+', 1)[0]
638 640
639 641 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
640 642 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[0].splitlines()
641 643 if version:
642 644 version = version[0]
643 645 if sys.version_info[0] == 3:
644 646 version = version.decode('utf-8')
645 647 xcode4 = (version.startswith('Xcode') and
646 648 StrictVersion(version.split()[1]) >= StrictVersion('4.0'))
647 649 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
648 650 else:
649 651 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
650 652 # installed, but instead with only command-line tools. Assume
651 653 # that only happens on >= Lion, thus no PPC support.
652 654 xcode4 = True
653 655 xcode51 = False
654 656
655 657 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
656 658 # distutils.sysconfig
657 659 if xcode4:
658 660 os.environ['ARCHFLAGS'] = ''
659 661
660 662 # XCode 5.1 changes clang such that it now fails to compile if the
661 663 # -mno-fused-madd flag is passed, but the version of Python shipped with
662 664 # OS X 10.9 Mavericks includes this flag. This causes problems in all
663 665 # C extension modules, and a bug has been filed upstream at
664 666 # http://bugs.python.org/issue21244. We also need to patch this here
665 667 # so Mercurial can continue to compile in the meantime.
666 668 if xcode51:
667 669 cflags = get_config_var('CFLAGS')
668 670 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
669 671 os.environ['CFLAGS'] = (
670 672 os.environ.get('CFLAGS', '') + ' -Qunused-arguments')
671 673
672 674 setup(name='mercurial',
673 675 version=setupversion,
674 676 author='Matt Mackall and many others',
675 677 author_email='mercurial@selenic.com',
676 678 url='https://mercurial-scm.org/',
677 679 download_url='https://mercurial-scm.org/release/',
678 680 description=('Fast scalable distributed SCM (revision control, version '
679 681 'control) system'),
680 682 long_description=('Mercurial is a distributed SCM tool written in Python.'
681 683 ' It is used by a number of large projects that require'
682 684 ' fast, reliable distributed revision control, such as '
683 685 'Mozilla.'),
684 686 license='GNU GPLv2 or any later version',
685 687 classifiers=[
686 688 'Development Status :: 6 - Mature',
687 689 'Environment :: Console',
688 690 'Intended Audience :: Developers',
689 691 'Intended Audience :: System Administrators',
690 692 'License :: OSI Approved :: GNU General Public License (GPL)',
691 693 'Natural Language :: Danish',
692 694 'Natural Language :: English',
693 695 'Natural Language :: German',
694 696 'Natural Language :: Italian',
695 697 'Natural Language :: Japanese',
696 698 'Natural Language :: Portuguese (Brazilian)',
697 699 'Operating System :: Microsoft :: Windows',
698 700 'Operating System :: OS Independent',
699 701 'Operating System :: POSIX',
700 702 'Programming Language :: C',
701 703 'Programming Language :: Python',
702 704 'Topic :: Software Development :: Version Control',
703 705 ],
704 706 scripts=scripts,
705 707 packages=packages,
706 708 ext_modules=extmodules,
707 709 data_files=datafiles,
708 710 package_data=packagedata,
709 711 cmdclass=cmdclass,
710 712 distclass=hgdist,
711 713 options={'py2exe': {'packages': ['hgext', 'email']},
712 714 'bdist_mpkg': {'zipdist': False,
713 715 'license': 'COPYING',
714 716 'readme': 'contrib/macosx/Readme.html',
715 717 'welcome': 'contrib/macosx/Welcome.html',
716 718 },
717 719 },
718 720 **extra)
General Comments 0
You need to be logged in to leave comments. Login now