#
# This is the mercurial setup script.
#
# 'python setup.py install', or
# 'python setup.py --help' for more options
import os
# Mercurial can't work on 3.6.0 or 3.6.1 due to a bug in % formatting
# in bytestrings.
supportedpy = ','.join(
[
'>=3.6.2',
]
)
import sys, platform
import sysconfig
def sysstr(s):
return s.decode('latin-1')
def eprint(*args, **kwargs):
kwargs['file'] = sys.stderr
print(*args, **kwargs)
import ssl
# ssl.HAS_TLSv1* are preferred to check support but they were added in Python
# 3.7. Prior to CPython commit 6e8cda91d92da72800d891b2fc2073ecbc134d98
# (backported to the 3.7 branch), ssl.PROTOCOL_TLSv1_1 / ssl.PROTOCOL_TLSv1_2
# were defined only if compiled against a OpenSSL version with TLS 1.1 / 1.2
# support. At the mentioned commit, they were unconditionally defined.
_notset = object()
has_tlsv1_1 = getattr(ssl, 'HAS_TLSv1_1', _notset)
if has_tlsv1_1 is _notset:
has_tlsv1_1 = getattr(ssl, 'PROTOCOL_TLSv1_1', _notset) is not _notset
has_tlsv1_2 = getattr(ssl, 'HAS_TLSv1_2', _notset)
if has_tlsv1_2 is _notset:
has_tlsv1_2 = getattr(ssl, 'PROTOCOL_TLSv1_2', _notset) is not _notset
if not (has_tlsv1_1 or has_tlsv1_2):
error = """
The `ssl` module does not advertise support for TLS 1.1 or TLS 1.2.
Please make sure that your Python installation was compiled against an OpenSSL
version enabling these features (likely this requires the OpenSSL version to
be at least 1.0.1).
"""
print(error, file=sys.stderr)
sys.exit(1)
DYLIB_SUFFIX = sysconfig.get_config_vars()['EXT_SUFFIX']
# Solaris Python packaging brain damage
try:
import hashlib
sha = hashlib.sha1()
except ImportError:
try:
import sha
sha.sha # silence unused import warning
except ImportError:
raise SystemExit(
"Couldn't import standard hashlib (incomplete Python install)."
)
try:
import zlib
zlib.compressobj # silence unused import warning
except ImportError:
raise SystemExit(
"Couldn't import standard zlib (incomplete Python install)."
)
# The base IronPython distribution (as of 2.7.1) doesn't support bz2
isironpython = False
try:
isironpython = (
platform.python_implementation().lower().find("ironpython") != -1
)
except AttributeError:
pass
if isironpython:
sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
else:
try:
import bz2
bz2.BZ2Compressor # silence unused import warning
except ImportError:
raise SystemExit(
"Couldn't import standard bz2 (incomplete Python install)."
)
ispypy = "PyPy" in sys.version
import ctypes
import stat, subprocess, time
import re
import shutil
import tempfile
# 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.
issetuptools = os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ
if issetuptools:
from setuptools import setup
else:
try:
from distutils.core import setup
except ModuleNotFoundError:
from setuptools import setup
from distutils.ccompiler import new_compiler
from distutils.core import Command, Extension
from distutils.dist import Distribution
from distutils.command.build import build
from distutils.command.build_ext import build_ext
from distutils.command.build_py import build_py
from distutils.command.build_scripts import build_scripts
from distutils.command.install import install
from distutils.command.install_lib import install_lib
from distutils.command.install_scripts import install_scripts
from distutils import log
from distutils.spawn import spawn, find_executable
from distutils import file_util
from distutils.errors import (
CCompilerError,
DistutilsError,
DistutilsExecError,
)
from distutils.sysconfig import get_python_inc
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)
scripts = ['hg']
if os.name == 'nt':
# We remove hg.bat if we are able to build hg.exe.
scripts.append('contrib/win32/hg.bat')
def cancompile(cc, code):
tmpdir = tempfile.mkdtemp(prefix='hg-install-')
devnull = oldstderr = None
try:
fname = os.path.join(tmpdir, 'testcomp.c')
f = open(fname, 'w')
f.write(code)
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"))
return True
except Exception:
return False
finally:
if oldstderr is not None:
os.dup2(oldstderr, sys.stderr.fileno())
if devnull is not None:
devnull.close()
shutil.rmtree(tmpdir)
# 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)
def hasheader(cc, headername):
code = '#include <%s>\nint main(void) { return 0; }\n' % headername
return cancompile(cc, code)
# py2exe needs to be installed to work
try:
import py2exe
py2exe.patch_distutils()
py2exeloaded = True
# import py2exe's patched Distribution class
from distutils.core import Distribution
except ImportError:
py2exeloaded = False
def runcmd(cmd, env, cwd=None):
p = subprocess.Popen(
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, cwd=cwd
)
out, err = p.communicate()
return p.returncode, out, err
class hgcommand:
def __init__(self, cmd, env):
self.cmd = cmd
self.env = env
def run(self, args):
cmd = self.cmd + args
returncode, out, err = runcmd(cmd, self.env)
err = filterhgerr(err)
if err:
print("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
print(err, file=sys.stderr)
if returncode != 0:
return b''
return out
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')
and not e.startswith(b'obsolete feature not enabled')
and not e.startswith(b'*** failed to import extension')
and not e.startswith(b'devel-warn:')
and not (
e.startswith(b'(third party extension')
and e.endswith(b'or newer of Mercurial; disabling)')
)
)
]
return b'\n'.join(b' ' + e for e in err)
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
# 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.
check_cmd = ['log', '-r.', '-Ttest']
if os.name != 'nt' or not os.path.exists("hg.exe"):
try:
retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
except EnvironmentError:
retcode = -1
if retcode == 0 and not filterhgerr(err):
return hgcommand(hgcmd, hgenv)
# 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
if retcode == 0 and not filterhgerr(err):
return hgcommand(hgcmd, hgenv)
eprint("/!\\")
eprint(r"/!\ Unable to find a working hg binary")
eprint(r"/!\ Version cannot be extract from the repository")
eprint(r"/!\ Re-run the setup once a first version is built")
return None
def localhgenv():
"""Get an environment dictionary to use for invoking or importing
mercurial from the local repository."""
# 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']
return env
version = ''
def _try_get_version():
hg = findhg()
if hg is None:
return ''
hgid = None
numerictags = []
cmd = ['log', '-r', '.', '--template', '{tags}\n']
pieces = sysstr(hg.run(cmd)).split()
numerictags = [t for t in pieces if t[0:1].isdigit()]
hgid = sysstr(hg.run(['id', '-i'])).strip()
if hgid.count('+') == 2:
hgid = hgid.replace("+", ".", 1)
if not hgid:
eprint("/!\\")
eprint(r"/!\ Unable to determine hg version from local repository")
eprint(r"/!\ Failed to retrieve current revision tags")
return ''
if numerictags: # tag(s) found
version = numerictags[-1]
if hgid.endswith('+'): # propagate the dirty status to the tag
version += '+'
else: # no tag found on the checked out revision
ltagcmd = ['log', '--rev', 'wdir()', '--template', '{latesttag}']
ltag = sysstr(hg.run(ltagcmd))
if not ltag:
eprint("/!\\")
eprint(r"/!\ Unable to determine hg version from local repository")
eprint(
r"/!\ Failed to retrieve current revision distance to lated tag"
)
return ''
changessincecmd = [
'log',
'-T',
'x\n',