setup.py
1383 lines
| 50.3 KiB
| text/x-python
|
PythonLexer
|
r575 | # | ||
# This is the mercurial setup script. | ||||
|
r0 | # | ||
|
r4816 | # 'python setup.py install', or | ||
# 'python setup.py --help' for more options | ||||
|
r0 | |||
|
r33592 | import os | ||
|
r33588 | supportedpy = '~= 2.7' | ||
|
r33592 | if os.environ.get('HGALLOWPYTHON3', ''): | ||
|
r33588 | # Mercurial will never work on Python 3 before 3.5 due to a lack | ||
# of % formatting on bytestrings, and can't work on 3.6.0 or 3.6.1 | ||||
# due to a bug in % formatting in bytestrings. | ||||
|
r39608 | # We cannot support Python 3.5.0, 3.5.1, 3.5.2 because of bug in | ||
# codecs.escape_encode() where it raises SystemError on empty bytestring | ||||
# bug link: https://bugs.python.org/issue25270 | ||||
|
r33588 | # | ||
# TODO: when we actually work on Python 3, use this string as the | ||||
# actual supportedpy string. | ||||
supportedpy = ','.join([ | ||||
'>=2.7', | ||||
'!=3.0.*', | ||||
'!=3.1.*', | ||||
'!=3.2.*', | ||||
'!=3.3.*', | ||||
'!=3.4.*', | ||||
|
r39608 | '!=3.5.0', | ||
'!=3.5.1', | ||||
'!=3.5.2', | ||||
|
r33588 | '!=3.6.0', | ||
'!=3.6.1', | ||||
]) | ||||
|
r14295 | import sys, platform | ||
|
r11532 | if sys.version_info[0] >= 3: | ||
|
r20696 | printf = eval('print') | ||
libdir_escape = 'unicode_escape' | ||||
|
r35246 | def sysstr(s): | ||
return s.decode('latin-1') | ||||
|
r11532 | else: | ||
|
r20696 | libdir_escape = 'string_escape' | ||
def printf(*args, **kwargs): | ||||
f = kwargs.get('file', sys.stdout) | ||||
end = kwargs.get('end', '\n') | ||||
|
r27348 | f.write(b' '.join(args) + end) | ||
|
r35246 | def sysstr(s): | ||
return s | ||||
|
r11532 | |||
|
r33589 | # Attempt to guide users to a modern pip - this means that 2.6 users | ||
# should have a chance of getting a 4.2 release, and when we ratchet | ||||
# the version requirement forward again hopefully everyone will get | ||||
# something that works for them. | ||||
if sys.version_info < (2, 7, 0, 'final'): | ||||
pip_message = ('This may be due to an out of date pip. ' | ||||
'Make sure you have pip >= 9.0.1.') | ||||
try: | ||||
import pip | ||||
pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]]) | ||||
if pip_version < (9, 0, 1) : | ||||
pip_message = ( | ||||
'Your pip version is out of date, please install ' | ||||
'pip >= 9.0.1. pip {} detected.'.format(pip.__version__)) | ||||
else: | ||||
# pip is new enough - it must be something else | ||||
pip_message = '' | ||||
except Exception: | ||||
pass | ||||
error = """ | ||||
Mercurial does not support Python older than 2.7. | ||||
Python {py} detected. | ||||
{pip} | ||||
""".format(py=sys.version_info, pip=pip_message) | ||||
printf(error, file=sys.stderr) | ||||
sys.exit(1) | ||||
|
r36404 | # We don't yet officially support Python 3. But we want to allow developers to | ||
# hack on. Detect and disallow running on Python 3 by default. But provide a | ||||
# backdoor to enable working on Python 3. | ||||
if sys.version_info[0] != 2: | ||||
badpython = True | ||||
# Allow Python 3 from source checkouts. | ||||
|
r38744 | if os.path.isdir('.hg') or 'HGPYTHON3' in os.environ: | ||
|
r36404 | badpython = False | ||
if badpython: | ||||
error = """ | ||||
Mercurial only supports Python 2.7. | ||||
Python {py} detected. | ||||
Please re-run with Python 2.7. | ||||
""".format(py=sys.version_info) | ||||
printf(error, file=sys.stderr) | ||||
sys.exit(1) | ||||
|
r7558 | # Solaris Python packaging brain damage | ||
try: | ||||
import hashlib | ||||
sha = hashlib.sha1() | ||||
|
r16688 | except ImportError: | ||
|
r7558 | try: | ||
import sha | ||||
|
r22198 | sha.sha # silence unused import warning | ||
|
r16688 | except ImportError: | ||
|
r7558 | raise SystemExit( | ||
"Couldn't import standard hashlib (incomplete Python install).") | ||||
try: | ||||
import zlib | ||||
|
r22198 | zlib.compressobj # silence unused import warning | ||
|
r16688 | except ImportError: | ||
|
r7558 | raise SystemExit( | ||
"Couldn't import standard zlib (incomplete Python install).") | ||||
|
r14295 | # The base IronPython distribution (as of 2.7.1) doesn't support bz2 | ||
isironpython = False | ||||
|
r10761 | try: | ||
|
r16683 | isironpython = (platform.python_implementation() | ||
.lower().find("ironpython") != -1) | ||||
|
r16688 | except AttributeError: | ||
|
r14295 | pass | ||
if isironpython: | ||||
|
r15492 | sys.stderr.write("warning: IronPython detected (no bz2 support)\n") | ||
|
r14295 | else: | ||
try: | ||||
import bz2 | ||||
|
r22198 | bz2.BZ2Compressor # silence unused import warning | ||
|
r16688 | except ImportError: | ||
|
r14295 | raise SystemExit( | ||
"Couldn't import standard bz2 (incomplete Python install).") | ||||
|
r10761 | |||
|
r24192 | ispypy = "PyPy" in sys.version | ||
|
r41003 | hgrustext = os.environ.get('HGWITHRUSTEXT') | ||
# TODO record it for proper rebuild upon changes | ||||
# (see mercurial/__modulepolicy__.py) | ||||
if hgrustext != 'cpython' and hgrustext is not None: | ||||
hgrustext = 'direct-ffi' | ||||
|
r40309 | |||
|
r29020 | import ctypes | ||
|
r41002 | import errno | ||
|
r33592 | import stat, subprocess, time | ||
|
r21038 | import re | ||
|
r6251 | import shutil | ||
import tempfile | ||||
|
r11468 | from distutils import log | ||
|
r31289 | # We have issues with setuptools on some platforms and builders. Until | ||
# those are resolved, setuptools is opt-in except for platforms where | ||||
# we don't have issues. | ||||
|
r33601 | issetuptools = (os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ) | ||
if issetuptools: | ||||
|
r26600 | from setuptools import setup | ||
else: | ||||
from distutils.core import setup | ||||
|
r30408 | from distutils.ccompiler import new_compiler | ||
|
r26600 | from distutils.core import Command, Extension | ||
|
r7722 | from distutils.dist import Distribution | ||
|
r7649 | from distutils.command.build import build | ||
|
r11468 | from distutils.command.build_ext import build_ext | ||
|
r7722 | from distutils.command.build_py import build_py | ||
|
r27268 | from distutils.command.build_scripts import build_scripts | ||
|
r32647 | from distutils.command.install import install | ||
|
r22640 | from distutils.command.install_lib import install_lib | ||
|
r12661 | from distutils.command.install_scripts import install_scripts | ||
|
r7649 | from distutils.spawn import spawn, find_executable | ||
|
r23677 | from distutils import file_util | ||
|
r27268 | from distutils.errors import ( | ||
CCompilerError, | ||||
DistutilsError, | ||||
DistutilsExecError, | ||||
) | ||||
|
r21038 | from distutils.sysconfig import get_python_inc, get_config_var | ||
|
r13594 | from distutils.version import StrictVersion | ||
|
r157 | |||
|
r40485 | # Explain to distutils.StrictVersion how our release candidates are versionned | ||
StrictVersion.version_re = re.compile(r'^(\d+)\.(\d+)(\.(\d+))?-?(rc(\d+))?$') | ||||
|
r35229 | def write_if_changed(path, content): | ||
"""Write content to a file iff the content hasn't changed.""" | ||||
if os.path.exists(path): | ||||
with open(path, 'rb') as fh: | ||||
current = fh.read() | ||||
else: | ||||
current = b'' | ||||
if current != content: | ||||
with open(path, 'wb') as fh: | ||||
fh.write(content) | ||||
|
r6513 | scripts = ['hg'] | ||
if os.name == 'nt': | ||||
|
r27268 | # We remove hg.bat if we are able to build hg.exe. | ||
|
r6513 | scripts.append('contrib/win32/hg.bat') | ||
|
r3893 | |||
|
r31559 | def cancompile(cc, code): | ||
|
r6251 | tmpdir = tempfile.mkdtemp(prefix='hg-install-') | ||
|
r6373 | devnull = oldstderr = None | ||
|
r6251 | try: | ||
|
r31559 | fname = os.path.join(tmpdir, 'testcomp.c') | ||
|
r25089 | f = open(fname, 'w') | ||
|
r31559 | f.write(code) | ||
|
r25089 | f.close() | ||
# Redirect stderr to /dev/null to hide any error messages | ||||
# from the compiler. | ||||
# This will have to be changed if we ever have to check | ||||
# for a function on Windows. | ||||
devnull = open('/dev/null', 'w') | ||||
oldstderr = os.dup(sys.stderr.fileno()) | ||||
os.dup2(devnull.fileno(), sys.stderr.fileno()) | ||||
objects = cc.compile([fname], output_dir=tmpdir) | ||||
cc.link_executable(objects, os.path.join(tmpdir, "a.out")) | ||||
|
r6251 | return True | ||
|
r25089 | except Exception: | ||
return False | ||||
|
r6251 | finally: | ||
|
r6373 | if oldstderr is not None: | ||
os.dup2(oldstderr, sys.stderr.fileno()) | ||||
if devnull is not None: | ||||
devnull.close() | ||||
|
r6251 | shutil.rmtree(tmpdir) | ||
|
r31559 | # simplified version of distutils.ccompiler.CCompiler.has_function | ||
# that actually removes its temporary files. | ||||
def hasfunction(cc, funcname): | ||||
code = 'int main(void) { %s(); }\n' % funcname | ||||
return cancompile(cc, code) | ||||
|
r31560 | def hasheader(cc, headername): | ||
code = '#include <%s>\nint main(void) { return 0; }\n' % headername | ||||
return cancompile(cc, code) | ||||
|
r1283 | # py2exe needs to be installed to work | ||
try: | ||||
|
r1294 | import py2exe | ||
|
r22198 | py2exe.Distribution # silence unused import warning | ||
|
r10400 | py2exeloaded = True | ||
|
r15527 | # import py2exe's patched Distribution class | ||
from distutils.core import Distribution | ||||
|
r1284 | except ImportError: | ||
|
r10400 | py2exeloaded = False | ||
|
r1283 | |||
|
r42016 | def runcmd(cmd, env, cwd=None): | ||
|
r32886 | p = subprocess.Popen(cmd, stdout=subprocess.PIPE, | ||
|
r42016 | stderr=subprocess.PIPE, env=env, cwd=cwd) | ||
|
r32886 | out, err = p.communicate() | ||
|
r33111 | return p.returncode, out, err | ||
|
r13636 | |||
|
r33113 | class hgcommand(object): | ||
|
r33114 | def __init__(self, cmd, env): | ||
self.cmd = cmd | ||||
self.env = env | ||||
|
r9615 | |||
|
r33113 | def run(self, args): | ||
cmd = self.cmd + args | ||||
returncode, out, err = runcmd(cmd, self.env) | ||||
|
r33599 | err = filterhgerr(err) | ||
|
r33113 | if err or returncode != 0: | ||
printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr) | ||||
|
r33599 | printf(err, file=sys.stderr) | ||
|
r33113 | return '' | ||
return out | ||||
|
r8548 | |||
|
r33599 | def filterhgerr(err): | ||
# If root is executing setup.py, but the repository is owned by | ||||
# another user (as in "sudo python setup.py install") we will get | ||||
# trust warnings since the .hg/hgrc file is untrusted. That is | ||||
# fine, we don't want to load it anyway. Python may warn about | ||||
# a missing __init__.py in mercurial/locale, we also ignore that. | ||||
err = [e for e in err.splitlines() | ||||
if (not e.startswith(b'not trusting file') | ||||
and not e.startswith(b'warning: Not importing') | ||||
|
r34924 | and not e.startswith(b'obsolete feature not enabled') | ||
|
r36702 | and not e.startswith(b'*** failed to import extension') | ||
|
r40022 | and not e.startswith(b'devel-warn:') | ||
and not (e.startswith(b'(third party extension') | ||||
and e.endswith(b'or newer of Mercurial; disabling)')))] | ||||
|
r33599 | return b'\n'.join(b' ' + e for e in err) | ||
|
r33114 | def findhg(): | ||
"""Try to figure out how we should invoke hg for examining the local | ||||
repository contents. | ||||
Returns an hgcommand object.""" | ||||
# By default, prefer the "hg" command in the user's path. This was | ||||
# presumably the hg command that the user used to create this repository. | ||||
# | ||||
# This repository may require extensions or other settings that would not | ||||
# be enabled by running the hg script directly from this local repository. | ||||
hgenv = os.environ.copy() | ||||
# Use HGPLAIN to disable hgrc settings that would change output formatting, | ||||
# and disable localization for the same reasons. | ||||
hgenv['HGPLAIN'] = '1' | ||||
hgenv['LANGUAGE'] = 'C' | ||||
hgcmd = ['hg'] | ||||
# Run a simple "hg log" command just to see if using hg from the user's | ||||
|
r41016 | # path works and can successfully interact with this repository. Windows | ||
# gives precedence to hg.exe in the current directory, so fall back to the | ||||
# python invocation of local hg, where pythonXY.dll can always be found. | ||||
|
r33114 | check_cmd = ['log', '-r.', '-Ttest'] | ||
|
r41016 | if os.name != 'nt': | ||
try: | ||||
retcode, out, err = runcmd(hgcmd + check_cmd, hgenv) | ||||
except EnvironmentError: | ||||
retcode = -1 | ||||
if retcode == 0 and not filterhgerr(err): | ||||
return hgcommand(hgcmd, hgenv) | ||||
|
r33114 | |||
# Fall back to trying the local hg installation. | ||||
hgenv = localhgenv() | ||||
hgcmd = [sys.executable, 'hg'] | ||||
try: | ||||
retcode, out, err = runcmd(hgcmd + check_cmd, hgenv) | ||||
except EnvironmentError: | ||||
retcode = -1 | ||||
|
r33599 | if retcode == 0 and not filterhgerr(err): | ||
|
r33114 | return hgcommand(hgcmd, hgenv) | ||
raise SystemExit('Unable to find a working hg binary to extract the ' | ||||
'version from the repository tags') | ||||
def localhgenv(): | ||||
"""Get an environment dictionary to use for invoking or importing | ||||
mercurial from the local repository.""" | ||||
|
r33112 | # Execute hg out of this directory with a custom environment which takes | ||
# care to not use any hgrc files and do no localization. | ||||
env = {'HGMODULEPOLICY': 'py', | ||||
'HGRCPATH': '', | ||||
'LANGUAGE': 'C', | ||||
'PATH': ''} # make pypi modules that use os.environ['PATH'] happy | ||||
if 'LD_LIBRARY_PATH' in os.environ: | ||||
env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH'] | ||||
if 'SystemRoot' in os.environ: | ||||
# SystemRoot is required by Windows to load various DLLs. See: | ||||
# https://bugs.python.org/issue13524#msg148850 | ||||
env['SystemRoot'] = os.environ['SystemRoot'] | ||||
|
r33117 | return env | ||
|
r33112 | |||
version = '' | ||||
|
r15367 | |||
|
r8547 | if os.path.isdir('.hg'): | ||
|
r33114 | hg = findhg() | ||
|
r33113 | cmd = ['log', '-r', '.', '--template', '{tags}\n'] | ||
|
r35246 | numerictags = [t for t in sysstr(hg.run(cmd)).split() if t[0:1].isdigit()] | ||
hgid = sysstr(hg.run(['id', '-i'])).strip() | ||||
|
r33110 | if not hgid: | ||
# Bail out if hg is having problems interacting with this repository, | ||||
# rather than falling through and producing a bogus version number. | ||||
# Continuing with an invalid version number will break extensions | ||||
# that define minimumhgversion. | ||||
raise SystemExit('Unable to determine hg version from local repository') | ||||
|
r17709 | if numerictags: # tag(s) found | ||
version = numerictags[-1] | ||||
if hgid.endswith('+'): # propagate the dirty status to the tag | ||||
|
r9615 | version += '+' | ||
|
r17709 | else: # no tag found | ||
|
r33113 | ltagcmd = ['parents', '--template', '{latesttag}'] | ||
|
r35246 | ltag = sysstr(hg.run(ltagcmd)) | ||
|
r33113 | changessincecmd = ['log', '-T', 'x\n', '-r', "only(.,'%s')" % ltag] | ||
changessince = len(hg.run(changessincecmd).splitlines()) | ||||
|
r23647 | version = '%s+%s-%s' % (ltag, changessince, hgid) | ||
|
r9615 | if version.endswith('+'): | ||
version += time.strftime('%Y%m%d') | ||||
elif os.path.exists('.hg_archival.txt'): | ||||
|
r10124 | kw = dict([[t.strip() for t in l.split(':', 1)] | ||
for l in open('.hg_archival.txt')]) | ||||
|
r9615 | if 'tag' in kw: | ||
|
r27637 | version = kw['tag'] | ||
|
r9615 | elif 'latesttag' in kw: | ||
|
r23646 | if 'changessincelatesttag' in kw: | ||
version = '%(latesttag)s+%(changessincelatesttag)s-%(node).12s' % kw | ||||
else: | ||||
version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw | ||||
|
r8547 | else: | ||
|
r9615 | version = kw.get('node', '')[:12] | ||
|
r7632 | |||
|
r8548 | if version: | ||
|
r35229 | versionb = version | ||
if not isinstance(versionb, bytes): | ||||
versionb = versionb.encode('ascii') | ||||
write_if_changed('mercurial/__version__.py', b''.join([ | ||||
b'# this file is autogenerated by setup.py\n' | ||||
|
r38207 | b'version = b"%s"\n' % versionb, | ||
|
r35229 | ])) | ||
|
r8493 | |||
try: | ||||
|
r28431 | oldpolicy = os.environ.get('HGMODULEPOLICY', None) | ||
os.environ['HGMODULEPOLICY'] = 'py' | ||||
|
r8493 | from mercurial import __version__ | ||
version = __version__.version | ||||
except ImportError: | ||||
|
r38744 | version = b'unknown' | ||
|
r28431 | finally: | ||
if oldpolicy is None: | ||||
del os.environ['HGMODULEPOLICY'] | ||||
else: | ||||
os.environ['HGMODULEPOLICY'] = oldpolicy | ||||
|
r7632 | |||
|
r15460 | class hgbuild(build): | ||
# Insert hgbuildmo first so that files in mercurial/locale/ are found | ||||
# when build_py is run next. | ||||
|
r28398 | sub_commands = [('build_mo', None)] + build.sub_commands | ||
|
r15460 | |||
|
r15523 | class hgbuildmo(build): | ||
|
r7649 | |||
description = "build translations (.mo files)" | ||||
def run(self): | ||||
if not find_executable('msgfmt'): | ||||
self.warn("could not find msgfmt executable, no translations " | ||||
"will be built") | ||||
return | ||||
podir = 'i18n' | ||||
if not os.path.isdir(podir): | ||||
self.warn("could not find %s/ directory" % podir) | ||||
return | ||||
join = os.path.join | ||||
for po in os.listdir(podir): | ||||
if not po.endswith('.po'): | ||||
continue | ||||
pofile = join(podir, po) | ||||
modir = join('locale', po[:-3], 'LC_MESSAGES') | ||||
mofile = join(modir, 'hg.mo') | ||||
|
r9999 | mobuildfile = join('mercurial', mofile) | ||
cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile] | ||||
|
r7720 | if sys.platform != 'sunos5': | ||
# msgfmt on Solaris does not know about -c | ||||
cmd.append('-c') | ||||
|
r9999 | self.mkpath(join('mercurial', modir)) | ||
self.make_file([pofile], mobuildfile, spawn, (cmd,)) | ||||
|
r7649 | |||
|
r12661 | |||
|
r15458 | class hgdist(Distribution): | ||
|
r29505 | pure = False | ||
cffi = ispypy | ||||
|
r15458 | |||
|
r41925 | global_options = Distribution.global_options + [ | ||
('pure', None, "use pure (slow) Python code instead of C extensions"), | ||||
] | ||||
|
r7722 | |||
|
r15459 | def has_ext_modules(self): | ||
# self.ext_modules is emptied in hgbuildpy.finalize_options which is | ||||
# too late for some cases | ||||
return not self.pure and Distribution.has_ext_modules(self) | ||||
|
r30450 | # This is ugly as a one-liner. So use a variable. | ||
buildextnegops = dict(getattr(build_ext, 'negative_options', {})) | ||||
buildextnegops['no-zstd'] = 'zstd' | ||||
|
r11468 | class hgbuildext(build_ext): | ||
|
r30450 | user_options = build_ext.user_options + [ | ||
('zstd', None, 'compile zstd bindings [default]'), | ||||
('no-zstd', None, 'do not compile zstd bindings'), | ||||
] | ||||
boolean_options = build_ext.boolean_options + ['zstd'] | ||||
negative_opt = buildextnegops | ||||
def initialize_options(self): | ||||
self.zstd = True | ||||
return build_ext.initialize_options(self) | ||||
def build_extensions(self): | ||||
|
r41003 | ruststandalones = [e for e in self.extensions | ||
if isinstance(e, RustStandaloneExtension)] | ||||
self.extensions = [e for e in self.extensions | ||||
if e not in ruststandalones] | ||||
|
r30450 | # Filter out zstd if disabled via argument. | ||
if not self.zstd: | ||||
self.extensions = [e for e in self.extensions | ||||
if e.name != 'mercurial.zstd'] | ||||
|
r41003 | for rustext in ruststandalones: | ||
rustext.build('' if self.inplace else self.build_lib) | ||||
|
r30450 | return build_ext.build_extensions(self) | ||
|
r11468 | |||
def build_extension(self, ext): | ||||
|
r40309 | if isinstance(ext, RustExtension): | ||
ext.rustbuild() | ||||
|
r11468 | try: | ||
build_ext.build_extension(self, ext) | ||||
except CCompilerError: | ||||
|
r12501 | if not getattr(ext, 'optional', False): | ||
|
r11468 | raise | ||
log.warn("Failed to build optional extension '%s' (skipping)", | ||||
ext.name) | ||||
|
r27268 | class hgbuildscripts(build_scripts): | ||
def run(self): | ||||
|
r28041 | if os.name != 'nt' or self.distribution.pure: | ||
|
r27268 | return build_scripts.run(self) | ||
exebuilt = False | ||||
try: | ||||
self.run_command('build_hgexe') | ||||
exebuilt = True | ||||
except (DistutilsError, CCompilerError): | ||||
log.warn('failed to build optional hg.exe') | ||||
if exebuilt: | ||||
# Copying hg.exe to the scripts build directory ensures it is | ||||
# installed by the install_scripts command. | ||||
hgexecommand = self.get_finalized_command('build_hgexe') | ||||
dest = os.path.join(self.build_dir, 'hg.exe') | ||||
self.mkpath(self.build_dir) | ||||
self.copy_file(hgexecommand.hgexepath, dest) | ||||
# Remove hg.bat because it is redundant with hg.exe. | ||||
self.scripts.remove('contrib/win32/hg.bat') | ||||
return build_scripts.run(self) | ||||
|
r10000 | class hgbuildpy(build_py): | ||
|
r7722 | def finalize_options(self): | ||
build_py.finalize_options(self) | ||||
if self.distribution.pure: | ||||
self.distribution.ext_modules = [] | ||||
|
r29505 | elif self.distribution.cffi: | ||
|
r30346 | from mercurial.cffi import ( | ||
|
r32505 | bdiffbuild, | ||
mpatchbuild, | ||||
|
r30346 | ) | ||
|
r32505 | exts = [mpatchbuild.ffi.distutils_extension(), | ||
bdiffbuild.ffi.distutils_extension()] | ||||
|
r29505 | # cffi modules go here | ||
|
r29600 | if sys.platform == 'darwin': | ||
|
r32505 | from mercurial.cffi import osutilbuild | ||
exts.append(osutilbuild.ffi.distutils_extension()) | ||||
|
r29505 | self.distribution.ext_modules = exts | ||
|
r12649 | else: | ||
|
r18905 | h = os.path.join(get_python_inc(), 'Python.h') | ||
if not os.path.exists(h): | ||||
|
r16683 | raise SystemExit('Python headers are required to build ' | ||
|
r18905 | 'Mercurial but weren\'t found in %s' % h) | ||
|
r7722 | |||
|
r28430 | def run(self): | ||
|
r32653 | basepath = os.path.join(self.build_lib, 'mercurial') | ||
self.mkpath(basepath) | ||||
|
r28430 | if self.distribution.pure: | ||
modulepolicy = 'py' | ||||
|
r32251 | elif self.build_lib == '.': | ||
# in-place build should run without rebuilding C extensions | ||||
modulepolicy = 'allow' | ||||
|
r28430 | else: | ||
modulepolicy = 'c' | ||||
|
r35229 | |||
content = b''.join([ | ||||
b'# this file is autogenerated by setup.py\n', | ||||
b'modulepolicy = b"%s"\n' % modulepolicy.encode('ascii'), | ||||
]) | ||||
write_if_changed(os.path.join(basepath, '__modulepolicy__.py'), | ||||
content) | ||||
|
r27222 | |||
|
r28430 | build_py.run(self) | ||
|
r7722 | |||
|
r14538 | class buildhgextindex(Command): | ||
description = 'generate prebuilt index of hgext (for frozen package)' | ||||
user_options = [] | ||||
_indexfilename = 'hgext/__index__.py' | ||||
def initialize_options(self): | ||||
pass | ||||
def finalize_options(self): | ||||
pass | ||||
def run(self): | ||||
if os.path.exists(self._indexfilename): | ||||
|
r28418 | with open(self._indexfilename, 'w') as f: | ||
f.write('# empty\n') | ||||
|
r14538 | |||
# here no extension enabled, disabled() lists up everything | ||||
code = ('import pprint; from mercurial import extensions; ' | ||||
'pprint.pprint(extensions.disabled())') | ||||
|
r33114 | returncode, out, err = runcmd([sys.executable, '-c', code], | ||
localhgenv()) | ||||
|
r33111 | if err or returncode != 0: | ||
|
r14538 | raise DistutilsExecError(err) | ||
|
r28418 | with open(self._indexfilename, 'w') as f: | ||
f.write('# this file is autogenerated by setup.py\n') | ||||
f.write('docs = ') | ||||
f.write(out) | ||||
|
r14538 | |||
|
r17061 | class buildhgexe(build_ext): | ||
description = 'compile hg.exe from mercurial/exewrapper.c' | ||||
|
r34531 | user_options = build_ext.user_options + [ | ||
('long-paths-support', None, 'enable support for long paths on ' | ||||
'Windows (off by default and ' | ||||
'experimental)'), | ||||
] | ||||
LONG_PATHS_MANIFEST = """ | ||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> | ||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> | ||||
<application> | ||||
<windowsSettings | ||||
xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings"> | ||||
<ws2:longPathAware>true</ws2:longPathAware> | ||||
</windowsSettings> | ||||
</application> | ||||
</assembly>""" | ||||
def initialize_options(self): | ||||
build_ext.initialize_options(self) | ||||
self.long_paths_support = False | ||||
|
r17061 | |||
def build_extensions(self): | ||||
if os.name != 'nt': | ||||
return | ||||
|
r17246 | if isinstance(self.compiler, HackedMingw32CCompiler): | ||
self.compiler.compiler_so = self.compiler.compiler # no -mdll | ||||
self.compiler.dll_libraries = [] # no -lmsrvc90 | ||||
|
r29020 | |||
# Different Python installs can have different Python library | ||||
# names. e.g. the official CPython distribution uses pythonXY.dll | ||||
# and MinGW uses libpythonX.Y.dll. | ||||
_kernel32 = ctypes.windll.kernel32 | ||||
_kernel32.GetModuleFileNameA.argtypes = [ctypes.c_void_p, | ||||
ctypes.c_void_p, | ||||
ctypes.c_ulong] | ||||
_kernel32.GetModuleFileNameA.restype = ctypes.c_ulong | ||||
size = 1000 | ||||
buf = ctypes.create_string_buffer(size + 1) | ||||
filelen = _kernel32.GetModuleFileNameA(sys.dllhandle, ctypes.byref(buf), | ||||
size) | ||||
if filelen > 0 and filelen != size: | ||||
dllbasename = os.path.basename(buf.value) | ||||
|
r39644 | if not dllbasename.lower().endswith(b'.dll'): | ||
|
r29020 | raise SystemExit('Python DLL does not end with .dll: %s' % | ||
dllbasename) | ||||
pythonlib = dllbasename[:-4] | ||||
else: | ||||
log.warn('could not determine Python DLL filename; ' | ||||
'assuming pythonXY') | ||||
hv = sys.hexversion | ||||
pythonlib = 'python%d%d' % (hv >> 24, (hv >> 16) & 0xff) | ||||
log.info('using %s as Python library name' % pythonlib) | ||||
|
r28418 | with open('mercurial/hgpythonlib.h', 'wb') as f: | ||
|
r39644 | f.write(b'/* this file is autogenerated by setup.py */\n') | ||
f.write(b'#define HGPYTHONLIB "%s"\n' % pythonlib) | ||||
|
r40433 | |||
macros = None | ||||
if sys.version_info[0] >= 3: | ||||
macros = [('_UNICODE', None), ('UNICODE', None)] | ||||
|
r17061 | objects = self.compiler.compile(['mercurial/exewrapper.c'], | ||
|
r40433 | output_dir=self.build_temp, | ||
macros=macros) | ||||
|
r17061 | dir = os.path.dirname(self.get_ext_fullpath('dummy')) | ||
|
r34531 | self.hgtarget = os.path.join(dir, 'hg') | ||
self.compiler.link_executable(objects, self.hgtarget, | ||||
|
r17732 | libraries=[], | ||
|
r17061 | output_dir=self.build_temp) | ||
|
r34531 | if self.long_paths_support: | ||
self.addlongpathsmanifest() | ||||
def addlongpathsmanifest(self): | ||||
|
r41674 | r"""Add manifest pieces so that hg.exe understands long paths | ||
|
r34531 | |||
This is an EXPERIMENTAL feature, use with care. | ||||
To enable long paths support, one needs to do two things: | ||||
- build Mercurial with --long-paths-support option | ||||
- change HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\ | ||||
LongPathsEnabled to have value 1. | ||||
Please ignore 'warning 81010002: Unrecognized Element "longPathAware"'; | ||||
it happens because Mercurial uses mt.exe circa 2008, which is not | ||||
yet aware of long paths support in the manifest (I think so at least). | ||||
This does not stop mt.exe from embedding/merging the XML properly. | ||||
Why resource #1 should be used for .exe manifests? I don't know and | ||||
wasn't able to find an explanation for mortals. But it seems to work. | ||||
""" | ||||
exefname = self.compiler.executable_filename(self.hgtarget) | ||||
fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest') | ||||
os.close(fdauto) | ||||
with open(manfname, 'w') as f: | ||||
f.write(self.LONG_PATHS_MANIFEST) | ||||
log.info("long paths manifest is written to '%s'" % manfname) | ||||
inputresource = '-inputresource:%s;#1' % exefname | ||||
outputresource = '-outputresource:%s;#1' % exefname | ||||
log.info("running mt.exe to update hg.exe's manifest in-place") | ||||
# supplying both -manifest and -inputresource to mt.exe makes | ||||
# it merge the embedded and supplied manifests in the -outputresource | ||||
self.spawn(['mt.exe', '-nologo', '-manifest', manfname, | ||||
inputresource, outputresource]) | ||||
log.info("done updating hg.exe's manifest") | ||||
os.remove(manfname) | ||||
|
r17061 | |||
|
r27268 | @property | ||
def hgexepath(self): | ||||
dir = os.path.dirname(self.get_ext_fullpath('dummy')) | ||||
return os.path.join(self.build_temp, dir, 'hg.exe') | ||||
|
r42016 | class hgbuilddoc(Command): | ||
description = 'build documentation' | ||||
user_options = [ | ||||
('man', None, 'generate man pages'), | ||||
('html', None, 'generate html pages'), | ||||
] | ||||
def initialize_options(self): | ||||
self.man = None | ||||
self.html = None | ||||
def finalize_options(self): | ||||
# If --man or --html are set, only generate what we're told to. | ||||
# Otherwise generate everything. | ||||
have_subset = self.man is not None or self.html is not None | ||||
if have_subset: | ||||
self.man = True if self.man else False | ||||
self.html = True if self.html else False | ||||
else: | ||||
self.man = True | ||||
self.html = True | ||||
def run(self): | ||||
def normalizecrlf(p): | ||||
with open(p, 'rb') as fh: | ||||
orig = fh.read() | ||||
if b'\r\n' not in orig: | ||||
return | ||||
log.info('normalizing %s to LF line endings' % p) | ||||
with open(p, 'wb') as fh: | ||||
fh.write(orig.replace(b'\r\n', b'\n')) | ||||
def gentxt(root): | ||||
txt = 'doc/%s.txt' % root | ||||
log.info('generating %s' % txt) | ||||
res, out, err = runcmd( | ||||
[sys.executable, 'gendoc.py', root], | ||||
os.environ, | ||||
cwd='doc') | ||||
if res: | ||||
raise SystemExit('error running gendoc.py: %s' % | ||||
'\n'.join([out, err])) | ||||
with open(txt, 'wb') as fh: | ||||
fh.write(out) | ||||
def gengendoc(root): | ||||
gendoc = 'doc/%s.gendoc.txt' % root | ||||
log.info('generating %s' % gendoc) | ||||
res, out, err = runcmd( | ||||
[sys.executable, 'gendoc.py', '%s.gendoc' % root], | ||||
os.environ, | ||||
cwd='doc') | ||||
if res: | ||||
raise SystemExit('error running gendoc: %s' % | ||||
'\n'.join([out, err])) | ||||
with open(gendoc, 'wb') as fh: | ||||
fh.write(out) | ||||
def genman(root): | ||||
log.info('generating doc/%s' % root) | ||||
res, out, err = runcmd( | ||||
[sys.executable, 'runrst', 'hgmanpage', '--halt', 'warning', | ||||
'--strip-elements-with-class', 'htmlonly', | ||||
'%s.txt' % root, root], | ||||
os.environ, | ||||
cwd='doc') | ||||
if res: | ||||
raise SystemExit('error running runrst: %s' % | ||||
'\n'.join([out, err])) | ||||
normalizecrlf('doc/%s' % root) | ||||
def genhtml(root): | ||||
log.info('generating doc/%s.html' % root) | ||||
res, out, err = runcmd( | ||||
[sys.executable, 'runrst', 'html', '--halt', 'warning', | ||||
'--link-stylesheet', '--stylesheet-path', 'style.css', | ||||
'%s.txt' % root, '%s.html' % root], | ||||
os.environ, | ||||
cwd='doc') | ||||
if res: | ||||
raise SystemExit('error running runrst: %s' % | ||||
'\n'.join([out, err])) | ||||
normalizecrlf('doc/%s.html' % root) | ||||
# This logic is duplicated in doc/Makefile. | ||||
sources = {f for f in os.listdir('mercurial/help') | ||||
if re.search('[0-9]\.txt$', f)} | ||||
# common.txt is a one-off. | ||||
gentxt('common') | ||||
for source in sorted(sources): | ||||
assert source[-4:] == '.txt' | ||||
root = source[:-4] | ||||
gentxt(root) | ||||
gengendoc(root) | ||||
if self.man: | ||||
genman(root) | ||||
if self.html: | ||||
genhtml(root) | ||||
|
r32647 | class hginstall(install): | ||
|
r32725 | |||
user_options = install.user_options + [ | ||||
('old-and-unmanageable', None, | ||||
'noop, present for eggless setuptools compat'), | ||||
('single-version-externally-managed', None, | ||||
'noop, present for eggless setuptools compat'), | ||||
] | ||||
# Also helps setuptools not be sad while we refuse to create eggs. | ||||
single_version_externally_managed = True | ||||
|
r32647 | def get_sub_commands(self): | ||
# Screen out egg related commands to prevent egg generation. But allow | ||||
# mercurial.egg-info generation, since that is part of modern | ||||
# packaging. | ||||
|
r33590 | excl = set(['bdist_egg']) | ||
|
r32647 | return filter(lambda x: x not in excl, install.get_sub_commands(self)) | ||
|
r22640 | class hginstalllib(install_lib): | ||
''' | ||||
This is a specialization of install_lib that replaces the copy_file used | ||||
there so that it supports setting the mode of files after copying them, | ||||
instead of just preserving the mode that the files originally had. If your | ||||
system has a umask of something like 027, preserving the permissions when | ||||
copying will lead to a broken install. | ||||
Note that just passing keep_permissions=False to copy_file would be | ||||
insufficient, as it might still be applying a umask. | ||||
''' | ||||
def run(self): | ||||
realcopyfile = file_util.copy_file | ||||
def copyfileandsetmode(*args, **kwargs): | ||||
src, dst = args[0], args[1] | ||||
dst, copied = realcopyfile(*args, **kwargs) | ||||
if copied: | ||||
st = os.stat(src) | ||||
# Persist executable bit (apply it to group and other if user | ||||
# has it) | ||||
if st[stat.ST_MODE] & stat.S_IXUSR: | ||||
|
r24941 | setmode = int('0755', 8) | ||
|
r22640 | else: | ||
|
r24941 | setmode = int('0644', 8) | ||
m = stat.S_IMODE(st[stat.ST_MODE]) | ||||
m = (m & ~int('0777', 8)) | setmode | ||||
os.chmod(dst, m) | ||||
|
r22640 | file_util.copy_file = copyfileandsetmode | ||
try: | ||||
install_lib.run(self) | ||||
finally: | ||||
file_util.copy_file = realcopyfile | ||||
|
r12661 | class hginstallscripts(install_scripts): | ||
''' | ||||
This is a specialization of install_scripts that replaces the @LIBDIR@ with | ||||
the configured directory for modules. If possible, the path is made relative | ||||
to the directory for scripts. | ||||
''' | ||||
def initialize_options(self): | ||||
install_scripts.initialize_options(self) | ||||
self.install_lib = None | ||||
def finalize_options(self): | ||||
install_scripts.finalize_options(self) | ||||
self.set_undefined_options('install', | ||||
('install_lib', 'install_lib')) | ||||
def run(self): | ||||
install_scripts.run(self) | ||||
|
r27269 | # It only makes sense to replace @LIBDIR@ with the install path if | ||
# the install path is known. For wheels, the logic below calculates | ||||
# the libdir to be "../..". This is because the internal layout of a | ||||
# wheel archive looks like: | ||||
# | ||||
# mercurial-3.6.1.data/scripts/hg | ||||
# mercurial/__init__.py | ||||
# | ||||
# When installing wheels, the subdirectories of the "<pkg>.data" | ||||
# directory are translated to system local paths and files therein | ||||
# are copied in place. The mercurial/* files are installed into the | ||||
# site-packages directory. However, the site-packages directory | ||||
# isn't known until wheel install time. This means we have no clue | ||||
# at wheel generation time what the installed site-packages directory | ||||
# will be. And, wheels don't appear to provide the ability to register | ||||
# custom code to run during wheel installation. This all means that | ||||
# we can't reliably set the libdir in wheels: the default behavior | ||||
# of looking in sys.path must do. | ||||
|
r12661 | if (os.path.splitdrive(self.install_dir)[0] != | ||
os.path.splitdrive(self.install_lib)[0]): | ||||
# can't make relative paths from one drive to another, so use an | ||||
# absolute path instead | ||||
libdir = self.install_lib | ||||
else: | ||||
common = os.path.commonprefix((self.install_dir, self.install_lib)) | ||||
rest = self.install_dir[len(common):] | ||||
uplevel = len([n for n in os.path.split(rest) if n]) | ||||
|
r27637 | libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common):] | ||
|
r12661 | |||
for outfile in self.outfiles: | ||||
|
r28418 | with open(outfile, 'rb') as fp: | ||
data = fp.read() | ||||
|
r12661 | |||
# skip binary files | ||||
|
r27348 | if b'\0' in data: | ||
|
r12661 | continue | ||
|
r27269 | # During local installs, the shebang will be rewritten to the final | ||
# install path. During wheel packaging, the shebang has a special | ||||
# value. | ||||
if data.startswith(b'#!python'): | ||||
log.info('not rewriting @LIBDIR@ in %s because install path ' | ||||
'not known' % outfile) | ||||
continue | ||||
|
r27348 | data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape)) | ||
|
r28418 | with open(outfile, 'wb') as fp: | ||
fp.write(data) | ||||
|
r12661 | |||
|
r15460 | cmdclass = {'build': hgbuild, | ||
|
r42016 | 'build_doc': hgbuilddoc, | ||
|
r15460 | 'build_mo': hgbuildmo, | ||
|
r11468 | 'build_ext': hgbuildext, | ||
|
r12661 | 'build_py': hgbuildpy, | ||
|
r27268 | 'build_scripts': hgbuildscripts, | ||
|
r14538 | 'build_hgextindex': buildhgextindex, | ||
|
r32647 | 'install': hginstall, | ||
|
r22640 | 'install_lib': hginstalllib, | ||
|
r17061 | 'install_scripts': hginstallscripts, | ||
'build_hgexe': buildhgexe, | ||||
} | ||||
|
r3238 | |||
|
r32206 | packages = ['mercurial', | ||
'mercurial.cext', | ||||
|
r32506 | 'mercurial.cffi', | ||
|
r32206 | 'mercurial.hgweb', | ||
|
r27222 | 'mercurial.pure', | ||
|
r34398 | 'mercurial.thirdparty', | ||
'mercurial.thirdparty.attr', | ||||
|
r37197 | 'mercurial.thirdparty.zope', | ||
'mercurial.thirdparty.zope.interface', | ||||
|
r36625 | 'mercurial.utils', | ||
|
r39365 | 'mercurial.revlogutils', | ||
|
r39808 | 'mercurial.testing', | ||
|
r28625 | 'hgext', 'hgext.convert', 'hgext.fsmonitor', | ||
|
r39243 | 'hgext.fastannotate', | ||
|
r37321 | 'hgext.fsmonitor.pywatchman', | ||
'hgext.infinitepush', | ||||
'hgext.highlight', | ||||
|
r36096 | 'hgext.largefiles', 'hgext.lfs', 'hgext.narrow', | ||
|
r40530 | 'hgext.remotefilelog', | ||
|
r36096 | 'hgext.zeroconf', 'hgext3rd', | ||
|
r32420 | 'hgdemandimport'] | ||
|
r39649 | if sys.version_info[0] == 2: | ||
packages.extend(['mercurial.thirdparty.concurrent', | ||||
'mercurial.thirdparty.concurrent.futures']) | ||||
|
r10521 | |||
|
r29444 | common_depends = ['mercurial/bitmanipulation.h', | ||
'mercurial/compat.h', | ||||
|
r32384 | 'mercurial/cext/util.h'] | ||
|
r32206 | common_include_dirs = ['mercurial'] | ||
|
r19724 | |||
|
r30408 | osutil_cflags = [] | ||
|
r25073 | osutil_ldflags = [] | ||
|
r31561 | # platform specific macros | ||
|
r31622 | for plat, func in [('bsd', 'setproctitle')]: | ||
|
r31561 | if re.search(plat, sys.platform) and hasfunction(new_compiler(), func): | ||
|
r30408 | osutil_cflags.append('-DHAVE_%s' % func.upper()) | ||
|
r31596 | for plat, macro, code in [ | ||
('bsd|darwin', 'BSD_STATFS', ''' | ||||
#include <sys/param.h> | ||||
#include <sys/mount.h> | ||||
int main() { struct statfs s; return sizeof(s.f_fstypename); } | ||||
'''), | ||||
|
r31622 | ('linux', 'LINUX_STATFS', ''' | ||
#include <linux/magic.h> | ||||
#include <sys/vfs.h> | ||||
int main() { struct statfs s; return sizeof(s.f_type); } | ||||
'''), | ||||
|
r31596 | ]: | ||
if re.search(plat, sys.platform) and cancompile(new_compiler(), code): | ||||
osutil_cflags.append('-DHAVE_%s' % macro) | ||||
|
r25073 | if sys.platform == 'darwin': | ||
osutil_ldflags += ['-framework', 'ApplicationServices'] | ||||
|
r36693 | xdiff_srcs = [ | ||
'mercurial/thirdparty/xdiff/xdiffi.c', | ||||
'mercurial/thirdparty/xdiff/xprepare.c', | ||||
'mercurial/thirdparty/xdiff/xutils.c', | ||||
] | ||||
xdiff_headers = [ | ||||
'mercurial/thirdparty/xdiff/xdiff.h', | ||||
'mercurial/thirdparty/xdiff/xdiffi.h', | ||||
'mercurial/thirdparty/xdiff/xinclude.h', | ||||
'mercurial/thirdparty/xdiff/xmacros.h', | ||||
'mercurial/thirdparty/xdiff/xprepare.h', | ||||
'mercurial/thirdparty/xdiff/xtypes.h', | ||||
'mercurial/thirdparty/xdiff/xutils.h', | ||||
] | ||||
|
r41002 | class RustCompilationError(CCompilerError): | ||
"""Exception class for Rust compilation errors.""" | ||||
|
r40309 | class RustExtension(Extension): | ||
|
r41003 | """Base classes for concrete Rust Extension classes. | ||
|
r40309 | """ | ||
rusttargetdir = os.path.join('rust', 'target', 'release') | ||||
|
r41021 | def __init__(self, mpath, sources, rustlibname, subcrate, | ||
py3_features=None, **kw): | ||||
|
r40309 | Extension.__init__(self, mpath, sources, **kw) | ||
|
r41003 | if hgrustext is None: | ||
|
r40309 | return | ||
srcdir = self.rustsrcdir = os.path.join('rust', subcrate) | ||||
|
r41021 | self.py3_features = py3_features | ||
|
r40309 | |||
# adding Rust source and control files to depends so that the extension | ||||
# gets rebuilt if they've changed | ||||
self.depends.append(os.path.join(srcdir, 'Cargo.toml')) | ||||
cargo_lock = os.path.join(srcdir, 'Cargo.lock') | ||||
if os.path.exists(cargo_lock): | ||||
self.depends.append(cargo_lock) | ||||
for dirpath, subdir, fnames in os.walk(os.path.join(srcdir, 'src')): | ||||
self.depends.extend(os.path.join(dirpath, fname) | ||||
for fname in fnames | ||||
if os.path.splitext(fname)[1] == '.rs') | ||||
def rustbuild(self): | ||||
|
r41003 | if hgrustext is None: | ||
|
r40309 | return | ||
env = os.environ.copy() | ||||
if 'HGTEST_RESTOREENV' in env: | ||||
# Mercurial tests change HOME to a temporary directory, | ||||
# but, if installed with rustup, the Rust toolchain needs | ||||
# HOME to be correct (otherwise the 'no default toolchain' | ||||
# error message is issued and the build fails). | ||||
# This happens currently with test-hghave.t, which does | ||||
# invoke this build. | ||||
# Unix only fix (os.path.expanduser not really reliable if | ||||
# HOME is shadowed like this) | ||||
import pwd | ||||
env['HOME'] = pwd.getpwuid(os.getuid()).pw_dir | ||||
|
r41002 | cargocmd = ['cargo', 'build', '-vv', '--release'] | ||
|
r41021 | if sys.version_info[0] == 3 and self.py3_features is not None: | ||
cargocmd.extend(('--features', self.py3_features, | ||||
'--no-default-features')) | ||||
|
r41002 | try: | ||
subprocess.check_call(cargocmd, env=env, cwd=self.rustsrcdir) | ||||
except OSError as exc: | ||||
if exc.errno == errno.ENOENT: | ||||
raise RustCompilationError("Cargo not found") | ||||
elif exc.errno == errno.EACCES: | ||||
raise RustCompilationError( | ||||
"Cargo found, but permisssion to execute it is denied") | ||||
else: | ||||
raise | ||||
except subprocess.CalledProcessError: | ||||
raise RustCompilationError( | ||||
"Cargo failed. Working directory: %r, " | ||||
"command: %r, environment: %r" % (self.rustsrcdir, cmd, env)) | ||||
|
r40309 | |||
|
r41003 | class RustEnhancedExtension(RustExtension): | ||
"""A C Extension, conditionally enhanced with Rust code. | ||||
If the HGRUSTEXT environment variable is set to something else | ||||
than 'cpython', the Rust sources get compiled and linked within the | ||||
C target shared library object. | ||||
""" | ||||
def __init__(self, mpath, sources, rustlibname, subcrate, **kw): | ||||
RustExtension.__init__(self, mpath, sources, rustlibname, subcrate, | ||||
**kw) | ||||
if hgrustext != 'direct-ffi': | ||||
return | ||||
self.extra_compile_args.append('-DWITH_RUST') | ||||
self.libraries.append(rustlibname) | ||||
self.library_dirs.append(self.rusttargetdir) | ||||
class RustStandaloneExtension(RustExtension): | ||||
def __init__(self, pydottedname, rustcrate, dylibname, **kw): | ||||
RustExtension.__init__(self, pydottedname, [], dylibname, rustcrate, | ||||
**kw) | ||||
self.dylibname = dylibname | ||||
def build(self, target_dir): | ||||
self.rustbuild() | ||||
target = [target_dir] | ||||
target.extend(self.name.split('.')) | ||||
ext = '.so' # TODO Unix only | ||||
target[-1] += ext | ||||
shutil.copy2(os.path.join(self.rusttargetdir, self.dylibname + ext), | ||||
os.path.join(*target)) | ||||
|
r10000 | extmodules = [ | ||
|
r32368 | Extension('mercurial.cext.base85', ['mercurial/cext/base85.c'], | ||
|
r32206 | include_dirs=common_include_dirs, | ||
|
r19724 | depends=common_depends), | ||
|
r32369 | Extension('mercurial.cext.bdiff', ['mercurial/bdiff.c', | ||
|
r36693 | 'mercurial/cext/bdiff.c'] + xdiff_srcs, | ||
|
r32206 | include_dirs=common_include_dirs, | ||
|
r36693 | depends=common_depends + ['mercurial/bdiff.h'] + xdiff_headers), | ||
|
r32371 | Extension('mercurial.cext.mpatch', ['mercurial/mpatch.c', | ||
'mercurial/cext/mpatch.c'], | ||||
|
r32206 | include_dirs=common_include_dirs, | ||
|
r19724 | depends=common_depends), | ||
|
r41003 | RustEnhancedExtension( | ||
'mercurial.cext.parsers', ['mercurial/cext/charencode.c', | ||||
'mercurial/cext/dirs.c', | ||||
'mercurial/cext/manifest.c', | ||||
'mercurial/cext/parsers.c', | ||||
'mercurial/cext/pathencode.c', | ||||
'mercurial/cext/revlog.c'], | ||||
'hgdirectffi', | ||||
'hg-direct-ffi', | ||||
include_dirs=common_include_dirs, | ||||
depends=common_depends + ['mercurial/cext/charencode.h', | ||||
'mercurial/cext/revlog.h', | ||||
'rust/hg-core/src/ancestors.rs', | ||||
'rust/hg-core/src/lib.rs']), | ||||
|
r32367 | Extension('mercurial.cext.osutil', ['mercurial/cext/osutil.c'], | ||
|
r32206 | include_dirs=common_include_dirs, | ||
|
r30408 | extra_compile_args=osutil_cflags, | ||
|
r25074 | extra_link_args=osutil_ldflags, | ||
depends=common_depends), | ||||
|
r37197 | Extension( | ||
'mercurial.thirdparty.zope.interface._zope_interface_coptimizations', [ | ||||
'mercurial/thirdparty/zope/interface/_zope_interface_coptimizations.c', | ||||
]), | ||||
|
r28432 | Extension('hgext.fsmonitor.pywatchman.bser', | ||
['hgext/fsmonitor/pywatchman/bser.c']), | ||||
|
r5396 | ] | ||
|
r41003 | if hgrustext == 'cpython': | ||
extmodules.append( | ||||
|
r41021 | RustStandaloneExtension('mercurial.rustext', 'hg-cpython', 'librusthg', | ||
py3_features='python3') | ||||
|
r41003 | ) | ||
|
r30436 | sys.path.insert(0, 'contrib/python-zstandard') | ||
import setup_zstd | ||||
|
r40157 | extmodules.append(setup_zstd.get_c_extension( | ||
name='mercurial.zstd', | ||||
root=os.path.abspath(os.path.dirname(__file__)))) | ||||
|
r30436 | |||
|
r23677 | try: | ||
from distutils import cygwinccompiler | ||||
# the -mno-cygwin option has been deprecated for years | ||||
|
r33780 | mingw32compilerclass = cygwinccompiler.Mingw32CCompiler | ||
|
r17121 | |||
|
r23677 | class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler): | ||
def __init__(self, *args, **kwargs): | ||||
|
r33780 | mingw32compilerclass.__init__(self, *args, **kwargs) | ||
|
r23677 | for i in 'compiler compiler_so linker_exe linker_so'.split(): | ||
try: | ||||
getattr(self, i).remove('-mno-cygwin') | ||||
except ValueError: | ||||
pass | ||||
|
r17121 | |||
|
r23677 | cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler | ||
except ImportError: | ||||
# the cygwinccompiler package is not available on some Python | ||||
# distributions like the ones from the optware project for Synology | ||||
# DiskStation boxes | ||||
class HackedMingw32CCompiler(object): | ||||
pass | ||||
|
r17121 | |||
|
r32782 | if os.name == 'nt': | ||
# Allow compiler/linker flags to be added to Visual Studio builds. Passing | ||||
# extra_link_args to distutils.extensions.Extension() doesn't have any | ||||
# effect. | ||||
from distutils import msvccompiler | ||||
|
r33780 | msvccompilerclass = msvccompiler.MSVCCompiler | ||
|
r32782 | |||
class HackedMSVCCompiler(msvccompiler.MSVCCompiler): | ||||
def initialize(self): | ||||
|
r33780 | msvccompilerclass.initialize(self) | ||
|
r32782 | # "warning LNK4197: export 'func' specified multiple times" | ||
self.ldflags_shared.append('/ignore:4197') | ||||
self.ldflags_shared_debug.append('/ignore:4197') | ||||
msvccompiler.MSVCCompiler = HackedMSVCCompiler | ||||
|
r9999 | packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo', | ||
|
r22575 | 'help/*.txt', | ||
|
r27374 | 'help/internals/*.txt', | ||
|
r23142 | 'default.d/*.rc', | ||
|
r22575 | 'dummycert.pem']} | ||
|
r9999 | |||
def ordinarypath(p): | ||||
return p and p[0] != '.' and p[-1] != '~' | ||||
|
r10282 | for root in ('templates',): | ||
|
r9999 | for curdir, dirs, files in os.walk(os.path.join('mercurial', root)): | ||
curdir = curdir.split(os.sep, 1)[1] | ||||
dirs[:] = filter(ordinarypath, dirs) | ||||
for f in filter(ordinarypath, files): | ||||
f = os.path.join(curdir, f) | ||||
packagedata['mercurial'].append(f) | ||||
|
r7648 | datafiles = [] | ||
|
r31316 | |||
# distutils expects version to be str/unicode. Converting it to | ||||
# unicode on Python 2 still works because it won't contain any | ||||
# non-ascii bytes and will be implicitly converted back to bytes | ||||
# when operated on. | ||||
assert isinstance(version, bytes) | ||||
setupversion = version.decode('ascii') | ||||
|
r10400 | extra = {} | ||
|
r42017 | py2exepackages = [ | ||
'hgdemandimport', | ||||
'hgext', | ||||
'email', | ||||
# implicitly imported per module policy | ||||
# (cffi wouldn't be used as a frozen exe) | ||||
'mercurial.cext', | ||||
#'mercurial.cffi', | ||||
'mercurial.pure', | ||||
] | ||||
|
r33601 | if issetuptools: | ||
extra['python_requires'] = supportedpy | ||||
|
r42017 | |||
|
r10400 | if py2exeloaded: | ||
extra['console'] = [ | ||||
{'script':'hg', | ||||
r41534 | 'copyright':'Copyright (C) 2005-2019 Matt Mackall and others', | |||
|
r10400 | 'product_version':version}] | ||
|
r14538 | # sub command of 'build' because 'py2exe' does not handle sub_commands | ||
build.sub_commands.insert(0, ('build_hgextindex', None)) | ||||
|
r25409 | # put dlls in sub directory so that they won't pollute PATH | ||
extra['zipfile'] = 'lib/library.zip' | ||||
|
r10400 | |||
|
r42017 | try: | ||
import dulwich | ||||
dulwich.__version__ | ||||
py2exepackages.append('dulwich') | ||||
except ImportError: | ||||
pass | ||||
try: | ||||
import keyring | ||||
keyring.util | ||||
py2exepackages.append('keyring') | ||||
except ImportError: | ||||
pass | ||||
try: | ||||
import pygments | ||||
pygments.__version__ | ||||
py2exepackages.append('pygments') | ||||
except ImportError: | ||||
pass | ||||
try: | ||||
|
r42020 | import win32ctypes | ||
win32ctypes.__version__ | ||||
py2exepackages.append('win32ctypes') | ||||
|
r42017 | except ImportError: | ||
pass | ||||
|
r10400 | if os.name == 'nt': | ||
# Windows binary file versions for exe/dll files must have the | ||||
# form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535 | ||||
|
r40419 | setupversion = setupversion.split(r'+', 1)[0] | ||
|
r7648 | |||
|
r13583 | if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'): | ||
|
r33119 | version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines() | ||
|
r16187 | if version: | ||
|
r16209 | version = version[0] | ||
|
r25043 | if sys.version_info[0] == 3: | ||
version = version.decode('utf-8') | ||||
|
r16187 | xcode4 = (version.startswith('Xcode') and | ||
StrictVersion(version.split()[1]) >= StrictVersion('4.0')) | ||||
|
r21558 | xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None | ||
|
r16187 | else: | ||
# xcodebuild returns empty on OS X Lion with XCode 4.3 not | ||||
# installed, but instead with only command-line tools. Assume | ||||
# that only happens on >= Lion, thus no PPC support. | ||||
xcode4 = True | ||||
|
r21038 | xcode51 = False | ||
|
r16187 | |||
|
r21038 | # XCode 4.0 dropped support for ppc architecture, which is hardcoded in | ||
# distutils.sysconfig | ||||
|
r16187 | if xcode4: | ||
|
r14324 | os.environ['ARCHFLAGS'] = '' | ||
|
r13583 | |||
|
r21038 | # XCode 5.1 changes clang such that it now fails to compile if the | ||
# -mno-fused-madd flag is passed, but the version of Python shipped with | ||||
# OS X 10.9 Mavericks includes this flag. This causes problems in all | ||||
# C extension modules, and a bug has been filed upstream at | ||||
# http://bugs.python.org/issue21244. We also need to patch this here | ||||
# so Mercurial can continue to compile in the meantime. | ||||
if xcode51: | ||||
cflags = get_config_var('CFLAGS') | ||||
|
r21839 | if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None: | ||
|
r21038 | os.environ['CFLAGS'] = ( | ||
os.environ.get('CFLAGS', '') + ' -Qunused-arguments') | ||||
|
r1977 | setup(name='mercurial', | ||
|
r10400 | version=setupversion, | ||
|
r18753 | author='Matt Mackall and many others', | ||
|
r30888 | author_email='mercurial@mercurial-scm.org', | ||
|
r26421 | url='https://mercurial-scm.org/', | ||
download_url='https://mercurial-scm.org/release/', | ||||
|
r18753 | description=('Fast scalable distributed SCM (revision control, version ' | ||
'control) system'), | ||||
long_description=('Mercurial is a distributed SCM tool written in Python.' | ||||
' It is used by a number of large projects that require' | ||||
' fast, reliable distributed revision control, such as ' | ||||
'Mozilla.'), | ||||
license='GNU GPLv2 or any later version', | ||||
classifiers=[ | ||||
'Development Status :: 6 - Mature', | ||||
'Environment :: Console', | ||||
'Intended Audience :: Developers', | ||||
'Intended Audience :: System Administrators', | ||||
'License :: OSI Approved :: GNU General Public License (GPL)', | ||||
'Natural Language :: Danish', | ||||
'Natural Language :: English', | ||||
'Natural Language :: German', | ||||
'Natural Language :: Italian', | ||||
'Natural Language :: Japanese', | ||||
'Natural Language :: Portuguese (Brazilian)', | ||||
'Operating System :: Microsoft :: Windows', | ||||
'Operating System :: OS Independent', | ||||
'Operating System :: POSIX', | ||||
'Programming Language :: C', | ||||
'Programming Language :: Python', | ||||
'Topic :: Software Development :: Version Control', | ||||
], | ||||
|
r6513 | scripts=scripts, | ||
|
r6239 | packages=packages, | ||
|
r10000 | ext_modules=extmodules, | ||
|
r7648 | data_files=datafiles, | ||
|
r9999 | package_data=packagedata, | ||
|
r3238 | cmdclass=cmdclass, | ||
|
r15458 | distclass=hgdist, | ||
|
r38022 | options={ | ||
'py2exe': { | ||||
|
r42017 | 'packages': py2exepackages, | ||
|
r38022 | }, | ||
'bdist_mpkg': { | ||||
'zipdist': False, | ||||
'license': 'COPYING', | ||||
|
r38032 | 'readme': 'contrib/packaging/macosx/Readme.html', | ||
'welcome': 'contrib/packaging/macosx/Welcome.html', | ||||
|
r38022 | }, | ||
}, | ||||
|
r3893 | **extra) | ||