setup.py
1776 lines
| 58.1 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 | ||||
|
r33592 | import os | ||
|
r49705 | # Mercurial can't work on 3.6.0 or 3.6.1 due to a bug in % formatting | ||
# in bytestrings. | ||||
|
r43666 | supportedpy = ','.join( | ||
[ | ||||
|
r49705 | '>=3.6.2', | ||
|
r43666 | ] | ||
) | ||||
|
r33588 | |||
|
r14295 | import sys, platform | ||
|
r42657 | import sysconfig | ||
|
r43346 | |||
|
r49681 | def sysstr(s): | ||
return s.decode('latin-1') | ||||
|
r11532 | |||
|
r43346 | |||
|
r45410 | import ssl | ||
|
r45429 | # 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). | ||||
""" | ||||
|
r49682 | print(error, file=sys.stderr) | ||
|
r45429 | sys.exit(1) | ||
|
r49685 | DYLIB_SUFFIX = sysconfig.get_config_vars()['EXT_SUFFIX'] | ||
|
r42657 | |||
|
r7558 | # Solaris Python packaging brain damage | ||
try: | ||||
import hashlib | ||||
|
r43346 | |||
|
r7558 | sha = hashlib.sha1() | ||
|
r16688 | except ImportError: | ||
|
r7558 | try: | ||
import sha | ||||
|
r43346 | |||
sha.sha # silence unused import warning | ||||
|
r16688 | except ImportError: | ||
|
r7558 | raise SystemExit( | ||
|
r43346 | "Couldn't import standard hashlib (incomplete Python install)." | ||
) | ||||
|
r7558 | |||
try: | ||||
import zlib | ||||
|
r43346 | |||
zlib.compressobj # silence unused import warning | ||||
|
r16688 | except ImportError: | ||
|
r7558 | raise SystemExit( | ||
|
r43346 | "Couldn't import standard zlib (incomplete Python install)." | ||
) | ||||
|
r7558 | |||
|
r14295 | # The base IronPython distribution (as of 2.7.1) doesn't support bz2 | ||
isironpython = False | ||||
|
r10761 | try: | ||
|
r43346 | 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 | ||||
|
r43346 | |||
bz2.BZ2Compressor # silence unused import warning | ||||
|
r16688 | except ImportError: | ||
|
r14295 | raise SystemExit( | ||
|
r43346 | "Couldn't import standard bz2 (incomplete Python install)." | ||
) | ||||
|
r10761 | |||
|
r24192 | ispypy = "PyPy" in sys.version | ||
|
r29020 | import ctypes | ||
|
r41002 | import errno | ||
|
r33592 | import stat, subprocess, time | ||
|
r21038 | import re | ||
|
r6251 | import shutil | ||
import tempfile | ||||
|
r43346 | |||
|
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. | ||||
|
r43346 | issetuptools = os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ | ||
|
r33601 | 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 | ||
|
r46850 | from distutils import log | ||
|
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 | |||
|
r49333 | # Explain to distutils.StrictVersion how our release candidates are versioned | ||
|
r40485 | StrictVersion.version_re = re.compile(r'^(\d+)\.(\d+)(\.(\d+))?-?(rc(\d+))?$') | ||
|
r43346 | |||
|
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) | ||||
|
r43346 | |||
|
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 | |||
|
r43346 | |||
|
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) | ||
|
r43346 | |||
|
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) | ||||
|
r43346 | |||
|
r31560 | def hasheader(cc, headername): | ||
code = '#include <%s>\nint main(void) { return 0; }\n' % headername | ||||
return cancompile(cc, code) | ||||
|
r43346 | |||
|
r1283 | # py2exe needs to be installed to work | ||
try: | ||||
|
r1294 | import py2exe | ||
|
r43346 | |||
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 | |||
|
r43346 | |||
|
r42016 | def runcmd(cmd, env, cwd=None): | ||
|
r43346 | p = subprocess.Popen( | ||
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, cwd=cwd | ||||
) | ||||
|
r32886 | out, err = p.communicate() | ||
|
r33111 | return p.returncode, out, err | ||
|
r13636 | |||
|
r43346 | |||
|
r49801 | class hgcommand: | ||
|
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: | ||
|
r49682 | print("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr) | ||
print(err, file=sys.stderr) | ||||
|
r45094 | return b'' | ||
|
r33113 | return out | ||
|
r8548 | |||
|
r43346 | |||
|
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. | ||||
|
r43346 | 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)') | ||||
) | ||||
) | ||||
] | ||||
|
r33599 | return b'\n'.join(b' ' + e for e in err) | ||
|
r43346 | |||
|
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'] | ||
|
r44684 | if os.name != 'nt' or not os.path.exists("hg.exe"): | ||
|
r41016 | 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) | ||
|
r43346 | raise SystemExit( | ||
'Unable to find a working hg binary to extract the ' | ||||
'version from the repository tags' | ||||
) | ||||
|
r33114 | |||
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. | ||||
|
r43346 | env = { | ||
'HGMODULEPOLICY': 'py', | ||||
'HGRCPATH': '', | ||||
'LANGUAGE': 'C', | ||||
'PATH': '', | ||||
} # make pypi modules that use os.environ['PATH'] happy | ||||
|
r33112 | 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 | |||
|
r43346 | |||
|
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') | ||||
|
r43346 | if numerictags: # tag(s) found | ||
|
r17709 | version = numerictags[-1] | ||
|
r43346 | if hgid.endswith('+'): # propagate the dirty status to the tag | ||
|
r9615 | version += '+' | ||
|
r43346 | 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()) | ||||
|
r47262 | version = '%s+hg%s.%s' % (ltag, changessince, hgid) | ||
|
r9615 | if version.endswith('+'): | ||
|
r47262 | version = version[:-1] + 'local' + time.strftime('%Y%m%d') | ||
|
r9615 | elif os.path.exists('.hg_archival.txt'): | ||
|
r43346 | 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: | ||
|
r47262 | version = ( | ||
|
r47273 | '%(latesttag)s+hg%(changessincelatesttag)s.%(node).12s' % kw | ||
|
r47262 | ) | ||
|
r23646 | else: | ||
|
r47273 | version = '%(latesttag)s+hg%(latesttagdistance)s.%(node).12s' % kw | ||
|
r8547 | else: | ||
|
r47273 | version = '0+hg' + kw.get('node', '')[:12] | ||
|
r47759 | elif os.path.exists('mercurial/__version__.py'): | ||
|
r47772 | with open('mercurial/__version__.py') as f: | ||
data = f.read() | ||||
|
r47759 | version = re.search('version = b"(.*)"', data).group(1) | ||
|
r7632 | |||
|
r8548 | if version: | ||
|
r35229 | versionb = version | ||
if not isinstance(versionb, bytes): | ||||
versionb = versionb.encode('ascii') | ||||
|
r43346 | write_if_changed( | ||
'mercurial/__version__.py', | ||||
b''.join( | ||||
[ | ||||
b'# this file is autogenerated by setup.py\n' | ||||
b'version = b"%s"\n' % versionb, | ||||
] | ||||
), | ||||
) | ||||
|
r8493 | |||
|
r43346 | |||
|
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 | |||
|
r43346 | |||
|
r15523 | class hgbuildmo(build): | ||
|
r7649 | |||
description = "build translations (.mo files)" | ||||
def run(self): | ||||
if not find_executable('msgfmt'): | ||||
|
r43346 | self.warn( | ||
"could not find msgfmt executable, no translations " | ||||
"will be built" | ||||
) | ||||
|
r7649 | 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 | ||
r44971 | rust = False | |||
no_rust = False | ||||
|
r29505 | cffi = ispypy | ||
|
r15458 | |||
|
r41925 | global_options = Distribution.global_options + [ | ||
('pure', None, "use pure (slow) Python code instead of C extensions"), | ||||
|
r42653 | ('rust', None, "use Rust extensions additionally to C extensions"), | ||
r44971 | ( | |||
'no-rust', | ||||
None, | ||||
"do not use Rust extensions additionally to C extensions", | ||||
), | ||||
|
r41925 | ] | ||
|
r7722 | |||
r44971 | negative_opt = Distribution.negative_opt.copy() | |||
boolean_options = ['pure', 'rust', 'no-rust'] | ||||
negative_opt['no-rust'] = 'rust' | ||||
def _set_command_options(self, command_obj, option_dict=None): | ||||
|
r45120 | # Not all distutils versions in the wild have boolean_options. | ||
# This should be cleaned up when we're Python 3 only. | ||||
command_obj.boolean_options = ( | ||||
getattr(command_obj, 'boolean_options', []) + self.boolean_options | ||||
) | ||||
r44971 | return Distribution._set_command_options( | |||
self, command_obj, option_dict=option_dict | ||||
) | ||||
def parse_command_line(self): | ||||
ret = Distribution.parse_command_line(self) | ||||
if not (self.rust or self.no_rust): | ||||
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: | ||||
if hgrustext: | ||||
|
r49252 | msg = 'unknown HGWITHRUSTEXT value: %s' % hgrustext | ||
|
r49682 | print(msg, file=sys.stderr) | ||
r44971 | hgrustext = None | |||
self.rust = hgrustext is not None | ||||
self.no_rust = not self.rust | ||||
return ret | ||||
|
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) | ||||
|
r43346 | |||
|
r30450 | # This is ugly as a one-liner. So use a variable. | ||
buildextnegops = dict(getattr(build_ext, 'negative_options', {})) | ||||
buildextnegops['no-zstd'] = 'zstd' | ||||
|
r42653 | buildextnegops['no-rust'] = 'rust' | ||
|
r30450 | |||
|
r43346 | |||
|
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'), | ||||
|
r43346 | ( | ||
'rust', | ||||
None, | ||||
'compile Rust extensions if they are in use ' | ||||
'(requires Cargo) [default]', | ||||
), | ||||
|
r42653 | ('no-rust', None, 'do not compile Rust extensions'), | ||
|
r30450 | ] | ||
|
r42653 | boolean_options = build_ext.boolean_options + ['zstd', 'rust'] | ||
|
r30450 | negative_opt = buildextnegops | ||
def initialize_options(self): | ||||
self.zstd = True | ||||
|
r42653 | self.rust = True | ||
|
r30450 | return build_ext.initialize_options(self) | ||
|
r43314 | def finalize_options(self): | ||
# Unless overridden by the end user, build extensions in parallel. | ||||
# Only influences behavior on Python 3.5+. | ||||
if getattr(self, 'parallel', None) is None: | ||||
self.parallel = True | ||||
return build_ext.finalize_options(self) | ||||
|
r30450 | def build_extensions(self): | ||
|
r43346 | 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: | ||||
|
r43346 | self.extensions = [ | ||
e for e in self.extensions if e.name != 'mercurial.zstd' | ||||
] | ||||
|
r30450 | |||
|
r49333 | # Build Rust standalone extensions if it'll be used | ||
# and its build is not explicitly disabled (for external build | ||||
|
r42653 | # as Linux distributions would do) | ||
r44956 | if self.distribution.rust and self.rust: | |||
|
r47081 | if not sys.platform.startswith('linux'): | ||
self.warn( | ||||
"rust extensions have only been tested on Linux " | ||||
"and may not behave correctly on other platforms" | ||||
) | ||||
|
r42653 | for rustext in ruststandalones: | ||
rustext.build('' if self.inplace else self.build_lib) | ||||
|
r41003 | |||
|
r30450 | return build_ext.build_extensions(self) | ||
|
r11468 | |||
def build_extension(self, ext): | ||||
|
r43346 | if ( | ||
self.distribution.rust | ||||
and self.rust | ||||
and isinstance(ext, RustExtension) | ||||
): | ||||
ext.rustbuild() | ||||
|
r11468 | try: | ||
build_ext.build_extension(self, ext) | ||||
except CCompilerError: | ||||
|
r12501 | if not getattr(ext, 'optional', False): | ||
|
r11468 | raise | ||
|
r43346 | log.warn( | ||
"Failed to build optional extension '%s' (skipping)", ext.name | ||||
) | ||||
|
r11468 | |||
|
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) | ||||
|
r43346 | |||
|
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 | ) | ||
|
r43346 | |||
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 | ||
|
r43346 | |||
|
r32505 | 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): | ||||
|
r43346 | raise SystemExit( | ||
'Python headers are required to build ' | ||||
'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) | ||||
|
r42653 | rust = self.distribution.rust | ||
|
r28430 | if self.distribution.pure: | ||
modulepolicy = 'py' | ||||
|
r32251 | elif self.build_lib == '.': | ||
|
r42653 | # in-place build should run without rebuilding and Rust extensions | ||
modulepolicy = 'rust+c-allow' if rust else 'allow' | ||||
|
r28430 | else: | ||
|
r42653 | modulepolicy = 'rust+c' if rust else 'c' | ||
|
r35229 | |||
|
r43346 | 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 | |||
|
r43346 | |||
|
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 | ||||
|
r43346 | code = ( | ||
'import pprint; from mercurial import extensions; ' | ||||
|
r44862 | 'ext = extensions.disabled();' | ||
'ext.pop("__index__", None);' | ||||
'pprint.pprint(ext)' | ||||
|
r43346 | ) | ||
returncode, out, err = runcmd( | ||||
[sys.executable, '-c', code], localhgenv() | ||||
) | ||||
|
r33111 | if err or returncode != 0: | ||
|
r14538 | raise DistutilsExecError(err) | ||
|
r42244 | with open(self._indexfilename, 'wb') as f: | ||
f.write(b'# this file is autogenerated by setup.py\n') | ||||
f.write(b'docs = ') | ||||
|
r28418 | f.write(out) | ||
|
r14538 | |||
|
r43346 | |||
|
r17061 | class buildhgexe(build_ext): | ||
description = 'compile hg.exe from mercurial/exewrapper.c' | ||||
|
r34531 | user_options = build_ext.user_options + [ | ||
|
r43346 | ( | ||
'long-paths-support', | ||||
None, | ||||
'enable support for long paths on ' | ||||
'Windows (off by default and ' | ||||
'experimental)', | ||||
), | ||||
|
r34531 | ] | ||
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): | ||
|
r43346 | self.compiler.compiler_so = self.compiler.compiler # no -mdll | ||
self.compiler.dll_libraries = [] # no -lmsrvc90 | ||||
|
r29020 | |||
|
r44073 | pythonlib = None | ||
|
r29020 | |||
|
r49124 | dir = os.path.dirname(self.get_ext_fullpath('dummy')) | ||
self.hgtarget = os.path.join(dir, 'hg') | ||||
|
r44073 | if getattr(sys, 'dllhandle', None): | ||
# 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) | ||||
if not dllbasename.lower().endswith(b'.dll'): | ||||
raise SystemExit( | ||||
'Python DLL does not end with .dll: %s' % dllbasename | ||||
) | ||||
pythonlib = dllbasename[:-4] | ||||
|
r49124 | # Copy the pythonXY.dll next to the binary so that it runs | ||
# without tampering with PATH. | ||||
dest = os.path.join( | ||||
os.path.dirname(self.hgtarget), | ||||
|
r49684 | os.fsdecode(dllbasename), | ||
|
r49124 | ) | ||
if not os.path.exists(dest): | ||||
shutil.copy(buf.value, dest) | ||||
# Also overwrite python3.dll so that hgext.git is usable. | ||||
# TODO: also handle the MSYS flavor | ||||
|
r49686 | python_x = os.path.join( | ||
os.path.dirname(os.fsdecode(buf.value)), | ||||
"python3.dll", | ||||
) | ||||
if os.path.exists(python_x): | ||||
dest = os.path.join( | ||||
os.path.dirname(self.hgtarget), | ||||
os.path.basename(python_x), | ||||
|
r49124 | ) | ||
|
r49686 | shutil.copy(python_x, dest) | ||
|
r49124 | |||
|
r44073 | if not pythonlib: | ||
|
r43346 | log.warn( | ||
|
r44071 | 'could not determine Python DLL filename; assuming pythonXY' | ||
|
r43346 | ) | ||
|
r29020 | |||
hv = sys.hexversion | ||||
|
r44072 | pythonlib = b'python%d%d' % (hv >> 24, (hv >> 16) & 0xFF) | ||
|
r29020 | |||
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 | |||
|
r43346 | objects = self.compiler.compile( | ||
['mercurial/exewrapper.c'], | ||||
output_dir=self.build_temp, | ||||
|
r49687 | macros=[('_UNICODE', None), ('UNICODE', None)], | ||
|
r43346 | ) | ||
self.compiler.link_executable( | ||||
objects, self.hgtarget, libraries=[], 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 | ||||
|
r43346 | self.spawn( | ||
[ | ||||
'mt.exe', | ||||
'-nologo', | ||||
'-manifest', | ||||
manfname, | ||||
inputresource, | ||||
outputresource, | ||||
] | ||||
) | ||||
|
r34531 | 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') | ||||
|
r43346 | |||
|
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( | ||||
|
r43346 | [sys.executable, 'gendoc.py', root], os.environ, cwd='doc' | ||
) | ||||
|
r42016 | if res: | ||
|
r43346 | raise SystemExit( | ||
|
r45244 | 'error running gendoc.py: %s' | ||
% '\n'.join([sysstr(out), sysstr(err)]) | ||||
|
r43346 | ) | ||
|
r42016 | |||
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, | ||||
|
r43346 | cwd='doc', | ||
) | ||||
|
r42016 | if res: | ||
|
r43346 | raise SystemExit( | ||
|
r45244 | 'error running gendoc: %s' | ||
% '\n'.join([sysstr(out), sysstr(err)]) | ||||
|
r43346 | ) | ||
|
r42016 | |||
with open(gendoc, 'wb') as fh: | ||||
fh.write(out) | ||||
def genman(root): | ||||
log.info('generating doc/%s' % root) | ||||
res, out, err = runcmd( | ||||
|
r43346 | [ | ||
sys.executable, | ||||
'runrst', | ||||
'hgmanpage', | ||||
'--halt', | ||||
'warning', | ||||
'--strip-elements-with-class', | ||||
'htmlonly', | ||||
'%s.txt' % root, | ||||
root, | ||||
], | ||||
|
r42016 | os.environ, | ||
|
r43346 | cwd='doc', | ||
) | ||||
|
r42016 | if res: | ||
|
r43346 | raise SystemExit( | ||
|
r45244 | 'error running runrst: %s' | ||
% '\n'.join([sysstr(out), sysstr(err)]) | ||||
|
r43346 | ) | ||
|
r42016 | |||
normalizecrlf('doc/%s' % root) | ||||
def genhtml(root): | ||||
log.info('generating doc/%s.html' % root) | ||||
res, out, err = runcmd( | ||||
|
r43346 | [ | ||
sys.executable, | ||||
'runrst', | ||||
'html', | ||||
'--halt', | ||||
'warning', | ||||
'--link-stylesheet', | ||||
'--stylesheet-path', | ||||
'style.css', | ||||
'%s.txt' % root, | ||||
'%s.html' % root, | ||||
], | ||||
|
r42016 | os.environ, | ||
|
r43346 | cwd='doc', | ||
) | ||||
|
r42016 | if res: | ||
|
r43346 | raise SystemExit( | ||
|
r45244 | 'error running runrst: %s' | ||
% '\n'.join([sysstr(out), sysstr(err)]) | ||||
|
r43346 | ) | ||
|
r42016 | |||
normalizecrlf('doc/%s.html' % root) | ||||
# This logic is duplicated in doc/Makefile. | ||||
|
r44937 | sources = { | ||
|
r43346 | f | ||
|
r44188 | for f in os.listdir('mercurial/helptext') | ||
|
r43346 | if re.search(r'[0-9]\.txt$', f) | ||
|
r44937 | } | ||
|
r42016 | |||
# 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) | ||||
|
r43346 | |||
|
r32647 | class hginstall(install): | ||
|
r32725 | |||
user_options = install.user_options + [ | ||||
|
r43346 | ( | ||
'old-and-unmanageable', | ||||
None, | ||||
'noop, present for eggless setuptools compat', | ||||
), | ||||
( | ||||
'single-version-externally-managed', | ||||
None, | ||||
'noop, present for eggless setuptools compat', | ||||
), | ||||
|
r32725 | ] | ||
|
r49845 | sub_commands = install.sub_commands + [ | ||
('install_completion', lambda self: True) | ||||
] | ||||
|
r32725 | # 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. | ||||
|
r44937 | excl = {'bdist_egg'} | ||
r47033 | return filter(lambda x: x not in excl, install.get_sub_commands(self)) | |||
|
r32647 | |||
|
r43346 | |||
|
r22640 | class hginstalllib(install_lib): | ||
|
r46554 | """ | ||
|
r22640 | 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. | ||||
|
r46554 | """ | ||
|
r22640 | |||
def run(self): | ||||
realcopyfile = file_util.copy_file | ||||
|
r43346 | |||
|
r22640 | 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) | ||||
|
r43346 | |||
|
r22640 | file_util.copy_file = copyfileandsetmode | ||
try: | ||||
install_lib.run(self) | ||||
finally: | ||||
file_util.copy_file = realcopyfile | ||||
|
r43346 | |||
|
r12661 | class hginstallscripts(install_scripts): | ||
|
r46554 | """ | ||
|
r12661 | 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. | ||||
|
r46554 | """ | ||
|
r12661 | |||
def initialize_options(self): | ||||
install_scripts.initialize_options(self) | ||||
self.install_lib = None | ||||
def finalize_options(self): | ||||
install_scripts.finalize_options(self) | ||||
|
r43346 | self.set_undefined_options('install', ('install_lib', 'install_lib')) | ||
|
r12661 | |||
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. | ||||
|
r43346 | if ( | ||
os.path.splitdrive(self.install_dir)[0] | ||||
!= os.path.splitdrive(self.install_lib)[0] | ||||
): | ||||
|
r12661 | # can't make relative paths from one drive to another, so use an | ||
# absolute path instead | ||||
libdir = self.install_lib | ||||
else: | ||||
|
r44539 | libdir = os.path.relpath(self.install_lib, self.install_dir) | ||
|
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'): | ||||
|
r43346 | log.info( | ||
'not rewriting @LIBDIR@ in %s because install path ' | ||||
'not known' % outfile | ||||
) | ||||
|
r27269 | continue | ||
|
r49683 | data = data.replace(b'@LIBDIR@', libdir.encode('unicode_escape')) | ||
|
r28418 | with open(outfile, 'wb') as fp: | ||
fp.write(data) | ||||
|
r12661 | |||
|
r43346 | |||
|
r49845 | class hginstallcompletion(Command): | ||
description = 'Install shell completion' | ||||
def initialize_options(self): | ||||
self.install_dir = None | ||||
def finalize_options(self): | ||||
self.set_undefined_options( | ||||
'install_data', ('install_dir', 'install_dir') | ||||
) | ||||
def run(self): | ||||
for src, dir_path, dest in ( | ||||
( | ||||
'bash_completion', | ||||
('share', 'bash-completion', 'completions'), | ||||
'hg', | ||||
), | ||||
('zsh_completion', ('share', 'zsh', 'site-functions'), '_hg'), | ||||
): | ||||
dir = os.path.join(self.install_dir, *dir_path) | ||||
self.mkpath(dir) | ||||
self.copy_file( | ||||
os.path.join('contrib', src), os.path.join(dir, dest) | ||||
) | ||||
|
r42360 | # virtualenv installs custom distutils/__init__.py and | ||
# distutils/distutils.cfg files which essentially proxy back to the | ||||
# "real" distutils in the main Python install. The presence of this | ||||
# directory causes py2exe to pick up the "hacked" distutils package | ||||
# from the virtualenv and "import distutils" will fail from the py2exe | ||||
# build because the "real" distutils files can't be located. | ||||
# | ||||
# We work around this by monkeypatching the py2exe code finding Python | ||||
# modules to replace the found virtualenv distutils modules with the | ||||
# original versions via filesystem scanning. This is a bit hacky. But | ||||
# it allows us to use virtualenvs for py2exe packaging, which is more | ||||
# deterministic and reproducible. | ||||
# | ||||
# It's worth noting that the common StackOverflow suggestions for this | ||||
# problem involve copying the original distutils files into the | ||||
# virtualenv or into the staging directory after setup() is invoked. | ||||
# The former is very brittle and can easily break setup(). Our hacking | ||||
# of the found modules routine has a similar result as copying the files | ||||
# manually. But it makes fewer assumptions about how py2exe works and | ||||
# is less brittle. | ||||
# This only catches virtualenvs made with virtualenv (as opposed to | ||||
# venv, which is likely what Python 3 uses). | ||||
py2exehacked = py2exeloaded and getattr(sys, 'real_prefix', None) is not None | ||||
if py2exehacked: | ||||
from distutils.command.py2exe import py2exe as buildpy2exe | ||||
from py2exe.mf import Module as py2exemodule | ||||
class hgbuildpy2exe(buildpy2exe): | ||||
def find_needed_modules(self, mf, files, modules): | ||||
res = buildpy2exe.find_needed_modules(self, mf, files, modules) | ||||
# Replace virtualenv's distutils modules with the real ones. | ||||
|
r42365 | modules = {} | ||
for k, v in res.modules.items(): | ||||
if k != 'distutils' and not k.startswith('distutils.'): | ||||
modules[k] = v | ||||
res.modules = modules | ||||
|
r42360 | |||
import opcode | ||||
|
r43346 | |||
distutilsreal = os.path.join( | ||||
os.path.dirname(opcode.__file__), 'distutils' | ||||
) | ||||
|
r42360 | |||
for root, dirs, files in os.walk(distutilsreal): | ||||
for f in sorted(files): | ||||
if not f.endswith('.py'): | ||||
continue | ||||
full = os.path.join(root, f) | ||||
parents = ['distutils'] | ||||
if root != distutilsreal: | ||||
rel = os.path.relpath(root, distutilsreal) | ||||
parents.extend(p for p in rel.split(os.sep)) | ||||
modname = '%s.%s' % ('.'.join(parents), f[:-3]) | ||||
if modname.startswith('distutils.tests.'): | ||||
continue | ||||
if modname.endswith('.__init__'): | ||||
|
r43346 | modname = modname[: -len('.__init__')] | ||
|
r42360 | path = os.path.dirname(full) | ||
else: | ||||
path = None | ||||
|
r43346 | res.modules[modname] = py2exemodule( | ||
modname, full, path=path | ||||
) | ||||
|
r42360 | |||
if 'distutils' not in res.modules: | ||||
raise SystemExit('could not find distutils modules') | ||||
return res | ||||
|
r43346 | |||
cmdclass = { | ||||
'build': hgbuild, | ||||
'build_doc': hgbuilddoc, | ||||
'build_mo': hgbuildmo, | ||||
'build_ext': hgbuildext, | ||||
'build_py': hgbuildpy, | ||||
'build_scripts': hgbuildscripts, | ||||
'build_hgextindex': buildhgextindex, | ||||
'install': hginstall, | ||||
|
r49845 | 'install_completion': hginstallcompletion, | ||
|
r43346 | 'install_lib': hginstalllib, | ||
'install_scripts': hginstallscripts, | ||||
'build_hgexe': buildhgexe, | ||||
} | ||||
|
r3238 | |||
|
r42360 | if py2exehacked: | ||
cmdclass['py2exe'] = hgbuildpy2exe | ||||
|
r43346 | packages = [ | ||
'mercurial', | ||||
'mercurial.cext', | ||||
'mercurial.cffi', | ||||
|
r44478 | 'mercurial.defaultrc', | ||
|
r48474 | 'mercurial.dirstateutils', | ||
|
r44031 | 'mercurial.helptext', | ||
'mercurial.helptext.internals', | ||||
|
r43346 | 'mercurial.hgweb', | ||
'mercurial.interfaces', | ||||
'mercurial.pure', | ||||
|
r45864 | 'mercurial.templates', | ||
|
r43346 | 'mercurial.thirdparty', | ||
'mercurial.thirdparty.attr', | ||||
'mercurial.thirdparty.zope', | ||||
'mercurial.thirdparty.zope.interface', | ||||
r46661 | 'mercurial.upgrade_utils', | |||
|
r43346 | 'mercurial.utils', | ||
'mercurial.revlogutils', | ||||
'mercurial.testing', | ||||
'hgext', | ||||
'hgext.convert', | ||||
'hgext.fsmonitor', | ||||
'hgext.fastannotate', | ||||
'hgext.fsmonitor.pywatchman', | ||||
|
r44961 | 'hgext.git', | ||
|
r43346 | 'hgext.highlight', | ||
|
r44897 | 'hgext.hooklib', | ||
|
r43346 | 'hgext.infinitepush', | ||
'hgext.largefiles', | ||||
'hgext.lfs', | ||||
'hgext.narrow', | ||||
'hgext.remotefilelog', | ||||
'hgext.zeroconf', | ||||
'hgext3rd', | ||||
'hgdemandimport', | ||||
] | ||||
|
r45864 | |||
for name in os.listdir(os.path.join('mercurial', 'templates')): | ||||
if name != '__pycache__' and os.path.isdir( | ||||
os.path.join('mercurial', 'templates', name) | ||||
): | ||||
packages.append('mercurial.templates.%s' % name) | ||||
|
r42221 | if 'HG_PY2EXE_EXTRA_INSTALL_PACKAGES' in os.environ: | ||
# py2exe can't cope with namespace packages very well, so we have to | ||||
# install any hgext3rd.* extensions that we want in the final py2exe | ||||
# image here. This is gross, but you gotta do what you gotta do. | ||||
packages.extend(os.environ['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'].split(' ')) | ||||
|
r43346 | common_depends = [ | ||
'mercurial/bitmanipulation.h', | ||||
'mercurial/compat.h', | ||||
'mercurial/cext/util.h', | ||||
] | ||||
|
r32206 | common_include_dirs = ['mercurial'] | ||
|
r19724 | |||
|
r45079 | common_cflags = [] | ||
|
r45096 | # MSVC 2008 still needs declarations at the top of the scope, but Python 3.9 | ||
# makes declarations not at the top of a scope in the headers. | ||||
if os.name != 'nt' and sys.version_info[1] < 9: | ||||
|
r45079 | common_cflags = ['-Werror=declaration-after-statement'] | ||
|
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 [ | ||
|
r43346 | ( | ||
'bsd|darwin', | ||||
'BSD_STATFS', | ||||
''' | ||||
|
r31596 | #include <sys/param.h> | ||
#include <sys/mount.h> | ||||
int main() { struct statfs s; return sizeof(s.f_fstypename); } | ||||
|
r43346 | ''', | ||
), | ||||
( | ||||
'linux', | ||||
'LINUX_STATFS', | ||||
''' | ||||
|
r31622 | #include <linux/magic.h> | ||
#include <sys/vfs.h> | ||||
int main() { struct statfs s; return sizeof(s.f_type); } | ||||
|
r43346 | ''', | ||
), | ||||
|
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'] | ||||
|
r45199 | if sys.platform == 'sunos5': | ||
osutil_ldflags += ['-lsocket'] | ||||
|
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', | ||||
] | ||||
|
r43346 | |||
|
r41002 | class RustCompilationError(CCompilerError): | ||
"""Exception class for Rust compilation errors.""" | ||||
|
r43346 | |||
|
r40309 | class RustExtension(Extension): | ||
|
r46554 | """Base classes for concrete Rust Extension classes.""" | ||
|
r40309 | |||
rusttargetdir = os.path.join('rust', 'target', 'release') | ||||
|
r48996 | def __init__(self, mpath, sources, rustlibname, subcrate, **kw): | ||
|
r40309 | Extension.__init__(self, mpath, sources, **kw) | ||
srcdir = self.rustsrcdir = os.path.join('rust', subcrate) | ||||
# 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')): | ||||
|
r43346 | self.depends.extend( | ||
os.path.join(dirpath, fname) | ||||
for fname in fnames | ||||
if os.path.splitext(fname)[1] == '.rs' | ||||
) | ||||
|
r40309 | |||
|
r42657 | @staticmethod | ||
def rustdylibsuffix(): | ||||
"""Return the suffix for shared libraries produced by rustc. | ||||
See also: https://doc.rust-lang.org/reference/linkage.html | ||||
""" | ||||
if sys.platform == 'darwin': | ||||
return '.dylib' | ||||
elif os.name == 'nt': | ||||
return '.dll' | ||||
else: | ||||
return '.so' | ||||
|
r40309 | def rustbuild(self): | ||
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 | ||||
|
r43346 | |||
|
r40309 | env['HOME'] = pwd.getpwuid(os.getuid()).pw_dir | ||
|
r45365 | cargocmd = ['cargo', 'rustc', '--release'] | ||
|
r44786 | |||
rust_features = env.get("HG_RUST_FEATURES") | ||||
if rust_features: | ||||
|
r49697 | cargocmd.extend(('--features', rust_features)) | ||
|
r44786 | |||
|
r42659 | cargocmd.append('--') | ||
|
r42658 | if sys.platform == 'darwin': | ||
|
r43346 | cargocmd.extend( | ||
("-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup") | ||||
) | ||||
|
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( | ||||
|
r49333 | "Cargo found, but permission to execute it is denied" | ||
|
r43346 | ) | ||
|
r41002 | else: | ||
raise | ||||
except subprocess.CalledProcessError: | ||||
raise RustCompilationError( | ||||
"Cargo failed. Working directory: %r, " | ||||
|
r42243 | "command: %r, environment: %r" | ||
|
r43346 | % (self.rustsrcdir, cargocmd, env) | ||
) | ||||
|
r40309 | |||
|
r41003 | class RustStandaloneExtension(RustExtension): | ||
def __init__(self, pydottedname, rustcrate, dylibname, **kw): | ||||
|
r43346 | RustExtension.__init__( | ||
self, pydottedname, [], dylibname, rustcrate, **kw | ||||
) | ||||
|
r41003 | self.dylibname = dylibname | ||
def build(self, target_dir): | ||||
self.rustbuild() | ||||
target = [target_dir] | ||||
target.extend(self.name.split('.')) | ||||
|
r42657 | target[-1] += DYLIB_SUFFIX | ||
|
r43346 | shutil.copy2( | ||
os.path.join( | ||||
self.rusttargetdir, self.dylibname + self.rustdylibsuffix() | ||||
), | ||||
os.path.join(*target), | ||||
) | ||||
|
r41003 | |||
|
r10000 | extmodules = [ | ||
|
r43346 | Extension( | ||
'mercurial.cext.base85', | ||||
['mercurial/cext/base85.c'], | ||||
include_dirs=common_include_dirs, | ||||
|
r45079 | extra_compile_args=common_cflags, | ||
|
r43346 | depends=common_depends, | ||
), | ||||
Extension( | ||||
'mercurial.cext.bdiff', | ||||
['mercurial/bdiff.c', 'mercurial/cext/bdiff.c'] + xdiff_srcs, | ||||
include_dirs=common_include_dirs, | ||||
|
r45079 | extra_compile_args=common_cflags, | ||
|
r43346 | depends=common_depends + ['mercurial/bdiff.h'] + xdiff_headers, | ||
), | ||||
Extension( | ||||
'mercurial.cext.mpatch', | ||||
['mercurial/mpatch.c', 'mercurial/cext/mpatch.c'], | ||||
include_dirs=common_include_dirs, | ||||
|
r45079 | extra_compile_args=common_cflags, | ||
|
r43346 | depends=common_depends, | ||
), | ||||
r44956 | Extension( | |||
|
r43346 | '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', | ||||
], | ||||
|
r41003 | include_dirs=common_include_dirs, | ||
|
r45079 | extra_compile_args=common_cflags, | ||
|
r43346 | depends=common_depends | ||
|
r46554 | + [ | ||
'mercurial/cext/charencode.h', | ||||
'mercurial/cext/revlog.h', | ||||
], | ||||
|
r43346 | ), | ||
|
r37197 | Extension( | ||
|
r43346 | 'mercurial.cext.osutil', | ||
['mercurial/cext/osutil.c'], | ||||
include_dirs=common_include_dirs, | ||||
|
r45079 | extra_compile_args=common_cflags + osutil_cflags, | ||
|
r43346 | extra_link_args=osutil_ldflags, | ||
depends=common_depends, | ||||
), | ||||
Extension( | ||||
'mercurial.thirdparty.zope.interface._zope_interface_coptimizations', | ||||
[ | ||||
'mercurial/thirdparty/zope/interface/_zope_interface_coptimizations.c', | ||||
], | ||||
|
r45079 | extra_compile_args=common_cflags, | ||
|
r43346 | ), | ||
Extension( | ||||
|
r44510 | 'mercurial.thirdparty.sha1dc', | ||
[ | ||||
'mercurial/thirdparty/sha1dc/cext.c', | ||||
'mercurial/thirdparty/sha1dc/lib/sha1.c', | ||||
'mercurial/thirdparty/sha1dc/lib/ubc_check.c', | ||||
], | ||||
|
r45079 | extra_compile_args=common_cflags, | ||
|
r44510 | ), | ||
Extension( | ||||
|
r45079 | 'hgext.fsmonitor.pywatchman.bser', | ||
['hgext/fsmonitor/pywatchman/bser.c'], | ||||
extra_compile_args=common_cflags, | ||||
|
r43346 | ), | ||
RustStandaloneExtension( | ||||
|
r48996 | 'mercurial.rustext', | ||
'hg-cpython', | ||||
'librusthg', | ||||
|
r43346 | ), | ||
] | ||||
|
r5396 | |||
|
r41003 | |||
|
r30436 | sys.path.insert(0, 'contrib/python-zstandard') | ||
import setup_zstd | ||||
|
r43346 | |||
|
r45079 | zstd = setup_zstd.get_c_extension( | ||
name='mercurial.zstd', root=os.path.abspath(os.path.dirname(__file__)) | ||||
|
r43346 | ) | ||
|
r45079 | zstd.extra_compile_args += common_cflags | ||
extmodules.append(zstd) | ||||
|
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 | ||||
|
r49801 | class HackedMingw32CCompiler: | ||
|
r23677 | pass | ||
|
r17121 | |||
|
r43346 | |||
|
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 | ||||
|
r43346 | packagedata = { | ||
|
r46554 | 'mercurial': [ | ||
'locale/*/LC_MESSAGES/hg.mo', | ||||
'dummycert.pem', | ||||
], | ||||
'mercurial.defaultrc': [ | ||||
'*.rc', | ||||
], | ||||
'mercurial.helptext': [ | ||||
'*.txt', | ||||
], | ||||
'mercurial.helptext.internals': [ | ||||
'*.txt', | ||||
], | ||||
|
r43346 | } | ||
|
r9999 | |||
def ordinarypath(p): | ||||
return p and p[0] != '.' and p[-1] != '~' | ||||
|
r43346 | |||
|
r10282 | for root in ('templates',): | ||
|
r9999 | for curdir, dirs, files in os.walk(os.path.join('mercurial', root)): | ||
|
r45865 | packagename = curdir.replace(os.sep, '.') | ||
packagedata[packagename] = list(filter(ordinarypath, files)) | ||||
|
r9999 | |||
|
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. | ||||
|
r47262 | assert isinstance(version, str) | ||
setupversion = version | ||||
|
r31316 | |||
|
r10400 | extra = {} | ||
|
r42017 | py2exepackages = [ | ||
'hgdemandimport', | ||||
|
r42084 | 'hgext3rd', | ||
|
r42017 | 'hgext', | ||
'email', | ||||
# implicitly imported per module policy | ||||
# (cffi wouldn't be used as a frozen exe) | ||||
'mercurial.cext', | ||||
#'mercurial.cffi', | ||||
'mercurial.pure', | ||||
] | ||||
|
r47109 | py2exe_includes = [] | ||
|
r42082 | py2exeexcludes = [] | ||
|
r42120 | py2exedllexcludes = ['crypt32.dll'] | ||
|
r42082 | |||
|
r33601 | if issetuptools: | ||
extra['python_requires'] = supportedpy | ||||
|
r42017 | |||
|
r10400 | if py2exeloaded: | ||
extra['console'] = [ | ||||
|
r43346 | { | ||
'script': 'hg', | ||||
|
r49616 | 'copyright': 'Copyright (C) 2005-2022 Olivia Mackall and others', | ||
|
r43346 | 'product_version': version, | ||
} | ||||
] | ||||
|
r42083 | # Sub command of 'build' because 'py2exe' does not handle sub_commands. | ||
# Need to override hgbuild because it has a private copy of | ||||
# build.sub_commands. | ||||
hgbuild.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 | |||
|
r42082 | # We allow some configuration to be supplemented via environment | ||
# variables. This is better than setup.cfg files because it allows | ||||
# supplementing configs instead of replacing them. | ||||
extrapackages = os.environ.get('HG_PY2EXE_EXTRA_PACKAGES') | ||||
if extrapackages: | ||||
py2exepackages.extend(extrapackages.split(' ')) | ||||
|
r42017 | |||
|
r47109 | extra_includes = os.environ.get('HG_PY2EXE_EXTRA_INCLUDES') | ||
if extra_includes: | ||||
py2exe_includes.extend(extra_includes.split(' ')) | ||||
|
r42082 | excludes = os.environ.get('HG_PY2EXE_EXTRA_EXCLUDES') | ||
if excludes: | ||||
py2exeexcludes.extend(excludes.split(' ')) | ||||
|
r42017 | |||
|
r42082 | dllexcludes = os.environ.get('HG_PY2EXE_EXTRA_DLL_EXCLUDES') | ||
if dllexcludes: | ||||
py2exedllexcludes.extend(dllexcludes.split(' ')) | ||||
|
r42017 | |||
|
r45692 | if os.environ.get('PYOXIDIZER'): | ||
hgbuild.sub_commands.insert(0, ('build_hgextindex', None)) | ||||
|
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: | ||
|
r49693 | version = version[0].decode('utf-8') | ||
|
r43346 | 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'] = ( | ||
|
r43346 | os.environ.get('CFLAGS', '') + ' -Qunused-arguments' | ||
) | ||||
|
r21038 | |||
|
r43346 | setup( | ||
name='mercurial', | ||||
version=setupversion, | ||||
|
r47575 | author='Olivia Mackall and many others', | ||
|
r43346 | author_email='mercurial@mercurial-scm.org', | ||
url='https://mercurial-scm.org/', | ||||
download_url='https://mercurial-scm.org/release/', | ||||
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', | ||||
], | ||||
scripts=scripts, | ||||
packages=packages, | ||||
ext_modules=extmodules, | ||||
data_files=datafiles, | ||||
package_data=packagedata, | ||||
cmdclass=cmdclass, | ||||
distclass=hgdist, | ||||
options={ | ||||
'py2exe': { | ||||
'bundle_files': 3, | ||||
'dll_excludes': py2exedllexcludes, | ||||
|
r47109 | 'includes': py2exe_includes, | ||
|
r43346 | 'excludes': py2exeexcludes, | ||
'packages': py2exepackages, | ||||
}, | ||||
'bdist_mpkg': { | ||||
'zipdist': False, | ||||
'license': 'COPYING', | ||||
'readme': 'contrib/packaging/macosx/Readme.html', | ||||
'welcome': 'contrib/packaging/macosx/Welcome.html', | ||||
}, | ||||
}, | ||||
**extra | ||||
) | ||||