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