##// END OF EJS Templates
lint: fix failing code check...
Raphaël Gomès -
r47772:fceccc36 stable
parent child Browse files
Show More
@@ -1,1855 +1,1856 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 import os
7 7
8 8 # Mercurial will never work on Python 3 before 3.5 due to a lack
9 9 # of % formatting on bytestrings, and can't work on 3.6.0 or 3.6.1
10 10 # due to a bug in % formatting in bytestrings.
11 11 # We cannot support Python 3.5.0, 3.5.1, 3.5.2 because of bug in
12 12 # codecs.escape_encode() where it raises SystemError on empty bytestring
13 13 # bug link: https://bugs.python.org/issue25270
14 14 supportedpy = ','.join(
15 15 [
16 16 '>=2.7.4',
17 17 '!=3.0.*',
18 18 '!=3.1.*',
19 19 '!=3.2.*',
20 20 '!=3.3.*',
21 21 '!=3.4.*',
22 22 '!=3.5.0',
23 23 '!=3.5.1',
24 24 '!=3.5.2',
25 25 '!=3.6.0',
26 26 '!=3.6.1',
27 27 ]
28 28 )
29 29
30 30 import sys, platform
31 31 import sysconfig
32 32
33 33 if sys.version_info[0] >= 3:
34 34 printf = eval('print')
35 35 libdir_escape = 'unicode_escape'
36 36
37 37 def sysstr(s):
38 38 return s.decode('latin-1')
39 39
40 40
41 41 else:
42 42 libdir_escape = 'string_escape'
43 43
44 44 def printf(*args, **kwargs):
45 45 f = kwargs.get('file', sys.stdout)
46 46 end = kwargs.get('end', '\n')
47 47 f.write(b' '.join(args) + end)
48 48
49 49 def sysstr(s):
50 50 return s
51 51
52 52
53 53 # Attempt to guide users to a modern pip - this means that 2.6 users
54 54 # should have a chance of getting a 4.2 release, and when we ratchet
55 55 # the version requirement forward again hopefully everyone will get
56 56 # something that works for them.
57 57 if sys.version_info < (2, 7, 4, 'final'):
58 58 pip_message = (
59 59 'This may be due to an out of date pip. '
60 60 'Make sure you have pip >= 9.0.1.'
61 61 )
62 62 try:
63 63 import pip
64 64
65 65 pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
66 66 if pip_version < (9, 0, 1):
67 67 pip_message = (
68 68 'Your pip version is out of date, please install '
69 69 'pip >= 9.0.1. pip {} detected.'.format(pip.__version__)
70 70 )
71 71 else:
72 72 # pip is new enough - it must be something else
73 73 pip_message = ''
74 74 except Exception:
75 75 pass
76 76 error = """
77 77 Mercurial does not support Python older than 2.7.4.
78 78 Python {py} detected.
79 79 {pip}
80 80 """.format(
81 81 py=sys.version_info, pip=pip_message
82 82 )
83 83 printf(error, file=sys.stderr)
84 84 sys.exit(1)
85 85
86 86 import ssl
87 87
88 88 try:
89 89 ssl.SSLContext
90 90 except AttributeError:
91 91 error = """
92 92 The `ssl` module does not have the `SSLContext` class. This indicates an old
93 93 Python version which does not support modern security features (which were
94 94 added to Python 2.7 as part of "PEP 466"). Please make sure you have installed
95 95 at least Python 2.7.9 or a Python version with backports of these security
96 96 features.
97 97 """
98 98 printf(error, file=sys.stderr)
99 99 sys.exit(1)
100 100
101 101 # ssl.HAS_TLSv1* are preferred to check support but they were added in Python
102 102 # 3.7. Prior to CPython commit 6e8cda91d92da72800d891b2fc2073ecbc134d98
103 103 # (backported to the 3.7 branch), ssl.PROTOCOL_TLSv1_1 / ssl.PROTOCOL_TLSv1_2
104 104 # were defined only if compiled against a OpenSSL version with TLS 1.1 / 1.2
105 105 # support. At the mentioned commit, they were unconditionally defined.
106 106 _notset = object()
107 107 has_tlsv1_1 = getattr(ssl, 'HAS_TLSv1_1', _notset)
108 108 if has_tlsv1_1 is _notset:
109 109 has_tlsv1_1 = getattr(ssl, 'PROTOCOL_TLSv1_1', _notset) is not _notset
110 110 has_tlsv1_2 = getattr(ssl, 'HAS_TLSv1_2', _notset)
111 111 if has_tlsv1_2 is _notset:
112 112 has_tlsv1_2 = getattr(ssl, 'PROTOCOL_TLSv1_2', _notset) is not _notset
113 113 if not (has_tlsv1_1 or has_tlsv1_2):
114 114 error = """
115 115 The `ssl` module does not advertise support for TLS 1.1 or TLS 1.2.
116 116 Please make sure that your Python installation was compiled against an OpenSSL
117 117 version enabling these features (likely this requires the OpenSSL version to
118 118 be at least 1.0.1).
119 119 """
120 120 printf(error, file=sys.stderr)
121 121 sys.exit(1)
122 122
123 123 if sys.version_info[0] >= 3:
124 124 DYLIB_SUFFIX = sysconfig.get_config_vars()['EXT_SUFFIX']
125 125 else:
126 126 # deprecated in Python 3
127 127 DYLIB_SUFFIX = sysconfig.get_config_vars()['SO']
128 128
129 129 # Solaris Python packaging brain damage
130 130 try:
131 131 import hashlib
132 132
133 133 sha = hashlib.sha1()
134 134 except ImportError:
135 135 try:
136 136 import sha
137 137
138 138 sha.sha # silence unused import warning
139 139 except ImportError:
140 140 raise SystemExit(
141 141 "Couldn't import standard hashlib (incomplete Python install)."
142 142 )
143 143
144 144 try:
145 145 import zlib
146 146
147 147 zlib.compressobj # silence unused import warning
148 148 except ImportError:
149 149 raise SystemExit(
150 150 "Couldn't import standard zlib (incomplete Python install)."
151 151 )
152 152
153 153 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
154 154 isironpython = False
155 155 try:
156 156 isironpython = (
157 157 platform.python_implementation().lower().find("ironpython") != -1
158 158 )
159 159 except AttributeError:
160 160 pass
161 161
162 162 if isironpython:
163 163 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
164 164 else:
165 165 try:
166 166 import bz2
167 167
168 168 bz2.BZ2Compressor # silence unused import warning
169 169 except ImportError:
170 170 raise SystemExit(
171 171 "Couldn't import standard bz2 (incomplete Python install)."
172 172 )
173 173
174 174 ispypy = "PyPy" in sys.version
175 175
176 176 import ctypes
177 177 import errno
178 178 import stat, subprocess, time
179 179 import re
180 180 import shutil
181 181 import tempfile
182 182
183 183 # We have issues with setuptools on some platforms and builders. Until
184 184 # those are resolved, setuptools is opt-in except for platforms where
185 185 # we don't have issues.
186 186 issetuptools = os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ
187 187 if issetuptools:
188 188 from setuptools import setup
189 189 else:
190 190 from distutils.core import setup
191 191 from distutils.ccompiler import new_compiler
192 192 from distutils.core import Command, Extension
193 193 from distutils.dist import Distribution
194 194 from distutils.command.build import build
195 195 from distutils.command.build_ext import build_ext
196 196 from distutils.command.build_py import build_py
197 197 from distutils.command.build_scripts import build_scripts
198 198 from distutils.command.install import install
199 199 from distutils.command.install_lib import install_lib
200 200 from distutils.command.install_scripts import install_scripts
201 201 from distutils import log
202 202 from distutils.spawn import spawn, find_executable
203 203 from distutils import file_util
204 204 from distutils.errors import (
205 205 CCompilerError,
206 206 DistutilsError,
207 207 DistutilsExecError,
208 208 )
209 209 from distutils.sysconfig import get_python_inc, get_config_var
210 210 from distutils.version import StrictVersion
211 211
212 212 # Explain to distutils.StrictVersion how our release candidates are versionned
213 213 StrictVersion.version_re = re.compile(r'^(\d+)\.(\d+)(\.(\d+))?-?(rc(\d+))?$')
214 214
215 215
216 216 def write_if_changed(path, content):
217 217 """Write content to a file iff the content hasn't changed."""
218 218 if os.path.exists(path):
219 219 with open(path, 'rb') as fh:
220 220 current = fh.read()
221 221 else:
222 222 current = b''
223 223
224 224 if current != content:
225 225 with open(path, 'wb') as fh:
226 226 fh.write(content)
227 227
228 228
229 229 scripts = ['hg']
230 230 if os.name == 'nt':
231 231 # We remove hg.bat if we are able to build hg.exe.
232 232 scripts.append('contrib/win32/hg.bat')
233 233
234 234
235 235 def cancompile(cc, code):
236 236 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
237 237 devnull = oldstderr = None
238 238 try:
239 239 fname = os.path.join(tmpdir, 'testcomp.c')
240 240 f = open(fname, 'w')
241 241 f.write(code)
242 242 f.close()
243 243 # Redirect stderr to /dev/null to hide any error messages
244 244 # from the compiler.
245 245 # This will have to be changed if we ever have to check
246 246 # for a function on Windows.
247 247 devnull = open('/dev/null', 'w')
248 248 oldstderr = os.dup(sys.stderr.fileno())
249 249 os.dup2(devnull.fileno(), sys.stderr.fileno())
250 250 objects = cc.compile([fname], output_dir=tmpdir)
251 251 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
252 252 return True
253 253 except Exception:
254 254 return False
255 255 finally:
256 256 if oldstderr is not None:
257 257 os.dup2(oldstderr, sys.stderr.fileno())
258 258 if devnull is not None:
259 259 devnull.close()
260 260 shutil.rmtree(tmpdir)
261 261
262 262
263 263 # simplified version of distutils.ccompiler.CCompiler.has_function
264 264 # that actually removes its temporary files.
265 265 def hasfunction(cc, funcname):
266 266 code = 'int main(void) { %s(); }\n' % funcname
267 267 return cancompile(cc, code)
268 268
269 269
270 270 def hasheader(cc, headername):
271 271 code = '#include <%s>\nint main(void) { return 0; }\n' % headername
272 272 return cancompile(cc, code)
273 273
274 274
275 275 # py2exe needs to be installed to work
276 276 try:
277 277 import py2exe
278 278
279 279 py2exe.Distribution # silence unused import warning
280 280 py2exeloaded = True
281 281 # import py2exe's patched Distribution class
282 282 from distutils.core import Distribution
283 283 except ImportError:
284 284 py2exeloaded = False
285 285
286 286
287 287 def runcmd(cmd, env, cwd=None):
288 288 p = subprocess.Popen(
289 289 cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, cwd=cwd
290 290 )
291 291 out, err = p.communicate()
292 292 return p.returncode, out, err
293 293
294 294
295 295 class hgcommand(object):
296 296 def __init__(self, cmd, env):
297 297 self.cmd = cmd
298 298 self.env = env
299 299
300 300 def run(self, args):
301 301 cmd = self.cmd + args
302 302 returncode, out, err = runcmd(cmd, self.env)
303 303 err = filterhgerr(err)
304 304 if err or returncode != 0:
305 305 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
306 306 printf(err, file=sys.stderr)
307 307 return b''
308 308 return out
309 309
310 310
311 311 def filterhgerr(err):
312 312 # If root is executing setup.py, but the repository is owned by
313 313 # another user (as in "sudo python setup.py install") we will get
314 314 # trust warnings since the .hg/hgrc file is untrusted. That is
315 315 # fine, we don't want to load it anyway. Python may warn about
316 316 # a missing __init__.py in mercurial/locale, we also ignore that.
317 317 err = [
318 318 e
319 319 for e in err.splitlines()
320 320 if (
321 321 not e.startswith(b'not trusting file')
322 322 and not e.startswith(b'warning: Not importing')
323 323 and not e.startswith(b'obsolete feature not enabled')
324 324 and not e.startswith(b'*** failed to import extension')
325 325 and not e.startswith(b'devel-warn:')
326 326 and not (
327 327 e.startswith(b'(third party extension')
328 328 and e.endswith(b'or newer of Mercurial; disabling)')
329 329 )
330 330 )
331 331 ]
332 332 return b'\n'.join(b' ' + e for e in err)
333 333
334 334
335 335 def findhg():
336 336 """Try to figure out how we should invoke hg for examining the local
337 337 repository contents.
338 338
339 339 Returns an hgcommand object."""
340 340 # By default, prefer the "hg" command in the user's path. This was
341 341 # presumably the hg command that the user used to create this repository.
342 342 #
343 343 # This repository may require extensions or other settings that would not
344 344 # be enabled by running the hg script directly from this local repository.
345 345 hgenv = os.environ.copy()
346 346 # Use HGPLAIN to disable hgrc settings that would change output formatting,
347 347 # and disable localization for the same reasons.
348 348 hgenv['HGPLAIN'] = '1'
349 349 hgenv['LANGUAGE'] = 'C'
350 350 hgcmd = ['hg']
351 351 # Run a simple "hg log" command just to see if using hg from the user's
352 352 # path works and can successfully interact with this repository. Windows
353 353 # gives precedence to hg.exe in the current directory, so fall back to the
354 354 # python invocation of local hg, where pythonXY.dll can always be found.
355 355 check_cmd = ['log', '-r.', '-Ttest']
356 356 if os.name != 'nt' or not os.path.exists("hg.exe"):
357 357 try:
358 358 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
359 359 except EnvironmentError:
360 360 retcode = -1
361 361 if retcode == 0 and not filterhgerr(err):
362 362 return hgcommand(hgcmd, hgenv)
363 363
364 364 # Fall back to trying the local hg installation.
365 365 hgenv = localhgenv()
366 366 hgcmd = [sys.executable, 'hg']
367 367 try:
368 368 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
369 369 except EnvironmentError:
370 370 retcode = -1
371 371 if retcode == 0 and not filterhgerr(err):
372 372 return hgcommand(hgcmd, hgenv)
373 373
374 374 raise SystemExit(
375 375 'Unable to find a working hg binary to extract the '
376 376 'version from the repository tags'
377 377 )
378 378
379 379
380 380 def localhgenv():
381 381 """Get an environment dictionary to use for invoking or importing
382 382 mercurial from the local repository."""
383 383 # Execute hg out of this directory with a custom environment which takes
384 384 # care to not use any hgrc files and do no localization.
385 385 env = {
386 386 'HGMODULEPOLICY': 'py',
387 387 'HGRCPATH': '',
388 388 'LANGUAGE': 'C',
389 389 'PATH': '',
390 390 } # make pypi modules that use os.environ['PATH'] happy
391 391 if 'LD_LIBRARY_PATH' in os.environ:
392 392 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
393 393 if 'SystemRoot' in os.environ:
394 394 # SystemRoot is required by Windows to load various DLLs. See:
395 395 # https://bugs.python.org/issue13524#msg148850
396 396 env['SystemRoot'] = os.environ['SystemRoot']
397 397 return env
398 398
399 399
400 400 version = ''
401 401
402 402 if os.path.isdir('.hg'):
403 403 hg = findhg()
404 404 cmd = ['log', '-r', '.', '--template', '{tags}\n']
405 405 numerictags = [t for t in sysstr(hg.run(cmd)).split() if t[0:1].isdigit()]
406 406 hgid = sysstr(hg.run(['id', '-i'])).strip()
407 407 if not hgid:
408 408 # Bail out if hg is having problems interacting with this repository,
409 409 # rather than falling through and producing a bogus version number.
410 410 # Continuing with an invalid version number will break extensions
411 411 # that define minimumhgversion.
412 412 raise SystemExit('Unable to determine hg version from local repository')
413 413 if numerictags: # tag(s) found
414 414 version = numerictags[-1]
415 415 if hgid.endswith('+'): # propagate the dirty status to the tag
416 416 version += '+'
417 417 else: # no tag found
418 418 ltagcmd = ['parents', '--template', '{latesttag}']
419 419 ltag = sysstr(hg.run(ltagcmd))
420 420 changessincecmd = ['log', '-T', 'x\n', '-r', "only(.,'%s')" % ltag]
421 421 changessince = len(hg.run(changessincecmd).splitlines())
422 422 version = '%s+hg%s.%s' % (ltag, changessince, hgid)
423 423 if version.endswith('+'):
424 424 version = version[:-1] + 'local' + time.strftime('%Y%m%d')
425 425 elif os.path.exists('.hg_archival.txt'):
426 426 kw = dict(
427 427 [[t.strip() for t in l.split(':', 1)] for l in open('.hg_archival.txt')]
428 428 )
429 429 if 'tag' in kw:
430 430 version = kw['tag']
431 431 elif 'latesttag' in kw:
432 432 if 'changessincelatesttag' in kw:
433 433 version = (
434 434 '%(latesttag)s+hg%(changessincelatesttag)s.%(node).12s' % kw
435 435 )
436 436 else:
437 437 version = '%(latesttag)s+hg%(latesttagdistance)s.%(node).12s' % kw
438 438 else:
439 439 version = '0+hg' + kw.get('node', '')[:12]
440 440 elif os.path.exists('mercurial/__version__.py'):
441 data = open('mercurial/__version__.py').read()
441 with open('mercurial/__version__.py') as f:
442 data = f.read()
442 443 version = re.search('version = b"(.*)"', data).group(1)
443 444
444 445 if version:
445 446 versionb = version
446 447 if not isinstance(versionb, bytes):
447 448 versionb = versionb.encode('ascii')
448 449
449 450 write_if_changed(
450 451 'mercurial/__version__.py',
451 452 b''.join(
452 453 [
453 454 b'# this file is autogenerated by setup.py\n'
454 455 b'version = b"%s"\n' % versionb,
455 456 ]
456 457 ),
457 458 )
458 459
459 460
460 461 class hgbuild(build):
461 462 # Insert hgbuildmo first so that files in mercurial/locale/ are found
462 463 # when build_py is run next.
463 464 sub_commands = [('build_mo', None)] + build.sub_commands
464 465
465 466
466 467 class hgbuildmo(build):
467 468
468 469 description = "build translations (.mo files)"
469 470
470 471 def run(self):
471 472 if not find_executable('msgfmt'):
472 473 self.warn(
473 474 "could not find msgfmt executable, no translations "
474 475 "will be built"
475 476 )
476 477 return
477 478
478 479 podir = 'i18n'
479 480 if not os.path.isdir(podir):
480 481 self.warn("could not find %s/ directory" % podir)
481 482 return
482 483
483 484 join = os.path.join
484 485 for po in os.listdir(podir):
485 486 if not po.endswith('.po'):
486 487 continue
487 488 pofile = join(podir, po)
488 489 modir = join('locale', po[:-3], 'LC_MESSAGES')
489 490 mofile = join(modir, 'hg.mo')
490 491 mobuildfile = join('mercurial', mofile)
491 492 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
492 493 if sys.platform != 'sunos5':
493 494 # msgfmt on Solaris does not know about -c
494 495 cmd.append('-c')
495 496 self.mkpath(join('mercurial', modir))
496 497 self.make_file([pofile], mobuildfile, spawn, (cmd,))
497 498
498 499
499 500 class hgdist(Distribution):
500 501 pure = False
501 502 rust = False
502 503 no_rust = False
503 504 cffi = ispypy
504 505
505 506 global_options = Distribution.global_options + [
506 507 ('pure', None, "use pure (slow) Python code instead of C extensions"),
507 508 ('rust', None, "use Rust extensions additionally to C extensions"),
508 509 (
509 510 'no-rust',
510 511 None,
511 512 "do not use Rust extensions additionally to C extensions",
512 513 ),
513 514 ]
514 515
515 516 negative_opt = Distribution.negative_opt.copy()
516 517 boolean_options = ['pure', 'rust', 'no-rust']
517 518 negative_opt['no-rust'] = 'rust'
518 519
519 520 def _set_command_options(self, command_obj, option_dict=None):
520 521 # Not all distutils versions in the wild have boolean_options.
521 522 # This should be cleaned up when we're Python 3 only.
522 523 command_obj.boolean_options = (
523 524 getattr(command_obj, 'boolean_options', []) + self.boolean_options
524 525 )
525 526 return Distribution._set_command_options(
526 527 self, command_obj, option_dict=option_dict
527 528 )
528 529
529 530 def parse_command_line(self):
530 531 ret = Distribution.parse_command_line(self)
531 532 if not (self.rust or self.no_rust):
532 533 hgrustext = os.environ.get('HGWITHRUSTEXT')
533 534 # TODO record it for proper rebuild upon changes
534 535 # (see mercurial/__modulepolicy__.py)
535 536 if hgrustext != 'cpython' and hgrustext is not None:
536 537 if hgrustext:
537 538 msg = 'unkown HGWITHRUSTEXT value: %s' % hgrustext
538 539 printf(msg, file=sys.stderr)
539 540 hgrustext = None
540 541 self.rust = hgrustext is not None
541 542 self.no_rust = not self.rust
542 543 return ret
543 544
544 545 def has_ext_modules(self):
545 546 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
546 547 # too late for some cases
547 548 return not self.pure and Distribution.has_ext_modules(self)
548 549
549 550
550 551 # This is ugly as a one-liner. So use a variable.
551 552 buildextnegops = dict(getattr(build_ext, 'negative_options', {}))
552 553 buildextnegops['no-zstd'] = 'zstd'
553 554 buildextnegops['no-rust'] = 'rust'
554 555
555 556
556 557 class hgbuildext(build_ext):
557 558 user_options = build_ext.user_options + [
558 559 ('zstd', None, 'compile zstd bindings [default]'),
559 560 ('no-zstd', None, 'do not compile zstd bindings'),
560 561 (
561 562 'rust',
562 563 None,
563 564 'compile Rust extensions if they are in use '
564 565 '(requires Cargo) [default]',
565 566 ),
566 567 ('no-rust', None, 'do not compile Rust extensions'),
567 568 ]
568 569
569 570 boolean_options = build_ext.boolean_options + ['zstd', 'rust']
570 571 negative_opt = buildextnegops
571 572
572 573 def initialize_options(self):
573 574 self.zstd = True
574 575 self.rust = True
575 576
576 577 return build_ext.initialize_options(self)
577 578
578 579 def finalize_options(self):
579 580 # Unless overridden by the end user, build extensions in parallel.
580 581 # Only influences behavior on Python 3.5+.
581 582 if getattr(self, 'parallel', None) is None:
582 583 self.parallel = True
583 584
584 585 return build_ext.finalize_options(self)
585 586
586 587 def build_extensions(self):
587 588 ruststandalones = [
588 589 e for e in self.extensions if isinstance(e, RustStandaloneExtension)
589 590 ]
590 591 self.extensions = [
591 592 e for e in self.extensions if e not in ruststandalones
592 593 ]
593 594 # Filter out zstd if disabled via argument.
594 595 if not self.zstd:
595 596 self.extensions = [
596 597 e for e in self.extensions if e.name != 'mercurial.zstd'
597 598 ]
598 599
599 600 # Build Rust standalon extensions if it'll be used
600 601 # and its build is not explictely disabled (for external build
601 602 # as Linux distributions would do)
602 603 if self.distribution.rust and self.rust:
603 604 if not sys.platform.startswith('linux'):
604 605 self.warn(
605 606 "rust extensions have only been tested on Linux "
606 607 "and may not behave correctly on other platforms"
607 608 )
608 609
609 610 for rustext in ruststandalones:
610 611 rustext.build('' if self.inplace else self.build_lib)
611 612
612 613 return build_ext.build_extensions(self)
613 614
614 615 def build_extension(self, ext):
615 616 if (
616 617 self.distribution.rust
617 618 and self.rust
618 619 and isinstance(ext, RustExtension)
619 620 ):
620 621 ext.rustbuild()
621 622 try:
622 623 build_ext.build_extension(self, ext)
623 624 except CCompilerError:
624 625 if not getattr(ext, 'optional', False):
625 626 raise
626 627 log.warn(
627 628 "Failed to build optional extension '%s' (skipping)", ext.name
628 629 )
629 630
630 631
631 632 class hgbuildscripts(build_scripts):
632 633 def run(self):
633 634 if os.name != 'nt' or self.distribution.pure:
634 635 return build_scripts.run(self)
635 636
636 637 exebuilt = False
637 638 try:
638 639 self.run_command('build_hgexe')
639 640 exebuilt = True
640 641 except (DistutilsError, CCompilerError):
641 642 log.warn('failed to build optional hg.exe')
642 643
643 644 if exebuilt:
644 645 # Copying hg.exe to the scripts build directory ensures it is
645 646 # installed by the install_scripts command.
646 647 hgexecommand = self.get_finalized_command('build_hgexe')
647 648 dest = os.path.join(self.build_dir, 'hg.exe')
648 649 self.mkpath(self.build_dir)
649 650 self.copy_file(hgexecommand.hgexepath, dest)
650 651
651 652 # Remove hg.bat because it is redundant with hg.exe.
652 653 self.scripts.remove('contrib/win32/hg.bat')
653 654
654 655 return build_scripts.run(self)
655 656
656 657
657 658 class hgbuildpy(build_py):
658 659 def finalize_options(self):
659 660 build_py.finalize_options(self)
660 661
661 662 if self.distribution.pure:
662 663 self.distribution.ext_modules = []
663 664 elif self.distribution.cffi:
664 665 from mercurial.cffi import (
665 666 bdiffbuild,
666 667 mpatchbuild,
667 668 )
668 669
669 670 exts = [
670 671 mpatchbuild.ffi.distutils_extension(),
671 672 bdiffbuild.ffi.distutils_extension(),
672 673 ]
673 674 # cffi modules go here
674 675 if sys.platform == 'darwin':
675 676 from mercurial.cffi import osutilbuild
676 677
677 678 exts.append(osutilbuild.ffi.distutils_extension())
678 679 self.distribution.ext_modules = exts
679 680 else:
680 681 h = os.path.join(get_python_inc(), 'Python.h')
681 682 if not os.path.exists(h):
682 683 raise SystemExit(
683 684 'Python headers are required to build '
684 685 'Mercurial but weren\'t found in %s' % h
685 686 )
686 687
687 688 def run(self):
688 689 basepath = os.path.join(self.build_lib, 'mercurial')
689 690 self.mkpath(basepath)
690 691
691 692 rust = self.distribution.rust
692 693 if self.distribution.pure:
693 694 modulepolicy = 'py'
694 695 elif self.build_lib == '.':
695 696 # in-place build should run without rebuilding and Rust extensions
696 697 modulepolicy = 'rust+c-allow' if rust else 'allow'
697 698 else:
698 699 modulepolicy = 'rust+c' if rust else 'c'
699 700
700 701 content = b''.join(
701 702 [
702 703 b'# this file is autogenerated by setup.py\n',
703 704 b'modulepolicy = b"%s"\n' % modulepolicy.encode('ascii'),
704 705 ]
705 706 )
706 707 write_if_changed(os.path.join(basepath, '__modulepolicy__.py'), content)
707 708
708 709 build_py.run(self)
709 710
710 711
711 712 class buildhgextindex(Command):
712 713 description = 'generate prebuilt index of hgext (for frozen package)'
713 714 user_options = []
714 715 _indexfilename = 'hgext/__index__.py'
715 716
716 717 def initialize_options(self):
717 718 pass
718 719
719 720 def finalize_options(self):
720 721 pass
721 722
722 723 def run(self):
723 724 if os.path.exists(self._indexfilename):
724 725 with open(self._indexfilename, 'w') as f:
725 726 f.write('# empty\n')
726 727
727 728 # here no extension enabled, disabled() lists up everything
728 729 code = (
729 730 'import pprint; from mercurial import extensions; '
730 731 'ext = extensions.disabled();'
731 732 'ext.pop("__index__", None);'
732 733 'pprint.pprint(ext)'
733 734 )
734 735 returncode, out, err = runcmd(
735 736 [sys.executable, '-c', code], localhgenv()
736 737 )
737 738 if err or returncode != 0:
738 739 raise DistutilsExecError(err)
739 740
740 741 with open(self._indexfilename, 'wb') as f:
741 742 f.write(b'# this file is autogenerated by setup.py\n')
742 743 f.write(b'docs = ')
743 744 f.write(out)
744 745
745 746
746 747 class buildhgexe(build_ext):
747 748 description = 'compile hg.exe from mercurial/exewrapper.c'
748 749 user_options = build_ext.user_options + [
749 750 (
750 751 'long-paths-support',
751 752 None,
752 753 'enable support for long paths on '
753 754 'Windows (off by default and '
754 755 'experimental)',
755 756 ),
756 757 ]
757 758
758 759 LONG_PATHS_MANIFEST = """
759 760 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
760 761 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
761 762 <application>
762 763 <windowsSettings
763 764 xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
764 765 <ws2:longPathAware>true</ws2:longPathAware>
765 766 </windowsSettings>
766 767 </application>
767 768 </assembly>"""
768 769
769 770 def initialize_options(self):
770 771 build_ext.initialize_options(self)
771 772 self.long_paths_support = False
772 773
773 774 def build_extensions(self):
774 775 if os.name != 'nt':
775 776 return
776 777 if isinstance(self.compiler, HackedMingw32CCompiler):
777 778 self.compiler.compiler_so = self.compiler.compiler # no -mdll
778 779 self.compiler.dll_libraries = [] # no -lmsrvc90
779 780
780 781 pythonlib = None
781 782
782 783 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
783 784 self.hgtarget = os.path.join(dir, 'hg')
784 785
785 786 if getattr(sys, 'dllhandle', None):
786 787 # Different Python installs can have different Python library
787 788 # names. e.g. the official CPython distribution uses pythonXY.dll
788 789 # and MinGW uses libpythonX.Y.dll.
789 790 _kernel32 = ctypes.windll.kernel32
790 791 _kernel32.GetModuleFileNameA.argtypes = [
791 792 ctypes.c_void_p,
792 793 ctypes.c_void_p,
793 794 ctypes.c_ulong,
794 795 ]
795 796 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
796 797 size = 1000
797 798 buf = ctypes.create_string_buffer(size + 1)
798 799 filelen = _kernel32.GetModuleFileNameA(
799 800 sys.dllhandle, ctypes.byref(buf), size
800 801 )
801 802
802 803 if filelen > 0 and filelen != size:
803 804 dllbasename = os.path.basename(buf.value)
804 805 if not dllbasename.lower().endswith(b'.dll'):
805 806 raise SystemExit(
806 807 'Python DLL does not end with .dll: %s' % dllbasename
807 808 )
808 809 pythonlib = dllbasename[:-4]
809 810
810 811 # Copy the pythonXY.dll next to the binary so that it runs
811 812 # without tampering with PATH.
812 813 fsdecode = lambda x: x
813 814 if sys.version_info[0] >= 3:
814 815 fsdecode = os.fsdecode
815 816 dest = os.path.join(
816 817 os.path.dirname(self.hgtarget),
817 818 fsdecode(dllbasename),
818 819 )
819 820
820 821 if not os.path.exists(dest):
821 822 shutil.copy(buf.value, dest)
822 823
823 824 # Also overwrite python3.dll so that hgext.git is usable.
824 825 # TODO: also handle the MSYS flavor
825 826 if sys.version_info[0] >= 3:
826 827 python_x = os.path.join(
827 828 os.path.dirname(fsdecode(buf.value)),
828 829 "python3.dll",
829 830 )
830 831
831 832 if os.path.exists(python_x):
832 833 dest = os.path.join(
833 834 os.path.dirname(self.hgtarget),
834 835 os.path.basename(python_x),
835 836 )
836 837
837 838 shutil.copy(python_x, dest)
838 839
839 840 if not pythonlib:
840 841 log.warn(
841 842 'could not determine Python DLL filename; assuming pythonXY'
842 843 )
843 844
844 845 hv = sys.hexversion
845 846 pythonlib = b'python%d%d' % (hv >> 24, (hv >> 16) & 0xFF)
846 847
847 848 log.info('using %s as Python library name' % pythonlib)
848 849 with open('mercurial/hgpythonlib.h', 'wb') as f:
849 850 f.write(b'/* this file is autogenerated by setup.py */\n')
850 851 f.write(b'#define HGPYTHONLIB "%s"\n' % pythonlib)
851 852
852 853 macros = None
853 854 if sys.version_info[0] >= 3:
854 855 macros = [('_UNICODE', None), ('UNICODE', None)]
855 856
856 857 objects = self.compiler.compile(
857 858 ['mercurial/exewrapper.c'],
858 859 output_dir=self.build_temp,
859 860 macros=macros,
860 861 )
861 862 self.compiler.link_executable(
862 863 objects, self.hgtarget, libraries=[], output_dir=self.build_temp
863 864 )
864 865 if self.long_paths_support:
865 866 self.addlongpathsmanifest()
866 867
867 868 def addlongpathsmanifest(self):
868 869 r"""Add manifest pieces so that hg.exe understands long paths
869 870
870 871 This is an EXPERIMENTAL feature, use with care.
871 872 To enable long paths support, one needs to do two things:
872 873 - build Mercurial with --long-paths-support option
873 874 - change HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\
874 875 LongPathsEnabled to have value 1.
875 876
876 877 Please ignore 'warning 81010002: Unrecognized Element "longPathAware"';
877 878 it happens because Mercurial uses mt.exe circa 2008, which is not
878 879 yet aware of long paths support in the manifest (I think so at least).
879 880 This does not stop mt.exe from embedding/merging the XML properly.
880 881
881 882 Why resource #1 should be used for .exe manifests? I don't know and
882 883 wasn't able to find an explanation for mortals. But it seems to work.
883 884 """
884 885 exefname = self.compiler.executable_filename(self.hgtarget)
885 886 fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
886 887 os.close(fdauto)
887 888 with open(manfname, 'w') as f:
888 889 f.write(self.LONG_PATHS_MANIFEST)
889 890 log.info("long paths manifest is written to '%s'" % manfname)
890 891 inputresource = '-inputresource:%s;#1' % exefname
891 892 outputresource = '-outputresource:%s;#1' % exefname
892 893 log.info("running mt.exe to update hg.exe's manifest in-place")
893 894 # supplying both -manifest and -inputresource to mt.exe makes
894 895 # it merge the embedded and supplied manifests in the -outputresource
895 896 self.spawn(
896 897 [
897 898 'mt.exe',
898 899 '-nologo',
899 900 '-manifest',
900 901 manfname,
901 902 inputresource,
902 903 outputresource,
903 904 ]
904 905 )
905 906 log.info("done updating hg.exe's manifest")
906 907 os.remove(manfname)
907 908
908 909 @property
909 910 def hgexepath(self):
910 911 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
911 912 return os.path.join(self.build_temp, dir, 'hg.exe')
912 913
913 914
914 915 class hgbuilddoc(Command):
915 916 description = 'build documentation'
916 917 user_options = [
917 918 ('man', None, 'generate man pages'),
918 919 ('html', None, 'generate html pages'),
919 920 ]
920 921
921 922 def initialize_options(self):
922 923 self.man = None
923 924 self.html = None
924 925
925 926 def finalize_options(self):
926 927 # If --man or --html are set, only generate what we're told to.
927 928 # Otherwise generate everything.
928 929 have_subset = self.man is not None or self.html is not None
929 930
930 931 if have_subset:
931 932 self.man = True if self.man else False
932 933 self.html = True if self.html else False
933 934 else:
934 935 self.man = True
935 936 self.html = True
936 937
937 938 def run(self):
938 939 def normalizecrlf(p):
939 940 with open(p, 'rb') as fh:
940 941 orig = fh.read()
941 942
942 943 if b'\r\n' not in orig:
943 944 return
944 945
945 946 log.info('normalizing %s to LF line endings' % p)
946 947 with open(p, 'wb') as fh:
947 948 fh.write(orig.replace(b'\r\n', b'\n'))
948 949
949 950 def gentxt(root):
950 951 txt = 'doc/%s.txt' % root
951 952 log.info('generating %s' % txt)
952 953 res, out, err = runcmd(
953 954 [sys.executable, 'gendoc.py', root], os.environ, cwd='doc'
954 955 )
955 956 if res:
956 957 raise SystemExit(
957 958 'error running gendoc.py: %s'
958 959 % '\n'.join([sysstr(out), sysstr(err)])
959 960 )
960 961
961 962 with open(txt, 'wb') as fh:
962 963 fh.write(out)
963 964
964 965 def gengendoc(root):
965 966 gendoc = 'doc/%s.gendoc.txt' % root
966 967
967 968 log.info('generating %s' % gendoc)
968 969 res, out, err = runcmd(
969 970 [sys.executable, 'gendoc.py', '%s.gendoc' % root],
970 971 os.environ,
971 972 cwd='doc',
972 973 )
973 974 if res:
974 975 raise SystemExit(
975 976 'error running gendoc: %s'
976 977 % '\n'.join([sysstr(out), sysstr(err)])
977 978 )
978 979
979 980 with open(gendoc, 'wb') as fh:
980 981 fh.write(out)
981 982
982 983 def genman(root):
983 984 log.info('generating doc/%s' % root)
984 985 res, out, err = runcmd(
985 986 [
986 987 sys.executable,
987 988 'runrst',
988 989 'hgmanpage',
989 990 '--halt',
990 991 'warning',
991 992 '--strip-elements-with-class',
992 993 'htmlonly',
993 994 '%s.txt' % root,
994 995 root,
995 996 ],
996 997 os.environ,
997 998 cwd='doc',
998 999 )
999 1000 if res:
1000 1001 raise SystemExit(
1001 1002 'error running runrst: %s'
1002 1003 % '\n'.join([sysstr(out), sysstr(err)])
1003 1004 )
1004 1005
1005 1006 normalizecrlf('doc/%s' % root)
1006 1007
1007 1008 def genhtml(root):
1008 1009 log.info('generating doc/%s.html' % root)
1009 1010 res, out, err = runcmd(
1010 1011 [
1011 1012 sys.executable,
1012 1013 'runrst',
1013 1014 'html',
1014 1015 '--halt',
1015 1016 'warning',
1016 1017 '--link-stylesheet',
1017 1018 '--stylesheet-path',
1018 1019 'style.css',
1019 1020 '%s.txt' % root,
1020 1021 '%s.html' % root,
1021 1022 ],
1022 1023 os.environ,
1023 1024 cwd='doc',
1024 1025 )
1025 1026 if res:
1026 1027 raise SystemExit(
1027 1028 'error running runrst: %s'
1028 1029 % '\n'.join([sysstr(out), sysstr(err)])
1029 1030 )
1030 1031
1031 1032 normalizecrlf('doc/%s.html' % root)
1032 1033
1033 1034 # This logic is duplicated in doc/Makefile.
1034 1035 sources = {
1035 1036 f
1036 1037 for f in os.listdir('mercurial/helptext')
1037 1038 if re.search(r'[0-9]\.txt$', f)
1038 1039 }
1039 1040
1040 1041 # common.txt is a one-off.
1041 1042 gentxt('common')
1042 1043
1043 1044 for source in sorted(sources):
1044 1045 assert source[-4:] == '.txt'
1045 1046 root = source[:-4]
1046 1047
1047 1048 gentxt(root)
1048 1049 gengendoc(root)
1049 1050
1050 1051 if self.man:
1051 1052 genman(root)
1052 1053 if self.html:
1053 1054 genhtml(root)
1054 1055
1055 1056
1056 1057 class hginstall(install):
1057 1058
1058 1059 user_options = install.user_options + [
1059 1060 (
1060 1061 'old-and-unmanageable',
1061 1062 None,
1062 1063 'noop, present for eggless setuptools compat',
1063 1064 ),
1064 1065 (
1065 1066 'single-version-externally-managed',
1066 1067 None,
1067 1068 'noop, present for eggless setuptools compat',
1068 1069 ),
1069 1070 ]
1070 1071
1071 1072 # Also helps setuptools not be sad while we refuse to create eggs.
1072 1073 single_version_externally_managed = True
1073 1074
1074 1075 def get_sub_commands(self):
1075 1076 # Screen out egg related commands to prevent egg generation. But allow
1076 1077 # mercurial.egg-info generation, since that is part of modern
1077 1078 # packaging.
1078 1079 excl = {'bdist_egg'}
1079 1080 return filter(lambda x: x not in excl, install.get_sub_commands(self))
1080 1081
1081 1082
1082 1083 class hginstalllib(install_lib):
1083 1084 """
1084 1085 This is a specialization of install_lib that replaces the copy_file used
1085 1086 there so that it supports setting the mode of files after copying them,
1086 1087 instead of just preserving the mode that the files originally had. If your
1087 1088 system has a umask of something like 027, preserving the permissions when
1088 1089 copying will lead to a broken install.
1089 1090
1090 1091 Note that just passing keep_permissions=False to copy_file would be
1091 1092 insufficient, as it might still be applying a umask.
1092 1093 """
1093 1094
1094 1095 def run(self):
1095 1096 realcopyfile = file_util.copy_file
1096 1097
1097 1098 def copyfileandsetmode(*args, **kwargs):
1098 1099 src, dst = args[0], args[1]
1099 1100 dst, copied = realcopyfile(*args, **kwargs)
1100 1101 if copied:
1101 1102 st = os.stat(src)
1102 1103 # Persist executable bit (apply it to group and other if user
1103 1104 # has it)
1104 1105 if st[stat.ST_MODE] & stat.S_IXUSR:
1105 1106 setmode = int('0755', 8)
1106 1107 else:
1107 1108 setmode = int('0644', 8)
1108 1109 m = stat.S_IMODE(st[stat.ST_MODE])
1109 1110 m = (m & ~int('0777', 8)) | setmode
1110 1111 os.chmod(dst, m)
1111 1112
1112 1113 file_util.copy_file = copyfileandsetmode
1113 1114 try:
1114 1115 install_lib.run(self)
1115 1116 finally:
1116 1117 file_util.copy_file = realcopyfile
1117 1118
1118 1119
1119 1120 class hginstallscripts(install_scripts):
1120 1121 """
1121 1122 This is a specialization of install_scripts that replaces the @LIBDIR@ with
1122 1123 the configured directory for modules. If possible, the path is made relative
1123 1124 to the directory for scripts.
1124 1125 """
1125 1126
1126 1127 def initialize_options(self):
1127 1128 install_scripts.initialize_options(self)
1128 1129
1129 1130 self.install_lib = None
1130 1131
1131 1132 def finalize_options(self):
1132 1133 install_scripts.finalize_options(self)
1133 1134 self.set_undefined_options('install', ('install_lib', 'install_lib'))
1134 1135
1135 1136 def run(self):
1136 1137 install_scripts.run(self)
1137 1138
1138 1139 # It only makes sense to replace @LIBDIR@ with the install path if
1139 1140 # the install path is known. For wheels, the logic below calculates
1140 1141 # the libdir to be "../..". This is because the internal layout of a
1141 1142 # wheel archive looks like:
1142 1143 #
1143 1144 # mercurial-3.6.1.data/scripts/hg
1144 1145 # mercurial/__init__.py
1145 1146 #
1146 1147 # When installing wheels, the subdirectories of the "<pkg>.data"
1147 1148 # directory are translated to system local paths and files therein
1148 1149 # are copied in place. The mercurial/* files are installed into the
1149 1150 # site-packages directory. However, the site-packages directory
1150 1151 # isn't known until wheel install time. This means we have no clue
1151 1152 # at wheel generation time what the installed site-packages directory
1152 1153 # will be. And, wheels don't appear to provide the ability to register
1153 1154 # custom code to run during wheel installation. This all means that
1154 1155 # we can't reliably set the libdir in wheels: the default behavior
1155 1156 # of looking in sys.path must do.
1156 1157
1157 1158 if (
1158 1159 os.path.splitdrive(self.install_dir)[0]
1159 1160 != os.path.splitdrive(self.install_lib)[0]
1160 1161 ):
1161 1162 # can't make relative paths from one drive to another, so use an
1162 1163 # absolute path instead
1163 1164 libdir = self.install_lib
1164 1165 else:
1165 1166 libdir = os.path.relpath(self.install_lib, self.install_dir)
1166 1167
1167 1168 for outfile in self.outfiles:
1168 1169 with open(outfile, 'rb') as fp:
1169 1170 data = fp.read()
1170 1171
1171 1172 # skip binary files
1172 1173 if b'\0' in data:
1173 1174 continue
1174 1175
1175 1176 # During local installs, the shebang will be rewritten to the final
1176 1177 # install path. During wheel packaging, the shebang has a special
1177 1178 # value.
1178 1179 if data.startswith(b'#!python'):
1179 1180 log.info(
1180 1181 'not rewriting @LIBDIR@ in %s because install path '
1181 1182 'not known' % outfile
1182 1183 )
1183 1184 continue
1184 1185
1185 1186 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
1186 1187 with open(outfile, 'wb') as fp:
1187 1188 fp.write(data)
1188 1189
1189 1190
1190 1191 # virtualenv installs custom distutils/__init__.py and
1191 1192 # distutils/distutils.cfg files which essentially proxy back to the
1192 1193 # "real" distutils in the main Python install. The presence of this
1193 1194 # directory causes py2exe to pick up the "hacked" distutils package
1194 1195 # from the virtualenv and "import distutils" will fail from the py2exe
1195 1196 # build because the "real" distutils files can't be located.
1196 1197 #
1197 1198 # We work around this by monkeypatching the py2exe code finding Python
1198 1199 # modules to replace the found virtualenv distutils modules with the
1199 1200 # original versions via filesystem scanning. This is a bit hacky. But
1200 1201 # it allows us to use virtualenvs for py2exe packaging, which is more
1201 1202 # deterministic and reproducible.
1202 1203 #
1203 1204 # It's worth noting that the common StackOverflow suggestions for this
1204 1205 # problem involve copying the original distutils files into the
1205 1206 # virtualenv or into the staging directory after setup() is invoked.
1206 1207 # The former is very brittle and can easily break setup(). Our hacking
1207 1208 # of the found modules routine has a similar result as copying the files
1208 1209 # manually. But it makes fewer assumptions about how py2exe works and
1209 1210 # is less brittle.
1210 1211
1211 1212 # This only catches virtualenvs made with virtualenv (as opposed to
1212 1213 # venv, which is likely what Python 3 uses).
1213 1214 py2exehacked = py2exeloaded and getattr(sys, 'real_prefix', None) is not None
1214 1215
1215 1216 if py2exehacked:
1216 1217 from distutils.command.py2exe import py2exe as buildpy2exe
1217 1218 from py2exe.mf import Module as py2exemodule
1218 1219
1219 1220 class hgbuildpy2exe(buildpy2exe):
1220 1221 def find_needed_modules(self, mf, files, modules):
1221 1222 res = buildpy2exe.find_needed_modules(self, mf, files, modules)
1222 1223
1223 1224 # Replace virtualenv's distutils modules with the real ones.
1224 1225 modules = {}
1225 1226 for k, v in res.modules.items():
1226 1227 if k != 'distutils' and not k.startswith('distutils.'):
1227 1228 modules[k] = v
1228 1229
1229 1230 res.modules = modules
1230 1231
1231 1232 import opcode
1232 1233
1233 1234 distutilsreal = os.path.join(
1234 1235 os.path.dirname(opcode.__file__), 'distutils'
1235 1236 )
1236 1237
1237 1238 for root, dirs, files in os.walk(distutilsreal):
1238 1239 for f in sorted(files):
1239 1240 if not f.endswith('.py'):
1240 1241 continue
1241 1242
1242 1243 full = os.path.join(root, f)
1243 1244
1244 1245 parents = ['distutils']
1245 1246
1246 1247 if root != distutilsreal:
1247 1248 rel = os.path.relpath(root, distutilsreal)
1248 1249 parents.extend(p for p in rel.split(os.sep))
1249 1250
1250 1251 modname = '%s.%s' % ('.'.join(parents), f[:-3])
1251 1252
1252 1253 if modname.startswith('distutils.tests.'):
1253 1254 continue
1254 1255
1255 1256 if modname.endswith('.__init__'):
1256 1257 modname = modname[: -len('.__init__')]
1257 1258 path = os.path.dirname(full)
1258 1259 else:
1259 1260 path = None
1260 1261
1261 1262 res.modules[modname] = py2exemodule(
1262 1263 modname, full, path=path
1263 1264 )
1264 1265
1265 1266 if 'distutils' not in res.modules:
1266 1267 raise SystemExit('could not find distutils modules')
1267 1268
1268 1269 return res
1269 1270
1270 1271
1271 1272 cmdclass = {
1272 1273 'build': hgbuild,
1273 1274 'build_doc': hgbuilddoc,
1274 1275 'build_mo': hgbuildmo,
1275 1276 'build_ext': hgbuildext,
1276 1277 'build_py': hgbuildpy,
1277 1278 'build_scripts': hgbuildscripts,
1278 1279 'build_hgextindex': buildhgextindex,
1279 1280 'install': hginstall,
1280 1281 'install_lib': hginstalllib,
1281 1282 'install_scripts': hginstallscripts,
1282 1283 'build_hgexe': buildhgexe,
1283 1284 }
1284 1285
1285 1286 if py2exehacked:
1286 1287 cmdclass['py2exe'] = hgbuildpy2exe
1287 1288
1288 1289 packages = [
1289 1290 'mercurial',
1290 1291 'mercurial.cext',
1291 1292 'mercurial.cffi',
1292 1293 'mercurial.defaultrc',
1293 1294 'mercurial.helptext',
1294 1295 'mercurial.helptext.internals',
1295 1296 'mercurial.hgweb',
1296 1297 'mercurial.interfaces',
1297 1298 'mercurial.pure',
1298 1299 'mercurial.templates',
1299 1300 'mercurial.thirdparty',
1300 1301 'mercurial.thirdparty.attr',
1301 1302 'mercurial.thirdparty.zope',
1302 1303 'mercurial.thirdparty.zope.interface',
1303 1304 'mercurial.upgrade_utils',
1304 1305 'mercurial.utils',
1305 1306 'mercurial.revlogutils',
1306 1307 'mercurial.testing',
1307 1308 'hgext',
1308 1309 'hgext.convert',
1309 1310 'hgext.fsmonitor',
1310 1311 'hgext.fastannotate',
1311 1312 'hgext.fsmonitor.pywatchman',
1312 1313 'hgext.git',
1313 1314 'hgext.highlight',
1314 1315 'hgext.hooklib',
1315 1316 'hgext.infinitepush',
1316 1317 'hgext.largefiles',
1317 1318 'hgext.lfs',
1318 1319 'hgext.narrow',
1319 1320 'hgext.remotefilelog',
1320 1321 'hgext.zeroconf',
1321 1322 'hgext3rd',
1322 1323 'hgdemandimport',
1323 1324 ]
1324 1325
1325 1326 # The pygit2 dependency dropped py2 support with the 1.0 release in Dec 2019.
1326 1327 # Prior releases do not build at all on Windows, because Visual Studio 2008
1327 1328 # doesn't understand C 11. Older Linux releases are buggy.
1328 1329 if sys.version_info[0] == 2:
1329 1330 packages.remove('hgext.git')
1330 1331
1331 1332
1332 1333 for name in os.listdir(os.path.join('mercurial', 'templates')):
1333 1334 if name != '__pycache__' and os.path.isdir(
1334 1335 os.path.join('mercurial', 'templates', name)
1335 1336 ):
1336 1337 packages.append('mercurial.templates.%s' % name)
1337 1338
1338 1339 if sys.version_info[0] == 2:
1339 1340 packages.extend(
1340 1341 [
1341 1342 'mercurial.thirdparty.concurrent',
1342 1343 'mercurial.thirdparty.concurrent.futures',
1343 1344 ]
1344 1345 )
1345 1346
1346 1347 if 'HG_PY2EXE_EXTRA_INSTALL_PACKAGES' in os.environ:
1347 1348 # py2exe can't cope with namespace packages very well, so we have to
1348 1349 # install any hgext3rd.* extensions that we want in the final py2exe
1349 1350 # image here. This is gross, but you gotta do what you gotta do.
1350 1351 packages.extend(os.environ['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'].split(' '))
1351 1352
1352 1353 common_depends = [
1353 1354 'mercurial/bitmanipulation.h',
1354 1355 'mercurial/compat.h',
1355 1356 'mercurial/cext/util.h',
1356 1357 ]
1357 1358 common_include_dirs = ['mercurial']
1358 1359
1359 1360 common_cflags = []
1360 1361
1361 1362 # MSVC 2008 still needs declarations at the top of the scope, but Python 3.9
1362 1363 # makes declarations not at the top of a scope in the headers.
1363 1364 if os.name != 'nt' and sys.version_info[1] < 9:
1364 1365 common_cflags = ['-Werror=declaration-after-statement']
1365 1366
1366 1367 osutil_cflags = []
1367 1368 osutil_ldflags = []
1368 1369
1369 1370 # platform specific macros
1370 1371 for plat, func in [('bsd', 'setproctitle')]:
1371 1372 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
1372 1373 osutil_cflags.append('-DHAVE_%s' % func.upper())
1373 1374
1374 1375 for plat, macro, code in [
1375 1376 (
1376 1377 'bsd|darwin',
1377 1378 'BSD_STATFS',
1378 1379 '''
1379 1380 #include <sys/param.h>
1380 1381 #include <sys/mount.h>
1381 1382 int main() { struct statfs s; return sizeof(s.f_fstypename); }
1382 1383 ''',
1383 1384 ),
1384 1385 (
1385 1386 'linux',
1386 1387 'LINUX_STATFS',
1387 1388 '''
1388 1389 #include <linux/magic.h>
1389 1390 #include <sys/vfs.h>
1390 1391 int main() { struct statfs s; return sizeof(s.f_type); }
1391 1392 ''',
1392 1393 ),
1393 1394 ]:
1394 1395 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
1395 1396 osutil_cflags.append('-DHAVE_%s' % macro)
1396 1397
1397 1398 if sys.platform == 'darwin':
1398 1399 osutil_ldflags += ['-framework', 'ApplicationServices']
1399 1400
1400 1401 if sys.platform == 'sunos5':
1401 1402 osutil_ldflags += ['-lsocket']
1402 1403
1403 1404 xdiff_srcs = [
1404 1405 'mercurial/thirdparty/xdiff/xdiffi.c',
1405 1406 'mercurial/thirdparty/xdiff/xprepare.c',
1406 1407 'mercurial/thirdparty/xdiff/xutils.c',
1407 1408 ]
1408 1409
1409 1410 xdiff_headers = [
1410 1411 'mercurial/thirdparty/xdiff/xdiff.h',
1411 1412 'mercurial/thirdparty/xdiff/xdiffi.h',
1412 1413 'mercurial/thirdparty/xdiff/xinclude.h',
1413 1414 'mercurial/thirdparty/xdiff/xmacros.h',
1414 1415 'mercurial/thirdparty/xdiff/xprepare.h',
1415 1416 'mercurial/thirdparty/xdiff/xtypes.h',
1416 1417 'mercurial/thirdparty/xdiff/xutils.h',
1417 1418 ]
1418 1419
1419 1420
1420 1421 class RustCompilationError(CCompilerError):
1421 1422 """Exception class for Rust compilation errors."""
1422 1423
1423 1424
1424 1425 class RustExtension(Extension):
1425 1426 """Base classes for concrete Rust Extension classes."""
1426 1427
1427 1428 rusttargetdir = os.path.join('rust', 'target', 'release')
1428 1429
1429 1430 def __init__(
1430 1431 self, mpath, sources, rustlibname, subcrate, py3_features=None, **kw
1431 1432 ):
1432 1433 Extension.__init__(self, mpath, sources, **kw)
1433 1434 srcdir = self.rustsrcdir = os.path.join('rust', subcrate)
1434 1435 self.py3_features = py3_features
1435 1436
1436 1437 # adding Rust source and control files to depends so that the extension
1437 1438 # gets rebuilt if they've changed
1438 1439 self.depends.append(os.path.join(srcdir, 'Cargo.toml'))
1439 1440 cargo_lock = os.path.join(srcdir, 'Cargo.lock')
1440 1441 if os.path.exists(cargo_lock):
1441 1442 self.depends.append(cargo_lock)
1442 1443 for dirpath, subdir, fnames in os.walk(os.path.join(srcdir, 'src')):
1443 1444 self.depends.extend(
1444 1445 os.path.join(dirpath, fname)
1445 1446 for fname in fnames
1446 1447 if os.path.splitext(fname)[1] == '.rs'
1447 1448 )
1448 1449
1449 1450 @staticmethod
1450 1451 def rustdylibsuffix():
1451 1452 """Return the suffix for shared libraries produced by rustc.
1452 1453
1453 1454 See also: https://doc.rust-lang.org/reference/linkage.html
1454 1455 """
1455 1456 if sys.platform == 'darwin':
1456 1457 return '.dylib'
1457 1458 elif os.name == 'nt':
1458 1459 return '.dll'
1459 1460 else:
1460 1461 return '.so'
1461 1462
1462 1463 def rustbuild(self):
1463 1464 env = os.environ.copy()
1464 1465 if 'HGTEST_RESTOREENV' in env:
1465 1466 # Mercurial tests change HOME to a temporary directory,
1466 1467 # but, if installed with rustup, the Rust toolchain needs
1467 1468 # HOME to be correct (otherwise the 'no default toolchain'
1468 1469 # error message is issued and the build fails).
1469 1470 # This happens currently with test-hghave.t, which does
1470 1471 # invoke this build.
1471 1472
1472 1473 # Unix only fix (os.path.expanduser not really reliable if
1473 1474 # HOME is shadowed like this)
1474 1475 import pwd
1475 1476
1476 1477 env['HOME'] = pwd.getpwuid(os.getuid()).pw_dir
1477 1478
1478 1479 cargocmd = ['cargo', 'rustc', '--release']
1479 1480
1480 1481 feature_flags = []
1481 1482
1482 1483 if sys.version_info[0] == 3 and self.py3_features is not None:
1483 1484 feature_flags.append(self.py3_features)
1484 1485 cargocmd.append('--no-default-features')
1485 1486
1486 1487 rust_features = env.get("HG_RUST_FEATURES")
1487 1488 if rust_features:
1488 1489 feature_flags.append(rust_features)
1489 1490
1490 1491 cargocmd.extend(('--features', " ".join(feature_flags)))
1491 1492
1492 1493 cargocmd.append('--')
1493 1494 if sys.platform == 'darwin':
1494 1495 cargocmd.extend(
1495 1496 ("-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup")
1496 1497 )
1497 1498 try:
1498 1499 subprocess.check_call(cargocmd, env=env, cwd=self.rustsrcdir)
1499 1500 except OSError as exc:
1500 1501 if exc.errno == errno.ENOENT:
1501 1502 raise RustCompilationError("Cargo not found")
1502 1503 elif exc.errno == errno.EACCES:
1503 1504 raise RustCompilationError(
1504 1505 "Cargo found, but permisssion to execute it is denied"
1505 1506 )
1506 1507 else:
1507 1508 raise
1508 1509 except subprocess.CalledProcessError:
1509 1510 raise RustCompilationError(
1510 1511 "Cargo failed. Working directory: %r, "
1511 1512 "command: %r, environment: %r"
1512 1513 % (self.rustsrcdir, cargocmd, env)
1513 1514 )
1514 1515
1515 1516
1516 1517 class RustStandaloneExtension(RustExtension):
1517 1518 def __init__(self, pydottedname, rustcrate, dylibname, **kw):
1518 1519 RustExtension.__init__(
1519 1520 self, pydottedname, [], dylibname, rustcrate, **kw
1520 1521 )
1521 1522 self.dylibname = dylibname
1522 1523
1523 1524 def build(self, target_dir):
1524 1525 self.rustbuild()
1525 1526 target = [target_dir]
1526 1527 target.extend(self.name.split('.'))
1527 1528 target[-1] += DYLIB_SUFFIX
1528 1529 shutil.copy2(
1529 1530 os.path.join(
1530 1531 self.rusttargetdir, self.dylibname + self.rustdylibsuffix()
1531 1532 ),
1532 1533 os.path.join(*target),
1533 1534 )
1534 1535
1535 1536
1536 1537 extmodules = [
1537 1538 Extension(
1538 1539 'mercurial.cext.base85',
1539 1540 ['mercurial/cext/base85.c'],
1540 1541 include_dirs=common_include_dirs,
1541 1542 extra_compile_args=common_cflags,
1542 1543 depends=common_depends,
1543 1544 ),
1544 1545 Extension(
1545 1546 'mercurial.cext.bdiff',
1546 1547 ['mercurial/bdiff.c', 'mercurial/cext/bdiff.c'] + xdiff_srcs,
1547 1548 include_dirs=common_include_dirs,
1548 1549 extra_compile_args=common_cflags,
1549 1550 depends=common_depends + ['mercurial/bdiff.h'] + xdiff_headers,
1550 1551 ),
1551 1552 Extension(
1552 1553 'mercurial.cext.mpatch',
1553 1554 ['mercurial/mpatch.c', 'mercurial/cext/mpatch.c'],
1554 1555 include_dirs=common_include_dirs,
1555 1556 extra_compile_args=common_cflags,
1556 1557 depends=common_depends,
1557 1558 ),
1558 1559 Extension(
1559 1560 'mercurial.cext.parsers',
1560 1561 [
1561 1562 'mercurial/cext/charencode.c',
1562 1563 'mercurial/cext/dirs.c',
1563 1564 'mercurial/cext/manifest.c',
1564 1565 'mercurial/cext/parsers.c',
1565 1566 'mercurial/cext/pathencode.c',
1566 1567 'mercurial/cext/revlog.c',
1567 1568 ],
1568 1569 include_dirs=common_include_dirs,
1569 1570 extra_compile_args=common_cflags,
1570 1571 depends=common_depends
1571 1572 + [
1572 1573 'mercurial/cext/charencode.h',
1573 1574 'mercurial/cext/revlog.h',
1574 1575 ],
1575 1576 ),
1576 1577 Extension(
1577 1578 'mercurial.cext.osutil',
1578 1579 ['mercurial/cext/osutil.c'],
1579 1580 include_dirs=common_include_dirs,
1580 1581 extra_compile_args=common_cflags + osutil_cflags,
1581 1582 extra_link_args=osutil_ldflags,
1582 1583 depends=common_depends,
1583 1584 ),
1584 1585 Extension(
1585 1586 'mercurial.thirdparty.zope.interface._zope_interface_coptimizations',
1586 1587 [
1587 1588 'mercurial/thirdparty/zope/interface/_zope_interface_coptimizations.c',
1588 1589 ],
1589 1590 extra_compile_args=common_cflags,
1590 1591 ),
1591 1592 Extension(
1592 1593 'mercurial.thirdparty.sha1dc',
1593 1594 [
1594 1595 'mercurial/thirdparty/sha1dc/cext.c',
1595 1596 'mercurial/thirdparty/sha1dc/lib/sha1.c',
1596 1597 'mercurial/thirdparty/sha1dc/lib/ubc_check.c',
1597 1598 ],
1598 1599 extra_compile_args=common_cflags,
1599 1600 ),
1600 1601 Extension(
1601 1602 'hgext.fsmonitor.pywatchman.bser',
1602 1603 ['hgext/fsmonitor/pywatchman/bser.c'],
1603 1604 extra_compile_args=common_cflags,
1604 1605 ),
1605 1606 RustStandaloneExtension(
1606 1607 'mercurial.rustext', 'hg-cpython', 'librusthg', py3_features='python3'
1607 1608 ),
1608 1609 ]
1609 1610
1610 1611
1611 1612 sys.path.insert(0, 'contrib/python-zstandard')
1612 1613 import setup_zstd
1613 1614
1614 1615 zstd = setup_zstd.get_c_extension(
1615 1616 name='mercurial.zstd', root=os.path.abspath(os.path.dirname(__file__))
1616 1617 )
1617 1618 zstd.extra_compile_args += common_cflags
1618 1619 extmodules.append(zstd)
1619 1620
1620 1621 try:
1621 1622 from distutils import cygwinccompiler
1622 1623
1623 1624 # the -mno-cygwin option has been deprecated for years
1624 1625 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
1625 1626
1626 1627 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
1627 1628 def __init__(self, *args, **kwargs):
1628 1629 mingw32compilerclass.__init__(self, *args, **kwargs)
1629 1630 for i in 'compiler compiler_so linker_exe linker_so'.split():
1630 1631 try:
1631 1632 getattr(self, i).remove('-mno-cygwin')
1632 1633 except ValueError:
1633 1634 pass
1634 1635
1635 1636 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
1636 1637 except ImportError:
1637 1638 # the cygwinccompiler package is not available on some Python
1638 1639 # distributions like the ones from the optware project for Synology
1639 1640 # DiskStation boxes
1640 1641 class HackedMingw32CCompiler(object):
1641 1642 pass
1642 1643
1643 1644
1644 1645 if os.name == 'nt':
1645 1646 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
1646 1647 # extra_link_args to distutils.extensions.Extension() doesn't have any
1647 1648 # effect.
1648 1649 from distutils import msvccompiler
1649 1650
1650 1651 msvccompilerclass = msvccompiler.MSVCCompiler
1651 1652
1652 1653 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
1653 1654 def initialize(self):
1654 1655 msvccompilerclass.initialize(self)
1655 1656 # "warning LNK4197: export 'func' specified multiple times"
1656 1657 self.ldflags_shared.append('/ignore:4197')
1657 1658 self.ldflags_shared_debug.append('/ignore:4197')
1658 1659
1659 1660 msvccompiler.MSVCCompiler = HackedMSVCCompiler
1660 1661
1661 1662 packagedata = {
1662 1663 'mercurial': [
1663 1664 'locale/*/LC_MESSAGES/hg.mo',
1664 1665 'dummycert.pem',
1665 1666 ],
1666 1667 'mercurial.defaultrc': [
1667 1668 '*.rc',
1668 1669 ],
1669 1670 'mercurial.helptext': [
1670 1671 '*.txt',
1671 1672 ],
1672 1673 'mercurial.helptext.internals': [
1673 1674 '*.txt',
1674 1675 ],
1675 1676 }
1676 1677
1677 1678
1678 1679 def ordinarypath(p):
1679 1680 return p and p[0] != '.' and p[-1] != '~'
1680 1681
1681 1682
1682 1683 for root in ('templates',):
1683 1684 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
1684 1685 packagename = curdir.replace(os.sep, '.')
1685 1686 packagedata[packagename] = list(filter(ordinarypath, files))
1686 1687
1687 1688 datafiles = []
1688 1689
1689 1690 # distutils expects version to be str/unicode. Converting it to
1690 1691 # unicode on Python 2 still works because it won't contain any
1691 1692 # non-ascii bytes and will be implicitly converted back to bytes
1692 1693 # when operated on.
1693 1694 assert isinstance(version, str)
1694 1695 setupversion = version
1695 1696
1696 1697 extra = {}
1697 1698
1698 1699 py2exepackages = [
1699 1700 'hgdemandimport',
1700 1701 'hgext3rd',
1701 1702 'hgext',
1702 1703 'email',
1703 1704 # implicitly imported per module policy
1704 1705 # (cffi wouldn't be used as a frozen exe)
1705 1706 'mercurial.cext',
1706 1707 #'mercurial.cffi',
1707 1708 'mercurial.pure',
1708 1709 ]
1709 1710
1710 1711 py2exe_includes = []
1711 1712
1712 1713 py2exeexcludes = []
1713 1714 py2exedllexcludes = ['crypt32.dll']
1714 1715
1715 1716 if issetuptools:
1716 1717 extra['python_requires'] = supportedpy
1717 1718
1718 1719 if py2exeloaded:
1719 1720 extra['console'] = [
1720 1721 {
1721 1722 'script': 'hg',
1722 1723 'copyright': 'Copyright (C) 2005-2021 Olivia Mackall and others',
1723 1724 'product_version': version,
1724 1725 }
1725 1726 ]
1726 1727 # Sub command of 'build' because 'py2exe' does not handle sub_commands.
1727 1728 # Need to override hgbuild because it has a private copy of
1728 1729 # build.sub_commands.
1729 1730 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1730 1731 # put dlls in sub directory so that they won't pollute PATH
1731 1732 extra['zipfile'] = 'lib/library.zip'
1732 1733
1733 1734 # We allow some configuration to be supplemented via environment
1734 1735 # variables. This is better than setup.cfg files because it allows
1735 1736 # supplementing configs instead of replacing them.
1736 1737 extrapackages = os.environ.get('HG_PY2EXE_EXTRA_PACKAGES')
1737 1738 if extrapackages:
1738 1739 py2exepackages.extend(extrapackages.split(' '))
1739 1740
1740 1741 extra_includes = os.environ.get('HG_PY2EXE_EXTRA_INCLUDES')
1741 1742 if extra_includes:
1742 1743 py2exe_includes.extend(extra_includes.split(' '))
1743 1744
1744 1745 excludes = os.environ.get('HG_PY2EXE_EXTRA_EXCLUDES')
1745 1746 if excludes:
1746 1747 py2exeexcludes.extend(excludes.split(' '))
1747 1748
1748 1749 dllexcludes = os.environ.get('HG_PY2EXE_EXTRA_DLL_EXCLUDES')
1749 1750 if dllexcludes:
1750 1751 py2exedllexcludes.extend(dllexcludes.split(' '))
1751 1752
1752 1753 if os.environ.get('PYOXIDIZER'):
1753 1754 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1754 1755
1755 1756 if os.name == 'nt':
1756 1757 # Windows binary file versions for exe/dll files must have the
1757 1758 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
1758 1759 setupversion = setupversion.split(r'+', 1)[0]
1759 1760
1760 1761 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
1761 1762 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
1762 1763 if version:
1763 1764 version = version[0]
1764 1765 if sys.version_info[0] == 3:
1765 1766 version = version.decode('utf-8')
1766 1767 xcode4 = version.startswith('Xcode') and StrictVersion(
1767 1768 version.split()[1]
1768 1769 ) >= StrictVersion('4.0')
1769 1770 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
1770 1771 else:
1771 1772 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
1772 1773 # installed, but instead with only command-line tools. Assume
1773 1774 # that only happens on >= Lion, thus no PPC support.
1774 1775 xcode4 = True
1775 1776 xcode51 = False
1776 1777
1777 1778 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
1778 1779 # distutils.sysconfig
1779 1780 if xcode4:
1780 1781 os.environ['ARCHFLAGS'] = ''
1781 1782
1782 1783 # XCode 5.1 changes clang such that it now fails to compile if the
1783 1784 # -mno-fused-madd flag is passed, but the version of Python shipped with
1784 1785 # OS X 10.9 Mavericks includes this flag. This causes problems in all
1785 1786 # C extension modules, and a bug has been filed upstream at
1786 1787 # http://bugs.python.org/issue21244. We also need to patch this here
1787 1788 # so Mercurial can continue to compile in the meantime.
1788 1789 if xcode51:
1789 1790 cflags = get_config_var('CFLAGS')
1790 1791 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
1791 1792 os.environ['CFLAGS'] = (
1792 1793 os.environ.get('CFLAGS', '') + ' -Qunused-arguments'
1793 1794 )
1794 1795
1795 1796 setup(
1796 1797 name='mercurial',
1797 1798 version=setupversion,
1798 1799 author='Olivia Mackall and many others',
1799 1800 author_email='mercurial@mercurial-scm.org',
1800 1801 url='https://mercurial-scm.org/',
1801 1802 download_url='https://mercurial-scm.org/release/',
1802 1803 description=(
1803 1804 'Fast scalable distributed SCM (revision control, version '
1804 1805 'control) system'
1805 1806 ),
1806 1807 long_description=(
1807 1808 'Mercurial is a distributed SCM tool written in Python.'
1808 1809 ' It is used by a number of large projects that require'
1809 1810 ' fast, reliable distributed revision control, such as '
1810 1811 'Mozilla.'
1811 1812 ),
1812 1813 license='GNU GPLv2 or any later version',
1813 1814 classifiers=[
1814 1815 'Development Status :: 6 - Mature',
1815 1816 'Environment :: Console',
1816 1817 'Intended Audience :: Developers',
1817 1818 'Intended Audience :: System Administrators',
1818 1819 'License :: OSI Approved :: GNU General Public License (GPL)',
1819 1820 'Natural Language :: Danish',
1820 1821 'Natural Language :: English',
1821 1822 'Natural Language :: German',
1822 1823 'Natural Language :: Italian',
1823 1824 'Natural Language :: Japanese',
1824 1825 'Natural Language :: Portuguese (Brazilian)',
1825 1826 'Operating System :: Microsoft :: Windows',
1826 1827 'Operating System :: OS Independent',
1827 1828 'Operating System :: POSIX',
1828 1829 'Programming Language :: C',
1829 1830 'Programming Language :: Python',
1830 1831 'Topic :: Software Development :: Version Control',
1831 1832 ],
1832 1833 scripts=scripts,
1833 1834 packages=packages,
1834 1835 ext_modules=extmodules,
1835 1836 data_files=datafiles,
1836 1837 package_data=packagedata,
1837 1838 cmdclass=cmdclass,
1838 1839 distclass=hgdist,
1839 1840 options={
1840 1841 'py2exe': {
1841 1842 'bundle_files': 3,
1842 1843 'dll_excludes': py2exedllexcludes,
1843 1844 'includes': py2exe_includes,
1844 1845 'excludes': py2exeexcludes,
1845 1846 'packages': py2exepackages,
1846 1847 },
1847 1848 'bdist_mpkg': {
1848 1849 'zipdist': False,
1849 1850 'license': 'COPYING',
1850 1851 'readme': 'contrib/packaging/macosx/Readme.html',
1851 1852 'welcome': 'contrib/packaging/macosx/Welcome.html',
1852 1853 },
1853 1854 },
1854 1855 **extra
1855 1856 )
General Comments 0
You need to be logged in to leave comments. Login now