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