mirror of
https://github.com/facebook/sapling.git
synced 2024-10-11 01:07:15 +03:00
d768b47592
We give up using CPython's PythonXX.lib import libraries (and Python.h), and now "manually" call the LoadLibrary() / GetProcAddress() Windows API's instead. If there is a "hg-python" subdirectory (the canonical directory name for HackableMercurial's private Python copy) next to the hg.exe, we load the pythonXX.dll from there (feeding an absolute path to LoadLibrary) and we set Py_SetPythonHome() to that directory, so that the Python libraries are used from there as well. If there is no "hg-python" subdir found next to the hg.exe, we do not feed an absolute path to LoadLibrary. This continues to allow to find a globally installed Python DLL, as before this change - that is, without having to edit, delete, rename, or configure anything. Note that the hg.exe built is still bound to a *specific* major version of the pythonXX.dll (e.g. python27.dll). What version it is, is inferred from the version of the python interpreter that was used when calling setup.py. For example C:\python27_x86\python.exe setup.py build_hgexe -i --compiler=mingw32 builds a hg.exe (using the mingw32 tool chain) bound to (x86) Python 2.7. And C:\python27_x86\python.exe setup.py build_hgexe -i builds the same using the Microsoft C compiler/linker. (Note that the Microsoft toolchain combined with x64 CPython can be used to build an x64 hg.exe.) setup.py is changed to write the name of the pythonlib into the generated header file "mercurial/hgpythonlib.h", which is #included by exewrapper.c. For a Python 2.7 build, it for example contains: #define HGPYTHONLIB "python27" exewrapper.c then uses HGPYTHONLIB for the name of the Python dll to load. We don't want to track mercurial/hgpythonlib.h, so we add it to .hgignore.
539 lines
19 KiB
Python
539 lines
19 KiB
Python
#
|
|
# This is the mercurial setup script.
|
|
#
|
|
# 'python setup.py install', or
|
|
# 'python setup.py --help' for more options
|
|
|
|
import sys, platform
|
|
if getattr(sys, 'version_info', (0, 0, 0)) < (2, 4, 0, 'final'):
|
|
raise SystemExit("Mercurial requires Python 2.4 or later.")
|
|
|
|
if sys.version_info[0] >= 3:
|
|
def b(s):
|
|
'''A helper function to emulate 2.6+ bytes literals using string
|
|
literals.'''
|
|
return s.encode('latin1')
|
|
else:
|
|
def b(s):
|
|
'''A helper function to emulate 2.6+ bytes literals using string
|
|
literals.'''
|
|
return s
|
|
|
|
# Solaris Python packaging brain damage
|
|
try:
|
|
import hashlib
|
|
sha = hashlib.sha1()
|
|
except ImportError:
|
|
try:
|
|
import sha
|
|
except ImportError:
|
|
raise SystemExit(
|
|
"Couldn't import standard hashlib (incomplete Python install).")
|
|
|
|
try:
|
|
import zlib
|
|
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
|
|
except ImportError:
|
|
raise SystemExit(
|
|
"Couldn't import standard bz2 (incomplete Python install).")
|
|
|
|
import os, subprocess, time
|
|
import shutil
|
|
import tempfile
|
|
from distutils import log
|
|
from distutils.core import setup, 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.install_scripts import install_scripts
|
|
from distutils.spawn import spawn, find_executable
|
|
from distutils.ccompiler import new_compiler
|
|
from distutils import cygwinccompiler
|
|
from distutils.errors import CCompilerError, DistutilsExecError
|
|
from distutils.sysconfig import get_python_inc
|
|
from distutils.version import StrictVersion
|
|
|
|
convert2to3 = '--c2to3' in sys.argv
|
|
if convert2to3:
|
|
try:
|
|
from distutils.command.build_py import build_py_2to3 as build_py
|
|
from lib2to3.refactor import get_fixers_from_package as getfixers
|
|
except ImportError:
|
|
if sys.version_info[0] < 3:
|
|
raise SystemExit("--c2to3 is only compatible with python3.")
|
|
raise
|
|
sys.path.append('contrib')
|
|
elif sys.version_info[0] >= 3:
|
|
raise SystemExit("setup.py with python3 needs --c2to3 (experimental)")
|
|
|
|
scripts = ['hg']
|
|
if os.name == 'nt':
|
|
scripts.append('contrib/win32/hg.bat')
|
|
|
|
# simplified version of distutils.ccompiler.CCompiler.has_function
|
|
# that actually removes its temporary files.
|
|
def hasfunction(cc, funcname):
|
|
tmpdir = tempfile.mkdtemp(prefix='hg-install-')
|
|
devnull = oldstderr = None
|
|
try:
|
|
try:
|
|
fname = os.path.join(tmpdir, 'funcname.c')
|
|
f = open(fname, 'w')
|
|
f.write('int main(void) {\n')
|
|
f.write(' %s();\n' % funcname)
|
|
f.write('}\n')
|
|
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"))
|
|
except Exception:
|
|
return False
|
|
return True
|
|
finally:
|
|
if oldstderr is not None:
|
|
os.dup2(oldstderr, sys.stderr.fileno())
|
|
if devnull is not None:
|
|
devnull.close()
|
|
shutil.rmtree(tmpdir)
|
|
|
|
# py2exe needs to be installed to work
|
|
try:
|
|
import py2exe
|
|
py2exeloaded = True
|
|
# import py2exe's patched Distribution class
|
|
from distutils.core import Distribution
|
|
except ImportError:
|
|
py2exeloaded = False
|
|
|
|
def runcmd(cmd, env):
|
|
if sys.platform == 'plan9':
|
|
# subprocess kludge to work around issues in half-baked Python
|
|
# ports, notably bichued/python:
|
|
_, out, err = os.popen3(cmd)
|
|
return str(out), str(err)
|
|
else:
|
|
p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE, env=env)
|
|
out, err = p.communicate()
|
|
return out, err
|
|
|
|
def runhg(cmd, env):
|
|
out, err = runcmd(cmd, env)
|
|
# 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'))]
|
|
if err:
|
|
return ''
|
|
return out
|
|
|
|
version = ''
|
|
|
|
# Execute hg out of this directory with a custom environment which
|
|
# includes the pure Python modules in mercurial/pure. We also take
|
|
# care to not use any hgrc files and do no localization.
|
|
pypath = ['mercurial', os.path.join('mercurial', 'pure')]
|
|
env = {'PYTHONPATH': os.pathsep.join(pypath),
|
|
'HGRCPATH': '',
|
|
'LANGUAGE': 'C'}
|
|
if 'LD_LIBRARY_PATH' in os.environ:
|
|
env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
|
|
if 'SystemRoot' in os.environ:
|
|
# Copy SystemRoot into the custom environment for Python 2.6
|
|
# under Windows. Otherwise, the subprocess will fail with
|
|
# error 0xc0150004. See: http://bugs.python.org/issue3440
|
|
env['SystemRoot'] = os.environ['SystemRoot']
|
|
|
|
if os.path.isdir('.hg'):
|
|
cmd = [sys.executable, 'hg', 'log', '-r', '.', '--template', '{tags}\n']
|
|
numerictags = [t for t in runhg(cmd, env).split() if t[0].isdigit()]
|
|
hgid = runhg([sys.executable, 'hg', 'id', '-i'], env).strip()
|
|
if numerictags: # tag(s) found
|
|
version = numerictags[-1]
|
|
if hgid.endswith('+'): # propagate the dirty status to the tag
|
|
version += '+'
|
|
else: # no tag found
|
|
cmd = [sys.executable, 'hg', 'parents', '--template',
|
|
'{latesttag}+{latesttagdistance}-']
|
|
version = runhg(cmd, env) + hgid
|
|
if version.endswith('+'):
|
|
version += time.strftime('%Y%m%d')
|
|
elif os.path.exists('.hg_archival.txt'):
|
|
kw = dict([[t.strip() for t in l.split(':', 1)]
|
|
for l in open('.hg_archival.txt')])
|
|
if 'tag' in kw:
|
|
version = kw['tag']
|
|
elif 'latesttag' in kw:
|
|
version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
|
|
else:
|
|
version = kw.get('node', '')[:12]
|
|
|
|
if version:
|
|
f = open("mercurial/__version__.py", "w")
|
|
f.write('# this file is autogenerated by setup.py\n')
|
|
f.write('version = "%s"\n' % version)
|
|
f.close()
|
|
|
|
|
|
try:
|
|
from mercurial import __version__
|
|
version = __version__.version
|
|
except ImportError:
|
|
version = 'unknown'
|
|
|
|
class hgbuild(build):
|
|
# Insert hgbuildmo first so that files in mercurial/locale/ are found
|
|
# when build_py is run next.
|
|
sub_commands = [('build_mo', None),
|
|
|
|
# We also need build_ext before build_py. Otherwise, when 2to3 is
|
|
# called (in build_py), it will not find osutil & friends,
|
|
# thinking that those modules are global and, consequently, making
|
|
# a mess, now that all module imports are global.
|
|
|
|
('build_ext', build.has_ext_modules),
|
|
] + build.sub_commands
|
|
|
|
class hgbuildmo(build):
|
|
|
|
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')
|
|
mobuildfile = join('mercurial', mofile)
|
|
cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
|
|
if sys.platform != 'sunos5':
|
|
# msgfmt on Solaris does not know about -c
|
|
cmd.append('-c')
|
|
self.mkpath(join('mercurial', modir))
|
|
self.make_file([pofile], mobuildfile, spawn, (cmd,))
|
|
|
|
|
|
class hgdist(Distribution):
|
|
pure = 0
|
|
|
|
global_options = Distribution.global_options + \
|
|
[('pure', None, "use pure (slow) Python "
|
|
"code instead of C extensions"),
|
|
('c2to3', None, "(experimental!) convert "
|
|
"code with 2to3"),
|
|
]
|
|
|
|
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)
|
|
|
|
class hgbuildext(build_ext):
|
|
|
|
def build_extension(self, ext):
|
|
try:
|
|
build_ext.build_extension(self, ext)
|
|
except CCompilerError:
|
|
if not getattr(ext, 'optional', False):
|
|
raise
|
|
log.warn("Failed to build optional extension '%s' (skipping)",
|
|
ext.name)
|
|
|
|
class hgbuildpy(build_py):
|
|
if convert2to3:
|
|
fixer_names = sorted(set(getfixers("lib2to3.fixes") +
|
|
getfixers("hgfixes")))
|
|
|
|
def finalize_options(self):
|
|
build_py.finalize_options(self)
|
|
|
|
if self.distribution.pure:
|
|
if self.py_modules is None:
|
|
self.py_modules = []
|
|
for ext in self.distribution.ext_modules:
|
|
if ext.name.startswith("mercurial."):
|
|
self.py_modules.append("mercurial.pure.%s" % ext.name[10:])
|
|
self.distribution.ext_modules = []
|
|
else:
|
|
if not os.path.exists(os.path.join(get_python_inc(), 'Python.h')):
|
|
raise SystemExit('Python headers are required to build '
|
|
'Mercurial')
|
|
|
|
def find_modules(self):
|
|
modules = build_py.find_modules(self)
|
|
for module in modules:
|
|
if module[0] == "mercurial.pure":
|
|
if module[1] != "__init__":
|
|
yield ("mercurial", module[1], module[2])
|
|
else:
|
|
yield module
|
|
|
|
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):
|
|
os.unlink(self._indexfilename)
|
|
|
|
# here no extension enabled, disabled() lists up everything
|
|
code = ('import pprint; from mercurial import extensions; '
|
|
'pprint.pprint(extensions.disabled())')
|
|
out, err = runcmd([sys.executable, '-c', code], env)
|
|
if err:
|
|
raise DistutilsExecError(err)
|
|
|
|
f = open(self._indexfilename, 'w')
|
|
f.write('# this file is autogenerated by setup.py\n')
|
|
f.write('docs = ')
|
|
f.write(out)
|
|
f.close()
|
|
|
|
class buildhgexe(build_ext):
|
|
description = 'compile hg.exe from mercurial/exewrapper.c'
|
|
|
|
def build_extensions(self):
|
|
if os.name != 'nt':
|
|
return
|
|
if isinstance(self.compiler, HackedMingw32CCompiler):
|
|
self.compiler.compiler_so = self.compiler.compiler # no -mdll
|
|
self.compiler.dll_libraries = [] # no -lmsrvc90
|
|
hv = sys.hexversion
|
|
pythonlib = 'python%d%d' % (hv >> 24, (hv >> 16) & 0xff)
|
|
f = open('mercurial/hgpythonlib.h', 'wb')
|
|
f.write('/* this file is autogenerated by setup.py */\n')
|
|
f.write('#define HGPYTHONLIB "%s"\n' % pythonlib)
|
|
f.close()
|
|
objects = self.compiler.compile(['mercurial/exewrapper.c'],
|
|
output_dir=self.build_temp)
|
|
dir = os.path.dirname(self.get_ext_fullpath('dummy'))
|
|
target = os.path.join(dir, 'hg')
|
|
self.compiler.link_executable(objects, target,
|
|
libraries=[],
|
|
output_dir=self.build_temp)
|
|
|
|
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)
|
|
|
|
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])
|
|
|
|
libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common):]
|
|
|
|
for outfile in self.outfiles:
|
|
fp = open(outfile, 'rb')
|
|
data = fp.read()
|
|
fp.close()
|
|
|
|
# skip binary files
|
|
if b('\0') in data:
|
|
continue
|
|
|
|
data = data.replace('@LIBDIR@', libdir.encode('string_escape'))
|
|
fp = open(outfile, 'wb')
|
|
fp.write(data)
|
|
fp.close()
|
|
|
|
cmdclass = {'build': hgbuild,
|
|
'build_mo': hgbuildmo,
|
|
'build_ext': hgbuildext,
|
|
'build_py': hgbuildpy,
|
|
'build_hgextindex': buildhgextindex,
|
|
'install_scripts': hginstallscripts,
|
|
'build_hgexe': buildhgexe,
|
|
}
|
|
|
|
packages = ['mercurial', 'mercurial.hgweb', 'mercurial.httpclient',
|
|
'hgext', 'hgext.convert', 'hgext.highlight', 'hgext.zeroconf',
|
|
'hgext.largefiles']
|
|
|
|
pymodules = []
|
|
|
|
extmodules = [
|
|
Extension('mercurial.base85', ['mercurial/base85.c']),
|
|
Extension('mercurial.bdiff', ['mercurial/bdiff.c']),
|
|
Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c']),
|
|
Extension('mercurial.mpatch', ['mercurial/mpatch.c']),
|
|
Extension('mercurial.parsers', ['mercurial/parsers.c',
|
|
'mercurial/pathencode.c']),
|
|
]
|
|
|
|
osutil_ldflags = []
|
|
|
|
if sys.platform == 'darwin':
|
|
osutil_ldflags += ['-framework', 'ApplicationServices']
|
|
|
|
# disable osutil.c under windows + python 2.4 (issue1364)
|
|
if sys.platform == 'win32' and sys.version_info < (2, 5, 0, 'final'):
|
|
pymodules.append('mercurial.pure.osutil')
|
|
else:
|
|
extmodules.append(Extension('mercurial.osutil', ['mercurial/osutil.c'],
|
|
extra_link_args=osutil_ldflags))
|
|
|
|
# the -mno-cygwin option has been deprecated for years
|
|
Mingw32CCompiler = cygwinccompiler.Mingw32CCompiler
|
|
|
|
class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
|
|
def __init__(self, *args, **kwargs):
|
|
Mingw32CCompiler.__init__(self, *args, **kwargs)
|
|
for i in 'compiler compiler_so linker_exe linker_so'.split():
|
|
try:
|
|
getattr(self, i).remove('-mno-cygwin')
|
|
except ValueError:
|
|
pass
|
|
|
|
cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
|
|
|
|
if sys.platform.startswith('linux') and os.uname()[2] > '2.6':
|
|
# The inotify extension is only usable with Linux 2.6 kernels.
|
|
# You also need a reasonably recent C library.
|
|
# In any case, if it fails to build the error will be skipped ('optional').
|
|
cc = new_compiler()
|
|
if hasfunction(cc, 'inotify_add_watch'):
|
|
inotify = Extension('hgext.inotify.linux._inotify',
|
|
['hgext/inotify/linux/_inotify.c'],
|
|
['mercurial'])
|
|
inotify.optional = True
|
|
extmodules.append(inotify)
|
|
packages.extend(['hgext.inotify', 'hgext.inotify.linux'])
|
|
|
|
packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
|
|
'help/*.txt']}
|
|
|
|
def ordinarypath(p):
|
|
return p and p[0] != '.' and p[-1] != '~'
|
|
|
|
for root in ('templates',):
|
|
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)
|
|
|
|
datafiles = []
|
|
setupversion = version
|
|
extra = {}
|
|
|
|
if py2exeloaded:
|
|
extra['console'] = [
|
|
{'script':'hg',
|
|
'copyright':'Copyright (C) 2005-2010 Matt Mackall and others',
|
|
'product_version':version}]
|
|
# sub command of 'build' because 'py2exe' does not handle sub_commands
|
|
build.sub_commands.insert(0, ('build_hgextindex', None))
|
|
|
|
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
|
|
setupversion = version.split('+', 1)[0]
|
|
|
|
if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
|
|
# XCode 4.0 dropped support for ppc architecture, which is hardcoded in
|
|
# distutils.sysconfig
|
|
version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[0].splitlines()
|
|
if version:
|
|
version = version[0]
|
|
xcode4 = (version.startswith('Xcode') and
|
|
StrictVersion(version.split()[1]) >= StrictVersion('4.0'))
|
|
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
|
|
|
|
if xcode4:
|
|
os.environ['ARCHFLAGS'] = ''
|
|
|
|
setup(name='mercurial',
|
|
version=setupversion,
|
|
author='Matt Mackall',
|
|
author_email='mpm@selenic.com',
|
|
url='http://mercurial.selenic.com/',
|
|
description='Scalable distributed SCM',
|
|
license='GNU GPLv2+',
|
|
scripts=scripts,
|
|
packages=packages,
|
|
py_modules=pymodules,
|
|
ext_modules=extmodules,
|
|
data_files=datafiles,
|
|
package_data=packagedata,
|
|
cmdclass=cmdclass,
|
|
distclass=hgdist,
|
|
options=dict(py2exe=dict(packages=['hgext', 'email']),
|
|
bdist_mpkg=dict(zipdist=True,
|
|
license='COPYING',
|
|
readme='contrib/macosx/Readme.html',
|
|
welcome='contrib/macosx/Welcome.html')),
|
|
**extra)
|