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