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