##// END OF EJS Templates
setup: register zope.interface packages and compile C extension...
Gregory Szorc -
r37197:922b3fae default
parent child Browse files
Show More
@@ -1,778 +1,780 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2
2
3 from __future__ import absolute_import, print_function
3 from __future__ import absolute_import, print_function
4
4
5 import ast
5 import ast
6 import collections
6 import collections
7 import os
7 import os
8 import re
8 import re
9 import sys
9 import sys
10
10
11 # Import a minimal set of stdlib modules needed for list_stdlib_modules()
11 # Import a minimal set of stdlib modules needed for list_stdlib_modules()
12 # to work when run from a virtualenv. The modules were chosen empirically
12 # to work when run from a virtualenv. The modules were chosen empirically
13 # so that the return value matches the return value without virtualenv.
13 # so that the return value matches the return value without virtualenv.
14 if True: # disable lexical sorting checks
14 if True: # disable lexical sorting checks
15 try:
15 try:
16 import BaseHTTPServer as basehttpserver
16 import BaseHTTPServer as basehttpserver
17 except ImportError:
17 except ImportError:
18 basehttpserver = None
18 basehttpserver = None
19 import zlib
19 import zlib
20
20
21 # Whitelist of modules that symbols can be directly imported from.
21 # Whitelist of modules that symbols can be directly imported from.
22 allowsymbolimports = (
22 allowsymbolimports = (
23 '__future__',
23 '__future__',
24 'bzrlib',
24 'bzrlib',
25 'hgclient',
25 'hgclient',
26 'mercurial',
26 'mercurial',
27 'mercurial.hgweb.common',
27 'mercurial.hgweb.common',
28 'mercurial.hgweb.request',
28 'mercurial.hgweb.request',
29 'mercurial.i18n',
29 'mercurial.i18n',
30 'mercurial.node',
30 'mercurial.node',
31 # for cffi modules to re-export pure functions
31 # for cffi modules to re-export pure functions
32 'mercurial.pure.base85',
32 'mercurial.pure.base85',
33 'mercurial.pure.bdiff',
33 'mercurial.pure.bdiff',
34 'mercurial.pure.diffhelpers',
34 'mercurial.pure.diffhelpers',
35 'mercurial.pure.mpatch',
35 'mercurial.pure.mpatch',
36 'mercurial.pure.osutil',
36 'mercurial.pure.osutil',
37 'mercurial.pure.parsers',
37 'mercurial.pure.parsers',
38 # third-party imports should be directly imported
38 # third-party imports should be directly imported
39 'mercurial.thirdparty',
39 'mercurial.thirdparty',
40 'mercurial.thirdparty.zope',
41 'mercurial.thirdparty.zope.interface',
40 )
42 )
41
43
42 # Whitelist of symbols that can be directly imported.
44 # Whitelist of symbols that can be directly imported.
43 directsymbols = (
45 directsymbols = (
44 'demandimport',
46 'demandimport',
45 )
47 )
46
48
47 # Modules that must be aliased because they are commonly confused with
49 # Modules that must be aliased because they are commonly confused with
48 # common variables and can create aliasing and readability issues.
50 # common variables and can create aliasing and readability issues.
49 requirealias = {
51 requirealias = {
50 'ui': 'uimod',
52 'ui': 'uimod',
51 }
53 }
52
54
53 def usingabsolute(root):
55 def usingabsolute(root):
54 """Whether absolute imports are being used."""
56 """Whether absolute imports are being used."""
55 if sys.version_info[0] >= 3:
57 if sys.version_info[0] >= 3:
56 return True
58 return True
57
59
58 for node in ast.walk(root):
60 for node in ast.walk(root):
59 if isinstance(node, ast.ImportFrom):
61 if isinstance(node, ast.ImportFrom):
60 if node.module == '__future__':
62 if node.module == '__future__':
61 for n in node.names:
63 for n in node.names:
62 if n.name == 'absolute_import':
64 if n.name == 'absolute_import':
63 return True
65 return True
64
66
65 return False
67 return False
66
68
67 def walklocal(root):
69 def walklocal(root):
68 """Recursively yield all descendant nodes but not in a different scope"""
70 """Recursively yield all descendant nodes but not in a different scope"""
69 todo = collections.deque(ast.iter_child_nodes(root))
71 todo = collections.deque(ast.iter_child_nodes(root))
70 yield root, False
72 yield root, False
71 while todo:
73 while todo:
72 node = todo.popleft()
74 node = todo.popleft()
73 newscope = isinstance(node, ast.FunctionDef)
75 newscope = isinstance(node, ast.FunctionDef)
74 if not newscope:
76 if not newscope:
75 todo.extend(ast.iter_child_nodes(node))
77 todo.extend(ast.iter_child_nodes(node))
76 yield node, newscope
78 yield node, newscope
77
79
78 def dotted_name_of_path(path):
80 def dotted_name_of_path(path):
79 """Given a relative path to a source file, return its dotted module name.
81 """Given a relative path to a source file, return its dotted module name.
80
82
81 >>> dotted_name_of_path('mercurial/error.py')
83 >>> dotted_name_of_path('mercurial/error.py')
82 'mercurial.error'
84 'mercurial.error'
83 >>> dotted_name_of_path('zlibmodule.so')
85 >>> dotted_name_of_path('zlibmodule.so')
84 'zlib'
86 'zlib'
85 """
87 """
86 parts = path.replace(os.sep, '/').split('/')
88 parts = path.replace(os.sep, '/').split('/')
87 parts[-1] = parts[-1].split('.', 1)[0] # remove .py and .so and .ARCH.so
89 parts[-1] = parts[-1].split('.', 1)[0] # remove .py and .so and .ARCH.so
88 if parts[-1].endswith('module'):
90 if parts[-1].endswith('module'):
89 parts[-1] = parts[-1][:-6]
91 parts[-1] = parts[-1][:-6]
90 return '.'.join(parts)
92 return '.'.join(parts)
91
93
92 def fromlocalfunc(modulename, localmods):
94 def fromlocalfunc(modulename, localmods):
93 """Get a function to examine which locally defined module the
95 """Get a function to examine which locally defined module the
94 target source imports via a specified name.
96 target source imports via a specified name.
95
97
96 `modulename` is an `dotted_name_of_path()`-ed source file path,
98 `modulename` is an `dotted_name_of_path()`-ed source file path,
97 which may have `.__init__` at the end of it, of the target source.
99 which may have `.__init__` at the end of it, of the target source.
98
100
99 `localmods` is a set of absolute `dotted_name_of_path()`-ed source file
101 `localmods` is a set of absolute `dotted_name_of_path()`-ed source file
100 paths of locally defined (= Mercurial specific) modules.
102 paths of locally defined (= Mercurial specific) modules.
101
103
102 This function assumes that module names not existing in
104 This function assumes that module names not existing in
103 `localmods` are from the Python standard library.
105 `localmods` are from the Python standard library.
104
106
105 This function returns the function, which takes `name` argument,
107 This function returns the function, which takes `name` argument,
106 and returns `(absname, dottedpath, hassubmod)` tuple if `name`
108 and returns `(absname, dottedpath, hassubmod)` tuple if `name`
107 matches against locally defined module. Otherwise, it returns
109 matches against locally defined module. Otherwise, it returns
108 False.
110 False.
109
111
110 It is assumed that `name` doesn't have `.__init__`.
112 It is assumed that `name` doesn't have `.__init__`.
111
113
112 `absname` is an absolute module name of specified `name`
114 `absname` is an absolute module name of specified `name`
113 (e.g. "hgext.convert"). This can be used to compose prefix for sub
115 (e.g. "hgext.convert"). This can be used to compose prefix for sub
114 modules or so.
116 modules or so.
115
117
116 `dottedpath` is a `dotted_name_of_path()`-ed source file path
118 `dottedpath` is a `dotted_name_of_path()`-ed source file path
117 (e.g. "hgext.convert.__init__") of `name`. This is used to look
119 (e.g. "hgext.convert.__init__") of `name`. This is used to look
118 module up in `localmods` again.
120 module up in `localmods` again.
119
121
120 `hassubmod` is whether it may have sub modules under it (for
122 `hassubmod` is whether it may have sub modules under it (for
121 convenient, even though this is also equivalent to "absname !=
123 convenient, even though this is also equivalent to "absname !=
122 dottednpath")
124 dottednpath")
123
125
124 >>> localmods = {'foo.__init__', 'foo.foo1',
126 >>> localmods = {'foo.__init__', 'foo.foo1',
125 ... 'foo.bar.__init__', 'foo.bar.bar1',
127 ... 'foo.bar.__init__', 'foo.bar.bar1',
126 ... 'baz.__init__', 'baz.baz1'}
128 ... 'baz.__init__', 'baz.baz1'}
127 >>> fromlocal = fromlocalfunc('foo.xxx', localmods)
129 >>> fromlocal = fromlocalfunc('foo.xxx', localmods)
128 >>> # relative
130 >>> # relative
129 >>> fromlocal('foo1')
131 >>> fromlocal('foo1')
130 ('foo.foo1', 'foo.foo1', False)
132 ('foo.foo1', 'foo.foo1', False)
131 >>> fromlocal('bar')
133 >>> fromlocal('bar')
132 ('foo.bar', 'foo.bar.__init__', True)
134 ('foo.bar', 'foo.bar.__init__', True)
133 >>> fromlocal('bar.bar1')
135 >>> fromlocal('bar.bar1')
134 ('foo.bar.bar1', 'foo.bar.bar1', False)
136 ('foo.bar.bar1', 'foo.bar.bar1', False)
135 >>> # absolute
137 >>> # absolute
136 >>> fromlocal('baz')
138 >>> fromlocal('baz')
137 ('baz', 'baz.__init__', True)
139 ('baz', 'baz.__init__', True)
138 >>> fromlocal('baz.baz1')
140 >>> fromlocal('baz.baz1')
139 ('baz.baz1', 'baz.baz1', False)
141 ('baz.baz1', 'baz.baz1', False)
140 >>> # unknown = maybe standard library
142 >>> # unknown = maybe standard library
141 >>> fromlocal('os')
143 >>> fromlocal('os')
142 False
144 False
143 >>> fromlocal(None, 1)
145 >>> fromlocal(None, 1)
144 ('foo', 'foo.__init__', True)
146 ('foo', 'foo.__init__', True)
145 >>> fromlocal('foo1', 1)
147 >>> fromlocal('foo1', 1)
146 ('foo.foo1', 'foo.foo1', False)
148 ('foo.foo1', 'foo.foo1', False)
147 >>> fromlocal2 = fromlocalfunc('foo.xxx.yyy', localmods)
149 >>> fromlocal2 = fromlocalfunc('foo.xxx.yyy', localmods)
148 >>> fromlocal2(None, 2)
150 >>> fromlocal2(None, 2)
149 ('foo', 'foo.__init__', True)
151 ('foo', 'foo.__init__', True)
150 >>> fromlocal2('bar2', 1)
152 >>> fromlocal2('bar2', 1)
151 False
153 False
152 >>> fromlocal2('bar', 2)
154 >>> fromlocal2('bar', 2)
153 ('foo.bar', 'foo.bar.__init__', True)
155 ('foo.bar', 'foo.bar.__init__', True)
154 """
156 """
155 if not isinstance(modulename, str):
157 if not isinstance(modulename, str):
156 modulename = modulename.decode('ascii')
158 modulename = modulename.decode('ascii')
157 prefix = '.'.join(modulename.split('.')[:-1])
159 prefix = '.'.join(modulename.split('.')[:-1])
158 if prefix:
160 if prefix:
159 prefix += '.'
161 prefix += '.'
160 def fromlocal(name, level=0):
162 def fromlocal(name, level=0):
161 # name is false value when relative imports are used.
163 # name is false value when relative imports are used.
162 if not name:
164 if not name:
163 # If relative imports are used, level must not be absolute.
165 # If relative imports are used, level must not be absolute.
164 assert level > 0
166 assert level > 0
165 candidates = ['.'.join(modulename.split('.')[:-level])]
167 candidates = ['.'.join(modulename.split('.')[:-level])]
166 else:
168 else:
167 if not level:
169 if not level:
168 # Check relative name first.
170 # Check relative name first.
169 candidates = [prefix + name, name]
171 candidates = [prefix + name, name]
170 else:
172 else:
171 candidates = ['.'.join(modulename.split('.')[:-level]) +
173 candidates = ['.'.join(modulename.split('.')[:-level]) +
172 '.' + name]
174 '.' + name]
173
175
174 for n in candidates:
176 for n in candidates:
175 if n in localmods:
177 if n in localmods:
176 return (n, n, False)
178 return (n, n, False)
177 dottedpath = n + '.__init__'
179 dottedpath = n + '.__init__'
178 if dottedpath in localmods:
180 if dottedpath in localmods:
179 return (n, dottedpath, True)
181 return (n, dottedpath, True)
180 return False
182 return False
181 return fromlocal
183 return fromlocal
182
184
183 def populateextmods(localmods):
185 def populateextmods(localmods):
184 """Populate C extension modules based on pure modules"""
186 """Populate C extension modules based on pure modules"""
185 newlocalmods = set(localmods)
187 newlocalmods = set(localmods)
186 for n in localmods:
188 for n in localmods:
187 if n.startswith('mercurial.pure.'):
189 if n.startswith('mercurial.pure.'):
188 m = n[len('mercurial.pure.'):]
190 m = n[len('mercurial.pure.'):]
189 newlocalmods.add('mercurial.cext.' + m)
191 newlocalmods.add('mercurial.cext.' + m)
190 newlocalmods.add('mercurial.cffi._' + m)
192 newlocalmods.add('mercurial.cffi._' + m)
191 return newlocalmods
193 return newlocalmods
192
194
193 def list_stdlib_modules():
195 def list_stdlib_modules():
194 """List the modules present in the stdlib.
196 """List the modules present in the stdlib.
195
197
196 >>> py3 = sys.version_info[0] >= 3
198 >>> py3 = sys.version_info[0] >= 3
197 >>> mods = set(list_stdlib_modules())
199 >>> mods = set(list_stdlib_modules())
198 >>> 'BaseHTTPServer' in mods or py3
200 >>> 'BaseHTTPServer' in mods or py3
199 True
201 True
200
202
201 os.path isn't really a module, so it's missing:
203 os.path isn't really a module, so it's missing:
202
204
203 >>> 'os.path' in mods
205 >>> 'os.path' in mods
204 False
206 False
205
207
206 sys requires special treatment, because it's baked into the
208 sys requires special treatment, because it's baked into the
207 interpreter, but it should still appear:
209 interpreter, but it should still appear:
208
210
209 >>> 'sys' in mods
211 >>> 'sys' in mods
210 True
212 True
211
213
212 >>> 'collections' in mods
214 >>> 'collections' in mods
213 True
215 True
214
216
215 >>> 'cStringIO' in mods or py3
217 >>> 'cStringIO' in mods or py3
216 True
218 True
217
219
218 >>> 'cffi' in mods
220 >>> 'cffi' in mods
219 True
221 True
220 """
222 """
221 for m in sys.builtin_module_names:
223 for m in sys.builtin_module_names:
222 yield m
224 yield m
223 # These modules only exist on windows, but we should always
225 # These modules only exist on windows, but we should always
224 # consider them stdlib.
226 # consider them stdlib.
225 for m in ['msvcrt', '_winreg']:
227 for m in ['msvcrt', '_winreg']:
226 yield m
228 yield m
227 yield '__builtin__'
229 yield '__builtin__'
228 yield 'builtins' # python3 only
230 yield 'builtins' # python3 only
229 yield 'importlib.abc' # python3 only
231 yield 'importlib.abc' # python3 only
230 yield 'importlib.machinery' # python3 only
232 yield 'importlib.machinery' # python3 only
231 yield 'importlib.util' # python3 only
233 yield 'importlib.util' # python3 only
232 for m in 'fcntl', 'grp', 'pwd', 'termios': # Unix only
234 for m in 'fcntl', 'grp', 'pwd', 'termios': # Unix only
233 yield m
235 yield m
234 for m in 'cPickle', 'datetime': # in Python (not C) on PyPy
236 for m in 'cPickle', 'datetime': # in Python (not C) on PyPy
235 yield m
237 yield m
236 for m in ['cffi']:
238 for m in ['cffi']:
237 yield m
239 yield m
238 stdlib_prefixes = {sys.prefix, sys.exec_prefix}
240 stdlib_prefixes = {sys.prefix, sys.exec_prefix}
239 # We need to supplement the list of prefixes for the search to work
241 # We need to supplement the list of prefixes for the search to work
240 # when run from within a virtualenv.
242 # when run from within a virtualenv.
241 for mod in (basehttpserver, zlib):
243 for mod in (basehttpserver, zlib):
242 if mod is None:
244 if mod is None:
243 continue
245 continue
244 try:
246 try:
245 # Not all module objects have a __file__ attribute.
247 # Not all module objects have a __file__ attribute.
246 filename = mod.__file__
248 filename = mod.__file__
247 except AttributeError:
249 except AttributeError:
248 continue
250 continue
249 dirname = os.path.dirname(filename)
251 dirname = os.path.dirname(filename)
250 for prefix in stdlib_prefixes:
252 for prefix in stdlib_prefixes:
251 if dirname.startswith(prefix):
253 if dirname.startswith(prefix):
252 # Then this directory is redundant.
254 # Then this directory is redundant.
253 break
255 break
254 else:
256 else:
255 stdlib_prefixes.add(dirname)
257 stdlib_prefixes.add(dirname)
256 for libpath in sys.path:
258 for libpath in sys.path:
257 # We want to walk everything in sys.path that starts with
259 # We want to walk everything in sys.path that starts with
258 # something in stdlib_prefixes.
260 # something in stdlib_prefixes.
259 if not any(libpath.startswith(p) for p in stdlib_prefixes):
261 if not any(libpath.startswith(p) for p in stdlib_prefixes):
260 continue
262 continue
261 for top, dirs, files in os.walk(libpath):
263 for top, dirs, files in os.walk(libpath):
262 for i, d in reversed(list(enumerate(dirs))):
264 for i, d in reversed(list(enumerate(dirs))):
263 if (not os.path.exists(os.path.join(top, d, '__init__.py'))
265 if (not os.path.exists(os.path.join(top, d, '__init__.py'))
264 or top == libpath and d in ('hgdemandimport', 'hgext',
266 or top == libpath and d in ('hgdemandimport', 'hgext',
265 'mercurial')):
267 'mercurial')):
266 del dirs[i]
268 del dirs[i]
267 for name in files:
269 for name in files:
268 if not name.endswith(('.py', '.so', '.pyc', '.pyo', '.pyd')):
270 if not name.endswith(('.py', '.so', '.pyc', '.pyo', '.pyd')):
269 continue
271 continue
270 if name.startswith('__init__.py'):
272 if name.startswith('__init__.py'):
271 full_path = top
273 full_path = top
272 else:
274 else:
273 full_path = os.path.join(top, name)
275 full_path = os.path.join(top, name)
274 rel_path = full_path[len(libpath) + 1:]
276 rel_path = full_path[len(libpath) + 1:]
275 mod = dotted_name_of_path(rel_path)
277 mod = dotted_name_of_path(rel_path)
276 yield mod
278 yield mod
277
279
278 stdlib_modules = set(list_stdlib_modules())
280 stdlib_modules = set(list_stdlib_modules())
279
281
280 def imported_modules(source, modulename, f, localmods, ignore_nested=False):
282 def imported_modules(source, modulename, f, localmods, ignore_nested=False):
281 """Given the source of a file as a string, yield the names
283 """Given the source of a file as a string, yield the names
282 imported by that file.
284 imported by that file.
283
285
284 Args:
286 Args:
285 source: The python source to examine as a string.
287 source: The python source to examine as a string.
286 modulename: of specified python source (may have `__init__`)
288 modulename: of specified python source (may have `__init__`)
287 localmods: set of locally defined module names (may have `__init__`)
289 localmods: set of locally defined module names (may have `__init__`)
288 ignore_nested: If true, import statements that do not start in
290 ignore_nested: If true, import statements that do not start in
289 column zero will be ignored.
291 column zero will be ignored.
290
292
291 Returns:
293 Returns:
292 A list of absolute module names imported by the given source.
294 A list of absolute module names imported by the given source.
293
295
294 >>> f = 'foo/xxx.py'
296 >>> f = 'foo/xxx.py'
295 >>> modulename = 'foo.xxx'
297 >>> modulename = 'foo.xxx'
296 >>> localmods = {'foo.__init__': True,
298 >>> localmods = {'foo.__init__': True,
297 ... 'foo.foo1': True, 'foo.foo2': True,
299 ... 'foo.foo1': True, 'foo.foo2': True,
298 ... 'foo.bar.__init__': True, 'foo.bar.bar1': True,
300 ... 'foo.bar.__init__': True, 'foo.bar.bar1': True,
299 ... 'baz.__init__': True, 'baz.baz1': True }
301 ... 'baz.__init__': True, 'baz.baz1': True }
300 >>> # standard library (= not locally defined ones)
302 >>> # standard library (= not locally defined ones)
301 >>> sorted(imported_modules(
303 >>> sorted(imported_modules(
302 ... 'from stdlib1 import foo, bar; import stdlib2',
304 ... 'from stdlib1 import foo, bar; import stdlib2',
303 ... modulename, f, localmods))
305 ... modulename, f, localmods))
304 []
306 []
305 >>> # relative importing
307 >>> # relative importing
306 >>> sorted(imported_modules(
308 >>> sorted(imported_modules(
307 ... 'import foo1; from bar import bar1',
309 ... 'import foo1; from bar import bar1',
308 ... modulename, f, localmods))
310 ... modulename, f, localmods))
309 ['foo.bar.bar1', 'foo.foo1']
311 ['foo.bar.bar1', 'foo.foo1']
310 >>> sorted(imported_modules(
312 >>> sorted(imported_modules(
311 ... 'from bar.bar1 import name1, name2, name3',
313 ... 'from bar.bar1 import name1, name2, name3',
312 ... modulename, f, localmods))
314 ... modulename, f, localmods))
313 ['foo.bar.bar1']
315 ['foo.bar.bar1']
314 >>> # absolute importing
316 >>> # absolute importing
315 >>> sorted(imported_modules(
317 >>> sorted(imported_modules(
316 ... 'from baz import baz1, name1',
318 ... 'from baz import baz1, name1',
317 ... modulename, f, localmods))
319 ... modulename, f, localmods))
318 ['baz.__init__', 'baz.baz1']
320 ['baz.__init__', 'baz.baz1']
319 >>> # mixed importing, even though it shouldn't be recommended
321 >>> # mixed importing, even though it shouldn't be recommended
320 >>> sorted(imported_modules(
322 >>> sorted(imported_modules(
321 ... 'import stdlib, foo1, baz',
323 ... 'import stdlib, foo1, baz',
322 ... modulename, f, localmods))
324 ... modulename, f, localmods))
323 ['baz.__init__', 'foo.foo1']
325 ['baz.__init__', 'foo.foo1']
324 >>> # ignore_nested
326 >>> # ignore_nested
325 >>> sorted(imported_modules(
327 >>> sorted(imported_modules(
326 ... '''import foo
328 ... '''import foo
327 ... def wat():
329 ... def wat():
328 ... import bar
330 ... import bar
329 ... ''', modulename, f, localmods))
331 ... ''', modulename, f, localmods))
330 ['foo.__init__', 'foo.bar.__init__']
332 ['foo.__init__', 'foo.bar.__init__']
331 >>> sorted(imported_modules(
333 >>> sorted(imported_modules(
332 ... '''import foo
334 ... '''import foo
333 ... def wat():
335 ... def wat():
334 ... import bar
336 ... import bar
335 ... ''', modulename, f, localmods, ignore_nested=True))
337 ... ''', modulename, f, localmods, ignore_nested=True))
336 ['foo.__init__']
338 ['foo.__init__']
337 """
339 """
338 fromlocal = fromlocalfunc(modulename, localmods)
340 fromlocal = fromlocalfunc(modulename, localmods)
339 for node in ast.walk(ast.parse(source, f)):
341 for node in ast.walk(ast.parse(source, f)):
340 if ignore_nested and getattr(node, 'col_offset', 0) > 0:
342 if ignore_nested and getattr(node, 'col_offset', 0) > 0:
341 continue
343 continue
342 if isinstance(node, ast.Import):
344 if isinstance(node, ast.Import):
343 for n in node.names:
345 for n in node.names:
344 found = fromlocal(n.name)
346 found = fromlocal(n.name)
345 if not found:
347 if not found:
346 # this should import standard library
348 # this should import standard library
347 continue
349 continue
348 yield found[1]
350 yield found[1]
349 elif isinstance(node, ast.ImportFrom):
351 elif isinstance(node, ast.ImportFrom):
350 found = fromlocal(node.module, node.level)
352 found = fromlocal(node.module, node.level)
351 if not found:
353 if not found:
352 # this should import standard library
354 # this should import standard library
353 continue
355 continue
354
356
355 absname, dottedpath, hassubmod = found
357 absname, dottedpath, hassubmod = found
356 if not hassubmod:
358 if not hassubmod:
357 # "dottedpath" is not a package; must be imported
359 # "dottedpath" is not a package; must be imported
358 yield dottedpath
360 yield dottedpath
359 # examination of "node.names" should be redundant
361 # examination of "node.names" should be redundant
360 # e.g.: from mercurial.node import nullid, nullrev
362 # e.g.: from mercurial.node import nullid, nullrev
361 continue
363 continue
362
364
363 modnotfound = False
365 modnotfound = False
364 prefix = absname + '.'
366 prefix = absname + '.'
365 for n in node.names:
367 for n in node.names:
366 found = fromlocal(prefix + n.name)
368 found = fromlocal(prefix + n.name)
367 if not found:
369 if not found:
368 # this should be a function or a property of "node.module"
370 # this should be a function or a property of "node.module"
369 modnotfound = True
371 modnotfound = True
370 continue
372 continue
371 yield found[1]
373 yield found[1]
372 if modnotfound:
374 if modnotfound:
373 # "dottedpath" is a package, but imported because of non-module
375 # "dottedpath" is a package, but imported because of non-module
374 # lookup
376 # lookup
375 yield dottedpath
377 yield dottedpath
376
378
377 def verify_import_convention(module, source, localmods):
379 def verify_import_convention(module, source, localmods):
378 """Verify imports match our established coding convention.
380 """Verify imports match our established coding convention.
379
381
380 We have 2 conventions: legacy and modern. The modern convention is in
382 We have 2 conventions: legacy and modern. The modern convention is in
381 effect when using absolute imports.
383 effect when using absolute imports.
382
384
383 The legacy convention only looks for mixed imports. The modern convention
385 The legacy convention only looks for mixed imports. The modern convention
384 is much more thorough.
386 is much more thorough.
385 """
387 """
386 root = ast.parse(source)
388 root = ast.parse(source)
387 absolute = usingabsolute(root)
389 absolute = usingabsolute(root)
388
390
389 if absolute:
391 if absolute:
390 return verify_modern_convention(module, root, localmods)
392 return verify_modern_convention(module, root, localmods)
391 else:
393 else:
392 return verify_stdlib_on_own_line(root)
394 return verify_stdlib_on_own_line(root)
393
395
394 def verify_modern_convention(module, root, localmods, root_col_offset=0):
396 def verify_modern_convention(module, root, localmods, root_col_offset=0):
395 """Verify a file conforms to the modern import convention rules.
397 """Verify a file conforms to the modern import convention rules.
396
398
397 The rules of the modern convention are:
399 The rules of the modern convention are:
398
400
399 * Ordering is stdlib followed by local imports. Each group is lexically
401 * Ordering is stdlib followed by local imports. Each group is lexically
400 sorted.
402 sorted.
401 * Importing multiple modules via "import X, Y" is not allowed: use
403 * Importing multiple modules via "import X, Y" is not allowed: use
402 separate import statements.
404 separate import statements.
403 * Importing multiple modules via "from X import ..." is allowed if using
405 * Importing multiple modules via "from X import ..." is allowed if using
404 parenthesis and one entry per line.
406 parenthesis and one entry per line.
405 * Only 1 relative import statement per import level ("from .", "from ..")
407 * Only 1 relative import statement per import level ("from .", "from ..")
406 is allowed.
408 is allowed.
407 * Relative imports from higher levels must occur before lower levels. e.g.
409 * Relative imports from higher levels must occur before lower levels. e.g.
408 "from .." must be before "from .".
410 "from .." must be before "from .".
409 * Imports from peer packages should use relative import (e.g. do not
411 * Imports from peer packages should use relative import (e.g. do not
410 "import mercurial.foo" from a "mercurial.*" module).
412 "import mercurial.foo" from a "mercurial.*" module).
411 * Symbols can only be imported from specific modules (see
413 * Symbols can only be imported from specific modules (see
412 `allowsymbolimports`). For other modules, first import the module then
414 `allowsymbolimports`). For other modules, first import the module then
413 assign the symbol to a module-level variable. In addition, these imports
415 assign the symbol to a module-level variable. In addition, these imports
414 must be performed before other local imports. This rule only
416 must be performed before other local imports. This rule only
415 applies to import statements outside of any blocks.
417 applies to import statements outside of any blocks.
416 * Relative imports from the standard library are not allowed, unless that
418 * Relative imports from the standard library are not allowed, unless that
417 library is also a local module.
419 library is also a local module.
418 * Certain modules must be aliased to alternate names to avoid aliasing
420 * Certain modules must be aliased to alternate names to avoid aliasing
419 and readability problems. See `requirealias`.
421 and readability problems. See `requirealias`.
420 """
422 """
421 if not isinstance(module, str):
423 if not isinstance(module, str):
422 module = module.decode('ascii')
424 module = module.decode('ascii')
423 topmodule = module.split('.')[0]
425 topmodule = module.split('.')[0]
424 fromlocal = fromlocalfunc(module, localmods)
426 fromlocal = fromlocalfunc(module, localmods)
425
427
426 # Whether a local/non-stdlib import has been performed.
428 # Whether a local/non-stdlib import has been performed.
427 seenlocal = None
429 seenlocal = None
428 # Whether a local/non-stdlib, non-symbol import has been seen.
430 # Whether a local/non-stdlib, non-symbol import has been seen.
429 seennonsymbollocal = False
431 seennonsymbollocal = False
430 # The last name to be imported (for sorting).
432 # The last name to be imported (for sorting).
431 lastname = None
433 lastname = None
432 laststdlib = None
434 laststdlib = None
433 # Relative import levels encountered so far.
435 # Relative import levels encountered so far.
434 seenlevels = set()
436 seenlevels = set()
435
437
436 for node, newscope in walklocal(root):
438 for node, newscope in walklocal(root):
437 def msg(fmt, *args):
439 def msg(fmt, *args):
438 return (fmt % args, node.lineno)
440 return (fmt % args, node.lineno)
439 if newscope:
441 if newscope:
440 # Check for local imports in function
442 # Check for local imports in function
441 for r in verify_modern_convention(module, node, localmods,
443 for r in verify_modern_convention(module, node, localmods,
442 node.col_offset + 4):
444 node.col_offset + 4):
443 yield r
445 yield r
444 elif isinstance(node, ast.Import):
446 elif isinstance(node, ast.Import):
445 # Disallow "import foo, bar" and require separate imports
447 # Disallow "import foo, bar" and require separate imports
446 # for each module.
448 # for each module.
447 if len(node.names) > 1:
449 if len(node.names) > 1:
448 yield msg('multiple imported names: %s',
450 yield msg('multiple imported names: %s',
449 ', '.join(n.name for n in node.names))
451 ', '.join(n.name for n in node.names))
450
452
451 name = node.names[0].name
453 name = node.names[0].name
452 asname = node.names[0].asname
454 asname = node.names[0].asname
453
455
454 stdlib = name in stdlib_modules
456 stdlib = name in stdlib_modules
455
457
456 # Ignore sorting rules on imports inside blocks.
458 # Ignore sorting rules on imports inside blocks.
457 if node.col_offset == root_col_offset:
459 if node.col_offset == root_col_offset:
458 if lastname and name < lastname and laststdlib == stdlib:
460 if lastname and name < lastname and laststdlib == stdlib:
459 yield msg('imports not lexically sorted: %s < %s',
461 yield msg('imports not lexically sorted: %s < %s',
460 name, lastname)
462 name, lastname)
461
463
462 lastname = name
464 lastname = name
463 laststdlib = stdlib
465 laststdlib = stdlib
464
466
465 # stdlib imports should be before local imports.
467 # stdlib imports should be before local imports.
466 if stdlib and seenlocal and node.col_offset == root_col_offset:
468 if stdlib and seenlocal and node.col_offset == root_col_offset:
467 yield msg('stdlib import "%s" follows local import: %s',
469 yield msg('stdlib import "%s" follows local import: %s',
468 name, seenlocal)
470 name, seenlocal)
469
471
470 if not stdlib:
472 if not stdlib:
471 seenlocal = name
473 seenlocal = name
472
474
473 # Import of sibling modules should use relative imports.
475 # Import of sibling modules should use relative imports.
474 topname = name.split('.')[0]
476 topname = name.split('.')[0]
475 if topname == topmodule:
477 if topname == topmodule:
476 yield msg('import should be relative: %s', name)
478 yield msg('import should be relative: %s', name)
477
479
478 if name in requirealias and asname != requirealias[name]:
480 if name in requirealias and asname != requirealias[name]:
479 yield msg('%s module must be "as" aliased to %s',
481 yield msg('%s module must be "as" aliased to %s',
480 name, requirealias[name])
482 name, requirealias[name])
481
483
482 elif isinstance(node, ast.ImportFrom):
484 elif isinstance(node, ast.ImportFrom):
483 # Resolve the full imported module name.
485 # Resolve the full imported module name.
484 if node.level > 0:
486 if node.level > 0:
485 fullname = '.'.join(module.split('.')[:-node.level])
487 fullname = '.'.join(module.split('.')[:-node.level])
486 if node.module:
488 if node.module:
487 fullname += '.%s' % node.module
489 fullname += '.%s' % node.module
488 else:
490 else:
489 assert node.module
491 assert node.module
490 fullname = node.module
492 fullname = node.module
491
493
492 topname = fullname.split('.')[0]
494 topname = fullname.split('.')[0]
493 if topname == topmodule:
495 if topname == topmodule:
494 yield msg('import should be relative: %s', fullname)
496 yield msg('import should be relative: %s', fullname)
495
497
496 # __future__ is special since it needs to come first and use
498 # __future__ is special since it needs to come first and use
497 # symbol import.
499 # symbol import.
498 if fullname != '__future__':
500 if fullname != '__future__':
499 if not fullname or (
501 if not fullname or (
500 fullname in stdlib_modules
502 fullname in stdlib_modules
501 and fullname not in localmods
503 and fullname not in localmods
502 and fullname + '.__init__' not in localmods):
504 and fullname + '.__init__' not in localmods):
503 yield msg('relative import of stdlib module')
505 yield msg('relative import of stdlib module')
504 else:
506 else:
505 seenlocal = fullname
507 seenlocal = fullname
506
508
507 # Direct symbol import is only allowed from certain modules and
509 # Direct symbol import is only allowed from certain modules and
508 # must occur before non-symbol imports.
510 # must occur before non-symbol imports.
509 found = fromlocal(node.module, node.level)
511 found = fromlocal(node.module, node.level)
510 if found and found[2]: # node.module is a package
512 if found and found[2]: # node.module is a package
511 prefix = found[0] + '.'
513 prefix = found[0] + '.'
512 symbols = (n.name for n in node.names
514 symbols = (n.name for n in node.names
513 if not fromlocal(prefix + n.name))
515 if not fromlocal(prefix + n.name))
514 else:
516 else:
515 symbols = (n.name for n in node.names)
517 symbols = (n.name for n in node.names)
516 symbols = [sym for sym in symbols if sym not in directsymbols]
518 symbols = [sym for sym in symbols if sym not in directsymbols]
517 if node.module and node.col_offset == root_col_offset:
519 if node.module and node.col_offset == root_col_offset:
518 if symbols and fullname not in allowsymbolimports:
520 if symbols and fullname not in allowsymbolimports:
519 yield msg('direct symbol import %s from %s',
521 yield msg('direct symbol import %s from %s',
520 ', '.join(symbols), fullname)
522 ', '.join(symbols), fullname)
521
523
522 if symbols and seennonsymbollocal:
524 if symbols and seennonsymbollocal:
523 yield msg('symbol import follows non-symbol import: %s',
525 yield msg('symbol import follows non-symbol import: %s',
524 fullname)
526 fullname)
525 if not symbols and fullname not in stdlib_modules:
527 if not symbols and fullname not in stdlib_modules:
526 seennonsymbollocal = True
528 seennonsymbollocal = True
527
529
528 if not node.module:
530 if not node.module:
529 assert node.level
531 assert node.level
530
532
531 # Only allow 1 group per level.
533 # Only allow 1 group per level.
532 if (node.level in seenlevels
534 if (node.level in seenlevels
533 and node.col_offset == root_col_offset):
535 and node.col_offset == root_col_offset):
534 yield msg('multiple "from %s import" statements',
536 yield msg('multiple "from %s import" statements',
535 '.' * node.level)
537 '.' * node.level)
536
538
537 # Higher-level groups come before lower-level groups.
539 # Higher-level groups come before lower-level groups.
538 if any(node.level > l for l in seenlevels):
540 if any(node.level > l for l in seenlevels):
539 yield msg('higher-level import should come first: %s',
541 yield msg('higher-level import should come first: %s',
540 fullname)
542 fullname)
541
543
542 seenlevels.add(node.level)
544 seenlevels.add(node.level)
543
545
544 # Entries in "from .X import ( ... )" lists must be lexically
546 # Entries in "from .X import ( ... )" lists must be lexically
545 # sorted.
547 # sorted.
546 lastentryname = None
548 lastentryname = None
547
549
548 for n in node.names:
550 for n in node.names:
549 if lastentryname and n.name < lastentryname:
551 if lastentryname and n.name < lastentryname:
550 yield msg('imports from %s not lexically sorted: %s < %s',
552 yield msg('imports from %s not lexically sorted: %s < %s',
551 fullname, n.name, lastentryname)
553 fullname, n.name, lastentryname)
552
554
553 lastentryname = n.name
555 lastentryname = n.name
554
556
555 if n.name in requirealias and n.asname != requirealias[n.name]:
557 if n.name in requirealias and n.asname != requirealias[n.name]:
556 yield msg('%s from %s must be "as" aliased to %s',
558 yield msg('%s from %s must be "as" aliased to %s',
557 n.name, fullname, requirealias[n.name])
559 n.name, fullname, requirealias[n.name])
558
560
559 def verify_stdlib_on_own_line(root):
561 def verify_stdlib_on_own_line(root):
560 """Given some python source, verify that stdlib imports are done
562 """Given some python source, verify that stdlib imports are done
561 in separate statements from relative local module imports.
563 in separate statements from relative local module imports.
562
564
563 >>> list(verify_stdlib_on_own_line(ast.parse('import sys, foo')))
565 >>> list(verify_stdlib_on_own_line(ast.parse('import sys, foo')))
564 [('mixed imports\\n stdlib: sys\\n relative: foo', 1)]
566 [('mixed imports\\n stdlib: sys\\n relative: foo', 1)]
565 >>> list(verify_stdlib_on_own_line(ast.parse('import sys, os')))
567 >>> list(verify_stdlib_on_own_line(ast.parse('import sys, os')))
566 []
568 []
567 >>> list(verify_stdlib_on_own_line(ast.parse('import foo, bar')))
569 >>> list(verify_stdlib_on_own_line(ast.parse('import foo, bar')))
568 []
570 []
569 """
571 """
570 for node in ast.walk(root):
572 for node in ast.walk(root):
571 if isinstance(node, ast.Import):
573 if isinstance(node, ast.Import):
572 from_stdlib = {False: [], True: []}
574 from_stdlib = {False: [], True: []}
573 for n in node.names:
575 for n in node.names:
574 from_stdlib[n.name in stdlib_modules].append(n.name)
576 from_stdlib[n.name in stdlib_modules].append(n.name)
575 if from_stdlib[True] and from_stdlib[False]:
577 if from_stdlib[True] and from_stdlib[False]:
576 yield ('mixed imports\n stdlib: %s\n relative: %s' %
578 yield ('mixed imports\n stdlib: %s\n relative: %s' %
577 (', '.join(sorted(from_stdlib[True])),
579 (', '.join(sorted(from_stdlib[True])),
578 ', '.join(sorted(from_stdlib[False]))), node.lineno)
580 ', '.join(sorted(from_stdlib[False]))), node.lineno)
579
581
580 class CircularImport(Exception):
582 class CircularImport(Exception):
581 pass
583 pass
582
584
583 def checkmod(mod, imports):
585 def checkmod(mod, imports):
584 shortest = {}
586 shortest = {}
585 visit = [[mod]]
587 visit = [[mod]]
586 while visit:
588 while visit:
587 path = visit.pop(0)
589 path = visit.pop(0)
588 for i in sorted(imports.get(path[-1], [])):
590 for i in sorted(imports.get(path[-1], [])):
589 if len(path) < shortest.get(i, 1000):
591 if len(path) < shortest.get(i, 1000):
590 shortest[i] = len(path)
592 shortest[i] = len(path)
591 if i in path:
593 if i in path:
592 if i == path[0]:
594 if i == path[0]:
593 raise CircularImport(path)
595 raise CircularImport(path)
594 continue
596 continue
595 visit.append(path + [i])
597 visit.append(path + [i])
596
598
597 def rotatecycle(cycle):
599 def rotatecycle(cycle):
598 """arrange a cycle so that the lexicographically first module listed first
600 """arrange a cycle so that the lexicographically first module listed first
599
601
600 >>> rotatecycle(['foo', 'bar'])
602 >>> rotatecycle(['foo', 'bar'])
601 ['bar', 'foo', 'bar']
603 ['bar', 'foo', 'bar']
602 """
604 """
603 lowest = min(cycle)
605 lowest = min(cycle)
604 idx = cycle.index(lowest)
606 idx = cycle.index(lowest)
605 return cycle[idx:] + cycle[:idx] + [lowest]
607 return cycle[idx:] + cycle[:idx] + [lowest]
606
608
607 def find_cycles(imports):
609 def find_cycles(imports):
608 """Find cycles in an already-loaded import graph.
610 """Find cycles in an already-loaded import graph.
609
611
610 All module names recorded in `imports` should be absolute one.
612 All module names recorded in `imports` should be absolute one.
611
613
612 >>> from __future__ import print_function
614 >>> from __future__ import print_function
613 >>> imports = {'top.foo': ['top.bar', 'os.path', 'top.qux'],
615 >>> imports = {'top.foo': ['top.bar', 'os.path', 'top.qux'],
614 ... 'top.bar': ['top.baz', 'sys'],
616 ... 'top.bar': ['top.baz', 'sys'],
615 ... 'top.baz': ['top.foo'],
617 ... 'top.baz': ['top.foo'],
616 ... 'top.qux': ['top.foo']}
618 ... 'top.qux': ['top.foo']}
617 >>> print('\\n'.join(sorted(find_cycles(imports))))
619 >>> print('\\n'.join(sorted(find_cycles(imports))))
618 top.bar -> top.baz -> top.foo -> top.bar
620 top.bar -> top.baz -> top.foo -> top.bar
619 top.foo -> top.qux -> top.foo
621 top.foo -> top.qux -> top.foo
620 """
622 """
621 cycles = set()
623 cycles = set()
622 for mod in sorted(imports.keys()):
624 for mod in sorted(imports.keys()):
623 try:
625 try:
624 checkmod(mod, imports)
626 checkmod(mod, imports)
625 except CircularImport as e:
627 except CircularImport as e:
626 cycle = e.args[0]
628 cycle = e.args[0]
627 cycles.add(" -> ".join(rotatecycle(cycle)))
629 cycles.add(" -> ".join(rotatecycle(cycle)))
628 return cycles
630 return cycles
629
631
630 def _cycle_sortkey(c):
632 def _cycle_sortkey(c):
631 return len(c), c
633 return len(c), c
632
634
633 def embedded(f, modname, src):
635 def embedded(f, modname, src):
634 """Extract embedded python code
636 """Extract embedded python code
635
637
636 >>> def _forcestr(thing):
638 >>> def _forcestr(thing):
637 ... if not isinstance(thing, str):
639 ... if not isinstance(thing, str):
638 ... return thing.decode('ascii')
640 ... return thing.decode('ascii')
639 ... return thing
641 ... return thing
640 >>> def test(fn, lines):
642 >>> def test(fn, lines):
641 ... for s, m, f, l in embedded(fn, b"example", lines):
643 ... for s, m, f, l in embedded(fn, b"example", lines):
642 ... print("%s %s %d" % (_forcestr(m), _forcestr(f), l))
644 ... print("%s %s %d" % (_forcestr(m), _forcestr(f), l))
643 ... print(repr(_forcestr(s)))
645 ... print(repr(_forcestr(s)))
644 >>> lines = [
646 >>> lines = [
645 ... b'comment',
647 ... b'comment',
646 ... b' >>> from __future__ import print_function',
648 ... b' >>> from __future__ import print_function',
647 ... b" >>> ' multiline",
649 ... b" >>> ' multiline",
648 ... b" ... string'",
650 ... b" ... string'",
649 ... b' ',
651 ... b' ',
650 ... b'comment',
652 ... b'comment',
651 ... b' $ cat > foo.py <<EOF',
653 ... b' $ cat > foo.py <<EOF',
652 ... b' > from __future__ import print_function',
654 ... b' > from __future__ import print_function',
653 ... b' > EOF',
655 ... b' > EOF',
654 ... ]
656 ... ]
655 >>> test(b"example.t", lines)
657 >>> test(b"example.t", lines)
656 example[2] doctest.py 2
658 example[2] doctest.py 2
657 "from __future__ import print_function\\n' multiline\\nstring'\\n"
659 "from __future__ import print_function\\n' multiline\\nstring'\\n"
658 example[7] foo.py 7
660 example[7] foo.py 7
659 'from __future__ import print_function\\n'
661 'from __future__ import print_function\\n'
660 """
662 """
661 inlinepython = 0
663 inlinepython = 0
662 shpython = 0
664 shpython = 0
663 script = []
665 script = []
664 prefix = 6
666 prefix = 6
665 t = ''
667 t = ''
666 n = 0
668 n = 0
667 for l in src:
669 for l in src:
668 n += 1
670 n += 1
669 if not l.endswith(b'\n'):
671 if not l.endswith(b'\n'):
670 l += b'\n'
672 l += b'\n'
671 if l.startswith(b' >>> '): # python inlines
673 if l.startswith(b' >>> '): # python inlines
672 if shpython:
674 if shpython:
673 print("%s:%d: Parse Error" % (f, n))
675 print("%s:%d: Parse Error" % (f, n))
674 if not inlinepython:
676 if not inlinepython:
675 # We've just entered a Python block.
677 # We've just entered a Python block.
676 inlinepython = n
678 inlinepython = n
677 t = b'doctest.py'
679 t = b'doctest.py'
678 script.append(l[prefix:])
680 script.append(l[prefix:])
679 continue
681 continue
680 if l.startswith(b' ... '): # python inlines
682 if l.startswith(b' ... '): # python inlines
681 script.append(l[prefix:])
683 script.append(l[prefix:])
682 continue
684 continue
683 cat = re.search(br"\$ \s*cat\s*>\s*(\S+\.py)\s*<<\s*EOF", l)
685 cat = re.search(br"\$ \s*cat\s*>\s*(\S+\.py)\s*<<\s*EOF", l)
684 if cat:
686 if cat:
685 if inlinepython:
687 if inlinepython:
686 yield b''.join(script), (b"%s[%d]" %
688 yield b''.join(script), (b"%s[%d]" %
687 (modname, inlinepython)), t, inlinepython
689 (modname, inlinepython)), t, inlinepython
688 script = []
690 script = []
689 inlinepython = 0
691 inlinepython = 0
690 shpython = n
692 shpython = n
691 t = cat.group(1)
693 t = cat.group(1)
692 continue
694 continue
693 if shpython and l.startswith(b' > '): # sh continuation
695 if shpython and l.startswith(b' > '): # sh continuation
694 if l == b' > EOF\n':
696 if l == b' > EOF\n':
695 yield b''.join(script), (b"%s[%d]" %
697 yield b''.join(script), (b"%s[%d]" %
696 (modname, shpython)), t, shpython
698 (modname, shpython)), t, shpython
697 script = []
699 script = []
698 shpython = 0
700 shpython = 0
699 else:
701 else:
700 script.append(l[4:])
702 script.append(l[4:])
701 continue
703 continue
702 # If we have an empty line or a command for sh, we end the
704 # If we have an empty line or a command for sh, we end the
703 # inline script.
705 # inline script.
704 if inlinepython and (l == b' \n'
706 if inlinepython and (l == b' \n'
705 or l.startswith(b' $ ')):
707 or l.startswith(b' $ ')):
706 yield b''.join(script), (b"%s[%d]" %
708 yield b''.join(script), (b"%s[%d]" %
707 (modname, inlinepython)), t, inlinepython
709 (modname, inlinepython)), t, inlinepython
708 script = []
710 script = []
709 inlinepython = 0
711 inlinepython = 0
710 continue
712 continue
711
713
712 def sources(f, modname):
714 def sources(f, modname):
713 """Yields possibly multiple sources from a filepath
715 """Yields possibly multiple sources from a filepath
714
716
715 input: filepath, modulename
717 input: filepath, modulename
716 yields: script(string), modulename, filepath, linenumber
718 yields: script(string), modulename, filepath, linenumber
717
719
718 For embedded scripts, the modulename and filepath will be different
720 For embedded scripts, the modulename and filepath will be different
719 from the function arguments. linenumber is an offset relative to
721 from the function arguments. linenumber is an offset relative to
720 the input file.
722 the input file.
721 """
723 """
722 py = False
724 py = False
723 if not f.endswith('.t'):
725 if not f.endswith('.t'):
724 with open(f, 'rb') as src:
726 with open(f, 'rb') as src:
725 yield src.read(), modname, f, 0
727 yield src.read(), modname, f, 0
726 py = True
728 py = True
727 if py or f.endswith('.t'):
729 if py or f.endswith('.t'):
728 with open(f, 'rb') as src:
730 with open(f, 'rb') as src:
729 for script, modname, t, line in embedded(f, modname, src):
731 for script, modname, t, line in embedded(f, modname, src):
730 yield script, modname, t, line
732 yield script, modname, t, line
731
733
732 def main(argv):
734 def main(argv):
733 if len(argv) < 2 or (argv[1] == '-' and len(argv) > 2):
735 if len(argv) < 2 or (argv[1] == '-' and len(argv) > 2):
734 print('Usage: %s {-|file [file] [file] ...}')
736 print('Usage: %s {-|file [file] [file] ...}')
735 return 1
737 return 1
736 if argv[1] == '-':
738 if argv[1] == '-':
737 argv = argv[:1]
739 argv = argv[:1]
738 argv.extend(l.rstrip() for l in sys.stdin.readlines())
740 argv.extend(l.rstrip() for l in sys.stdin.readlines())
739 localmodpaths = {}
741 localmodpaths = {}
740 used_imports = {}
742 used_imports = {}
741 any_errors = False
743 any_errors = False
742 for source_path in argv[1:]:
744 for source_path in argv[1:]:
743 modname = dotted_name_of_path(source_path)
745 modname = dotted_name_of_path(source_path)
744 localmodpaths[modname] = source_path
746 localmodpaths[modname] = source_path
745 localmods = populateextmods(localmodpaths)
747 localmods = populateextmods(localmodpaths)
746 for localmodname, source_path in sorted(localmodpaths.items()):
748 for localmodname, source_path in sorted(localmodpaths.items()):
747 if not isinstance(localmodname, bytes):
749 if not isinstance(localmodname, bytes):
748 # This is only safe because all hg's files are ascii
750 # This is only safe because all hg's files are ascii
749 localmodname = localmodname.encode('ascii')
751 localmodname = localmodname.encode('ascii')
750 for src, modname, name, line in sources(source_path, localmodname):
752 for src, modname, name, line in sources(source_path, localmodname):
751 try:
753 try:
752 used_imports[modname] = sorted(
754 used_imports[modname] = sorted(
753 imported_modules(src, modname, name, localmods,
755 imported_modules(src, modname, name, localmods,
754 ignore_nested=True))
756 ignore_nested=True))
755 for error, lineno in verify_import_convention(modname, src,
757 for error, lineno in verify_import_convention(modname, src,
756 localmods):
758 localmods):
757 any_errors = True
759 any_errors = True
758 print('%s:%d: %s' % (source_path, lineno + line, error))
760 print('%s:%d: %s' % (source_path, lineno + line, error))
759 except SyntaxError as e:
761 except SyntaxError as e:
760 print('%s:%d: SyntaxError: %s' %
762 print('%s:%d: SyntaxError: %s' %
761 (source_path, e.lineno + line, e))
763 (source_path, e.lineno + line, e))
762 cycles = find_cycles(used_imports)
764 cycles = find_cycles(used_imports)
763 if cycles:
765 if cycles:
764 firstmods = set()
766 firstmods = set()
765 for c in sorted(cycles, key=_cycle_sortkey):
767 for c in sorted(cycles, key=_cycle_sortkey):
766 first = c.split()[0]
768 first = c.split()[0]
767 # As a rough cut, ignore any cycle that starts with the
769 # As a rough cut, ignore any cycle that starts with the
768 # same module as some other cycle. Otherwise we see lots
770 # same module as some other cycle. Otherwise we see lots
769 # of cycles that are effectively duplicates.
771 # of cycles that are effectively duplicates.
770 if first in firstmods:
772 if first in firstmods:
771 continue
773 continue
772 print('Import cycle:', c)
774 print('Import cycle:', c)
773 firstmods.add(first)
775 firstmods.add(first)
774 any_errors = True
776 any_errors = True
775 return any_errors != 0
777 return any_errors != 0
776
778
777 if __name__ == '__main__':
779 if __name__ == '__main__':
778 sys.exit(int(main(sys.argv)))
780 sys.exit(int(main(sys.argv)))
@@ -1,1074 +1,1080 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 #
14 #
15 # TODO: when we actually work on Python 3, use this string as the
15 # TODO: when we actually work on Python 3, use this string as the
16 # actual supportedpy string.
16 # actual supportedpy string.
17 supportedpy = ','.join([
17 supportedpy = ','.join([
18 '>=2.7',
18 '>=2.7',
19 '!=3.0.*',
19 '!=3.0.*',
20 '!=3.1.*',
20 '!=3.1.*',
21 '!=3.2.*',
21 '!=3.2.*',
22 '!=3.3.*',
22 '!=3.3.*',
23 '!=3.4.*',
23 '!=3.4.*',
24 '!=3.6.0',
24 '!=3.6.0',
25 '!=3.6.1',
25 '!=3.6.1',
26 ])
26 ])
27
27
28 import sys, platform
28 import sys, platform
29 if sys.version_info[0] >= 3:
29 if sys.version_info[0] >= 3:
30 printf = eval('print')
30 printf = eval('print')
31 libdir_escape = 'unicode_escape'
31 libdir_escape = 'unicode_escape'
32 def sysstr(s):
32 def sysstr(s):
33 return s.decode('latin-1')
33 return s.decode('latin-1')
34 else:
34 else:
35 libdir_escape = 'string_escape'
35 libdir_escape = 'string_escape'
36 def printf(*args, **kwargs):
36 def printf(*args, **kwargs):
37 f = kwargs.get('file', sys.stdout)
37 f = kwargs.get('file', sys.stdout)
38 end = kwargs.get('end', '\n')
38 end = kwargs.get('end', '\n')
39 f.write(b' '.join(args) + end)
39 f.write(b' '.join(args) + end)
40 def sysstr(s):
40 def sysstr(s):
41 return s
41 return s
42
42
43 # Attempt to guide users to a modern pip - this means that 2.6 users
43 # Attempt to guide users to a modern pip - this means that 2.6 users
44 # should have a chance of getting a 4.2 release, and when we ratchet
44 # should have a chance of getting a 4.2 release, and when we ratchet
45 # the version requirement forward again hopefully everyone will get
45 # the version requirement forward again hopefully everyone will get
46 # something that works for them.
46 # something that works for them.
47 if sys.version_info < (2, 7, 0, 'final'):
47 if sys.version_info < (2, 7, 0, 'final'):
48 pip_message = ('This may be due to an out of date pip. '
48 pip_message = ('This may be due to an out of date pip. '
49 'Make sure you have pip >= 9.0.1.')
49 'Make sure you have pip >= 9.0.1.')
50 try:
50 try:
51 import pip
51 import pip
52 pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
52 pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
53 if pip_version < (9, 0, 1) :
53 if pip_version < (9, 0, 1) :
54 pip_message = (
54 pip_message = (
55 'Your pip version is out of date, please install '
55 'Your pip version is out of date, please install '
56 'pip >= 9.0.1. pip {} detected.'.format(pip.__version__))
56 'pip >= 9.0.1. pip {} detected.'.format(pip.__version__))
57 else:
57 else:
58 # pip is new enough - it must be something else
58 # pip is new enough - it must be something else
59 pip_message = ''
59 pip_message = ''
60 except Exception:
60 except Exception:
61 pass
61 pass
62 error = """
62 error = """
63 Mercurial does not support Python older than 2.7.
63 Mercurial does not support Python older than 2.7.
64 Python {py} detected.
64 Python {py} detected.
65 {pip}
65 {pip}
66 """.format(py=sys.version_info, pip=pip_message)
66 """.format(py=sys.version_info, pip=pip_message)
67 printf(error, file=sys.stderr)
67 printf(error, file=sys.stderr)
68 sys.exit(1)
68 sys.exit(1)
69
69
70 # We don't yet officially support Python 3. But we want to allow developers to
70 # We don't yet officially support Python 3. But we want to allow developers to
71 # hack on. Detect and disallow running on Python 3 by default. But provide a
71 # hack on. Detect and disallow running on Python 3 by default. But provide a
72 # backdoor to enable working on Python 3.
72 # backdoor to enable working on Python 3.
73 if sys.version_info[0] != 2:
73 if sys.version_info[0] != 2:
74 badpython = True
74 badpython = True
75
75
76 # Allow Python 3 from source checkouts.
76 # Allow Python 3 from source checkouts.
77 if os.path.isdir('.hg'):
77 if os.path.isdir('.hg'):
78 badpython = False
78 badpython = False
79
79
80 if badpython:
80 if badpython:
81 error = """
81 error = """
82 Mercurial only supports Python 2.7.
82 Mercurial only supports Python 2.7.
83 Python {py} detected.
83 Python {py} detected.
84 Please re-run with Python 2.7.
84 Please re-run with Python 2.7.
85 """.format(py=sys.version_info)
85 """.format(py=sys.version_info)
86
86
87 printf(error, file=sys.stderr)
87 printf(error, file=sys.stderr)
88 sys.exit(1)
88 sys.exit(1)
89
89
90 # Solaris Python packaging brain damage
90 # Solaris Python packaging brain damage
91 try:
91 try:
92 import hashlib
92 import hashlib
93 sha = hashlib.sha1()
93 sha = hashlib.sha1()
94 except ImportError:
94 except ImportError:
95 try:
95 try:
96 import sha
96 import sha
97 sha.sha # silence unused import warning
97 sha.sha # silence unused import warning
98 except ImportError:
98 except ImportError:
99 raise SystemExit(
99 raise SystemExit(
100 "Couldn't import standard hashlib (incomplete Python install).")
100 "Couldn't import standard hashlib (incomplete Python install).")
101
101
102 try:
102 try:
103 import zlib
103 import zlib
104 zlib.compressobj # silence unused import warning
104 zlib.compressobj # silence unused import warning
105 except ImportError:
105 except ImportError:
106 raise SystemExit(
106 raise SystemExit(
107 "Couldn't import standard zlib (incomplete Python install).")
107 "Couldn't import standard zlib (incomplete Python install).")
108
108
109 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
109 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
110 isironpython = False
110 isironpython = False
111 try:
111 try:
112 isironpython = (platform.python_implementation()
112 isironpython = (platform.python_implementation()
113 .lower().find("ironpython") != -1)
113 .lower().find("ironpython") != -1)
114 except AttributeError:
114 except AttributeError:
115 pass
115 pass
116
116
117 if isironpython:
117 if isironpython:
118 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
118 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
119 else:
119 else:
120 try:
120 try:
121 import bz2
121 import bz2
122 bz2.BZ2Compressor # silence unused import warning
122 bz2.BZ2Compressor # silence unused import warning
123 except ImportError:
123 except ImportError:
124 raise SystemExit(
124 raise SystemExit(
125 "Couldn't import standard bz2 (incomplete Python install).")
125 "Couldn't import standard bz2 (incomplete Python install).")
126
126
127 ispypy = "PyPy" in sys.version
127 ispypy = "PyPy" in sys.version
128
128
129 import ctypes
129 import ctypes
130 import stat, subprocess, time
130 import stat, subprocess, time
131 import re
131 import re
132 import shutil
132 import shutil
133 import tempfile
133 import tempfile
134 from distutils import log
134 from distutils import log
135 # We have issues with setuptools on some platforms and builders. Until
135 # We have issues with setuptools on some platforms and builders. Until
136 # those are resolved, setuptools is opt-in except for platforms where
136 # those are resolved, setuptools is opt-in except for platforms where
137 # we don't have issues.
137 # we don't have issues.
138 issetuptools = (os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ)
138 issetuptools = (os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ)
139 if issetuptools:
139 if issetuptools:
140 from setuptools import setup
140 from setuptools import setup
141 else:
141 else:
142 from distutils.core import setup
142 from distutils.core import setup
143 from distutils.ccompiler import new_compiler
143 from distutils.ccompiler import new_compiler
144 from distutils.core import Command, Extension
144 from distutils.core import Command, Extension
145 from distutils.dist import Distribution
145 from distutils.dist import Distribution
146 from distutils.command.build import build
146 from distutils.command.build import build
147 from distutils.command.build_ext import build_ext
147 from distutils.command.build_ext import build_ext
148 from distutils.command.build_py import build_py
148 from distutils.command.build_py import build_py
149 from distutils.command.build_scripts import build_scripts
149 from distutils.command.build_scripts import build_scripts
150 from distutils.command.install import install
150 from distutils.command.install import install
151 from distutils.command.install_lib import install_lib
151 from distutils.command.install_lib import install_lib
152 from distutils.command.install_scripts import install_scripts
152 from distutils.command.install_scripts import install_scripts
153 from distutils.spawn import spawn, find_executable
153 from distutils.spawn import spawn, find_executable
154 from distutils import file_util
154 from distutils import file_util
155 from distutils.errors import (
155 from distutils.errors import (
156 CCompilerError,
156 CCompilerError,
157 DistutilsError,
157 DistutilsError,
158 DistutilsExecError,
158 DistutilsExecError,
159 )
159 )
160 from distutils.sysconfig import get_python_inc, get_config_var
160 from distutils.sysconfig import get_python_inc, get_config_var
161 from distutils.version import StrictVersion
161 from distutils.version import StrictVersion
162
162
163 def write_if_changed(path, content):
163 def write_if_changed(path, content):
164 """Write content to a file iff the content hasn't changed."""
164 """Write content to a file iff the content hasn't changed."""
165 if os.path.exists(path):
165 if os.path.exists(path):
166 with open(path, 'rb') as fh:
166 with open(path, 'rb') as fh:
167 current = fh.read()
167 current = fh.read()
168 else:
168 else:
169 current = b''
169 current = b''
170
170
171 if current != content:
171 if current != content:
172 with open(path, 'wb') as fh:
172 with open(path, 'wb') as fh:
173 fh.write(content)
173 fh.write(content)
174
174
175 scripts = ['hg']
175 scripts = ['hg']
176 if os.name == 'nt':
176 if os.name == 'nt':
177 # We remove hg.bat if we are able to build hg.exe.
177 # We remove hg.bat if we are able to build hg.exe.
178 scripts.append('contrib/win32/hg.bat')
178 scripts.append('contrib/win32/hg.bat')
179
179
180 def cancompile(cc, code):
180 def cancompile(cc, code):
181 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
181 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
182 devnull = oldstderr = None
182 devnull = oldstderr = None
183 try:
183 try:
184 fname = os.path.join(tmpdir, 'testcomp.c')
184 fname = os.path.join(tmpdir, 'testcomp.c')
185 f = open(fname, 'w')
185 f = open(fname, 'w')
186 f.write(code)
186 f.write(code)
187 f.close()
187 f.close()
188 # Redirect stderr to /dev/null to hide any error messages
188 # Redirect stderr to /dev/null to hide any error messages
189 # from the compiler.
189 # from the compiler.
190 # This will have to be changed if we ever have to check
190 # This will have to be changed if we ever have to check
191 # for a function on Windows.
191 # for a function on Windows.
192 devnull = open('/dev/null', 'w')
192 devnull = open('/dev/null', 'w')
193 oldstderr = os.dup(sys.stderr.fileno())
193 oldstderr = os.dup(sys.stderr.fileno())
194 os.dup2(devnull.fileno(), sys.stderr.fileno())
194 os.dup2(devnull.fileno(), sys.stderr.fileno())
195 objects = cc.compile([fname], output_dir=tmpdir)
195 objects = cc.compile([fname], output_dir=tmpdir)
196 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
196 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
197 return True
197 return True
198 except Exception:
198 except Exception:
199 return False
199 return False
200 finally:
200 finally:
201 if oldstderr is not None:
201 if oldstderr is not None:
202 os.dup2(oldstderr, sys.stderr.fileno())
202 os.dup2(oldstderr, sys.stderr.fileno())
203 if devnull is not None:
203 if devnull is not None:
204 devnull.close()
204 devnull.close()
205 shutil.rmtree(tmpdir)
205 shutil.rmtree(tmpdir)
206
206
207 # simplified version of distutils.ccompiler.CCompiler.has_function
207 # simplified version of distutils.ccompiler.CCompiler.has_function
208 # that actually removes its temporary files.
208 # that actually removes its temporary files.
209 def hasfunction(cc, funcname):
209 def hasfunction(cc, funcname):
210 code = 'int main(void) { %s(); }\n' % funcname
210 code = 'int main(void) { %s(); }\n' % funcname
211 return cancompile(cc, code)
211 return cancompile(cc, code)
212
212
213 def hasheader(cc, headername):
213 def hasheader(cc, headername):
214 code = '#include <%s>\nint main(void) { return 0; }\n' % headername
214 code = '#include <%s>\nint main(void) { return 0; }\n' % headername
215 return cancompile(cc, code)
215 return cancompile(cc, code)
216
216
217 # py2exe needs to be installed to work
217 # py2exe needs to be installed to work
218 try:
218 try:
219 import py2exe
219 import py2exe
220 py2exe.Distribution # silence unused import warning
220 py2exe.Distribution # silence unused import warning
221 py2exeloaded = True
221 py2exeloaded = True
222 # import py2exe's patched Distribution class
222 # import py2exe's patched Distribution class
223 from distutils.core import Distribution
223 from distutils.core import Distribution
224 except ImportError:
224 except ImportError:
225 py2exeloaded = False
225 py2exeloaded = False
226
226
227 def runcmd(cmd, env):
227 def runcmd(cmd, env):
228 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
228 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
229 stderr=subprocess.PIPE, env=env)
229 stderr=subprocess.PIPE, env=env)
230 out, err = p.communicate()
230 out, err = p.communicate()
231 return p.returncode, out, err
231 return p.returncode, out, err
232
232
233 class hgcommand(object):
233 class hgcommand(object):
234 def __init__(self, cmd, env):
234 def __init__(self, cmd, env):
235 self.cmd = cmd
235 self.cmd = cmd
236 self.env = env
236 self.env = env
237
237
238 def run(self, args):
238 def run(self, args):
239 cmd = self.cmd + args
239 cmd = self.cmd + args
240 returncode, out, err = runcmd(cmd, self.env)
240 returncode, out, err = runcmd(cmd, self.env)
241 err = filterhgerr(err)
241 err = filterhgerr(err)
242 if err or returncode != 0:
242 if err or returncode != 0:
243 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
243 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
244 printf(err, file=sys.stderr)
244 printf(err, file=sys.stderr)
245 return ''
245 return ''
246 return out
246 return out
247
247
248 def filterhgerr(err):
248 def filterhgerr(err):
249 # If root is executing setup.py, but the repository is owned by
249 # If root is executing setup.py, but the repository is owned by
250 # another user (as in "sudo python setup.py install") we will get
250 # another user (as in "sudo python setup.py install") we will get
251 # trust warnings since the .hg/hgrc file is untrusted. That is
251 # trust warnings since the .hg/hgrc file is untrusted. That is
252 # fine, we don't want to load it anyway. Python may warn about
252 # fine, we don't want to load it anyway. Python may warn about
253 # a missing __init__.py in mercurial/locale, we also ignore that.
253 # a missing __init__.py in mercurial/locale, we also ignore that.
254 err = [e for e in err.splitlines()
254 err = [e for e in err.splitlines()
255 if (not e.startswith(b'not trusting file')
255 if (not e.startswith(b'not trusting file')
256 and not e.startswith(b'warning: Not importing')
256 and not e.startswith(b'warning: Not importing')
257 and not e.startswith(b'obsolete feature not enabled')
257 and not e.startswith(b'obsolete feature not enabled')
258 and not e.startswith(b'*** failed to import extension')
258 and not e.startswith(b'*** failed to import extension')
259 and not e.startswith(b'devel-warn:'))]
259 and not e.startswith(b'devel-warn:'))]
260 return b'\n'.join(b' ' + e for e in err)
260 return b'\n'.join(b' ' + e for e in err)
261
261
262 def findhg():
262 def findhg():
263 """Try to figure out how we should invoke hg for examining the local
263 """Try to figure out how we should invoke hg for examining the local
264 repository contents.
264 repository contents.
265
265
266 Returns an hgcommand object."""
266 Returns an hgcommand object."""
267 # By default, prefer the "hg" command in the user's path. This was
267 # By default, prefer the "hg" command in the user's path. This was
268 # presumably the hg command that the user used to create this repository.
268 # presumably the hg command that the user used to create this repository.
269 #
269 #
270 # This repository may require extensions or other settings that would not
270 # This repository may require extensions or other settings that would not
271 # be enabled by running the hg script directly from this local repository.
271 # be enabled by running the hg script directly from this local repository.
272 hgenv = os.environ.copy()
272 hgenv = os.environ.copy()
273 # Use HGPLAIN to disable hgrc settings that would change output formatting,
273 # Use HGPLAIN to disable hgrc settings that would change output formatting,
274 # and disable localization for the same reasons.
274 # and disable localization for the same reasons.
275 hgenv['HGPLAIN'] = '1'
275 hgenv['HGPLAIN'] = '1'
276 hgenv['LANGUAGE'] = 'C'
276 hgenv['LANGUAGE'] = 'C'
277 hgcmd = ['hg']
277 hgcmd = ['hg']
278 # Run a simple "hg log" command just to see if using hg from the user's
278 # Run a simple "hg log" command just to see if using hg from the user's
279 # path works and can successfully interact with this repository.
279 # path works and can successfully interact with this repository.
280 check_cmd = ['log', '-r.', '-Ttest']
280 check_cmd = ['log', '-r.', '-Ttest']
281 try:
281 try:
282 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
282 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
283 except EnvironmentError:
283 except EnvironmentError:
284 retcode = -1
284 retcode = -1
285 if retcode == 0 and not filterhgerr(err):
285 if retcode == 0 and not filterhgerr(err):
286 return hgcommand(hgcmd, hgenv)
286 return hgcommand(hgcmd, hgenv)
287
287
288 # Fall back to trying the local hg installation.
288 # Fall back to trying the local hg installation.
289 hgenv = localhgenv()
289 hgenv = localhgenv()
290 hgcmd = [sys.executable, 'hg']
290 hgcmd = [sys.executable, 'hg']
291 try:
291 try:
292 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
292 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
293 except EnvironmentError:
293 except EnvironmentError:
294 retcode = -1
294 retcode = -1
295 if retcode == 0 and not filterhgerr(err):
295 if retcode == 0 and not filterhgerr(err):
296 return hgcommand(hgcmd, hgenv)
296 return hgcommand(hgcmd, hgenv)
297
297
298 raise SystemExit('Unable to find a working hg binary to extract the '
298 raise SystemExit('Unable to find a working hg binary to extract the '
299 'version from the repository tags')
299 'version from the repository tags')
300
300
301 def localhgenv():
301 def localhgenv():
302 """Get an environment dictionary to use for invoking or importing
302 """Get an environment dictionary to use for invoking or importing
303 mercurial from the local repository."""
303 mercurial from the local repository."""
304 # Execute hg out of this directory with a custom environment which takes
304 # Execute hg out of this directory with a custom environment which takes
305 # care to not use any hgrc files and do no localization.
305 # care to not use any hgrc files and do no localization.
306 env = {'HGMODULEPOLICY': 'py',
306 env = {'HGMODULEPOLICY': 'py',
307 'HGRCPATH': '',
307 'HGRCPATH': '',
308 'LANGUAGE': 'C',
308 'LANGUAGE': 'C',
309 'PATH': ''} # make pypi modules that use os.environ['PATH'] happy
309 'PATH': ''} # make pypi modules that use os.environ['PATH'] happy
310 if 'LD_LIBRARY_PATH' in os.environ:
310 if 'LD_LIBRARY_PATH' in os.environ:
311 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
311 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
312 if 'SystemRoot' in os.environ:
312 if 'SystemRoot' in os.environ:
313 # SystemRoot is required by Windows to load various DLLs. See:
313 # SystemRoot is required by Windows to load various DLLs. See:
314 # https://bugs.python.org/issue13524#msg148850
314 # https://bugs.python.org/issue13524#msg148850
315 env['SystemRoot'] = os.environ['SystemRoot']
315 env['SystemRoot'] = os.environ['SystemRoot']
316 return env
316 return env
317
317
318 version = ''
318 version = ''
319
319
320 if os.path.isdir('.hg'):
320 if os.path.isdir('.hg'):
321 hg = findhg()
321 hg = findhg()
322 cmd = ['log', '-r', '.', '--template', '{tags}\n']
322 cmd = ['log', '-r', '.', '--template', '{tags}\n']
323 numerictags = [t for t in sysstr(hg.run(cmd)).split() if t[0:1].isdigit()]
323 numerictags = [t for t in sysstr(hg.run(cmd)).split() if t[0:1].isdigit()]
324 hgid = sysstr(hg.run(['id', '-i'])).strip()
324 hgid = sysstr(hg.run(['id', '-i'])).strip()
325 if not hgid:
325 if not hgid:
326 # Bail out if hg is having problems interacting with this repository,
326 # Bail out if hg is having problems interacting with this repository,
327 # rather than falling through and producing a bogus version number.
327 # rather than falling through and producing a bogus version number.
328 # Continuing with an invalid version number will break extensions
328 # Continuing with an invalid version number will break extensions
329 # that define minimumhgversion.
329 # that define minimumhgversion.
330 raise SystemExit('Unable to determine hg version from local repository')
330 raise SystemExit('Unable to determine hg version from local repository')
331 if numerictags: # tag(s) found
331 if numerictags: # tag(s) found
332 version = numerictags[-1]
332 version = numerictags[-1]
333 if hgid.endswith('+'): # propagate the dirty status to the tag
333 if hgid.endswith('+'): # propagate the dirty status to the tag
334 version += '+'
334 version += '+'
335 else: # no tag found
335 else: # no tag found
336 ltagcmd = ['parents', '--template', '{latesttag}']
336 ltagcmd = ['parents', '--template', '{latesttag}']
337 ltag = sysstr(hg.run(ltagcmd))
337 ltag = sysstr(hg.run(ltagcmd))
338 changessincecmd = ['log', '-T', 'x\n', '-r', "only(.,'%s')" % ltag]
338 changessincecmd = ['log', '-T', 'x\n', '-r', "only(.,'%s')" % ltag]
339 changessince = len(hg.run(changessincecmd).splitlines())
339 changessince = len(hg.run(changessincecmd).splitlines())
340 version = '%s+%s-%s' % (ltag, changessince, hgid)
340 version = '%s+%s-%s' % (ltag, changessince, hgid)
341 if version.endswith('+'):
341 if version.endswith('+'):
342 version += time.strftime('%Y%m%d')
342 version += time.strftime('%Y%m%d')
343 elif os.path.exists('.hg_archival.txt'):
343 elif os.path.exists('.hg_archival.txt'):
344 kw = dict([[t.strip() for t in l.split(':', 1)]
344 kw = dict([[t.strip() for t in l.split(':', 1)]
345 for l in open('.hg_archival.txt')])
345 for l in open('.hg_archival.txt')])
346 if 'tag' in kw:
346 if 'tag' in kw:
347 version = kw['tag']
347 version = kw['tag']
348 elif 'latesttag' in kw:
348 elif 'latesttag' in kw:
349 if 'changessincelatesttag' in kw:
349 if 'changessincelatesttag' in kw:
350 version = '%(latesttag)s+%(changessincelatesttag)s-%(node).12s' % kw
350 version = '%(latesttag)s+%(changessincelatesttag)s-%(node).12s' % kw
351 else:
351 else:
352 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
352 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
353 else:
353 else:
354 version = kw.get('node', '')[:12]
354 version = kw.get('node', '')[:12]
355
355
356 if version:
356 if version:
357 versionb = version
357 versionb = version
358 if not isinstance(versionb, bytes):
358 if not isinstance(versionb, bytes):
359 versionb = versionb.encode('ascii')
359 versionb = versionb.encode('ascii')
360
360
361 write_if_changed('mercurial/__version__.py', b''.join([
361 write_if_changed('mercurial/__version__.py', b''.join([
362 b'# this file is autogenerated by setup.py\n'
362 b'# this file is autogenerated by setup.py\n'
363 b'version = "%s"\n' % versionb,
363 b'version = "%s"\n' % versionb,
364 ]))
364 ]))
365
365
366 try:
366 try:
367 oldpolicy = os.environ.get('HGMODULEPOLICY', None)
367 oldpolicy = os.environ.get('HGMODULEPOLICY', None)
368 os.environ['HGMODULEPOLICY'] = 'py'
368 os.environ['HGMODULEPOLICY'] = 'py'
369 from mercurial import __version__
369 from mercurial import __version__
370 version = __version__.version
370 version = __version__.version
371 except ImportError:
371 except ImportError:
372 version = 'unknown'
372 version = 'unknown'
373 finally:
373 finally:
374 if oldpolicy is None:
374 if oldpolicy is None:
375 del os.environ['HGMODULEPOLICY']
375 del os.environ['HGMODULEPOLICY']
376 else:
376 else:
377 os.environ['HGMODULEPOLICY'] = oldpolicy
377 os.environ['HGMODULEPOLICY'] = oldpolicy
378
378
379 class hgbuild(build):
379 class hgbuild(build):
380 # Insert hgbuildmo first so that files in mercurial/locale/ are found
380 # Insert hgbuildmo first so that files in mercurial/locale/ are found
381 # when build_py is run next.
381 # when build_py is run next.
382 sub_commands = [('build_mo', None)] + build.sub_commands
382 sub_commands = [('build_mo', None)] + build.sub_commands
383
383
384 class hgbuildmo(build):
384 class hgbuildmo(build):
385
385
386 description = "build translations (.mo files)"
386 description = "build translations (.mo files)"
387
387
388 def run(self):
388 def run(self):
389 if not find_executable('msgfmt'):
389 if not find_executable('msgfmt'):
390 self.warn("could not find msgfmt executable, no translations "
390 self.warn("could not find msgfmt executable, no translations "
391 "will be built")
391 "will be built")
392 return
392 return
393
393
394 podir = 'i18n'
394 podir = 'i18n'
395 if not os.path.isdir(podir):
395 if not os.path.isdir(podir):
396 self.warn("could not find %s/ directory" % podir)
396 self.warn("could not find %s/ directory" % podir)
397 return
397 return
398
398
399 join = os.path.join
399 join = os.path.join
400 for po in os.listdir(podir):
400 for po in os.listdir(podir):
401 if not po.endswith('.po'):
401 if not po.endswith('.po'):
402 continue
402 continue
403 pofile = join(podir, po)
403 pofile = join(podir, po)
404 modir = join('locale', po[:-3], 'LC_MESSAGES')
404 modir = join('locale', po[:-3], 'LC_MESSAGES')
405 mofile = join(modir, 'hg.mo')
405 mofile = join(modir, 'hg.mo')
406 mobuildfile = join('mercurial', mofile)
406 mobuildfile = join('mercurial', mofile)
407 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
407 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
408 if sys.platform != 'sunos5':
408 if sys.platform != 'sunos5':
409 # msgfmt on Solaris does not know about -c
409 # msgfmt on Solaris does not know about -c
410 cmd.append('-c')
410 cmd.append('-c')
411 self.mkpath(join('mercurial', modir))
411 self.mkpath(join('mercurial', modir))
412 self.make_file([pofile], mobuildfile, spawn, (cmd,))
412 self.make_file([pofile], mobuildfile, spawn, (cmd,))
413
413
414
414
415 class hgdist(Distribution):
415 class hgdist(Distribution):
416 pure = False
416 pure = False
417 cffi = ispypy
417 cffi = ispypy
418
418
419 global_options = Distribution.global_options + \
419 global_options = Distribution.global_options + \
420 [('pure', None, "use pure (slow) Python "
420 [('pure', None, "use pure (slow) Python "
421 "code instead of C extensions"),
421 "code instead of C extensions"),
422 ]
422 ]
423
423
424 def has_ext_modules(self):
424 def has_ext_modules(self):
425 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
425 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
426 # too late for some cases
426 # too late for some cases
427 return not self.pure and Distribution.has_ext_modules(self)
427 return not self.pure and Distribution.has_ext_modules(self)
428
428
429 # This is ugly as a one-liner. So use a variable.
429 # This is ugly as a one-liner. So use a variable.
430 buildextnegops = dict(getattr(build_ext, 'negative_options', {}))
430 buildextnegops = dict(getattr(build_ext, 'negative_options', {}))
431 buildextnegops['no-zstd'] = 'zstd'
431 buildextnegops['no-zstd'] = 'zstd'
432
432
433 class hgbuildext(build_ext):
433 class hgbuildext(build_ext):
434 user_options = build_ext.user_options + [
434 user_options = build_ext.user_options + [
435 ('zstd', None, 'compile zstd bindings [default]'),
435 ('zstd', None, 'compile zstd bindings [default]'),
436 ('no-zstd', None, 'do not compile zstd bindings'),
436 ('no-zstd', None, 'do not compile zstd bindings'),
437 ]
437 ]
438
438
439 boolean_options = build_ext.boolean_options + ['zstd']
439 boolean_options = build_ext.boolean_options + ['zstd']
440 negative_opt = buildextnegops
440 negative_opt = buildextnegops
441
441
442 def initialize_options(self):
442 def initialize_options(self):
443 self.zstd = True
443 self.zstd = True
444 return build_ext.initialize_options(self)
444 return build_ext.initialize_options(self)
445
445
446 def build_extensions(self):
446 def build_extensions(self):
447 # Filter out zstd if disabled via argument.
447 # Filter out zstd if disabled via argument.
448 if not self.zstd:
448 if not self.zstd:
449 self.extensions = [e for e in self.extensions
449 self.extensions = [e for e in self.extensions
450 if e.name != 'mercurial.zstd']
450 if e.name != 'mercurial.zstd']
451
451
452 return build_ext.build_extensions(self)
452 return build_ext.build_extensions(self)
453
453
454 def build_extension(self, ext):
454 def build_extension(self, ext):
455 try:
455 try:
456 build_ext.build_extension(self, ext)
456 build_ext.build_extension(self, ext)
457 except CCompilerError:
457 except CCompilerError:
458 if not getattr(ext, 'optional', False):
458 if not getattr(ext, 'optional', False):
459 raise
459 raise
460 log.warn("Failed to build optional extension '%s' (skipping)",
460 log.warn("Failed to build optional extension '%s' (skipping)",
461 ext.name)
461 ext.name)
462
462
463 class hgbuildscripts(build_scripts):
463 class hgbuildscripts(build_scripts):
464 def run(self):
464 def run(self):
465 if os.name != 'nt' or self.distribution.pure:
465 if os.name != 'nt' or self.distribution.pure:
466 return build_scripts.run(self)
466 return build_scripts.run(self)
467
467
468 exebuilt = False
468 exebuilt = False
469 try:
469 try:
470 self.run_command('build_hgexe')
470 self.run_command('build_hgexe')
471 exebuilt = True
471 exebuilt = True
472 except (DistutilsError, CCompilerError):
472 except (DistutilsError, CCompilerError):
473 log.warn('failed to build optional hg.exe')
473 log.warn('failed to build optional hg.exe')
474
474
475 if exebuilt:
475 if exebuilt:
476 # Copying hg.exe to the scripts build directory ensures it is
476 # Copying hg.exe to the scripts build directory ensures it is
477 # installed by the install_scripts command.
477 # installed by the install_scripts command.
478 hgexecommand = self.get_finalized_command('build_hgexe')
478 hgexecommand = self.get_finalized_command('build_hgexe')
479 dest = os.path.join(self.build_dir, 'hg.exe')
479 dest = os.path.join(self.build_dir, 'hg.exe')
480 self.mkpath(self.build_dir)
480 self.mkpath(self.build_dir)
481 self.copy_file(hgexecommand.hgexepath, dest)
481 self.copy_file(hgexecommand.hgexepath, dest)
482
482
483 # Remove hg.bat because it is redundant with hg.exe.
483 # Remove hg.bat because it is redundant with hg.exe.
484 self.scripts.remove('contrib/win32/hg.bat')
484 self.scripts.remove('contrib/win32/hg.bat')
485
485
486 return build_scripts.run(self)
486 return build_scripts.run(self)
487
487
488 class hgbuildpy(build_py):
488 class hgbuildpy(build_py):
489 def finalize_options(self):
489 def finalize_options(self):
490 build_py.finalize_options(self)
490 build_py.finalize_options(self)
491
491
492 if self.distribution.pure:
492 if self.distribution.pure:
493 self.distribution.ext_modules = []
493 self.distribution.ext_modules = []
494 elif self.distribution.cffi:
494 elif self.distribution.cffi:
495 from mercurial.cffi import (
495 from mercurial.cffi import (
496 bdiffbuild,
496 bdiffbuild,
497 mpatchbuild,
497 mpatchbuild,
498 )
498 )
499 exts = [mpatchbuild.ffi.distutils_extension(),
499 exts = [mpatchbuild.ffi.distutils_extension(),
500 bdiffbuild.ffi.distutils_extension()]
500 bdiffbuild.ffi.distutils_extension()]
501 # cffi modules go here
501 # cffi modules go here
502 if sys.platform == 'darwin':
502 if sys.platform == 'darwin':
503 from mercurial.cffi import osutilbuild
503 from mercurial.cffi import osutilbuild
504 exts.append(osutilbuild.ffi.distutils_extension())
504 exts.append(osutilbuild.ffi.distutils_extension())
505 self.distribution.ext_modules = exts
505 self.distribution.ext_modules = exts
506 else:
506 else:
507 h = os.path.join(get_python_inc(), 'Python.h')
507 h = os.path.join(get_python_inc(), 'Python.h')
508 if not os.path.exists(h):
508 if not os.path.exists(h):
509 raise SystemExit('Python headers are required to build '
509 raise SystemExit('Python headers are required to build '
510 'Mercurial but weren\'t found in %s' % h)
510 'Mercurial but weren\'t found in %s' % h)
511
511
512 def run(self):
512 def run(self):
513 basepath = os.path.join(self.build_lib, 'mercurial')
513 basepath = os.path.join(self.build_lib, 'mercurial')
514 self.mkpath(basepath)
514 self.mkpath(basepath)
515
515
516 if self.distribution.pure:
516 if self.distribution.pure:
517 modulepolicy = 'py'
517 modulepolicy = 'py'
518 elif self.build_lib == '.':
518 elif self.build_lib == '.':
519 # in-place build should run without rebuilding C extensions
519 # in-place build should run without rebuilding C extensions
520 modulepolicy = 'allow'
520 modulepolicy = 'allow'
521 else:
521 else:
522 modulepolicy = 'c'
522 modulepolicy = 'c'
523
523
524 content = b''.join([
524 content = b''.join([
525 b'# this file is autogenerated by setup.py\n',
525 b'# this file is autogenerated by setup.py\n',
526 b'modulepolicy = b"%s"\n' % modulepolicy.encode('ascii'),
526 b'modulepolicy = b"%s"\n' % modulepolicy.encode('ascii'),
527 ])
527 ])
528 write_if_changed(os.path.join(basepath, '__modulepolicy__.py'),
528 write_if_changed(os.path.join(basepath, '__modulepolicy__.py'),
529 content)
529 content)
530
530
531 build_py.run(self)
531 build_py.run(self)
532
532
533 class buildhgextindex(Command):
533 class buildhgextindex(Command):
534 description = 'generate prebuilt index of hgext (for frozen package)'
534 description = 'generate prebuilt index of hgext (for frozen package)'
535 user_options = []
535 user_options = []
536 _indexfilename = 'hgext/__index__.py'
536 _indexfilename = 'hgext/__index__.py'
537
537
538 def initialize_options(self):
538 def initialize_options(self):
539 pass
539 pass
540
540
541 def finalize_options(self):
541 def finalize_options(self):
542 pass
542 pass
543
543
544 def run(self):
544 def run(self):
545 if os.path.exists(self._indexfilename):
545 if os.path.exists(self._indexfilename):
546 with open(self._indexfilename, 'w') as f:
546 with open(self._indexfilename, 'w') as f:
547 f.write('# empty\n')
547 f.write('# empty\n')
548
548
549 # here no extension enabled, disabled() lists up everything
549 # here no extension enabled, disabled() lists up everything
550 code = ('import pprint; from mercurial import extensions; '
550 code = ('import pprint; from mercurial import extensions; '
551 'pprint.pprint(extensions.disabled())')
551 'pprint.pprint(extensions.disabled())')
552 returncode, out, err = runcmd([sys.executable, '-c', code],
552 returncode, out, err = runcmd([sys.executable, '-c', code],
553 localhgenv())
553 localhgenv())
554 if err or returncode != 0:
554 if err or returncode != 0:
555 raise DistutilsExecError(err)
555 raise DistutilsExecError(err)
556
556
557 with open(self._indexfilename, 'w') as f:
557 with open(self._indexfilename, 'w') as f:
558 f.write('# this file is autogenerated by setup.py\n')
558 f.write('# this file is autogenerated by setup.py\n')
559 f.write('docs = ')
559 f.write('docs = ')
560 f.write(out)
560 f.write(out)
561
561
562 class buildhgexe(build_ext):
562 class buildhgexe(build_ext):
563 description = 'compile hg.exe from mercurial/exewrapper.c'
563 description = 'compile hg.exe from mercurial/exewrapper.c'
564 user_options = build_ext.user_options + [
564 user_options = build_ext.user_options + [
565 ('long-paths-support', None, 'enable support for long paths on '
565 ('long-paths-support', None, 'enable support for long paths on '
566 'Windows (off by default and '
566 'Windows (off by default and '
567 'experimental)'),
567 'experimental)'),
568 ]
568 ]
569
569
570 LONG_PATHS_MANIFEST = """
570 LONG_PATHS_MANIFEST = """
571 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
571 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
572 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
572 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
573 <application>
573 <application>
574 <windowsSettings
574 <windowsSettings
575 xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
575 xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
576 <ws2:longPathAware>true</ws2:longPathAware>
576 <ws2:longPathAware>true</ws2:longPathAware>
577 </windowsSettings>
577 </windowsSettings>
578 </application>
578 </application>
579 </assembly>"""
579 </assembly>"""
580
580
581 def initialize_options(self):
581 def initialize_options(self):
582 build_ext.initialize_options(self)
582 build_ext.initialize_options(self)
583 self.long_paths_support = False
583 self.long_paths_support = False
584
584
585 def build_extensions(self):
585 def build_extensions(self):
586 if os.name != 'nt':
586 if os.name != 'nt':
587 return
587 return
588 if isinstance(self.compiler, HackedMingw32CCompiler):
588 if isinstance(self.compiler, HackedMingw32CCompiler):
589 self.compiler.compiler_so = self.compiler.compiler # no -mdll
589 self.compiler.compiler_so = self.compiler.compiler # no -mdll
590 self.compiler.dll_libraries = [] # no -lmsrvc90
590 self.compiler.dll_libraries = [] # no -lmsrvc90
591
591
592 # Different Python installs can have different Python library
592 # Different Python installs can have different Python library
593 # names. e.g. the official CPython distribution uses pythonXY.dll
593 # names. e.g. the official CPython distribution uses pythonXY.dll
594 # and MinGW uses libpythonX.Y.dll.
594 # and MinGW uses libpythonX.Y.dll.
595 _kernel32 = ctypes.windll.kernel32
595 _kernel32 = ctypes.windll.kernel32
596 _kernel32.GetModuleFileNameA.argtypes = [ctypes.c_void_p,
596 _kernel32.GetModuleFileNameA.argtypes = [ctypes.c_void_p,
597 ctypes.c_void_p,
597 ctypes.c_void_p,
598 ctypes.c_ulong]
598 ctypes.c_ulong]
599 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
599 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
600 size = 1000
600 size = 1000
601 buf = ctypes.create_string_buffer(size + 1)
601 buf = ctypes.create_string_buffer(size + 1)
602 filelen = _kernel32.GetModuleFileNameA(sys.dllhandle, ctypes.byref(buf),
602 filelen = _kernel32.GetModuleFileNameA(sys.dllhandle, ctypes.byref(buf),
603 size)
603 size)
604
604
605 if filelen > 0 and filelen != size:
605 if filelen > 0 and filelen != size:
606 dllbasename = os.path.basename(buf.value)
606 dllbasename = os.path.basename(buf.value)
607 if not dllbasename.lower().endswith('.dll'):
607 if not dllbasename.lower().endswith('.dll'):
608 raise SystemExit('Python DLL does not end with .dll: %s' %
608 raise SystemExit('Python DLL does not end with .dll: %s' %
609 dllbasename)
609 dllbasename)
610 pythonlib = dllbasename[:-4]
610 pythonlib = dllbasename[:-4]
611 else:
611 else:
612 log.warn('could not determine Python DLL filename; '
612 log.warn('could not determine Python DLL filename; '
613 'assuming pythonXY')
613 'assuming pythonXY')
614
614
615 hv = sys.hexversion
615 hv = sys.hexversion
616 pythonlib = 'python%d%d' % (hv >> 24, (hv >> 16) & 0xff)
616 pythonlib = 'python%d%d' % (hv >> 24, (hv >> 16) & 0xff)
617
617
618 log.info('using %s as Python library name' % pythonlib)
618 log.info('using %s as Python library name' % pythonlib)
619 with open('mercurial/hgpythonlib.h', 'wb') as f:
619 with open('mercurial/hgpythonlib.h', 'wb') as f:
620 f.write('/* this file is autogenerated by setup.py */\n')
620 f.write('/* this file is autogenerated by setup.py */\n')
621 f.write('#define HGPYTHONLIB "%s"\n' % pythonlib)
621 f.write('#define HGPYTHONLIB "%s"\n' % pythonlib)
622 objects = self.compiler.compile(['mercurial/exewrapper.c'],
622 objects = self.compiler.compile(['mercurial/exewrapper.c'],
623 output_dir=self.build_temp)
623 output_dir=self.build_temp)
624 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
624 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
625 self.hgtarget = os.path.join(dir, 'hg')
625 self.hgtarget = os.path.join(dir, 'hg')
626 self.compiler.link_executable(objects, self.hgtarget,
626 self.compiler.link_executable(objects, self.hgtarget,
627 libraries=[],
627 libraries=[],
628 output_dir=self.build_temp)
628 output_dir=self.build_temp)
629 if self.long_paths_support:
629 if self.long_paths_support:
630 self.addlongpathsmanifest()
630 self.addlongpathsmanifest()
631
631
632 def addlongpathsmanifest(self):
632 def addlongpathsmanifest(self):
633 """Add manifest pieces so that hg.exe understands long paths
633 """Add manifest pieces so that hg.exe understands long paths
634
634
635 This is an EXPERIMENTAL feature, use with care.
635 This is an EXPERIMENTAL feature, use with care.
636 To enable long paths support, one needs to do two things:
636 To enable long paths support, one needs to do two things:
637 - build Mercurial with --long-paths-support option
637 - build Mercurial with --long-paths-support option
638 - change HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\
638 - change HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\
639 LongPathsEnabled to have value 1.
639 LongPathsEnabled to have value 1.
640
640
641 Please ignore 'warning 81010002: Unrecognized Element "longPathAware"';
641 Please ignore 'warning 81010002: Unrecognized Element "longPathAware"';
642 it happens because Mercurial uses mt.exe circa 2008, which is not
642 it happens because Mercurial uses mt.exe circa 2008, which is not
643 yet aware of long paths support in the manifest (I think so at least).
643 yet aware of long paths support in the manifest (I think so at least).
644 This does not stop mt.exe from embedding/merging the XML properly.
644 This does not stop mt.exe from embedding/merging the XML properly.
645
645
646 Why resource #1 should be used for .exe manifests? I don't know and
646 Why resource #1 should be used for .exe manifests? I don't know and
647 wasn't able to find an explanation for mortals. But it seems to work.
647 wasn't able to find an explanation for mortals. But it seems to work.
648 """
648 """
649 exefname = self.compiler.executable_filename(self.hgtarget)
649 exefname = self.compiler.executable_filename(self.hgtarget)
650 fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
650 fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
651 os.close(fdauto)
651 os.close(fdauto)
652 with open(manfname, 'w') as f:
652 with open(manfname, 'w') as f:
653 f.write(self.LONG_PATHS_MANIFEST)
653 f.write(self.LONG_PATHS_MANIFEST)
654 log.info("long paths manifest is written to '%s'" % manfname)
654 log.info("long paths manifest is written to '%s'" % manfname)
655 inputresource = '-inputresource:%s;#1' % exefname
655 inputresource = '-inputresource:%s;#1' % exefname
656 outputresource = '-outputresource:%s;#1' % exefname
656 outputresource = '-outputresource:%s;#1' % exefname
657 log.info("running mt.exe to update hg.exe's manifest in-place")
657 log.info("running mt.exe to update hg.exe's manifest in-place")
658 # supplying both -manifest and -inputresource to mt.exe makes
658 # supplying both -manifest and -inputresource to mt.exe makes
659 # it merge the embedded and supplied manifests in the -outputresource
659 # it merge the embedded and supplied manifests in the -outputresource
660 self.spawn(['mt.exe', '-nologo', '-manifest', manfname,
660 self.spawn(['mt.exe', '-nologo', '-manifest', manfname,
661 inputresource, outputresource])
661 inputresource, outputresource])
662 log.info("done updating hg.exe's manifest")
662 log.info("done updating hg.exe's manifest")
663 os.remove(manfname)
663 os.remove(manfname)
664
664
665 @property
665 @property
666 def hgexepath(self):
666 def hgexepath(self):
667 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
667 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
668 return os.path.join(self.build_temp, dir, 'hg.exe')
668 return os.path.join(self.build_temp, dir, 'hg.exe')
669
669
670 class hginstall(install):
670 class hginstall(install):
671
671
672 user_options = install.user_options + [
672 user_options = install.user_options + [
673 ('old-and-unmanageable', None,
673 ('old-and-unmanageable', None,
674 'noop, present for eggless setuptools compat'),
674 'noop, present for eggless setuptools compat'),
675 ('single-version-externally-managed', None,
675 ('single-version-externally-managed', None,
676 'noop, present for eggless setuptools compat'),
676 'noop, present for eggless setuptools compat'),
677 ]
677 ]
678
678
679 # Also helps setuptools not be sad while we refuse to create eggs.
679 # Also helps setuptools not be sad while we refuse to create eggs.
680 single_version_externally_managed = True
680 single_version_externally_managed = True
681
681
682 def get_sub_commands(self):
682 def get_sub_commands(self):
683 # Screen out egg related commands to prevent egg generation. But allow
683 # Screen out egg related commands to prevent egg generation. But allow
684 # mercurial.egg-info generation, since that is part of modern
684 # mercurial.egg-info generation, since that is part of modern
685 # packaging.
685 # packaging.
686 excl = set(['bdist_egg'])
686 excl = set(['bdist_egg'])
687 return filter(lambda x: x not in excl, install.get_sub_commands(self))
687 return filter(lambda x: x not in excl, install.get_sub_commands(self))
688
688
689 class hginstalllib(install_lib):
689 class hginstalllib(install_lib):
690 '''
690 '''
691 This is a specialization of install_lib that replaces the copy_file used
691 This is a specialization of install_lib that replaces the copy_file used
692 there so that it supports setting the mode of files after copying them,
692 there so that it supports setting the mode of files after copying them,
693 instead of just preserving the mode that the files originally had. If your
693 instead of just preserving the mode that the files originally had. If your
694 system has a umask of something like 027, preserving the permissions when
694 system has a umask of something like 027, preserving the permissions when
695 copying will lead to a broken install.
695 copying will lead to a broken install.
696
696
697 Note that just passing keep_permissions=False to copy_file would be
697 Note that just passing keep_permissions=False to copy_file would be
698 insufficient, as it might still be applying a umask.
698 insufficient, as it might still be applying a umask.
699 '''
699 '''
700
700
701 def run(self):
701 def run(self):
702 realcopyfile = file_util.copy_file
702 realcopyfile = file_util.copy_file
703 def copyfileandsetmode(*args, **kwargs):
703 def copyfileandsetmode(*args, **kwargs):
704 src, dst = args[0], args[1]
704 src, dst = args[0], args[1]
705 dst, copied = realcopyfile(*args, **kwargs)
705 dst, copied = realcopyfile(*args, **kwargs)
706 if copied:
706 if copied:
707 st = os.stat(src)
707 st = os.stat(src)
708 # Persist executable bit (apply it to group and other if user
708 # Persist executable bit (apply it to group and other if user
709 # has it)
709 # has it)
710 if st[stat.ST_MODE] & stat.S_IXUSR:
710 if st[stat.ST_MODE] & stat.S_IXUSR:
711 setmode = int('0755', 8)
711 setmode = int('0755', 8)
712 else:
712 else:
713 setmode = int('0644', 8)
713 setmode = int('0644', 8)
714 m = stat.S_IMODE(st[stat.ST_MODE])
714 m = stat.S_IMODE(st[stat.ST_MODE])
715 m = (m & ~int('0777', 8)) | setmode
715 m = (m & ~int('0777', 8)) | setmode
716 os.chmod(dst, m)
716 os.chmod(dst, m)
717 file_util.copy_file = copyfileandsetmode
717 file_util.copy_file = copyfileandsetmode
718 try:
718 try:
719 install_lib.run(self)
719 install_lib.run(self)
720 finally:
720 finally:
721 file_util.copy_file = realcopyfile
721 file_util.copy_file = realcopyfile
722
722
723 class hginstallscripts(install_scripts):
723 class hginstallscripts(install_scripts):
724 '''
724 '''
725 This is a specialization of install_scripts that replaces the @LIBDIR@ with
725 This is a specialization of install_scripts that replaces the @LIBDIR@ with
726 the configured directory for modules. If possible, the path is made relative
726 the configured directory for modules. If possible, the path is made relative
727 to the directory for scripts.
727 to the directory for scripts.
728 '''
728 '''
729
729
730 def initialize_options(self):
730 def initialize_options(self):
731 install_scripts.initialize_options(self)
731 install_scripts.initialize_options(self)
732
732
733 self.install_lib = None
733 self.install_lib = None
734
734
735 def finalize_options(self):
735 def finalize_options(self):
736 install_scripts.finalize_options(self)
736 install_scripts.finalize_options(self)
737 self.set_undefined_options('install',
737 self.set_undefined_options('install',
738 ('install_lib', 'install_lib'))
738 ('install_lib', 'install_lib'))
739
739
740 def run(self):
740 def run(self):
741 install_scripts.run(self)
741 install_scripts.run(self)
742
742
743 # It only makes sense to replace @LIBDIR@ with the install path if
743 # It only makes sense to replace @LIBDIR@ with the install path if
744 # the install path is known. For wheels, the logic below calculates
744 # the install path is known. For wheels, the logic below calculates
745 # the libdir to be "../..". This is because the internal layout of a
745 # the libdir to be "../..". This is because the internal layout of a
746 # wheel archive looks like:
746 # wheel archive looks like:
747 #
747 #
748 # mercurial-3.6.1.data/scripts/hg
748 # mercurial-3.6.1.data/scripts/hg
749 # mercurial/__init__.py
749 # mercurial/__init__.py
750 #
750 #
751 # When installing wheels, the subdirectories of the "<pkg>.data"
751 # When installing wheels, the subdirectories of the "<pkg>.data"
752 # directory are translated to system local paths and files therein
752 # directory are translated to system local paths and files therein
753 # are copied in place. The mercurial/* files are installed into the
753 # are copied in place. The mercurial/* files are installed into the
754 # site-packages directory. However, the site-packages directory
754 # site-packages directory. However, the site-packages directory
755 # isn't known until wheel install time. This means we have no clue
755 # isn't known until wheel install time. This means we have no clue
756 # at wheel generation time what the installed site-packages directory
756 # at wheel generation time what the installed site-packages directory
757 # will be. And, wheels don't appear to provide the ability to register
757 # will be. And, wheels don't appear to provide the ability to register
758 # custom code to run during wheel installation. This all means that
758 # custom code to run during wheel installation. This all means that
759 # we can't reliably set the libdir in wheels: the default behavior
759 # we can't reliably set the libdir in wheels: the default behavior
760 # of looking in sys.path must do.
760 # of looking in sys.path must do.
761
761
762 if (os.path.splitdrive(self.install_dir)[0] !=
762 if (os.path.splitdrive(self.install_dir)[0] !=
763 os.path.splitdrive(self.install_lib)[0]):
763 os.path.splitdrive(self.install_lib)[0]):
764 # can't make relative paths from one drive to another, so use an
764 # can't make relative paths from one drive to another, so use an
765 # absolute path instead
765 # absolute path instead
766 libdir = self.install_lib
766 libdir = self.install_lib
767 else:
767 else:
768 common = os.path.commonprefix((self.install_dir, self.install_lib))
768 common = os.path.commonprefix((self.install_dir, self.install_lib))
769 rest = self.install_dir[len(common):]
769 rest = self.install_dir[len(common):]
770 uplevel = len([n for n in os.path.split(rest) if n])
770 uplevel = len([n for n in os.path.split(rest) if n])
771
771
772 libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common):]
772 libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common):]
773
773
774 for outfile in self.outfiles:
774 for outfile in self.outfiles:
775 with open(outfile, 'rb') as fp:
775 with open(outfile, 'rb') as fp:
776 data = fp.read()
776 data = fp.read()
777
777
778 # skip binary files
778 # skip binary files
779 if b'\0' in data:
779 if b'\0' in data:
780 continue
780 continue
781
781
782 # During local installs, the shebang will be rewritten to the final
782 # During local installs, the shebang will be rewritten to the final
783 # install path. During wheel packaging, the shebang has a special
783 # install path. During wheel packaging, the shebang has a special
784 # value.
784 # value.
785 if data.startswith(b'#!python'):
785 if data.startswith(b'#!python'):
786 log.info('not rewriting @LIBDIR@ in %s because install path '
786 log.info('not rewriting @LIBDIR@ in %s because install path '
787 'not known' % outfile)
787 'not known' % outfile)
788 continue
788 continue
789
789
790 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
790 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
791 with open(outfile, 'wb') as fp:
791 with open(outfile, 'wb') as fp:
792 fp.write(data)
792 fp.write(data)
793
793
794 cmdclass = {'build': hgbuild,
794 cmdclass = {'build': hgbuild,
795 'build_mo': hgbuildmo,
795 'build_mo': hgbuildmo,
796 'build_ext': hgbuildext,
796 'build_ext': hgbuildext,
797 'build_py': hgbuildpy,
797 'build_py': hgbuildpy,
798 'build_scripts': hgbuildscripts,
798 'build_scripts': hgbuildscripts,
799 'build_hgextindex': buildhgextindex,
799 'build_hgextindex': buildhgextindex,
800 'install': hginstall,
800 'install': hginstall,
801 'install_lib': hginstalllib,
801 'install_lib': hginstalllib,
802 'install_scripts': hginstallscripts,
802 'install_scripts': hginstallscripts,
803 'build_hgexe': buildhgexe,
803 'build_hgexe': buildhgexe,
804 }
804 }
805
805
806 packages = ['mercurial',
806 packages = ['mercurial',
807 'mercurial.cext',
807 'mercurial.cext',
808 'mercurial.cffi',
808 'mercurial.cffi',
809 'mercurial.hgweb',
809 'mercurial.hgweb',
810 'mercurial.pure',
810 'mercurial.pure',
811 'mercurial.thirdparty',
811 'mercurial.thirdparty',
812 'mercurial.thirdparty.attr',
812 'mercurial.thirdparty.attr',
813 'mercurial.thirdparty.cbor',
813 'mercurial.thirdparty.cbor',
814 'mercurial.thirdparty.cbor.cbor2',
814 'mercurial.thirdparty.cbor.cbor2',
815 'mercurial.thirdparty.zope',
816 'mercurial.thirdparty.zope.interface',
815 'mercurial.utils',
817 'mercurial.utils',
816 'hgext', 'hgext.convert', 'hgext.fsmonitor',
818 'hgext', 'hgext.convert', 'hgext.fsmonitor',
817 'hgext.fsmonitor.pywatchman', 'hgext.highlight',
819 'hgext.fsmonitor.pywatchman', 'hgext.highlight',
818 'hgext.largefiles', 'hgext.lfs', 'hgext.narrow',
820 'hgext.largefiles', 'hgext.lfs', 'hgext.narrow',
819 'hgext.zeroconf', 'hgext3rd',
821 'hgext.zeroconf', 'hgext3rd',
820 'hgdemandimport']
822 'hgdemandimport']
821
823
822 common_depends = ['mercurial/bitmanipulation.h',
824 common_depends = ['mercurial/bitmanipulation.h',
823 'mercurial/compat.h',
825 'mercurial/compat.h',
824 'mercurial/cext/util.h']
826 'mercurial/cext/util.h']
825 common_include_dirs = ['mercurial']
827 common_include_dirs = ['mercurial']
826
828
827 osutil_cflags = []
829 osutil_cflags = []
828 osutil_ldflags = []
830 osutil_ldflags = []
829
831
830 # platform specific macros
832 # platform specific macros
831 for plat, func in [('bsd', 'setproctitle')]:
833 for plat, func in [('bsd', 'setproctitle')]:
832 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
834 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
833 osutil_cflags.append('-DHAVE_%s' % func.upper())
835 osutil_cflags.append('-DHAVE_%s' % func.upper())
834
836
835 for plat, macro, code in [
837 for plat, macro, code in [
836 ('bsd|darwin', 'BSD_STATFS', '''
838 ('bsd|darwin', 'BSD_STATFS', '''
837 #include <sys/param.h>
839 #include <sys/param.h>
838 #include <sys/mount.h>
840 #include <sys/mount.h>
839 int main() { struct statfs s; return sizeof(s.f_fstypename); }
841 int main() { struct statfs s; return sizeof(s.f_fstypename); }
840 '''),
842 '''),
841 ('linux', 'LINUX_STATFS', '''
843 ('linux', 'LINUX_STATFS', '''
842 #include <linux/magic.h>
844 #include <linux/magic.h>
843 #include <sys/vfs.h>
845 #include <sys/vfs.h>
844 int main() { struct statfs s; return sizeof(s.f_type); }
846 int main() { struct statfs s; return sizeof(s.f_type); }
845 '''),
847 '''),
846 ]:
848 ]:
847 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
849 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
848 osutil_cflags.append('-DHAVE_%s' % macro)
850 osutil_cflags.append('-DHAVE_%s' % macro)
849
851
850 if sys.platform == 'darwin':
852 if sys.platform == 'darwin':
851 osutil_ldflags += ['-framework', 'ApplicationServices']
853 osutil_ldflags += ['-framework', 'ApplicationServices']
852
854
853 xdiff_srcs = [
855 xdiff_srcs = [
854 'mercurial/thirdparty/xdiff/xdiffi.c',
856 'mercurial/thirdparty/xdiff/xdiffi.c',
855 'mercurial/thirdparty/xdiff/xprepare.c',
857 'mercurial/thirdparty/xdiff/xprepare.c',
856 'mercurial/thirdparty/xdiff/xutils.c',
858 'mercurial/thirdparty/xdiff/xutils.c',
857 ]
859 ]
858
860
859 xdiff_headers = [
861 xdiff_headers = [
860 'mercurial/thirdparty/xdiff/xdiff.h',
862 'mercurial/thirdparty/xdiff/xdiff.h',
861 'mercurial/thirdparty/xdiff/xdiffi.h',
863 'mercurial/thirdparty/xdiff/xdiffi.h',
862 'mercurial/thirdparty/xdiff/xinclude.h',
864 'mercurial/thirdparty/xdiff/xinclude.h',
863 'mercurial/thirdparty/xdiff/xmacros.h',
865 'mercurial/thirdparty/xdiff/xmacros.h',
864 'mercurial/thirdparty/xdiff/xprepare.h',
866 'mercurial/thirdparty/xdiff/xprepare.h',
865 'mercurial/thirdparty/xdiff/xtypes.h',
867 'mercurial/thirdparty/xdiff/xtypes.h',
866 'mercurial/thirdparty/xdiff/xutils.h',
868 'mercurial/thirdparty/xdiff/xutils.h',
867 ]
869 ]
868
870
869 extmodules = [
871 extmodules = [
870 Extension('mercurial.cext.base85', ['mercurial/cext/base85.c'],
872 Extension('mercurial.cext.base85', ['mercurial/cext/base85.c'],
871 include_dirs=common_include_dirs,
873 include_dirs=common_include_dirs,
872 depends=common_depends),
874 depends=common_depends),
873 Extension('mercurial.cext.bdiff', ['mercurial/bdiff.c',
875 Extension('mercurial.cext.bdiff', ['mercurial/bdiff.c',
874 'mercurial/cext/bdiff.c'] + xdiff_srcs,
876 'mercurial/cext/bdiff.c'] + xdiff_srcs,
875 include_dirs=common_include_dirs,
877 include_dirs=common_include_dirs,
876 depends=common_depends + ['mercurial/bdiff.h'] + xdiff_headers),
878 depends=common_depends + ['mercurial/bdiff.h'] + xdiff_headers),
877 Extension('mercurial.cext.diffhelpers', ['mercurial/cext/diffhelpers.c'],
879 Extension('mercurial.cext.diffhelpers', ['mercurial/cext/diffhelpers.c'],
878 include_dirs=common_include_dirs,
880 include_dirs=common_include_dirs,
879 depends=common_depends),
881 depends=common_depends),
880 Extension('mercurial.cext.mpatch', ['mercurial/mpatch.c',
882 Extension('mercurial.cext.mpatch', ['mercurial/mpatch.c',
881 'mercurial/cext/mpatch.c'],
883 'mercurial/cext/mpatch.c'],
882 include_dirs=common_include_dirs,
884 include_dirs=common_include_dirs,
883 depends=common_depends),
885 depends=common_depends),
884 Extension('mercurial.cext.parsers', ['mercurial/cext/charencode.c',
886 Extension('mercurial.cext.parsers', ['mercurial/cext/charencode.c',
885 'mercurial/cext/dirs.c',
887 'mercurial/cext/dirs.c',
886 'mercurial/cext/manifest.c',
888 'mercurial/cext/manifest.c',
887 'mercurial/cext/parsers.c',
889 'mercurial/cext/parsers.c',
888 'mercurial/cext/pathencode.c',
890 'mercurial/cext/pathencode.c',
889 'mercurial/cext/revlog.c'],
891 'mercurial/cext/revlog.c'],
890 include_dirs=common_include_dirs,
892 include_dirs=common_include_dirs,
891 depends=common_depends + ['mercurial/cext/charencode.h']),
893 depends=common_depends + ['mercurial/cext/charencode.h']),
892 Extension('mercurial.cext.osutil', ['mercurial/cext/osutil.c'],
894 Extension('mercurial.cext.osutil', ['mercurial/cext/osutil.c'],
893 include_dirs=common_include_dirs,
895 include_dirs=common_include_dirs,
894 extra_compile_args=osutil_cflags,
896 extra_compile_args=osutil_cflags,
895 extra_link_args=osutil_ldflags,
897 extra_link_args=osutil_ldflags,
896 depends=common_depends),
898 depends=common_depends),
899 Extension(
900 'mercurial.thirdparty.zope.interface._zope_interface_coptimizations', [
901 'mercurial/thirdparty/zope/interface/_zope_interface_coptimizations.c',
902 ]),
897 Extension('hgext.fsmonitor.pywatchman.bser',
903 Extension('hgext.fsmonitor.pywatchman.bser',
898 ['hgext/fsmonitor/pywatchman/bser.c']),
904 ['hgext/fsmonitor/pywatchman/bser.c']),
899 ]
905 ]
900
906
901 sys.path.insert(0, 'contrib/python-zstandard')
907 sys.path.insert(0, 'contrib/python-zstandard')
902 import setup_zstd
908 import setup_zstd
903 extmodules.append(setup_zstd.get_c_extension(name='mercurial.zstd'))
909 extmodules.append(setup_zstd.get_c_extension(name='mercurial.zstd'))
904
910
905 try:
911 try:
906 from distutils import cygwinccompiler
912 from distutils import cygwinccompiler
907
913
908 # the -mno-cygwin option has been deprecated for years
914 # the -mno-cygwin option has been deprecated for years
909 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
915 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
910
916
911 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
917 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
912 def __init__(self, *args, **kwargs):
918 def __init__(self, *args, **kwargs):
913 mingw32compilerclass.__init__(self, *args, **kwargs)
919 mingw32compilerclass.__init__(self, *args, **kwargs)
914 for i in 'compiler compiler_so linker_exe linker_so'.split():
920 for i in 'compiler compiler_so linker_exe linker_so'.split():
915 try:
921 try:
916 getattr(self, i).remove('-mno-cygwin')
922 getattr(self, i).remove('-mno-cygwin')
917 except ValueError:
923 except ValueError:
918 pass
924 pass
919
925
920 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
926 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
921 except ImportError:
927 except ImportError:
922 # the cygwinccompiler package is not available on some Python
928 # the cygwinccompiler package is not available on some Python
923 # distributions like the ones from the optware project for Synology
929 # distributions like the ones from the optware project for Synology
924 # DiskStation boxes
930 # DiskStation boxes
925 class HackedMingw32CCompiler(object):
931 class HackedMingw32CCompiler(object):
926 pass
932 pass
927
933
928 if os.name == 'nt':
934 if os.name == 'nt':
929 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
935 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
930 # extra_link_args to distutils.extensions.Extension() doesn't have any
936 # extra_link_args to distutils.extensions.Extension() doesn't have any
931 # effect.
937 # effect.
932 from distutils import msvccompiler
938 from distutils import msvccompiler
933
939
934 msvccompilerclass = msvccompiler.MSVCCompiler
940 msvccompilerclass = msvccompiler.MSVCCompiler
935
941
936 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
942 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
937 def initialize(self):
943 def initialize(self):
938 msvccompilerclass.initialize(self)
944 msvccompilerclass.initialize(self)
939 # "warning LNK4197: export 'func' specified multiple times"
945 # "warning LNK4197: export 'func' specified multiple times"
940 self.ldflags_shared.append('/ignore:4197')
946 self.ldflags_shared.append('/ignore:4197')
941 self.ldflags_shared_debug.append('/ignore:4197')
947 self.ldflags_shared_debug.append('/ignore:4197')
942
948
943 msvccompiler.MSVCCompiler = HackedMSVCCompiler
949 msvccompiler.MSVCCompiler = HackedMSVCCompiler
944
950
945 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
951 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
946 'help/*.txt',
952 'help/*.txt',
947 'help/internals/*.txt',
953 'help/internals/*.txt',
948 'default.d/*.rc',
954 'default.d/*.rc',
949 'dummycert.pem']}
955 'dummycert.pem']}
950
956
951 def ordinarypath(p):
957 def ordinarypath(p):
952 return p and p[0] != '.' and p[-1] != '~'
958 return p and p[0] != '.' and p[-1] != '~'
953
959
954 for root in ('templates',):
960 for root in ('templates',):
955 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
961 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
956 curdir = curdir.split(os.sep, 1)[1]
962 curdir = curdir.split(os.sep, 1)[1]
957 dirs[:] = filter(ordinarypath, dirs)
963 dirs[:] = filter(ordinarypath, dirs)
958 for f in filter(ordinarypath, files):
964 for f in filter(ordinarypath, files):
959 f = os.path.join(curdir, f)
965 f = os.path.join(curdir, f)
960 packagedata['mercurial'].append(f)
966 packagedata['mercurial'].append(f)
961
967
962 datafiles = []
968 datafiles = []
963
969
964 # distutils expects version to be str/unicode. Converting it to
970 # distutils expects version to be str/unicode. Converting it to
965 # unicode on Python 2 still works because it won't contain any
971 # unicode on Python 2 still works because it won't contain any
966 # non-ascii bytes and will be implicitly converted back to bytes
972 # non-ascii bytes and will be implicitly converted back to bytes
967 # when operated on.
973 # when operated on.
968 assert isinstance(version, bytes)
974 assert isinstance(version, bytes)
969 setupversion = version.decode('ascii')
975 setupversion = version.decode('ascii')
970
976
971 extra = {}
977 extra = {}
972
978
973 if issetuptools:
979 if issetuptools:
974 extra['python_requires'] = supportedpy
980 extra['python_requires'] = supportedpy
975 if py2exeloaded:
981 if py2exeloaded:
976 extra['console'] = [
982 extra['console'] = [
977 {'script':'hg',
983 {'script':'hg',
978 'copyright':'Copyright (C) 2005-2018 Matt Mackall and others',
984 'copyright':'Copyright (C) 2005-2018 Matt Mackall and others',
979 'product_version':version}]
985 'product_version':version}]
980 # sub command of 'build' because 'py2exe' does not handle sub_commands
986 # sub command of 'build' because 'py2exe' does not handle sub_commands
981 build.sub_commands.insert(0, ('build_hgextindex', None))
987 build.sub_commands.insert(0, ('build_hgextindex', None))
982 # put dlls in sub directory so that they won't pollute PATH
988 # put dlls in sub directory so that they won't pollute PATH
983 extra['zipfile'] = 'lib/library.zip'
989 extra['zipfile'] = 'lib/library.zip'
984
990
985 if os.name == 'nt':
991 if os.name == 'nt':
986 # Windows binary file versions for exe/dll files must have the
992 # Windows binary file versions for exe/dll files must have the
987 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
993 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
988 setupversion = version.split('+', 1)[0]
994 setupversion = version.split('+', 1)[0]
989
995
990 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
996 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
991 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
997 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
992 if version:
998 if version:
993 version = version[0]
999 version = version[0]
994 if sys.version_info[0] == 3:
1000 if sys.version_info[0] == 3:
995 version = version.decode('utf-8')
1001 version = version.decode('utf-8')
996 xcode4 = (version.startswith('Xcode') and
1002 xcode4 = (version.startswith('Xcode') and
997 StrictVersion(version.split()[1]) >= StrictVersion('4.0'))
1003 StrictVersion(version.split()[1]) >= StrictVersion('4.0'))
998 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
1004 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
999 else:
1005 else:
1000 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
1006 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
1001 # installed, but instead with only command-line tools. Assume
1007 # installed, but instead with only command-line tools. Assume
1002 # that only happens on >= Lion, thus no PPC support.
1008 # that only happens on >= Lion, thus no PPC support.
1003 xcode4 = True
1009 xcode4 = True
1004 xcode51 = False
1010 xcode51 = False
1005
1011
1006 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
1012 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
1007 # distutils.sysconfig
1013 # distutils.sysconfig
1008 if xcode4:
1014 if xcode4:
1009 os.environ['ARCHFLAGS'] = ''
1015 os.environ['ARCHFLAGS'] = ''
1010
1016
1011 # XCode 5.1 changes clang such that it now fails to compile if the
1017 # XCode 5.1 changes clang such that it now fails to compile if the
1012 # -mno-fused-madd flag is passed, but the version of Python shipped with
1018 # -mno-fused-madd flag is passed, but the version of Python shipped with
1013 # OS X 10.9 Mavericks includes this flag. This causes problems in all
1019 # OS X 10.9 Mavericks includes this flag. This causes problems in all
1014 # C extension modules, and a bug has been filed upstream at
1020 # C extension modules, and a bug has been filed upstream at
1015 # http://bugs.python.org/issue21244. We also need to patch this here
1021 # http://bugs.python.org/issue21244. We also need to patch this here
1016 # so Mercurial can continue to compile in the meantime.
1022 # so Mercurial can continue to compile in the meantime.
1017 if xcode51:
1023 if xcode51:
1018 cflags = get_config_var('CFLAGS')
1024 cflags = get_config_var('CFLAGS')
1019 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
1025 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
1020 os.environ['CFLAGS'] = (
1026 os.environ['CFLAGS'] = (
1021 os.environ.get('CFLAGS', '') + ' -Qunused-arguments')
1027 os.environ.get('CFLAGS', '') + ' -Qunused-arguments')
1022
1028
1023 setup(name='mercurial',
1029 setup(name='mercurial',
1024 version=setupversion,
1030 version=setupversion,
1025 author='Matt Mackall and many others',
1031 author='Matt Mackall and many others',
1026 author_email='mercurial@mercurial-scm.org',
1032 author_email='mercurial@mercurial-scm.org',
1027 url='https://mercurial-scm.org/',
1033 url='https://mercurial-scm.org/',
1028 download_url='https://mercurial-scm.org/release/',
1034 download_url='https://mercurial-scm.org/release/',
1029 description=('Fast scalable distributed SCM (revision control, version '
1035 description=('Fast scalable distributed SCM (revision control, version '
1030 'control) system'),
1036 'control) system'),
1031 long_description=('Mercurial is a distributed SCM tool written in Python.'
1037 long_description=('Mercurial is a distributed SCM tool written in Python.'
1032 ' It is used by a number of large projects that require'
1038 ' It is used by a number of large projects that require'
1033 ' fast, reliable distributed revision control, such as '
1039 ' fast, reliable distributed revision control, such as '
1034 'Mozilla.'),
1040 'Mozilla.'),
1035 license='GNU GPLv2 or any later version',
1041 license='GNU GPLv2 or any later version',
1036 classifiers=[
1042 classifiers=[
1037 'Development Status :: 6 - Mature',
1043 'Development Status :: 6 - Mature',
1038 'Environment :: Console',
1044 'Environment :: Console',
1039 'Intended Audience :: Developers',
1045 'Intended Audience :: Developers',
1040 'Intended Audience :: System Administrators',
1046 'Intended Audience :: System Administrators',
1041 'License :: OSI Approved :: GNU General Public License (GPL)',
1047 'License :: OSI Approved :: GNU General Public License (GPL)',
1042 'Natural Language :: Danish',
1048 'Natural Language :: Danish',
1043 'Natural Language :: English',
1049 'Natural Language :: English',
1044 'Natural Language :: German',
1050 'Natural Language :: German',
1045 'Natural Language :: Italian',
1051 'Natural Language :: Italian',
1046 'Natural Language :: Japanese',
1052 'Natural Language :: Japanese',
1047 'Natural Language :: Portuguese (Brazilian)',
1053 'Natural Language :: Portuguese (Brazilian)',
1048 'Operating System :: Microsoft :: Windows',
1054 'Operating System :: Microsoft :: Windows',
1049 'Operating System :: OS Independent',
1055 'Operating System :: OS Independent',
1050 'Operating System :: POSIX',
1056 'Operating System :: POSIX',
1051 'Programming Language :: C',
1057 'Programming Language :: C',
1052 'Programming Language :: Python',
1058 'Programming Language :: Python',
1053 'Topic :: Software Development :: Version Control',
1059 'Topic :: Software Development :: Version Control',
1054 ],
1060 ],
1055 scripts=scripts,
1061 scripts=scripts,
1056 packages=packages,
1062 packages=packages,
1057 ext_modules=extmodules,
1063 ext_modules=extmodules,
1058 data_files=datafiles,
1064 data_files=datafiles,
1059 package_data=packagedata,
1065 package_data=packagedata,
1060 cmdclass=cmdclass,
1066 cmdclass=cmdclass,
1061 distclass=hgdist,
1067 distclass=hgdist,
1062 options={'py2exe': {'packages': ['hgdemandimport', 'hgext', 'email',
1068 options={'py2exe': {'packages': ['hgdemandimport', 'hgext', 'email',
1063 # implicitly imported per module policy
1069 # implicitly imported per module policy
1064 # (cffi wouldn't be used as a frozen exe)
1070 # (cffi wouldn't be used as a frozen exe)
1065 'mercurial.cext',
1071 'mercurial.cext',
1066 #'mercurial.cffi',
1072 #'mercurial.cffi',
1067 'mercurial.pure']},
1073 'mercurial.pure']},
1068 'bdist_mpkg': {'zipdist': False,
1074 'bdist_mpkg': {'zipdist': False,
1069 'license': 'COPYING',
1075 'license': 'COPYING',
1070 'readme': 'contrib/macosx/Readme.html',
1076 'readme': 'contrib/macosx/Readme.html',
1071 'welcome': 'contrib/macosx/Welcome.html',
1077 'welcome': 'contrib/macosx/Welcome.html',
1072 },
1078 },
1073 },
1079 },
1074 **extra)
1080 **extra)
General Comments 0
You need to be logged in to leave comments. Login now