##// END OF EJS Templates
rust-cpython: build and support for Python3...
Georges Racinet -
r41021:4277e20c default
parent child Browse files
Show More
@@ -1,299 +1,303 b''
1 1 # __init__.py - Startup and module loading logic for Mercurial.
2 2 #
3 3 # Copyright 2015 Gregory Szorc <gregory.szorc@gmail.com>
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 sys
11 11
12 12 # Allow 'from mercurial import demandimport' to keep working.
13 13 import hgdemandimport
14 14 demandimport = hgdemandimport
15 15
16 16 __all__ = []
17 17
18 18 # Python 3 uses a custom module loader that transforms source code between
19 19 # source file reading and compilation. This is done by registering a custom
20 20 # finder that changes the spec for Mercurial modules to use a custom loader.
21 21 if sys.version_info[0] >= 3:
22 22 import importlib
23 23 import importlib.abc
24 24 import io
25 25 import token
26 26 import tokenize
27 27
28 28 class hgpathentryfinder(importlib.abc.MetaPathFinder):
29 29 """A sys.meta_path finder that uses a custom module loader."""
30 30 def find_spec(self, fullname, path, target=None):
31 31 # Only handle Mercurial-related modules.
32 32 if not fullname.startswith(('mercurial.', 'hgext.', 'hgext3rd.')):
33 33 return None
34 34 # don't try to parse binary
35 35 if fullname.startswith('mercurial.cext.'):
36 36 return None
37 37 # third-party packages are expected to be dual-version clean
38 38 if fullname.startswith('mercurial.thirdparty'):
39 39 return None
40 40 # zstd is already dual-version clean, don't try and mangle it
41 41 if fullname.startswith('mercurial.zstd'):
42 42 return None
43 # rustext is built for the right python version,
44 # don't try and mangle it
45 if fullname.startswith('mercurial.rustext'):
46 return None
43 47 # pywatchman is already dual-version clean, don't try and mangle it
44 48 if fullname.startswith('hgext.fsmonitor.pywatchman'):
45 49 return None
46 50
47 51 # Try to find the module using other registered finders.
48 52 spec = None
49 53 for finder in sys.meta_path:
50 54 if finder == self:
51 55 continue
52 56
53 57 spec = finder.find_spec(fullname, path, target=target)
54 58 if spec:
55 59 break
56 60
57 61 # This is a Mercurial-related module but we couldn't find it
58 62 # using the previously-registered finders. This likely means
59 63 # the module doesn't exist.
60 64 if not spec:
61 65 return None
62 66
63 67 # TODO need to support loaders from alternate specs, like zip
64 68 # loaders.
65 69 loader = hgloader(spec.name, spec.origin)
66 70 # Can't use util.safehasattr here because that would require
67 71 # importing util, and we're in import code.
68 72 if hasattr(spec.loader, 'loader'): # hasattr-py3-only
69 73 # This is a nested loader (maybe a lazy loader?)
70 74 spec.loader.loader = loader
71 75 else:
72 76 spec.loader = loader
73 77 return spec
74 78
75 79 def replacetokens(tokens, fullname):
76 80 """Transform a stream of tokens from raw to Python 3.
77 81
78 82 It is called by the custom module loading machinery to rewrite
79 83 source/tokens between source decoding and compilation.
80 84
81 85 Returns a generator of possibly rewritten tokens.
82 86
83 87 The input token list may be mutated as part of processing. However,
84 88 its changes do not necessarily match the output token stream.
85 89
86 90 REMEMBER TO CHANGE ``BYTECODEHEADER`` WHEN CHANGING THIS FUNCTION
87 91 OR CACHED FILES WON'T GET INVALIDATED PROPERLY.
88 92 """
89 93 futureimpline = False
90 94
91 95 # The following utility functions access the tokens list and i index of
92 96 # the for i, t enumerate(tokens) loop below
93 97 def _isop(j, *o):
94 98 """Assert that tokens[j] is an OP with one of the given values"""
95 99 try:
96 100 return tokens[j].type == token.OP and tokens[j].string in o
97 101 except IndexError:
98 102 return False
99 103
100 104 def _findargnofcall(n):
101 105 """Find arg n of a call expression (start at 0)
102 106
103 107 Returns index of the first token of that argument, or None if
104 108 there is not that many arguments.
105 109
106 110 Assumes that token[i + 1] is '('.
107 111
108 112 """
109 113 nested = 0
110 114 for j in range(i + 2, len(tokens)):
111 115 if _isop(j, ')', ']', '}'):
112 116 # end of call, tuple, subscription or dict / set
113 117 nested -= 1
114 118 if nested < 0:
115 119 return None
116 120 elif n == 0:
117 121 # this is the starting position of arg
118 122 return j
119 123 elif _isop(j, '(', '[', '{'):
120 124 nested += 1
121 125 elif _isop(j, ',') and nested == 0:
122 126 n -= 1
123 127
124 128 return None
125 129
126 130 def _ensureunicode(j):
127 131 """Make sure the token at j is a unicode string
128 132
129 133 This rewrites a string token to include the unicode literal prefix
130 134 so the string transformer won't add the byte prefix.
131 135
132 136 Ignores tokens that are not strings. Assumes bounds checking has
133 137 already been done.
134 138
135 139 """
136 140 st = tokens[j]
137 141 if st.type == token.STRING and st.string.startswith(("'", '"')):
138 142 tokens[j] = st._replace(string='u%s' % st.string)
139 143
140 144 for i, t in enumerate(tokens):
141 145 # Convert most string literals to byte literals. String literals
142 146 # in Python 2 are bytes. String literals in Python 3 are unicode.
143 147 # Most strings in Mercurial are bytes and unicode strings are rare.
144 148 # Rather than rewrite all string literals to use ``b''`` to indicate
145 149 # byte strings, we apply this token transformer to insert the ``b``
146 150 # prefix nearly everywhere.
147 151 if t.type == token.STRING:
148 152 s = t.string
149 153
150 154 # Preserve docstrings as string literals. This is inconsistent
151 155 # with regular unprefixed strings. However, the
152 156 # "from __future__" parsing (which allows a module docstring to
153 157 # exist before it) doesn't properly handle the docstring if it
154 158 # is b''' prefixed, leading to a SyntaxError. We leave all
155 159 # docstrings as unprefixed to avoid this. This means Mercurial
156 160 # components touching docstrings need to handle unicode,
157 161 # unfortunately.
158 162 if s[0:3] in ("'''", '"""'):
159 163 yield t
160 164 continue
161 165
162 166 # If the first character isn't a quote, it is likely a string
163 167 # prefixing character (such as 'b', 'u', or 'r'. Ignore.
164 168 if s[0] not in ("'", '"'):
165 169 yield t
166 170 continue
167 171
168 172 # String literal. Prefix to make a b'' string.
169 173 yield t._replace(string='b%s' % t.string)
170 174 continue
171 175
172 176 # Insert compatibility imports at "from __future__ import" line.
173 177 # No '\n' should be added to preserve line numbers.
174 178 if (t.type == token.NAME and t.string == 'import' and
175 179 all(u.type == token.NAME for u in tokens[i - 2:i]) and
176 180 [u.string for u in tokens[i - 2:i]] == ['from', '__future__']):
177 181 futureimpline = True
178 182 if t.type == token.NEWLINE and futureimpline:
179 183 futureimpline = False
180 184 if fullname == 'mercurial.pycompat':
181 185 yield t
182 186 continue
183 187 r, c = t.start
184 188 l = (b'; from mercurial.pycompat import '
185 189 b'delattr, getattr, hasattr, setattr, '
186 190 b'open, unicode\n')
187 191 for u in tokenize.tokenize(io.BytesIO(l).readline):
188 192 if u.type in (tokenize.ENCODING, token.ENDMARKER):
189 193 continue
190 194 yield u._replace(
191 195 start=(r, c + u.start[1]), end=(r, c + u.end[1]))
192 196 continue
193 197
194 198 # This looks like a function call.
195 199 if t.type == token.NAME and _isop(i + 1, '('):
196 200 fn = t.string
197 201
198 202 # *attr() builtins don't accept byte strings to 2nd argument.
199 203 if (fn in ('getattr', 'setattr', 'hasattr', 'safehasattr') and
200 204 not _isop(i - 1, '.')):
201 205 arg1idx = _findargnofcall(1)
202 206 if arg1idx is not None:
203 207 _ensureunicode(arg1idx)
204 208
205 209 # .encode() and .decode() on str/bytes/unicode don't accept
206 210 # byte strings on Python 3.
207 211 elif fn in ('encode', 'decode') and _isop(i - 1, '.'):
208 212 for argn in range(2):
209 213 argidx = _findargnofcall(argn)
210 214 if argidx is not None:
211 215 _ensureunicode(argidx)
212 216
213 217 # It changes iteritems/values to items/values as they are not
214 218 # present in Python 3 world.
215 219 elif fn in ('iteritems', 'itervalues'):
216 220 yield t._replace(string=fn[4:])
217 221 continue
218 222
219 223 # Emit unmodified token.
220 224 yield t
221 225
222 226 # Header to add to bytecode files. This MUST be changed when
223 227 # ``replacetoken`` or any mechanism that changes semantics of module
224 228 # loading is changed. Otherwise cached bytecode may get loaded without
225 229 # the new transformation mechanisms applied.
226 230 BYTECODEHEADER = b'HG\x00\x0b'
227 231
228 232 class hgloader(importlib.machinery.SourceFileLoader):
229 233 """Custom module loader that transforms source code.
230 234
231 235 When the source code is converted to a code object, we transform
232 236 certain patterns to be Python 3 compatible. This allows us to write code
233 237 that is natively Python 2 and compatible with Python 3 without
234 238 making the code excessively ugly.
235 239
236 240 We do this by transforming the token stream between parse and compile.
237 241
238 242 Implementing transformations invalidates caching assumptions made
239 243 by the built-in importer. The built-in importer stores a header on
240 244 saved bytecode files indicating the Python/bytecode version. If the
241 245 version changes, the cached bytecode is ignored. The Mercurial
242 246 transformations could change at any time. This means we need to check
243 247 that cached bytecode was generated with the current transformation
244 248 code or there could be a mismatch between cached bytecode and what
245 249 would be generated from this class.
246 250
247 251 We supplement the bytecode caching layer by wrapping ``get_data``
248 252 and ``set_data``. These functions are called when the
249 253 ``SourceFileLoader`` retrieves and saves bytecode cache files,
250 254 respectively. We simply add an additional header on the file. As
251 255 long as the version in this file is changed when semantics change,
252 256 cached bytecode should be invalidated when transformations change.
253 257
254 258 The added header has the form ``HG<VERSION>``. That is a literal
255 259 ``HG`` with 2 binary bytes indicating the transformation version.
256 260 """
257 261 def get_data(self, path):
258 262 data = super(hgloader, self).get_data(path)
259 263
260 264 if not path.endswith(tuple(importlib.machinery.BYTECODE_SUFFIXES)):
261 265 return data
262 266
263 267 # There should be a header indicating the Mercurial transformation
264 268 # version. If it doesn't exist or doesn't match the current version,
265 269 # we raise an OSError because that is what
266 270 # ``SourceFileLoader.get_code()`` expects when loading bytecode
267 271 # paths to indicate the cached file is "bad."
268 272 if data[0:2] != b'HG':
269 273 raise OSError('no hg header')
270 274 if data[0:4] != BYTECODEHEADER:
271 275 raise OSError('hg header version mismatch')
272 276
273 277 return data[4:]
274 278
275 279 def set_data(self, path, data, *args, **kwargs):
276 280 if path.endswith(tuple(importlib.machinery.BYTECODE_SUFFIXES)):
277 281 data = BYTECODEHEADER + data
278 282
279 283 return super(hgloader, self).set_data(path, data, *args, **kwargs)
280 284
281 285 def source_to_code(self, data, path):
282 286 """Perform token transformation before compilation."""
283 287 buf = io.BytesIO(data)
284 288 tokens = tokenize.tokenize(buf.readline)
285 289 data = tokenize.untokenize(replacetokens(list(tokens), self.name))
286 290 # Python's built-in importer strips frames from exceptions raised
287 291 # for this code. Unfortunately, that mechanism isn't extensible
288 292 # and our frame will be blamed for the import failure. There
289 293 # are extremely hacky ways to do frame stripping. We haven't
290 294 # implemented them because they are very ugly.
291 295 return super(hgloader, self).source_to_code(data, path)
292 296
293 297 # We automagically register our custom importer as a side-effect of
294 298 # loading. This is necessary to ensure that any entry points are able
295 299 # to import mercurial.* modules without having to perform this
296 300 # registration themselves.
297 301 if not any(isinstance(x, hgpathentryfinder) for x in sys.meta_path):
298 302 # meta_path is used before any implicit finders and before sys.path.
299 303 sys.meta_path.insert(0, hgpathentryfinder())
@@ -1,148 +1,149 b''
1 1 [[package]]
2 2 name = "aho-corasick"
3 3 version = "0.6.9"
4 4 source = "registry+https://github.com/rust-lang/crates.io-index"
5 5 dependencies = [
6 6 "memchr 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
7 7 ]
8 8
9 9 [[package]]
10 10 name = "cfg-if"
11 11 version = "0.1.6"
12 12 source = "registry+https://github.com/rust-lang/crates.io-index"
13 13
14 14 [[package]]
15 15 name = "cpython"
16 16 version = "0.2.1"
17 17 source = "registry+https://github.com/rust-lang/crates.io-index"
18 18 dependencies = [
19 19 "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)",
20 20 "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
21 21 "python27-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
22 "python3-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
22 23 ]
23 24
24 25 [[package]]
25 26 name = "hg-core"
26 27 version = "0.1.0"
27 28
28 29 [[package]]
29 30 name = "hg-cpython"
30 31 version = "0.1.0"
31 32 dependencies = [
32 33 "cpython 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
33 34 "hg-core 0.1.0",
34 35 "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)",
35 36 "python27-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
36 37 "python3-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
37 38 ]
38 39
39 40 [[package]]
40 41 name = "hgdirectffi"
41 42 version = "0.1.0"
42 43 dependencies = [
43 44 "hg-core 0.1.0",
44 45 "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)",
45 46 ]
46 47
47 48 [[package]]
48 49 name = "lazy_static"
49 50 version = "1.2.0"
50 51 source = "registry+https://github.com/rust-lang/crates.io-index"
51 52
52 53 [[package]]
53 54 name = "libc"
54 55 version = "0.2.45"
55 56 source = "registry+https://github.com/rust-lang/crates.io-index"
56 57
57 58 [[package]]
58 59 name = "memchr"
59 60 version = "2.1.2"
60 61 source = "registry+https://github.com/rust-lang/crates.io-index"
61 62 dependencies = [
62 63 "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
63 64 "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)",
64 65 "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
65 66 ]
66 67
67 68 [[package]]
68 69 name = "num-traits"
69 70 version = "0.2.6"
70 71 source = "registry+https://github.com/rust-lang/crates.io-index"
71 72
72 73 [[package]]
73 74 name = "python27-sys"
74 75 version = "0.2.1"
75 76 source = "registry+https://github.com/rust-lang/crates.io-index"
76 77 dependencies = [
77 78 "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)",
78 79 "regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
79 80 ]
80 81
81 82 [[package]]
82 83 name = "python3-sys"
83 84 version = "0.2.1"
84 85 source = "registry+https://github.com/rust-lang/crates.io-index"
85 86 dependencies = [
86 87 "libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)",
87 88 "regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
88 89 ]
89 90
90 91 [[package]]
91 92 name = "regex"
92 93 version = "1.1.0"
93 94 source = "registry+https://github.com/rust-lang/crates.io-index"
94 95 dependencies = [
95 96 "aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)",
96 97 "memchr 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
97 98 "regex-syntax 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
98 99 "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
99 100 "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
100 101 ]
101 102
102 103 [[package]]
103 104 name = "regex-syntax"
104 105 version = "0.6.4"
105 106 source = "registry+https://github.com/rust-lang/crates.io-index"
106 107 dependencies = [
107 108 "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
108 109 ]
109 110
110 111 [[package]]
111 112 name = "thread_local"
112 113 version = "0.3.6"
113 114 source = "registry+https://github.com/rust-lang/crates.io-index"
114 115 dependencies = [
115 116 "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
116 117 ]
117 118
118 119 [[package]]
119 120 name = "ucd-util"
120 121 version = "0.1.3"
121 122 source = "registry+https://github.com/rust-lang/crates.io-index"
122 123
123 124 [[package]]
124 125 name = "utf8-ranges"
125 126 version = "1.0.2"
126 127 source = "registry+https://github.com/rust-lang/crates.io-index"
127 128
128 129 [[package]]
129 130 name = "version_check"
130 131 version = "0.1.5"
131 132 source = "registry+https://github.com/rust-lang/crates.io-index"
132 133
133 134 [metadata]
134 135 "checksum aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9a933f4e58658d7b12defcf96dc5c720f20832deebe3e0a19efd3b6aaeeb9e"
135 136 "checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4"
136 137 "checksum cpython 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b489034e723e7f5109fecd19b719e664f89ef925be785885252469e9822fa940"
137 138 "checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1"
138 139 "checksum libc 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)" = "2d2857ec59fadc0773853c664d2d18e7198e83883e7060b63c924cb077bd5c74"
139 140 "checksum memchr 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "db4c41318937f6e76648f42826b1d9ade5c09cafb5aef7e351240a70f39206e9"
140 141 "checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1"
141 142 "checksum python27-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "56114c37d4dca82526d74009df7782a28c871ac9d36b19d4cb9e67672258527e"
142 143 "checksum python3-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "61e4aac43f833fd637e429506cb2ac9d7df672c4b68f2eaaa163649b7fdc0444"
143 144 "checksum regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "37e7cbbd370869ce2e8dff25c7018702d10b21a20ef7135316f8daecd6c25b7f"
144 145 "checksum regex-syntax 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4e47a2ed29da7a9e1960e1639e7a982e6edc6d49be308a3b02daf511504a16d1"
145 146 "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
146 147 "checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86"
147 148 "checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737"
148 149 "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
@@ -1,29 +1,34 b''
1 1 [package]
2 2 name = "hg-cpython"
3 3 version = "0.1.0"
4 4 authors = ["Georges Racinet <gracinet@anybox.fr>"]
5 5
6 6 [lib]
7 7 name='rusthg'
8 8 crate-type = ["cdylib"]
9 9
10 10 [features]
11 default = ["python27", "python27-sys"]
11 default = ["python27"]
12 12
13 python27 = ["cpython/python27-sys", "cpython/extension-module-2-7"]
13 python27 = ["cpython/python27-sys",
14 "cpython/extension-module-2-7",
15 "python27-sys",
16 ]
17
18 python3 = ["python3-sys", "cpython/python3-sys", "cpython/extension-module"]
14 19
15 20 [dependencies]
16 21 hg-core = { path = "../hg-core" }
17 22 libc = '*'
18 23
19 24 [dependencies.cpython]
20 25 version = "*"
21 26 default-features = false
22 27
23 28 [dependencies.python27-sys]
24 29 version = "0.2.1"
25 30 optional = true
26 31
27 32 [dependencies.python3-sys]
28 33 version = "0.2.1"
29 34 optional = true
@@ -1,1237 +1,1243 b''
1 1 #
2 2 # This is the mercurial setup script.
3 3 #
4 4 # 'python setup.py install', or
5 5 # 'python setup.py --help' for more options
6 6
7 7 import os
8 8
9 9 supportedpy = '~= 2.7'
10 10 if os.environ.get('HGALLOWPYTHON3', ''):
11 11 # Mercurial will never work on Python 3 before 3.5 due to a lack
12 12 # of % formatting on bytestrings, and can't work on 3.6.0 or 3.6.1
13 13 # due to a bug in % formatting in bytestrings.
14 14 # We cannot support Python 3.5.0, 3.5.1, 3.5.2 because of bug in
15 15 # codecs.escape_encode() where it raises SystemError on empty bytestring
16 16 # bug link: https://bugs.python.org/issue25270
17 17 #
18 18 # TODO: when we actually work on Python 3, use this string as the
19 19 # actual supportedpy string.
20 20 supportedpy = ','.join([
21 21 '>=2.7',
22 22 '!=3.0.*',
23 23 '!=3.1.*',
24 24 '!=3.2.*',
25 25 '!=3.3.*',
26 26 '!=3.4.*',
27 27 '!=3.5.0',
28 28 '!=3.5.1',
29 29 '!=3.5.2',
30 30 '!=3.6.0',
31 31 '!=3.6.1',
32 32 ])
33 33
34 34 import sys, platform
35 35 if sys.version_info[0] >= 3:
36 36 printf = eval('print')
37 37 libdir_escape = 'unicode_escape'
38 38 def sysstr(s):
39 39 return s.decode('latin-1')
40 40 else:
41 41 libdir_escape = 'string_escape'
42 42 def printf(*args, **kwargs):
43 43 f = kwargs.get('file', sys.stdout)
44 44 end = kwargs.get('end', '\n')
45 45 f.write(b' '.join(args) + end)
46 46 def sysstr(s):
47 47 return s
48 48
49 49 # Attempt to guide users to a modern pip - this means that 2.6 users
50 50 # should have a chance of getting a 4.2 release, and when we ratchet
51 51 # the version requirement forward again hopefully everyone will get
52 52 # something that works for them.
53 53 if sys.version_info < (2, 7, 0, 'final'):
54 54 pip_message = ('This may be due to an out of date pip. '
55 55 'Make sure you have pip >= 9.0.1.')
56 56 try:
57 57 import pip
58 58 pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
59 59 if pip_version < (9, 0, 1) :
60 60 pip_message = (
61 61 'Your pip version is out of date, please install '
62 62 'pip >= 9.0.1. pip {} detected.'.format(pip.__version__))
63 63 else:
64 64 # pip is new enough - it must be something else
65 65 pip_message = ''
66 66 except Exception:
67 67 pass
68 68 error = """
69 69 Mercurial does not support Python older than 2.7.
70 70 Python {py} detected.
71 71 {pip}
72 72 """.format(py=sys.version_info, pip=pip_message)
73 73 printf(error, file=sys.stderr)
74 74 sys.exit(1)
75 75
76 76 # We don't yet officially support Python 3. But we want to allow developers to
77 77 # hack on. Detect and disallow running on Python 3 by default. But provide a
78 78 # backdoor to enable working on Python 3.
79 79 if sys.version_info[0] != 2:
80 80 badpython = True
81 81
82 82 # Allow Python 3 from source checkouts.
83 83 if os.path.isdir('.hg') or 'HGPYTHON3' in os.environ:
84 84 badpython = False
85 85
86 86 if badpython:
87 87 error = """
88 88 Mercurial only supports Python 2.7.
89 89 Python {py} detected.
90 90 Please re-run with Python 2.7.
91 91 """.format(py=sys.version_info)
92 92
93 93 printf(error, file=sys.stderr)
94 94 sys.exit(1)
95 95
96 96 # Solaris Python packaging brain damage
97 97 try:
98 98 import hashlib
99 99 sha = hashlib.sha1()
100 100 except ImportError:
101 101 try:
102 102 import sha
103 103 sha.sha # silence unused import warning
104 104 except ImportError:
105 105 raise SystemExit(
106 106 "Couldn't import standard hashlib (incomplete Python install).")
107 107
108 108 try:
109 109 import zlib
110 110 zlib.compressobj # silence unused import warning
111 111 except ImportError:
112 112 raise SystemExit(
113 113 "Couldn't import standard zlib (incomplete Python install).")
114 114
115 115 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
116 116 isironpython = False
117 117 try:
118 118 isironpython = (platform.python_implementation()
119 119 .lower().find("ironpython") != -1)
120 120 except AttributeError:
121 121 pass
122 122
123 123 if isironpython:
124 124 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
125 125 else:
126 126 try:
127 127 import bz2
128 128 bz2.BZ2Compressor # silence unused import warning
129 129 except ImportError:
130 130 raise SystemExit(
131 131 "Couldn't import standard bz2 (incomplete Python install).")
132 132
133 133 ispypy = "PyPy" in sys.version
134 134
135 135 hgrustext = os.environ.get('HGWITHRUSTEXT')
136 136 # TODO record it for proper rebuild upon changes
137 137 # (see mercurial/__modulepolicy__.py)
138 138 if hgrustext != 'cpython' and hgrustext is not None:
139 139 hgrustext = 'direct-ffi'
140 140
141 141 import ctypes
142 142 import errno
143 143 import stat, subprocess, time
144 144 import re
145 145 import shutil
146 146 import tempfile
147 147 from distutils import log
148 148 # We have issues with setuptools on some platforms and builders. Until
149 149 # those are resolved, setuptools is opt-in except for platforms where
150 150 # we don't have issues.
151 151 issetuptools = (os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ)
152 152 if issetuptools:
153 153 from setuptools import setup
154 154 else:
155 155 from distutils.core import setup
156 156 from distutils.ccompiler import new_compiler
157 157 from distutils.core import Command, Extension
158 158 from distutils.dist import Distribution
159 159 from distutils.command.build import build
160 160 from distutils.command.build_ext import build_ext
161 161 from distutils.command.build_py import build_py
162 162 from distutils.command.build_scripts import build_scripts
163 163 from distutils.command.install import install
164 164 from distutils.command.install_lib import install_lib
165 165 from distutils.command.install_scripts import install_scripts
166 166 from distutils.spawn import spawn, find_executable
167 167 from distutils import file_util
168 168 from distutils.errors import (
169 169 CCompilerError,
170 170 DistutilsError,
171 171 DistutilsExecError,
172 172 )
173 173 from distutils.sysconfig import get_python_inc, get_config_var
174 174 from distutils.version import StrictVersion
175 175
176 176 # Explain to distutils.StrictVersion how our release candidates are versionned
177 177 StrictVersion.version_re = re.compile(r'^(\d+)\.(\d+)(\.(\d+))?-?(rc(\d+))?$')
178 178
179 179 def write_if_changed(path, content):
180 180 """Write content to a file iff the content hasn't changed."""
181 181 if os.path.exists(path):
182 182 with open(path, 'rb') as fh:
183 183 current = fh.read()
184 184 else:
185 185 current = b''
186 186
187 187 if current != content:
188 188 with open(path, 'wb') as fh:
189 189 fh.write(content)
190 190
191 191 scripts = ['hg']
192 192 if os.name == 'nt':
193 193 # We remove hg.bat if we are able to build hg.exe.
194 194 scripts.append('contrib/win32/hg.bat')
195 195
196 196 def cancompile(cc, code):
197 197 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
198 198 devnull = oldstderr = None
199 199 try:
200 200 fname = os.path.join(tmpdir, 'testcomp.c')
201 201 f = open(fname, 'w')
202 202 f.write(code)
203 203 f.close()
204 204 # Redirect stderr to /dev/null to hide any error messages
205 205 # from the compiler.
206 206 # This will have to be changed if we ever have to check
207 207 # for a function on Windows.
208 208 devnull = open('/dev/null', 'w')
209 209 oldstderr = os.dup(sys.stderr.fileno())
210 210 os.dup2(devnull.fileno(), sys.stderr.fileno())
211 211 objects = cc.compile([fname], output_dir=tmpdir)
212 212 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
213 213 return True
214 214 except Exception:
215 215 return False
216 216 finally:
217 217 if oldstderr is not None:
218 218 os.dup2(oldstderr, sys.stderr.fileno())
219 219 if devnull is not None:
220 220 devnull.close()
221 221 shutil.rmtree(tmpdir)
222 222
223 223 # simplified version of distutils.ccompiler.CCompiler.has_function
224 224 # that actually removes its temporary files.
225 225 def hasfunction(cc, funcname):
226 226 code = 'int main(void) { %s(); }\n' % funcname
227 227 return cancompile(cc, code)
228 228
229 229 def hasheader(cc, headername):
230 230 code = '#include <%s>\nint main(void) { return 0; }\n' % headername
231 231 return cancompile(cc, code)
232 232
233 233 # py2exe needs to be installed to work
234 234 try:
235 235 import py2exe
236 236 py2exe.Distribution # silence unused import warning
237 237 py2exeloaded = True
238 238 # import py2exe's patched Distribution class
239 239 from distutils.core import Distribution
240 240 except ImportError:
241 241 py2exeloaded = False
242 242
243 243 def runcmd(cmd, env):
244 244 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
245 245 stderr=subprocess.PIPE, env=env)
246 246 out, err = p.communicate()
247 247 return p.returncode, out, err
248 248
249 249 class hgcommand(object):
250 250 def __init__(self, cmd, env):
251 251 self.cmd = cmd
252 252 self.env = env
253 253
254 254 def run(self, args):
255 255 cmd = self.cmd + args
256 256 returncode, out, err = runcmd(cmd, self.env)
257 257 err = filterhgerr(err)
258 258 if err or returncode != 0:
259 259 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
260 260 printf(err, file=sys.stderr)
261 261 return ''
262 262 return out
263 263
264 264 def filterhgerr(err):
265 265 # If root is executing setup.py, but the repository is owned by
266 266 # another user (as in "sudo python setup.py install") we will get
267 267 # trust warnings since the .hg/hgrc file is untrusted. That is
268 268 # fine, we don't want to load it anyway. Python may warn about
269 269 # a missing __init__.py in mercurial/locale, we also ignore that.
270 270 err = [e for e in err.splitlines()
271 271 if (not e.startswith(b'not trusting file')
272 272 and not e.startswith(b'warning: Not importing')
273 273 and not e.startswith(b'obsolete feature not enabled')
274 274 and not e.startswith(b'*** failed to import extension')
275 275 and not e.startswith(b'devel-warn:')
276 276 and not (e.startswith(b'(third party extension')
277 277 and e.endswith(b'or newer of Mercurial; disabling)')))]
278 278 return b'\n'.join(b' ' + e for e in err)
279 279
280 280 def findhg():
281 281 """Try to figure out how we should invoke hg for examining the local
282 282 repository contents.
283 283
284 284 Returns an hgcommand object."""
285 285 # By default, prefer the "hg" command in the user's path. This was
286 286 # presumably the hg command that the user used to create this repository.
287 287 #
288 288 # This repository may require extensions or other settings that would not
289 289 # be enabled by running the hg script directly from this local repository.
290 290 hgenv = os.environ.copy()
291 291 # Use HGPLAIN to disable hgrc settings that would change output formatting,
292 292 # and disable localization for the same reasons.
293 293 hgenv['HGPLAIN'] = '1'
294 294 hgenv['LANGUAGE'] = 'C'
295 295 hgcmd = ['hg']
296 296 # Run a simple "hg log" command just to see if using hg from the user's
297 297 # path works and can successfully interact with this repository. Windows
298 298 # gives precedence to hg.exe in the current directory, so fall back to the
299 299 # python invocation of local hg, where pythonXY.dll can always be found.
300 300 check_cmd = ['log', '-r.', '-Ttest']
301 301 if os.name != 'nt':
302 302 try:
303 303 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
304 304 except EnvironmentError:
305 305 retcode = -1
306 306 if retcode == 0 and not filterhgerr(err):
307 307 return hgcommand(hgcmd, hgenv)
308 308
309 309 # Fall back to trying the local hg installation.
310 310 hgenv = localhgenv()
311 311 hgcmd = [sys.executable, 'hg']
312 312 try:
313 313 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
314 314 except EnvironmentError:
315 315 retcode = -1
316 316 if retcode == 0 and not filterhgerr(err):
317 317 return hgcommand(hgcmd, hgenv)
318 318
319 319 raise SystemExit('Unable to find a working hg binary to extract the '
320 320 'version from the repository tags')
321 321
322 322 def localhgenv():
323 323 """Get an environment dictionary to use for invoking or importing
324 324 mercurial from the local repository."""
325 325 # Execute hg out of this directory with a custom environment which takes
326 326 # care to not use any hgrc files and do no localization.
327 327 env = {'HGMODULEPOLICY': 'py',
328 328 'HGRCPATH': '',
329 329 'LANGUAGE': 'C',
330 330 'PATH': ''} # make pypi modules that use os.environ['PATH'] happy
331 331 if 'LD_LIBRARY_PATH' in os.environ:
332 332 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
333 333 if 'SystemRoot' in os.environ:
334 334 # SystemRoot is required by Windows to load various DLLs. See:
335 335 # https://bugs.python.org/issue13524#msg148850
336 336 env['SystemRoot'] = os.environ['SystemRoot']
337 337 return env
338 338
339 339 version = ''
340 340
341 341 if os.path.isdir('.hg'):
342 342 hg = findhg()
343 343 cmd = ['log', '-r', '.', '--template', '{tags}\n']
344 344 numerictags = [t for t in sysstr(hg.run(cmd)).split() if t[0:1].isdigit()]
345 345 hgid = sysstr(hg.run(['id', '-i'])).strip()
346 346 if not hgid:
347 347 # Bail out if hg is having problems interacting with this repository,
348 348 # rather than falling through and producing a bogus version number.
349 349 # Continuing with an invalid version number will break extensions
350 350 # that define minimumhgversion.
351 351 raise SystemExit('Unable to determine hg version from local repository')
352 352 if numerictags: # tag(s) found
353 353 version = numerictags[-1]
354 354 if hgid.endswith('+'): # propagate the dirty status to the tag
355 355 version += '+'
356 356 else: # no tag found
357 357 ltagcmd = ['parents', '--template', '{latesttag}']
358 358 ltag = sysstr(hg.run(ltagcmd))
359 359 changessincecmd = ['log', '-T', 'x\n', '-r', "only(.,'%s')" % ltag]
360 360 changessince = len(hg.run(changessincecmd).splitlines())
361 361 version = '%s+%s-%s' % (ltag, changessince, hgid)
362 362 if version.endswith('+'):
363 363 version += time.strftime('%Y%m%d')
364 364 elif os.path.exists('.hg_archival.txt'):
365 365 kw = dict([[t.strip() for t in l.split(':', 1)]
366 366 for l in open('.hg_archival.txt')])
367 367 if 'tag' in kw:
368 368 version = kw['tag']
369 369 elif 'latesttag' in kw:
370 370 if 'changessincelatesttag' in kw:
371 371 version = '%(latesttag)s+%(changessincelatesttag)s-%(node).12s' % kw
372 372 else:
373 373 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
374 374 else:
375 375 version = kw.get('node', '')[:12]
376 376
377 377 if version:
378 378 versionb = version
379 379 if not isinstance(versionb, bytes):
380 380 versionb = versionb.encode('ascii')
381 381
382 382 write_if_changed('mercurial/__version__.py', b''.join([
383 383 b'# this file is autogenerated by setup.py\n'
384 384 b'version = b"%s"\n' % versionb,
385 385 ]))
386 386
387 387 try:
388 388 oldpolicy = os.environ.get('HGMODULEPOLICY', None)
389 389 os.environ['HGMODULEPOLICY'] = 'py'
390 390 from mercurial import __version__
391 391 version = __version__.version
392 392 except ImportError:
393 393 version = b'unknown'
394 394 finally:
395 395 if oldpolicy is None:
396 396 del os.environ['HGMODULEPOLICY']
397 397 else:
398 398 os.environ['HGMODULEPOLICY'] = oldpolicy
399 399
400 400 class hgbuild(build):
401 401 # Insert hgbuildmo first so that files in mercurial/locale/ are found
402 402 # when build_py is run next.
403 403 sub_commands = [('build_mo', None)] + build.sub_commands
404 404
405 405 class hgbuildmo(build):
406 406
407 407 description = "build translations (.mo files)"
408 408
409 409 def run(self):
410 410 if not find_executable('msgfmt'):
411 411 self.warn("could not find msgfmt executable, no translations "
412 412 "will be built")
413 413 return
414 414
415 415 podir = 'i18n'
416 416 if not os.path.isdir(podir):
417 417 self.warn("could not find %s/ directory" % podir)
418 418 return
419 419
420 420 join = os.path.join
421 421 for po in os.listdir(podir):
422 422 if not po.endswith('.po'):
423 423 continue
424 424 pofile = join(podir, po)
425 425 modir = join('locale', po[:-3], 'LC_MESSAGES')
426 426 mofile = join(modir, 'hg.mo')
427 427 mobuildfile = join('mercurial', mofile)
428 428 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
429 429 if sys.platform != 'sunos5':
430 430 # msgfmt on Solaris does not know about -c
431 431 cmd.append('-c')
432 432 self.mkpath(join('mercurial', modir))
433 433 self.make_file([pofile], mobuildfile, spawn, (cmd,))
434 434
435 435
436 436 class hgdist(Distribution):
437 437 pure = False
438 438 cffi = ispypy
439 439
440 440 global_options = Distribution.global_options + \
441 441 [('pure', None, "use pure (slow) Python "
442 442 "code instead of C extensions"),
443 443 ]
444 444
445 445 def has_ext_modules(self):
446 446 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
447 447 # too late for some cases
448 448 return not self.pure and Distribution.has_ext_modules(self)
449 449
450 450 # This is ugly as a one-liner. So use a variable.
451 451 buildextnegops = dict(getattr(build_ext, 'negative_options', {}))
452 452 buildextnegops['no-zstd'] = 'zstd'
453 453
454 454 class hgbuildext(build_ext):
455 455 user_options = build_ext.user_options + [
456 456 ('zstd', None, 'compile zstd bindings [default]'),
457 457 ('no-zstd', None, 'do not compile zstd bindings'),
458 458 ]
459 459
460 460 boolean_options = build_ext.boolean_options + ['zstd']
461 461 negative_opt = buildextnegops
462 462
463 463 def initialize_options(self):
464 464 self.zstd = True
465 465 return build_ext.initialize_options(self)
466 466
467 467 def build_extensions(self):
468 468 ruststandalones = [e for e in self.extensions
469 469 if isinstance(e, RustStandaloneExtension)]
470 470 self.extensions = [e for e in self.extensions
471 471 if e not in ruststandalones]
472 472 # Filter out zstd if disabled via argument.
473 473 if not self.zstd:
474 474 self.extensions = [e for e in self.extensions
475 475 if e.name != 'mercurial.zstd']
476 476
477 477 for rustext in ruststandalones:
478 478 rustext.build('' if self.inplace else self.build_lib)
479 479
480 480 return build_ext.build_extensions(self)
481 481
482 482 def build_extension(self, ext):
483 483 if isinstance(ext, RustExtension):
484 484 ext.rustbuild()
485 485 try:
486 486 build_ext.build_extension(self, ext)
487 487 except CCompilerError:
488 488 if not getattr(ext, 'optional', False):
489 489 raise
490 490 log.warn("Failed to build optional extension '%s' (skipping)",
491 491 ext.name)
492 492
493 493 class hgbuildscripts(build_scripts):
494 494 def run(self):
495 495 if os.name != 'nt' or self.distribution.pure:
496 496 return build_scripts.run(self)
497 497
498 498 exebuilt = False
499 499 try:
500 500 self.run_command('build_hgexe')
501 501 exebuilt = True
502 502 except (DistutilsError, CCompilerError):
503 503 log.warn('failed to build optional hg.exe')
504 504
505 505 if exebuilt:
506 506 # Copying hg.exe to the scripts build directory ensures it is
507 507 # installed by the install_scripts command.
508 508 hgexecommand = self.get_finalized_command('build_hgexe')
509 509 dest = os.path.join(self.build_dir, 'hg.exe')
510 510 self.mkpath(self.build_dir)
511 511 self.copy_file(hgexecommand.hgexepath, dest)
512 512
513 513 # Remove hg.bat because it is redundant with hg.exe.
514 514 self.scripts.remove('contrib/win32/hg.bat')
515 515
516 516 return build_scripts.run(self)
517 517
518 518 class hgbuildpy(build_py):
519 519 def finalize_options(self):
520 520 build_py.finalize_options(self)
521 521
522 522 if self.distribution.pure:
523 523 self.distribution.ext_modules = []
524 524 elif self.distribution.cffi:
525 525 from mercurial.cffi import (
526 526 bdiffbuild,
527 527 mpatchbuild,
528 528 )
529 529 exts = [mpatchbuild.ffi.distutils_extension(),
530 530 bdiffbuild.ffi.distutils_extension()]
531 531 # cffi modules go here
532 532 if sys.platform == 'darwin':
533 533 from mercurial.cffi import osutilbuild
534 534 exts.append(osutilbuild.ffi.distutils_extension())
535 535 self.distribution.ext_modules = exts
536 536 else:
537 537 h = os.path.join(get_python_inc(), 'Python.h')
538 538 if not os.path.exists(h):
539 539 raise SystemExit('Python headers are required to build '
540 540 'Mercurial but weren\'t found in %s' % h)
541 541
542 542 def run(self):
543 543 basepath = os.path.join(self.build_lib, 'mercurial')
544 544 self.mkpath(basepath)
545 545
546 546 if self.distribution.pure:
547 547 modulepolicy = 'py'
548 548 elif self.build_lib == '.':
549 549 # in-place build should run without rebuilding C extensions
550 550 modulepolicy = 'allow'
551 551 else:
552 552 modulepolicy = 'c'
553 553
554 554 content = b''.join([
555 555 b'# this file is autogenerated by setup.py\n',
556 556 b'modulepolicy = b"%s"\n' % modulepolicy.encode('ascii'),
557 557 ])
558 558 write_if_changed(os.path.join(basepath, '__modulepolicy__.py'),
559 559 content)
560 560
561 561 build_py.run(self)
562 562
563 563 class buildhgextindex(Command):
564 564 description = 'generate prebuilt index of hgext (for frozen package)'
565 565 user_options = []
566 566 _indexfilename = 'hgext/__index__.py'
567 567
568 568 def initialize_options(self):
569 569 pass
570 570
571 571 def finalize_options(self):
572 572 pass
573 573
574 574 def run(self):
575 575 if os.path.exists(self._indexfilename):
576 576 with open(self._indexfilename, 'w') as f:
577 577 f.write('# empty\n')
578 578
579 579 # here no extension enabled, disabled() lists up everything
580 580 code = ('import pprint; from mercurial import extensions; '
581 581 'pprint.pprint(extensions.disabled())')
582 582 returncode, out, err = runcmd([sys.executable, '-c', code],
583 583 localhgenv())
584 584 if err or returncode != 0:
585 585 raise DistutilsExecError(err)
586 586
587 587 with open(self._indexfilename, 'w') as f:
588 588 f.write('# this file is autogenerated by setup.py\n')
589 589 f.write('docs = ')
590 590 f.write(out)
591 591
592 592 class buildhgexe(build_ext):
593 593 description = 'compile hg.exe from mercurial/exewrapper.c'
594 594 user_options = build_ext.user_options + [
595 595 ('long-paths-support', None, 'enable support for long paths on '
596 596 'Windows (off by default and '
597 597 'experimental)'),
598 598 ]
599 599
600 600 LONG_PATHS_MANIFEST = """
601 601 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
602 602 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
603 603 <application>
604 604 <windowsSettings
605 605 xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
606 606 <ws2:longPathAware>true</ws2:longPathAware>
607 607 </windowsSettings>
608 608 </application>
609 609 </assembly>"""
610 610
611 611 def initialize_options(self):
612 612 build_ext.initialize_options(self)
613 613 self.long_paths_support = False
614 614
615 615 def build_extensions(self):
616 616 if os.name != 'nt':
617 617 return
618 618 if isinstance(self.compiler, HackedMingw32CCompiler):
619 619 self.compiler.compiler_so = self.compiler.compiler # no -mdll
620 620 self.compiler.dll_libraries = [] # no -lmsrvc90
621 621
622 622 # Different Python installs can have different Python library
623 623 # names. e.g. the official CPython distribution uses pythonXY.dll
624 624 # and MinGW uses libpythonX.Y.dll.
625 625 _kernel32 = ctypes.windll.kernel32
626 626 _kernel32.GetModuleFileNameA.argtypes = [ctypes.c_void_p,
627 627 ctypes.c_void_p,
628 628 ctypes.c_ulong]
629 629 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
630 630 size = 1000
631 631 buf = ctypes.create_string_buffer(size + 1)
632 632 filelen = _kernel32.GetModuleFileNameA(sys.dllhandle, ctypes.byref(buf),
633 633 size)
634 634
635 635 if filelen > 0 and filelen != size:
636 636 dllbasename = os.path.basename(buf.value)
637 637 if not dllbasename.lower().endswith(b'.dll'):
638 638 raise SystemExit('Python DLL does not end with .dll: %s' %
639 639 dllbasename)
640 640 pythonlib = dllbasename[:-4]
641 641 else:
642 642 log.warn('could not determine Python DLL filename; '
643 643 'assuming pythonXY')
644 644
645 645 hv = sys.hexversion
646 646 pythonlib = 'python%d%d' % (hv >> 24, (hv >> 16) & 0xff)
647 647
648 648 log.info('using %s as Python library name' % pythonlib)
649 649 with open('mercurial/hgpythonlib.h', 'wb') as f:
650 650 f.write(b'/* this file is autogenerated by setup.py */\n')
651 651 f.write(b'#define HGPYTHONLIB "%s"\n' % pythonlib)
652 652
653 653 macros = None
654 654 if sys.version_info[0] >= 3:
655 655 macros = [('_UNICODE', None), ('UNICODE', None)]
656 656
657 657 objects = self.compiler.compile(['mercurial/exewrapper.c'],
658 658 output_dir=self.build_temp,
659 659 macros=macros)
660 660 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
661 661 self.hgtarget = os.path.join(dir, 'hg')
662 662 self.compiler.link_executable(objects, self.hgtarget,
663 663 libraries=[],
664 664 output_dir=self.build_temp)
665 665 if self.long_paths_support:
666 666 self.addlongpathsmanifest()
667 667
668 668 def addlongpathsmanifest(self):
669 669 """Add manifest pieces so that hg.exe understands long paths
670 670
671 671 This is an EXPERIMENTAL feature, use with care.
672 672 To enable long paths support, one needs to do two things:
673 673 - build Mercurial with --long-paths-support option
674 674 - change HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\
675 675 LongPathsEnabled to have value 1.
676 676
677 677 Please ignore 'warning 81010002: Unrecognized Element "longPathAware"';
678 678 it happens because Mercurial uses mt.exe circa 2008, which is not
679 679 yet aware of long paths support in the manifest (I think so at least).
680 680 This does not stop mt.exe from embedding/merging the XML properly.
681 681
682 682 Why resource #1 should be used for .exe manifests? I don't know and
683 683 wasn't able to find an explanation for mortals. But it seems to work.
684 684 """
685 685 exefname = self.compiler.executable_filename(self.hgtarget)
686 686 fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
687 687 os.close(fdauto)
688 688 with open(manfname, 'w') as f:
689 689 f.write(self.LONG_PATHS_MANIFEST)
690 690 log.info("long paths manifest is written to '%s'" % manfname)
691 691 inputresource = '-inputresource:%s;#1' % exefname
692 692 outputresource = '-outputresource:%s;#1' % exefname
693 693 log.info("running mt.exe to update hg.exe's manifest in-place")
694 694 # supplying both -manifest and -inputresource to mt.exe makes
695 695 # it merge the embedded and supplied manifests in the -outputresource
696 696 self.spawn(['mt.exe', '-nologo', '-manifest', manfname,
697 697 inputresource, outputresource])
698 698 log.info("done updating hg.exe's manifest")
699 699 os.remove(manfname)
700 700
701 701 @property
702 702 def hgexepath(self):
703 703 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
704 704 return os.path.join(self.build_temp, dir, 'hg.exe')
705 705
706 706 class hginstall(install):
707 707
708 708 user_options = install.user_options + [
709 709 ('old-and-unmanageable', None,
710 710 'noop, present for eggless setuptools compat'),
711 711 ('single-version-externally-managed', None,
712 712 'noop, present for eggless setuptools compat'),
713 713 ]
714 714
715 715 # Also helps setuptools not be sad while we refuse to create eggs.
716 716 single_version_externally_managed = True
717 717
718 718 def get_sub_commands(self):
719 719 # Screen out egg related commands to prevent egg generation. But allow
720 720 # mercurial.egg-info generation, since that is part of modern
721 721 # packaging.
722 722 excl = set(['bdist_egg'])
723 723 return filter(lambda x: x not in excl, install.get_sub_commands(self))
724 724
725 725 class hginstalllib(install_lib):
726 726 '''
727 727 This is a specialization of install_lib that replaces the copy_file used
728 728 there so that it supports setting the mode of files after copying them,
729 729 instead of just preserving the mode that the files originally had. If your
730 730 system has a umask of something like 027, preserving the permissions when
731 731 copying will lead to a broken install.
732 732
733 733 Note that just passing keep_permissions=False to copy_file would be
734 734 insufficient, as it might still be applying a umask.
735 735 '''
736 736
737 737 def run(self):
738 738 realcopyfile = file_util.copy_file
739 739 def copyfileandsetmode(*args, **kwargs):
740 740 src, dst = args[0], args[1]
741 741 dst, copied = realcopyfile(*args, **kwargs)
742 742 if copied:
743 743 st = os.stat(src)
744 744 # Persist executable bit (apply it to group and other if user
745 745 # has it)
746 746 if st[stat.ST_MODE] & stat.S_IXUSR:
747 747 setmode = int('0755', 8)
748 748 else:
749 749 setmode = int('0644', 8)
750 750 m = stat.S_IMODE(st[stat.ST_MODE])
751 751 m = (m & ~int('0777', 8)) | setmode
752 752 os.chmod(dst, m)
753 753 file_util.copy_file = copyfileandsetmode
754 754 try:
755 755 install_lib.run(self)
756 756 finally:
757 757 file_util.copy_file = realcopyfile
758 758
759 759 class hginstallscripts(install_scripts):
760 760 '''
761 761 This is a specialization of install_scripts that replaces the @LIBDIR@ with
762 762 the configured directory for modules. If possible, the path is made relative
763 763 to the directory for scripts.
764 764 '''
765 765
766 766 def initialize_options(self):
767 767 install_scripts.initialize_options(self)
768 768
769 769 self.install_lib = None
770 770
771 771 def finalize_options(self):
772 772 install_scripts.finalize_options(self)
773 773 self.set_undefined_options('install',
774 774 ('install_lib', 'install_lib'))
775 775
776 776 def run(self):
777 777 install_scripts.run(self)
778 778
779 779 # It only makes sense to replace @LIBDIR@ with the install path if
780 780 # the install path is known. For wheels, the logic below calculates
781 781 # the libdir to be "../..". This is because the internal layout of a
782 782 # wheel archive looks like:
783 783 #
784 784 # mercurial-3.6.1.data/scripts/hg
785 785 # mercurial/__init__.py
786 786 #
787 787 # When installing wheels, the subdirectories of the "<pkg>.data"
788 788 # directory are translated to system local paths and files therein
789 789 # are copied in place. The mercurial/* files are installed into the
790 790 # site-packages directory. However, the site-packages directory
791 791 # isn't known until wheel install time. This means we have no clue
792 792 # at wheel generation time what the installed site-packages directory
793 793 # will be. And, wheels don't appear to provide the ability to register
794 794 # custom code to run during wheel installation. This all means that
795 795 # we can't reliably set the libdir in wheels: the default behavior
796 796 # of looking in sys.path must do.
797 797
798 798 if (os.path.splitdrive(self.install_dir)[0] !=
799 799 os.path.splitdrive(self.install_lib)[0]):
800 800 # can't make relative paths from one drive to another, so use an
801 801 # absolute path instead
802 802 libdir = self.install_lib
803 803 else:
804 804 common = os.path.commonprefix((self.install_dir, self.install_lib))
805 805 rest = self.install_dir[len(common):]
806 806 uplevel = len([n for n in os.path.split(rest) if n])
807 807
808 808 libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common):]
809 809
810 810 for outfile in self.outfiles:
811 811 with open(outfile, 'rb') as fp:
812 812 data = fp.read()
813 813
814 814 # skip binary files
815 815 if b'\0' in data:
816 816 continue
817 817
818 818 # During local installs, the shebang will be rewritten to the final
819 819 # install path. During wheel packaging, the shebang has a special
820 820 # value.
821 821 if data.startswith(b'#!python'):
822 822 log.info('not rewriting @LIBDIR@ in %s because install path '
823 823 'not known' % outfile)
824 824 continue
825 825
826 826 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
827 827 with open(outfile, 'wb') as fp:
828 828 fp.write(data)
829 829
830 830 cmdclass = {'build': hgbuild,
831 831 'build_mo': hgbuildmo,
832 832 'build_ext': hgbuildext,
833 833 'build_py': hgbuildpy,
834 834 'build_scripts': hgbuildscripts,
835 835 'build_hgextindex': buildhgextindex,
836 836 'install': hginstall,
837 837 'install_lib': hginstalllib,
838 838 'install_scripts': hginstallscripts,
839 839 'build_hgexe': buildhgexe,
840 840 }
841 841
842 842 packages = ['mercurial',
843 843 'mercurial.cext',
844 844 'mercurial.cffi',
845 845 'mercurial.hgweb',
846 846 'mercurial.pure',
847 847 'mercurial.thirdparty',
848 848 'mercurial.thirdparty.attr',
849 849 'mercurial.thirdparty.cbor',
850 850 'mercurial.thirdparty.cbor.cbor2',
851 851 'mercurial.thirdparty.zope',
852 852 'mercurial.thirdparty.zope.interface',
853 853 'mercurial.utils',
854 854 'mercurial.revlogutils',
855 855 'mercurial.testing',
856 856 'hgext', 'hgext.convert', 'hgext.fsmonitor',
857 857 'hgext.fastannotate',
858 858 'hgext.fsmonitor.pywatchman',
859 859 'hgext.infinitepush',
860 860 'hgext.highlight',
861 861 'hgext.largefiles', 'hgext.lfs', 'hgext.narrow',
862 862 'hgext.remotefilelog',
863 863 'hgext.zeroconf', 'hgext3rd',
864 864 'hgdemandimport']
865 865 if sys.version_info[0] == 2:
866 866 packages.extend(['mercurial.thirdparty.concurrent',
867 867 'mercurial.thirdparty.concurrent.futures'])
868 868
869 869 common_depends = ['mercurial/bitmanipulation.h',
870 870 'mercurial/compat.h',
871 871 'mercurial/cext/util.h']
872 872 common_include_dirs = ['mercurial']
873 873
874 874 osutil_cflags = []
875 875 osutil_ldflags = []
876 876
877 877 # platform specific macros
878 878 for plat, func in [('bsd', 'setproctitle')]:
879 879 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
880 880 osutil_cflags.append('-DHAVE_%s' % func.upper())
881 881
882 882 for plat, macro, code in [
883 883 ('bsd|darwin', 'BSD_STATFS', '''
884 884 #include <sys/param.h>
885 885 #include <sys/mount.h>
886 886 int main() { struct statfs s; return sizeof(s.f_fstypename); }
887 887 '''),
888 888 ('linux', 'LINUX_STATFS', '''
889 889 #include <linux/magic.h>
890 890 #include <sys/vfs.h>
891 891 int main() { struct statfs s; return sizeof(s.f_type); }
892 892 '''),
893 893 ]:
894 894 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
895 895 osutil_cflags.append('-DHAVE_%s' % macro)
896 896
897 897 if sys.platform == 'darwin':
898 898 osutil_ldflags += ['-framework', 'ApplicationServices']
899 899
900 900 xdiff_srcs = [
901 901 'mercurial/thirdparty/xdiff/xdiffi.c',
902 902 'mercurial/thirdparty/xdiff/xprepare.c',
903 903 'mercurial/thirdparty/xdiff/xutils.c',
904 904 ]
905 905
906 906 xdiff_headers = [
907 907 'mercurial/thirdparty/xdiff/xdiff.h',
908 908 'mercurial/thirdparty/xdiff/xdiffi.h',
909 909 'mercurial/thirdparty/xdiff/xinclude.h',
910 910 'mercurial/thirdparty/xdiff/xmacros.h',
911 911 'mercurial/thirdparty/xdiff/xprepare.h',
912 912 'mercurial/thirdparty/xdiff/xtypes.h',
913 913 'mercurial/thirdparty/xdiff/xutils.h',
914 914 ]
915 915
916 916 class RustCompilationError(CCompilerError):
917 917 """Exception class for Rust compilation errors."""
918 918
919 919 class RustExtension(Extension):
920 920 """Base classes for concrete Rust Extension classes.
921 921 """
922 922
923 923 rusttargetdir = os.path.join('rust', 'target', 'release')
924 924
925 def __init__(self, mpath, sources, rustlibname, subcrate, **kw):
925 def __init__(self, mpath, sources, rustlibname, subcrate,
926 py3_features=None, **kw):
926 927 Extension.__init__(self, mpath, sources, **kw)
927 928 if hgrustext is None:
928 929 return
929 930 srcdir = self.rustsrcdir = os.path.join('rust', subcrate)
931 self.py3_features = py3_features
930 932
931 933 # adding Rust source and control files to depends so that the extension
932 934 # gets rebuilt if they've changed
933 935 self.depends.append(os.path.join(srcdir, 'Cargo.toml'))
934 936 cargo_lock = os.path.join(srcdir, 'Cargo.lock')
935 937 if os.path.exists(cargo_lock):
936 938 self.depends.append(cargo_lock)
937 939 for dirpath, subdir, fnames in os.walk(os.path.join(srcdir, 'src')):
938 940 self.depends.extend(os.path.join(dirpath, fname)
939 941 for fname in fnames
940 942 if os.path.splitext(fname)[1] == '.rs')
941 943
942 944 def rustbuild(self):
943 945 if hgrustext is None:
944 946 return
945 947 env = os.environ.copy()
946 948 if 'HGTEST_RESTOREENV' in env:
947 949 # Mercurial tests change HOME to a temporary directory,
948 950 # but, if installed with rustup, the Rust toolchain needs
949 951 # HOME to be correct (otherwise the 'no default toolchain'
950 952 # error message is issued and the build fails).
951 953 # This happens currently with test-hghave.t, which does
952 954 # invoke this build.
953 955
954 956 # Unix only fix (os.path.expanduser not really reliable if
955 957 # HOME is shadowed like this)
956 958 import pwd
957 959 env['HOME'] = pwd.getpwuid(os.getuid()).pw_dir
958 960
959 961 cargocmd = ['cargo', 'build', '-vv', '--release']
962 if sys.version_info[0] == 3 and self.py3_features is not None:
963 cargocmd.extend(('--features', self.py3_features,
964 '--no-default-features'))
960 965 try:
961 966 subprocess.check_call(cargocmd, env=env, cwd=self.rustsrcdir)
962 967 except OSError as exc:
963 968 if exc.errno == errno.ENOENT:
964 969 raise RustCompilationError("Cargo not found")
965 970 elif exc.errno == errno.EACCES:
966 971 raise RustCompilationError(
967 972 "Cargo found, but permisssion to execute it is denied")
968 973 else:
969 974 raise
970 975 except subprocess.CalledProcessError:
971 976 raise RustCompilationError(
972 977 "Cargo failed. Working directory: %r, "
973 978 "command: %r, environment: %r" % (self.rustsrcdir, cmd, env))
974 979
975 980 class RustEnhancedExtension(RustExtension):
976 981 """A C Extension, conditionally enhanced with Rust code.
977 982
978 983 If the HGRUSTEXT environment variable is set to something else
979 984 than 'cpython', the Rust sources get compiled and linked within the
980 985 C target shared library object.
981 986 """
982 987
983 988 def __init__(self, mpath, sources, rustlibname, subcrate, **kw):
984 989 RustExtension.__init__(self, mpath, sources, rustlibname, subcrate,
985 990 **kw)
986 991 if hgrustext != 'direct-ffi':
987 992 return
988 993 self.extra_compile_args.append('-DWITH_RUST')
989 994 self.libraries.append(rustlibname)
990 995 self.library_dirs.append(self.rusttargetdir)
991 996
992 997 class RustStandaloneExtension(RustExtension):
993 998
994 999 def __init__(self, pydottedname, rustcrate, dylibname, **kw):
995 1000 RustExtension.__init__(self, pydottedname, [], dylibname, rustcrate,
996 1001 **kw)
997 1002 self.dylibname = dylibname
998 1003
999 1004 def build(self, target_dir):
1000 1005 self.rustbuild()
1001 1006 target = [target_dir]
1002 1007 target.extend(self.name.split('.'))
1003 1008 ext = '.so' # TODO Unix only
1004 1009 target[-1] += ext
1005 1010 shutil.copy2(os.path.join(self.rusttargetdir, self.dylibname + ext),
1006 1011 os.path.join(*target))
1007 1012
1008 1013
1009 1014 extmodules = [
1010 1015 Extension('mercurial.cext.base85', ['mercurial/cext/base85.c'],
1011 1016 include_dirs=common_include_dirs,
1012 1017 depends=common_depends),
1013 1018 Extension('mercurial.cext.bdiff', ['mercurial/bdiff.c',
1014 1019 'mercurial/cext/bdiff.c'] + xdiff_srcs,
1015 1020 include_dirs=common_include_dirs,
1016 1021 depends=common_depends + ['mercurial/bdiff.h'] + xdiff_headers),
1017 1022 Extension('mercurial.cext.mpatch', ['mercurial/mpatch.c',
1018 1023 'mercurial/cext/mpatch.c'],
1019 1024 include_dirs=common_include_dirs,
1020 1025 depends=common_depends),
1021 1026 RustEnhancedExtension(
1022 1027 'mercurial.cext.parsers', ['mercurial/cext/charencode.c',
1023 1028 'mercurial/cext/dirs.c',
1024 1029 'mercurial/cext/manifest.c',
1025 1030 'mercurial/cext/parsers.c',
1026 1031 'mercurial/cext/pathencode.c',
1027 1032 'mercurial/cext/revlog.c'],
1028 1033 'hgdirectffi',
1029 1034 'hg-direct-ffi',
1030 1035 include_dirs=common_include_dirs,
1031 1036 depends=common_depends + ['mercurial/cext/charencode.h',
1032 1037 'mercurial/cext/revlog.h',
1033 1038 'rust/hg-core/src/ancestors.rs',
1034 1039 'rust/hg-core/src/lib.rs']),
1035 1040 Extension('mercurial.cext.osutil', ['mercurial/cext/osutil.c'],
1036 1041 include_dirs=common_include_dirs,
1037 1042 extra_compile_args=osutil_cflags,
1038 1043 extra_link_args=osutil_ldflags,
1039 1044 depends=common_depends),
1040 1045 Extension(
1041 1046 'mercurial.thirdparty.zope.interface._zope_interface_coptimizations', [
1042 1047 'mercurial/thirdparty/zope/interface/_zope_interface_coptimizations.c',
1043 1048 ]),
1044 1049 Extension('hgext.fsmonitor.pywatchman.bser',
1045 1050 ['hgext/fsmonitor/pywatchman/bser.c']),
1046 1051 ]
1047 1052
1048 1053 if hgrustext == 'cpython':
1049 1054 extmodules.append(
1050 RustStandaloneExtension('mercurial.rustext', 'hg-cpython', 'librusthg')
1055 RustStandaloneExtension('mercurial.rustext', 'hg-cpython', 'librusthg',
1056 py3_features='python3')
1051 1057 )
1052 1058
1053 1059
1054 1060 sys.path.insert(0, 'contrib/python-zstandard')
1055 1061 import setup_zstd
1056 1062 extmodules.append(setup_zstd.get_c_extension(
1057 1063 name='mercurial.zstd',
1058 1064 root=os.path.abspath(os.path.dirname(__file__))))
1059 1065
1060 1066 try:
1061 1067 from distutils import cygwinccompiler
1062 1068
1063 1069 # the -mno-cygwin option has been deprecated for years
1064 1070 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
1065 1071
1066 1072 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
1067 1073 def __init__(self, *args, **kwargs):
1068 1074 mingw32compilerclass.__init__(self, *args, **kwargs)
1069 1075 for i in 'compiler compiler_so linker_exe linker_so'.split():
1070 1076 try:
1071 1077 getattr(self, i).remove('-mno-cygwin')
1072 1078 except ValueError:
1073 1079 pass
1074 1080
1075 1081 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
1076 1082 except ImportError:
1077 1083 # the cygwinccompiler package is not available on some Python
1078 1084 # distributions like the ones from the optware project for Synology
1079 1085 # DiskStation boxes
1080 1086 class HackedMingw32CCompiler(object):
1081 1087 pass
1082 1088
1083 1089 if os.name == 'nt':
1084 1090 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
1085 1091 # extra_link_args to distutils.extensions.Extension() doesn't have any
1086 1092 # effect.
1087 1093 from distutils import msvccompiler
1088 1094
1089 1095 msvccompilerclass = msvccompiler.MSVCCompiler
1090 1096
1091 1097 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
1092 1098 def initialize(self):
1093 1099 msvccompilerclass.initialize(self)
1094 1100 # "warning LNK4197: export 'func' specified multiple times"
1095 1101 self.ldflags_shared.append('/ignore:4197')
1096 1102 self.ldflags_shared_debug.append('/ignore:4197')
1097 1103
1098 1104 msvccompiler.MSVCCompiler = HackedMSVCCompiler
1099 1105
1100 1106 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
1101 1107 'help/*.txt',
1102 1108 'help/internals/*.txt',
1103 1109 'default.d/*.rc',
1104 1110 'dummycert.pem']}
1105 1111
1106 1112 def ordinarypath(p):
1107 1113 return p and p[0] != '.' and p[-1] != '~'
1108 1114
1109 1115 for root in ('templates',):
1110 1116 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
1111 1117 curdir = curdir.split(os.sep, 1)[1]
1112 1118 dirs[:] = filter(ordinarypath, dirs)
1113 1119 for f in filter(ordinarypath, files):
1114 1120 f = os.path.join(curdir, f)
1115 1121 packagedata['mercurial'].append(f)
1116 1122
1117 1123 datafiles = []
1118 1124
1119 1125 # distutils expects version to be str/unicode. Converting it to
1120 1126 # unicode on Python 2 still works because it won't contain any
1121 1127 # non-ascii bytes and will be implicitly converted back to bytes
1122 1128 # when operated on.
1123 1129 assert isinstance(version, bytes)
1124 1130 setupversion = version.decode('ascii')
1125 1131
1126 1132 extra = {}
1127 1133
1128 1134 if issetuptools:
1129 1135 extra['python_requires'] = supportedpy
1130 1136 if py2exeloaded:
1131 1137 extra['console'] = [
1132 1138 {'script':'hg',
1133 1139 'copyright':'Copyright (C) 2005-2018 Matt Mackall and others',
1134 1140 'product_version':version}]
1135 1141 # sub command of 'build' because 'py2exe' does not handle sub_commands
1136 1142 build.sub_commands.insert(0, ('build_hgextindex', None))
1137 1143 # put dlls in sub directory so that they won't pollute PATH
1138 1144 extra['zipfile'] = 'lib/library.zip'
1139 1145
1140 1146 if os.name == 'nt':
1141 1147 # Windows binary file versions for exe/dll files must have the
1142 1148 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
1143 1149 setupversion = setupversion.split(r'+', 1)[0]
1144 1150
1145 1151 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
1146 1152 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
1147 1153 if version:
1148 1154 version = version[0]
1149 1155 if sys.version_info[0] == 3:
1150 1156 version = version.decode('utf-8')
1151 1157 xcode4 = (version.startswith('Xcode') and
1152 1158 StrictVersion(version.split()[1]) >= StrictVersion('4.0'))
1153 1159 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
1154 1160 else:
1155 1161 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
1156 1162 # installed, but instead with only command-line tools. Assume
1157 1163 # that only happens on >= Lion, thus no PPC support.
1158 1164 xcode4 = True
1159 1165 xcode51 = False
1160 1166
1161 1167 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
1162 1168 # distutils.sysconfig
1163 1169 if xcode4:
1164 1170 os.environ['ARCHFLAGS'] = ''
1165 1171
1166 1172 # XCode 5.1 changes clang such that it now fails to compile if the
1167 1173 # -mno-fused-madd flag is passed, but the version of Python shipped with
1168 1174 # OS X 10.9 Mavericks includes this flag. This causes problems in all
1169 1175 # C extension modules, and a bug has been filed upstream at
1170 1176 # http://bugs.python.org/issue21244. We also need to patch this here
1171 1177 # so Mercurial can continue to compile in the meantime.
1172 1178 if xcode51:
1173 1179 cflags = get_config_var('CFLAGS')
1174 1180 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
1175 1181 os.environ['CFLAGS'] = (
1176 1182 os.environ.get('CFLAGS', '') + ' -Qunused-arguments')
1177 1183
1178 1184 setup(name='mercurial',
1179 1185 version=setupversion,
1180 1186 author='Matt Mackall and many others',
1181 1187 author_email='mercurial@mercurial-scm.org',
1182 1188 url='https://mercurial-scm.org/',
1183 1189 download_url='https://mercurial-scm.org/release/',
1184 1190 description=('Fast scalable distributed SCM (revision control, version '
1185 1191 'control) system'),
1186 1192 long_description=('Mercurial is a distributed SCM tool written in Python.'
1187 1193 ' It is used by a number of large projects that require'
1188 1194 ' fast, reliable distributed revision control, such as '
1189 1195 'Mozilla.'),
1190 1196 license='GNU GPLv2 or any later version',
1191 1197 classifiers=[
1192 1198 'Development Status :: 6 - Mature',
1193 1199 'Environment :: Console',
1194 1200 'Intended Audience :: Developers',
1195 1201 'Intended Audience :: System Administrators',
1196 1202 'License :: OSI Approved :: GNU General Public License (GPL)',
1197 1203 'Natural Language :: Danish',
1198 1204 'Natural Language :: English',
1199 1205 'Natural Language :: German',
1200 1206 'Natural Language :: Italian',
1201 1207 'Natural Language :: Japanese',
1202 1208 'Natural Language :: Portuguese (Brazilian)',
1203 1209 'Operating System :: Microsoft :: Windows',
1204 1210 'Operating System :: OS Independent',
1205 1211 'Operating System :: POSIX',
1206 1212 'Programming Language :: C',
1207 1213 'Programming Language :: Python',
1208 1214 'Topic :: Software Development :: Version Control',
1209 1215 ],
1210 1216 scripts=scripts,
1211 1217 packages=packages,
1212 1218 ext_modules=extmodules,
1213 1219 data_files=datafiles,
1214 1220 package_data=packagedata,
1215 1221 cmdclass=cmdclass,
1216 1222 distclass=hgdist,
1217 1223 options={
1218 1224 'py2exe': {
1219 1225 'packages': [
1220 1226 'hgdemandimport',
1221 1227 'hgext',
1222 1228 'email',
1223 1229 # implicitly imported per module policy
1224 1230 # (cffi wouldn't be used as a frozen exe)
1225 1231 'mercurial.cext',
1226 1232 #'mercurial.cffi',
1227 1233 'mercurial.pure',
1228 1234 ],
1229 1235 },
1230 1236 'bdist_mpkg': {
1231 1237 'zipdist': False,
1232 1238 'license': 'COPYING',
1233 1239 'readme': 'contrib/packaging/macosx/Readme.html',
1234 1240 'welcome': 'contrib/packaging/macosx/Welcome.html',
1235 1241 },
1236 1242 },
1237 1243 **extra)
General Comments 0
You need to be logged in to leave comments. Login now