##// END OF EJS Templates
py2exe: add workaround to allow bundling of hgext3rd.* extensions...
Augie Fackler -
r42221:399ed3e8 default
parent child Browse files
Show More
@@ -1,146 +1,150 b''
1 # py2exe.py - Functionality for performing py2exe builds.
1 # py2exe.py - Functionality for performing py2exe builds.
2 #
2 #
3 # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com>
3 # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 # no-check-code because Python 3 native.
8 # no-check-code because Python 3 native.
9
9
10 import os
10 import os
11 import pathlib
11 import pathlib
12 import subprocess
12 import subprocess
13
13
14 from .downloads import (
14 from .downloads import (
15 download_entry,
15 download_entry,
16 )
16 )
17 from .util import (
17 from .util import (
18 extract_tar_to_directory,
18 extract_tar_to_directory,
19 extract_zip_to_directory,
19 extract_zip_to_directory,
20 python_exe_info,
20 python_exe_info,
21 )
21 )
22
22
23
23
24 def build_py2exe(source_dir: pathlib.Path, build_dir: pathlib.Path,
24 def build_py2exe(source_dir: pathlib.Path, build_dir: pathlib.Path,
25 python_exe: pathlib.Path, build_name: str,
25 python_exe: pathlib.Path, build_name: str,
26 venv_requirements_txt: pathlib.Path,
26 venv_requirements_txt: pathlib.Path,
27 extra_packages=None, extra_excludes=None,
27 extra_packages=None, extra_excludes=None,
28 extra_dll_excludes=None,
28 extra_dll_excludes=None,
29 extra_packages_script=None):
29 extra_packages_script=None):
30 """Build Mercurial with py2exe.
30 """Build Mercurial with py2exe.
31
31
32 Build files will be placed in ``build_dir``.
32 Build files will be placed in ``build_dir``.
33
33
34 py2exe's setup.py doesn't use setuptools. It doesn't have modern logic
34 py2exe's setup.py doesn't use setuptools. It doesn't have modern logic
35 for finding the Python 2.7 toolchain. So, we require the environment
35 for finding the Python 2.7 toolchain. So, we require the environment
36 to already be configured with an active toolchain.
36 to already be configured with an active toolchain.
37 """
37 """
38 if 'VCINSTALLDIR' not in os.environ:
38 if 'VCINSTALLDIR' not in os.environ:
39 raise Exception('not running from a Visual C++ build environment; '
39 raise Exception('not running from a Visual C++ build environment; '
40 'execute the "Visual C++ <version> Command Prompt" '
40 'execute the "Visual C++ <version> Command Prompt" '
41 'application shortcut or a vcsvarsall.bat file')
41 'application shortcut or a vcsvarsall.bat file')
42
42
43 # Identity x86/x64 and validate the environment matches the Python
43 # Identity x86/x64 and validate the environment matches the Python
44 # architecture.
44 # architecture.
45 vc_x64 = r'\x64' in os.environ['LIB']
45 vc_x64 = r'\x64' in os.environ['LIB']
46
46
47 py_info = python_exe_info(python_exe)
47 py_info = python_exe_info(python_exe)
48
48
49 if vc_x64:
49 if vc_x64:
50 if py_info['arch'] != '64bit':
50 if py_info['arch'] != '64bit':
51 raise Exception('architecture mismatch: Visual C++ environment '
51 raise Exception('architecture mismatch: Visual C++ environment '
52 'is configured for 64-bit but Python is 32-bit')
52 'is configured for 64-bit but Python is 32-bit')
53 else:
53 else:
54 if py_info['arch'] != '32bit':
54 if py_info['arch'] != '32bit':
55 raise Exception('architecture mismatch: Visual C++ environment '
55 raise Exception('architecture mismatch: Visual C++ environment '
56 'is configured for 32-bit but Python is 64-bit')
56 'is configured for 32-bit but Python is 64-bit')
57
57
58 if py_info['py3']:
58 if py_info['py3']:
59 raise Exception('Only Python 2 is currently supported')
59 raise Exception('Only Python 2 is currently supported')
60
60
61 build_dir.mkdir(exist_ok=True)
61 build_dir.mkdir(exist_ok=True)
62
62
63 gettext_pkg, gettext_entry = download_entry('gettext', build_dir)
63 gettext_pkg, gettext_entry = download_entry('gettext', build_dir)
64 gettext_dep_pkg = download_entry('gettext-dep', build_dir)[0]
64 gettext_dep_pkg = download_entry('gettext-dep', build_dir)[0]
65 virtualenv_pkg, virtualenv_entry = download_entry('virtualenv', build_dir)
65 virtualenv_pkg, virtualenv_entry = download_entry('virtualenv', build_dir)
66 py2exe_pkg, py2exe_entry = download_entry('py2exe', build_dir)
66 py2exe_pkg, py2exe_entry = download_entry('py2exe', build_dir)
67
67
68 venv_path = build_dir / ('venv-%s-%s' % (build_name,
68 venv_path = build_dir / ('venv-%s-%s' % (build_name,
69 'x64' if vc_x64 else 'x86'))
69 'x64' if vc_x64 else 'x86'))
70
70
71 gettext_root = build_dir / (
71 gettext_root = build_dir / (
72 'gettext-win-%s' % gettext_entry['version'])
72 'gettext-win-%s' % gettext_entry['version'])
73
73
74 if not gettext_root.exists():
74 if not gettext_root.exists():
75 extract_zip_to_directory(gettext_pkg, gettext_root)
75 extract_zip_to_directory(gettext_pkg, gettext_root)
76 extract_zip_to_directory(gettext_dep_pkg, gettext_root)
76 extract_zip_to_directory(gettext_dep_pkg, gettext_root)
77
77
78 # This assumes Python 2. We don't need virtualenv on Python 3.
78 # This assumes Python 2. We don't need virtualenv on Python 3.
79 virtualenv_src_path = build_dir / (
79 virtualenv_src_path = build_dir / (
80 'virtualenv-%s' % virtualenv_entry['version'])
80 'virtualenv-%s' % virtualenv_entry['version'])
81 virtualenv_py = virtualenv_src_path / 'virtualenv.py'
81 virtualenv_py = virtualenv_src_path / 'virtualenv.py'
82
82
83 if not virtualenv_src_path.exists():
83 if not virtualenv_src_path.exists():
84 extract_tar_to_directory(virtualenv_pkg, build_dir)
84 extract_tar_to_directory(virtualenv_pkg, build_dir)
85
85
86 py2exe_source_path = build_dir / ('py2exe-%s' % py2exe_entry['version'])
86 py2exe_source_path = build_dir / ('py2exe-%s' % py2exe_entry['version'])
87
87
88 if not py2exe_source_path.exists():
88 if not py2exe_source_path.exists():
89 extract_zip_to_directory(py2exe_pkg, build_dir)
89 extract_zip_to_directory(py2exe_pkg, build_dir)
90
90
91 if not venv_path.exists():
91 if not venv_path.exists():
92 print('creating virtualenv with dependencies')
92 print('creating virtualenv with dependencies')
93 subprocess.run(
93 subprocess.run(
94 [str(python_exe), str(virtualenv_py), str(venv_path)],
94 [str(python_exe), str(virtualenv_py), str(venv_path)],
95 check=True)
95 check=True)
96
96
97 venv_python = venv_path / 'Scripts' / 'python.exe'
97 venv_python = venv_path / 'Scripts' / 'python.exe'
98 venv_pip = venv_path / 'Scripts' / 'pip.exe'
98 venv_pip = venv_path / 'Scripts' / 'pip.exe'
99
99
100 subprocess.run([str(venv_pip), 'install', '-r', str(venv_requirements_txt)],
100 subprocess.run([str(venv_pip), 'install', '-r', str(venv_requirements_txt)],
101 check=True)
101 check=True)
102
102
103 # Force distutils to use VC++ settings from environment, which was
103 # Force distutils to use VC++ settings from environment, which was
104 # validated above.
104 # validated above.
105 env = dict(os.environ)
105 env = dict(os.environ)
106 env['DISTUTILS_USE_SDK'] = '1'
106 env['DISTUTILS_USE_SDK'] = '1'
107 env['MSSdk'] = '1'
107 env['MSSdk'] = '1'
108
108
109 if extra_packages_script:
109 if extra_packages_script:
110 more_packages = set(subprocess.check_output(
110 more_packages = set(subprocess.check_output(
111 extra_packages_script,
111 extra_packages_script,
112 cwd=build_dir).split(b'\0')[-1].strip().decode('utf-8').splitlines())
112 cwd=build_dir).split(b'\0')[-1].strip().decode('utf-8').splitlines())
113 if more_packages:
113 if more_packages:
114 if not extra_packages:
114 if not extra_packages:
115 extra_packages = more_packages
115 extra_packages = more_packages
116 else:
116 else:
117 extra_packages |= more_packages
117 extra_packages |= more_packages
118
118
119 if extra_packages:
119 if extra_packages:
120 env['HG_PY2EXE_EXTRA_PACKAGES'] = ' '.join(sorted(extra_packages))
120 env['HG_PY2EXE_EXTRA_PACKAGES'] = ' '.join(sorted(extra_packages))
121 hgext3rd_extras = sorted(
122 e for e in extra_packages if e.startswith('hgext3rd.'))
123 if hgext3rd_extras:
124 env['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'] = ' '.join(hgext3rd_extras)
121 if extra_excludes:
125 if extra_excludes:
122 env['HG_PY2EXE_EXTRA_EXCLUDES'] = ' '.join(sorted(extra_excludes))
126 env['HG_PY2EXE_EXTRA_EXCLUDES'] = ' '.join(sorted(extra_excludes))
123 if extra_dll_excludes:
127 if extra_dll_excludes:
124 env['HG_PY2EXE_EXTRA_DLL_EXCLUDES'] = ' '.join(
128 env['HG_PY2EXE_EXTRA_DLL_EXCLUDES'] = ' '.join(
125 sorted(extra_dll_excludes))
129 sorted(extra_dll_excludes))
126
130
127 py2exe_py_path = venv_path / 'Lib' / 'site-packages' / 'py2exe'
131 py2exe_py_path = venv_path / 'Lib' / 'site-packages' / 'py2exe'
128 if not py2exe_py_path.exists():
132 if not py2exe_py_path.exists():
129 print('building py2exe')
133 print('building py2exe')
130 subprocess.run([str(venv_python), 'setup.py', 'install'],
134 subprocess.run([str(venv_python), 'setup.py', 'install'],
131 cwd=py2exe_source_path,
135 cwd=py2exe_source_path,
132 env=env,
136 env=env,
133 check=True)
137 check=True)
134
138
135 # Register location of msgfmt and other binaries.
139 # Register location of msgfmt and other binaries.
136 env['PATH'] = '%s%s%s' % (
140 env['PATH'] = '%s%s%s' % (
137 env['PATH'], os.pathsep, str(gettext_root / 'bin'))
141 env['PATH'], os.pathsep, str(gettext_root / 'bin'))
138
142
139 print('building Mercurial')
143 print('building Mercurial')
140 subprocess.run(
144 subprocess.run(
141 [str(venv_python), 'setup.py',
145 [str(venv_python), 'setup.py',
142 'py2exe',
146 'py2exe',
143 'build_doc', '--html'],
147 'build_doc', '--html'],
144 cwd=str(source_dir),
148 cwd=str(source_dir),
145 env=env,
149 env=env,
146 check=True)
150 check=True)
@@ -1,1379 +1,1385 b''
1 #
1 #
2 # This is the mercurial setup script.
2 # This is the mercurial setup script.
3 #
3 #
4 # 'python setup.py install', or
4 # 'python setup.py install', or
5 # 'python setup.py --help' for more options
5 # 'python setup.py --help' for more options
6
6
7 import os
7 import os
8
8
9 supportedpy = '~= 2.7'
9 supportedpy = '~= 2.7'
10 if os.environ.get('HGALLOWPYTHON3', ''):
10 if os.environ.get('HGALLOWPYTHON3', ''):
11 # Mercurial will never work on Python 3 before 3.5 due to a lack
11 # Mercurial will never work on Python 3 before 3.5 due to a lack
12 # of % formatting on bytestrings, and can't work on 3.6.0 or 3.6.1
12 # of % formatting on bytestrings, and can't work on 3.6.0 or 3.6.1
13 # due to a bug in % formatting in bytestrings.
13 # due to a bug in % formatting in bytestrings.
14 # We cannot support Python 3.5.0, 3.5.1, 3.5.2 because of bug in
14 # We cannot support Python 3.5.0, 3.5.1, 3.5.2 because of bug in
15 # codecs.escape_encode() where it raises SystemError on empty bytestring
15 # codecs.escape_encode() where it raises SystemError on empty bytestring
16 # bug link: https://bugs.python.org/issue25270
16 # bug link: https://bugs.python.org/issue25270
17 #
17 #
18 # TODO: when we actually work on Python 3, use this string as the
18 # TODO: when we actually work on Python 3, use this string as the
19 # actual supportedpy string.
19 # actual supportedpy string.
20 supportedpy = ','.join([
20 supportedpy = ','.join([
21 '>=2.7',
21 '>=2.7',
22 '!=3.0.*',
22 '!=3.0.*',
23 '!=3.1.*',
23 '!=3.1.*',
24 '!=3.2.*',
24 '!=3.2.*',
25 '!=3.3.*',
25 '!=3.3.*',
26 '!=3.4.*',
26 '!=3.4.*',
27 '!=3.5.0',
27 '!=3.5.0',
28 '!=3.5.1',
28 '!=3.5.1',
29 '!=3.5.2',
29 '!=3.5.2',
30 '!=3.6.0',
30 '!=3.6.0',
31 '!=3.6.1',
31 '!=3.6.1',
32 ])
32 ])
33
33
34 import sys, platform
34 import sys, platform
35 if sys.version_info[0] >= 3:
35 if sys.version_info[0] >= 3:
36 printf = eval('print')
36 printf = eval('print')
37 libdir_escape = 'unicode_escape'
37 libdir_escape = 'unicode_escape'
38 def sysstr(s):
38 def sysstr(s):
39 return s.decode('latin-1')
39 return s.decode('latin-1')
40 else:
40 else:
41 libdir_escape = 'string_escape'
41 libdir_escape = 'string_escape'
42 def printf(*args, **kwargs):
42 def printf(*args, **kwargs):
43 f = kwargs.get('file', sys.stdout)
43 f = kwargs.get('file', sys.stdout)
44 end = kwargs.get('end', '\n')
44 end = kwargs.get('end', '\n')
45 f.write(b' '.join(args) + end)
45 f.write(b' '.join(args) + end)
46 def sysstr(s):
46 def sysstr(s):
47 return s
47 return s
48
48
49 # Attempt to guide users to a modern pip - this means that 2.6 users
49 # Attempt to guide users to a modern pip - this means that 2.6 users
50 # should have a chance of getting a 4.2 release, and when we ratchet
50 # should have a chance of getting a 4.2 release, and when we ratchet
51 # the version requirement forward again hopefully everyone will get
51 # the version requirement forward again hopefully everyone will get
52 # something that works for them.
52 # something that works for them.
53 if sys.version_info < (2, 7, 0, 'final'):
53 if sys.version_info < (2, 7, 0, 'final'):
54 pip_message = ('This may be due to an out of date pip. '
54 pip_message = ('This may be due to an out of date pip. '
55 'Make sure you have pip >= 9.0.1.')
55 'Make sure you have pip >= 9.0.1.')
56 try:
56 try:
57 import pip
57 import pip
58 pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
58 pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
59 if pip_version < (9, 0, 1) :
59 if pip_version < (9, 0, 1) :
60 pip_message = (
60 pip_message = (
61 'Your pip version is out of date, please install '
61 'Your pip version is out of date, please install '
62 'pip >= 9.0.1. pip {} detected.'.format(pip.__version__))
62 'pip >= 9.0.1. pip {} detected.'.format(pip.__version__))
63 else:
63 else:
64 # pip is new enough - it must be something else
64 # pip is new enough - it must be something else
65 pip_message = ''
65 pip_message = ''
66 except Exception:
66 except Exception:
67 pass
67 pass
68 error = """
68 error = """
69 Mercurial does not support Python older than 2.7.
69 Mercurial does not support Python older than 2.7.
70 Python {py} detected.
70 Python {py} detected.
71 {pip}
71 {pip}
72 """.format(py=sys.version_info, pip=pip_message)
72 """.format(py=sys.version_info, pip=pip_message)
73 printf(error, file=sys.stderr)
73 printf(error, file=sys.stderr)
74 sys.exit(1)
74 sys.exit(1)
75
75
76 # We don't yet officially support Python 3. But we want to allow developers to
76 # We don't yet officially support Python 3. But we want to allow developers to
77 # hack on. Detect and disallow running on Python 3 by default. But provide a
77 # hack on. Detect and disallow running on Python 3 by default. But provide a
78 # backdoor to enable working on Python 3.
78 # backdoor to enable working on Python 3.
79 if sys.version_info[0] != 2:
79 if sys.version_info[0] != 2:
80 badpython = True
80 badpython = True
81
81
82 # Allow Python 3 from source checkouts.
82 # Allow Python 3 from source checkouts.
83 if os.path.isdir('.hg') or 'HGPYTHON3' in os.environ:
83 if os.path.isdir('.hg') or 'HGPYTHON3' in os.environ:
84 badpython = False
84 badpython = False
85
85
86 if badpython:
86 if badpython:
87 error = """
87 error = """
88 Mercurial only supports Python 2.7.
88 Mercurial only supports Python 2.7.
89 Python {py} detected.
89 Python {py} detected.
90 Please re-run with Python 2.7.
90 Please re-run with Python 2.7.
91 """.format(py=sys.version_info)
91 """.format(py=sys.version_info)
92
92
93 printf(error, file=sys.stderr)
93 printf(error, file=sys.stderr)
94 sys.exit(1)
94 sys.exit(1)
95
95
96 # Solaris Python packaging brain damage
96 # Solaris Python packaging brain damage
97 try:
97 try:
98 import hashlib
98 import hashlib
99 sha = hashlib.sha1()
99 sha = hashlib.sha1()
100 except ImportError:
100 except ImportError:
101 try:
101 try:
102 import sha
102 import sha
103 sha.sha # silence unused import warning
103 sha.sha # silence unused import warning
104 except ImportError:
104 except ImportError:
105 raise SystemExit(
105 raise SystemExit(
106 "Couldn't import standard hashlib (incomplete Python install).")
106 "Couldn't import standard hashlib (incomplete Python install).")
107
107
108 try:
108 try:
109 import zlib
109 import zlib
110 zlib.compressobj # silence unused import warning
110 zlib.compressobj # silence unused import warning
111 except ImportError:
111 except ImportError:
112 raise SystemExit(
112 raise SystemExit(
113 "Couldn't import standard zlib (incomplete Python install).")
113 "Couldn't import standard zlib (incomplete Python install).")
114
114
115 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
115 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
116 isironpython = False
116 isironpython = False
117 try:
117 try:
118 isironpython = (platform.python_implementation()
118 isironpython = (platform.python_implementation()
119 .lower().find("ironpython") != -1)
119 .lower().find("ironpython") != -1)
120 except AttributeError:
120 except AttributeError:
121 pass
121 pass
122
122
123 if isironpython:
123 if isironpython:
124 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
124 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
125 else:
125 else:
126 try:
126 try:
127 import bz2
127 import bz2
128 bz2.BZ2Compressor # silence unused import warning
128 bz2.BZ2Compressor # silence unused import warning
129 except ImportError:
129 except ImportError:
130 raise SystemExit(
130 raise SystemExit(
131 "Couldn't import standard bz2 (incomplete Python install).")
131 "Couldn't import standard bz2 (incomplete Python install).")
132
132
133 ispypy = "PyPy" in sys.version
133 ispypy = "PyPy" in sys.version
134
134
135 hgrustext = os.environ.get('HGWITHRUSTEXT')
135 hgrustext = os.environ.get('HGWITHRUSTEXT')
136 # TODO record it for proper rebuild upon changes
136 # TODO record it for proper rebuild upon changes
137 # (see mercurial/__modulepolicy__.py)
137 # (see mercurial/__modulepolicy__.py)
138 if hgrustext != 'cpython' and hgrustext is not None:
138 if hgrustext != 'cpython' and hgrustext is not None:
139 hgrustext = 'direct-ffi'
139 hgrustext = 'direct-ffi'
140
140
141 import ctypes
141 import ctypes
142 import errno
142 import errno
143 import stat, subprocess, time
143 import stat, subprocess, time
144 import re
144 import re
145 import shutil
145 import shutil
146 import tempfile
146 import tempfile
147 from distutils import log
147 from distutils import log
148 # We have issues with setuptools on some platforms and builders. Until
148 # We have issues with setuptools on some platforms and builders. Until
149 # those are resolved, setuptools is opt-in except for platforms where
149 # those are resolved, setuptools is opt-in except for platforms where
150 # we don't have issues.
150 # we don't have issues.
151 issetuptools = (os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ)
151 issetuptools = (os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ)
152 if issetuptools:
152 if issetuptools:
153 from setuptools import setup
153 from setuptools import setup
154 else:
154 else:
155 from distutils.core import setup
155 from distutils.core import setup
156 from distutils.ccompiler import new_compiler
156 from distutils.ccompiler import new_compiler
157 from distutils.core import Command, Extension
157 from distutils.core import Command, Extension
158 from distutils.dist import Distribution
158 from distutils.dist import Distribution
159 from distutils.command.build import build
159 from distutils.command.build import build
160 from distutils.command.build_ext import build_ext
160 from distutils.command.build_ext import build_ext
161 from distutils.command.build_py import build_py
161 from distutils.command.build_py import build_py
162 from distutils.command.build_scripts import build_scripts
162 from distutils.command.build_scripts import build_scripts
163 from distutils.command.install import install
163 from distutils.command.install import install
164 from distutils.command.install_lib import install_lib
164 from distutils.command.install_lib import install_lib
165 from distutils.command.install_scripts import install_scripts
165 from distutils.command.install_scripts import install_scripts
166 from distutils.spawn import spawn, find_executable
166 from distutils.spawn import spawn, find_executable
167 from distutils import file_util
167 from distutils import file_util
168 from distutils.errors import (
168 from distutils.errors import (
169 CCompilerError,
169 CCompilerError,
170 DistutilsError,
170 DistutilsError,
171 DistutilsExecError,
171 DistutilsExecError,
172 )
172 )
173 from distutils.sysconfig import get_python_inc, get_config_var
173 from distutils.sysconfig import get_python_inc, get_config_var
174 from distutils.version import StrictVersion
174 from distutils.version import StrictVersion
175
175
176 # Explain to distutils.StrictVersion how our release candidates are versionned
176 # Explain to distutils.StrictVersion how our release candidates are versionned
177 StrictVersion.version_re = re.compile(r'^(\d+)\.(\d+)(\.(\d+))?-?(rc(\d+))?$')
177 StrictVersion.version_re = re.compile(r'^(\d+)\.(\d+)(\.(\d+))?-?(rc(\d+))?$')
178
178
179 def write_if_changed(path, content):
179 def write_if_changed(path, content):
180 """Write content to a file iff the content hasn't changed."""
180 """Write content to a file iff the content hasn't changed."""
181 if os.path.exists(path):
181 if os.path.exists(path):
182 with open(path, 'rb') as fh:
182 with open(path, 'rb') as fh:
183 current = fh.read()
183 current = fh.read()
184 else:
184 else:
185 current = b''
185 current = b''
186
186
187 if current != content:
187 if current != content:
188 with open(path, 'wb') as fh:
188 with open(path, 'wb') as fh:
189 fh.write(content)
189 fh.write(content)
190
190
191 scripts = ['hg']
191 scripts = ['hg']
192 if os.name == 'nt':
192 if os.name == 'nt':
193 # We remove hg.bat if we are able to build hg.exe.
193 # We remove hg.bat if we are able to build hg.exe.
194 scripts.append('contrib/win32/hg.bat')
194 scripts.append('contrib/win32/hg.bat')
195
195
196 def cancompile(cc, code):
196 def cancompile(cc, code):
197 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
197 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
198 devnull = oldstderr = None
198 devnull = oldstderr = None
199 try:
199 try:
200 fname = os.path.join(tmpdir, 'testcomp.c')
200 fname = os.path.join(tmpdir, 'testcomp.c')
201 f = open(fname, 'w')
201 f = open(fname, 'w')
202 f.write(code)
202 f.write(code)
203 f.close()
203 f.close()
204 # Redirect stderr to /dev/null to hide any error messages
204 # Redirect stderr to /dev/null to hide any error messages
205 # from the compiler.
205 # from the compiler.
206 # This will have to be changed if we ever have to check
206 # This will have to be changed if we ever have to check
207 # for a function on Windows.
207 # for a function on Windows.
208 devnull = open('/dev/null', 'w')
208 devnull = open('/dev/null', 'w')
209 oldstderr = os.dup(sys.stderr.fileno())
209 oldstderr = os.dup(sys.stderr.fileno())
210 os.dup2(devnull.fileno(), sys.stderr.fileno())
210 os.dup2(devnull.fileno(), sys.stderr.fileno())
211 objects = cc.compile([fname], output_dir=tmpdir)
211 objects = cc.compile([fname], output_dir=tmpdir)
212 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
212 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
213 return True
213 return True
214 except Exception:
214 except Exception:
215 return False
215 return False
216 finally:
216 finally:
217 if oldstderr is not None:
217 if oldstderr is not None:
218 os.dup2(oldstderr, sys.stderr.fileno())
218 os.dup2(oldstderr, sys.stderr.fileno())
219 if devnull is not None:
219 if devnull is not None:
220 devnull.close()
220 devnull.close()
221 shutil.rmtree(tmpdir)
221 shutil.rmtree(tmpdir)
222
222
223 # simplified version of distutils.ccompiler.CCompiler.has_function
223 # simplified version of distutils.ccompiler.CCompiler.has_function
224 # that actually removes its temporary files.
224 # that actually removes its temporary files.
225 def hasfunction(cc, funcname):
225 def hasfunction(cc, funcname):
226 code = 'int main(void) { %s(); }\n' % funcname
226 code = 'int main(void) { %s(); }\n' % funcname
227 return cancompile(cc, code)
227 return cancompile(cc, code)
228
228
229 def hasheader(cc, headername):
229 def hasheader(cc, headername):
230 code = '#include <%s>\nint main(void) { return 0; }\n' % headername
230 code = '#include <%s>\nint main(void) { return 0; }\n' % headername
231 return cancompile(cc, code)
231 return cancompile(cc, code)
232
232
233 # py2exe needs to be installed to work
233 # py2exe needs to be installed to work
234 try:
234 try:
235 import py2exe
235 import py2exe
236 py2exe.Distribution # silence unused import warning
236 py2exe.Distribution # silence unused import warning
237 py2exeloaded = True
237 py2exeloaded = True
238 # import py2exe's patched Distribution class
238 # import py2exe's patched Distribution class
239 from distutils.core import Distribution
239 from distutils.core import Distribution
240 except ImportError:
240 except ImportError:
241 py2exeloaded = False
241 py2exeloaded = False
242
242
243 def runcmd(cmd, env, cwd=None):
243 def runcmd(cmd, env, cwd=None):
244 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
244 p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
245 stderr=subprocess.PIPE, env=env, cwd=cwd)
245 stderr=subprocess.PIPE, env=env, cwd=cwd)
246 out, err = p.communicate()
246 out, err = p.communicate()
247 return p.returncode, out, err
247 return p.returncode, out, err
248
248
249 class hgcommand(object):
249 class hgcommand(object):
250 def __init__(self, cmd, env):
250 def __init__(self, cmd, env):
251 self.cmd = cmd
251 self.cmd = cmd
252 self.env = env
252 self.env = env
253
253
254 def run(self, args):
254 def run(self, args):
255 cmd = self.cmd + args
255 cmd = self.cmd + args
256 returncode, out, err = runcmd(cmd, self.env)
256 returncode, out, err = runcmd(cmd, self.env)
257 err = filterhgerr(err)
257 err = filterhgerr(err)
258 if err or returncode != 0:
258 if err or returncode != 0:
259 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
259 printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
260 printf(err, file=sys.stderr)
260 printf(err, file=sys.stderr)
261 return ''
261 return ''
262 return out
262 return out
263
263
264 def filterhgerr(err):
264 def filterhgerr(err):
265 # If root is executing setup.py, but the repository is owned by
265 # If root is executing setup.py, but the repository is owned by
266 # another user (as in "sudo python setup.py install") we will get
266 # another user (as in "sudo python setup.py install") we will get
267 # trust warnings since the .hg/hgrc file is untrusted. That is
267 # trust warnings since the .hg/hgrc file is untrusted. That is
268 # fine, we don't want to load it anyway. Python may warn about
268 # fine, we don't want to load it anyway. Python may warn about
269 # a missing __init__.py in mercurial/locale, we also ignore that.
269 # a missing __init__.py in mercurial/locale, we also ignore that.
270 err = [e for e in err.splitlines()
270 err = [e for e in err.splitlines()
271 if (not e.startswith(b'not trusting file')
271 if (not e.startswith(b'not trusting file')
272 and not e.startswith(b'warning: Not importing')
272 and not e.startswith(b'warning: Not importing')
273 and not e.startswith(b'obsolete feature not enabled')
273 and not e.startswith(b'obsolete feature not enabled')
274 and not e.startswith(b'*** failed to import extension')
274 and not e.startswith(b'*** failed to import extension')
275 and not e.startswith(b'devel-warn:')
275 and not e.startswith(b'devel-warn:')
276 and not (e.startswith(b'(third party extension')
276 and not (e.startswith(b'(third party extension')
277 and e.endswith(b'or newer of Mercurial; disabling)')))]
277 and e.endswith(b'or newer of Mercurial; disabling)')))]
278 return b'\n'.join(b' ' + e for e in err)
278 return b'\n'.join(b' ' + e for e in err)
279
279
280 def findhg():
280 def findhg():
281 """Try to figure out how we should invoke hg for examining the local
281 """Try to figure out how we should invoke hg for examining the local
282 repository contents.
282 repository contents.
283
283
284 Returns an hgcommand object."""
284 Returns an hgcommand object."""
285 # By default, prefer the "hg" command in the user's path. This was
285 # By default, prefer the "hg" command in the user's path. This was
286 # presumably the hg command that the user used to create this repository.
286 # presumably the hg command that the user used to create this repository.
287 #
287 #
288 # This repository may require extensions or other settings that would not
288 # This repository may require extensions or other settings that would not
289 # be enabled by running the hg script directly from this local repository.
289 # be enabled by running the hg script directly from this local repository.
290 hgenv = os.environ.copy()
290 hgenv = os.environ.copy()
291 # Use HGPLAIN to disable hgrc settings that would change output formatting,
291 # Use HGPLAIN to disable hgrc settings that would change output formatting,
292 # and disable localization for the same reasons.
292 # and disable localization for the same reasons.
293 hgenv['HGPLAIN'] = '1'
293 hgenv['HGPLAIN'] = '1'
294 hgenv['LANGUAGE'] = 'C'
294 hgenv['LANGUAGE'] = 'C'
295 hgcmd = ['hg']
295 hgcmd = ['hg']
296 # Run a simple "hg log" command just to see if using hg from the user's
296 # Run a simple "hg log" command just to see if using hg from the user's
297 # path works and can successfully interact with this repository. Windows
297 # path works and can successfully interact with this repository. Windows
298 # gives precedence to hg.exe in the current directory, so fall back to the
298 # gives precedence to hg.exe in the current directory, so fall back to the
299 # python invocation of local hg, where pythonXY.dll can always be found.
299 # python invocation of local hg, where pythonXY.dll can always be found.
300 check_cmd = ['log', '-r.', '-Ttest']
300 check_cmd = ['log', '-r.', '-Ttest']
301 if os.name != 'nt':
301 if os.name != 'nt':
302 try:
302 try:
303 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
303 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
304 except EnvironmentError:
304 except EnvironmentError:
305 retcode = -1
305 retcode = -1
306 if retcode == 0 and not filterhgerr(err):
306 if retcode == 0 and not filterhgerr(err):
307 return hgcommand(hgcmd, hgenv)
307 return hgcommand(hgcmd, hgenv)
308
308
309 # Fall back to trying the local hg installation.
309 # Fall back to trying the local hg installation.
310 hgenv = localhgenv()
310 hgenv = localhgenv()
311 hgcmd = [sys.executable, 'hg']
311 hgcmd = [sys.executable, 'hg']
312 try:
312 try:
313 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
313 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
314 except EnvironmentError:
314 except EnvironmentError:
315 retcode = -1
315 retcode = -1
316 if retcode == 0 and not filterhgerr(err):
316 if retcode == 0 and not filterhgerr(err):
317 return hgcommand(hgcmd, hgenv)
317 return hgcommand(hgcmd, hgenv)
318
318
319 raise SystemExit('Unable to find a working hg binary to extract the '
319 raise SystemExit('Unable to find a working hg binary to extract the '
320 'version from the repository tags')
320 'version from the repository tags')
321
321
322 def localhgenv():
322 def localhgenv():
323 """Get an environment dictionary to use for invoking or importing
323 """Get an environment dictionary to use for invoking or importing
324 mercurial from the local repository."""
324 mercurial from the local repository."""
325 # Execute hg out of this directory with a custom environment which takes
325 # Execute hg out of this directory with a custom environment which takes
326 # care to not use any hgrc files and do no localization.
326 # care to not use any hgrc files and do no localization.
327 env = {'HGMODULEPOLICY': 'py',
327 env = {'HGMODULEPOLICY': 'py',
328 'HGRCPATH': '',
328 'HGRCPATH': '',
329 'LANGUAGE': 'C',
329 'LANGUAGE': 'C',
330 'PATH': ''} # make pypi modules that use os.environ['PATH'] happy
330 'PATH': ''} # make pypi modules that use os.environ['PATH'] happy
331 if 'LD_LIBRARY_PATH' in os.environ:
331 if 'LD_LIBRARY_PATH' in os.environ:
332 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
332 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
333 if 'SystemRoot' in os.environ:
333 if 'SystemRoot' in os.environ:
334 # SystemRoot is required by Windows to load various DLLs. See:
334 # SystemRoot is required by Windows to load various DLLs. See:
335 # https://bugs.python.org/issue13524#msg148850
335 # https://bugs.python.org/issue13524#msg148850
336 env['SystemRoot'] = os.environ['SystemRoot']
336 env['SystemRoot'] = os.environ['SystemRoot']
337 return env
337 return env
338
338
339 version = ''
339 version = ''
340
340
341 if os.path.isdir('.hg'):
341 if os.path.isdir('.hg'):
342 hg = findhg()
342 hg = findhg()
343 cmd = ['log', '-r', '.', '--template', '{tags}\n']
343 cmd = ['log', '-r', '.', '--template', '{tags}\n']
344 numerictags = [t for t in sysstr(hg.run(cmd)).split() if t[0:1].isdigit()]
344 numerictags = [t for t in sysstr(hg.run(cmd)).split() if t[0:1].isdigit()]
345 hgid = sysstr(hg.run(['id', '-i'])).strip()
345 hgid = sysstr(hg.run(['id', '-i'])).strip()
346 if not hgid:
346 if not hgid:
347 # Bail out if hg is having problems interacting with this repository,
347 # Bail out if hg is having problems interacting with this repository,
348 # rather than falling through and producing a bogus version number.
348 # rather than falling through and producing a bogus version number.
349 # Continuing with an invalid version number will break extensions
349 # Continuing with an invalid version number will break extensions
350 # that define minimumhgversion.
350 # that define minimumhgversion.
351 raise SystemExit('Unable to determine hg version from local repository')
351 raise SystemExit('Unable to determine hg version from local repository')
352 if numerictags: # tag(s) found
352 if numerictags: # tag(s) found
353 version = numerictags[-1]
353 version = numerictags[-1]
354 if hgid.endswith('+'): # propagate the dirty status to the tag
354 if hgid.endswith('+'): # propagate the dirty status to the tag
355 version += '+'
355 version += '+'
356 else: # no tag found
356 else: # no tag found
357 ltagcmd = ['parents', '--template', '{latesttag}']
357 ltagcmd = ['parents', '--template', '{latesttag}']
358 ltag = sysstr(hg.run(ltagcmd))
358 ltag = sysstr(hg.run(ltagcmd))
359 changessincecmd = ['log', '-T', 'x\n', '-r', "only(.,'%s')" % ltag]
359 changessincecmd = ['log', '-T', 'x\n', '-r', "only(.,'%s')" % ltag]
360 changessince = len(hg.run(changessincecmd).splitlines())
360 changessince = len(hg.run(changessincecmd).splitlines())
361 version = '%s+%s-%s' % (ltag, changessince, hgid)
361 version = '%s+%s-%s' % (ltag, changessince, hgid)
362 if version.endswith('+'):
362 if version.endswith('+'):
363 version += time.strftime('%Y%m%d')
363 version += time.strftime('%Y%m%d')
364 elif os.path.exists('.hg_archival.txt'):
364 elif os.path.exists('.hg_archival.txt'):
365 kw = dict([[t.strip() for t in l.split(':', 1)]
365 kw = dict([[t.strip() for t in l.split(':', 1)]
366 for l in open('.hg_archival.txt')])
366 for l in open('.hg_archival.txt')])
367 if 'tag' in kw:
367 if 'tag' in kw:
368 version = kw['tag']
368 version = kw['tag']
369 elif 'latesttag' in kw:
369 elif 'latesttag' in kw:
370 if 'changessincelatesttag' in kw:
370 if 'changessincelatesttag' in kw:
371 version = '%(latesttag)s+%(changessincelatesttag)s-%(node).12s' % kw
371 version = '%(latesttag)s+%(changessincelatesttag)s-%(node).12s' % kw
372 else:
372 else:
373 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
373 version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw
374 else:
374 else:
375 version = kw.get('node', '')[:12]
375 version = kw.get('node', '')[:12]
376
376
377 if version:
377 if version:
378 versionb = version
378 versionb = version
379 if not isinstance(versionb, bytes):
379 if not isinstance(versionb, bytes):
380 versionb = versionb.encode('ascii')
380 versionb = versionb.encode('ascii')
381
381
382 write_if_changed('mercurial/__version__.py', b''.join([
382 write_if_changed('mercurial/__version__.py', b''.join([
383 b'# this file is autogenerated by setup.py\n'
383 b'# this file is autogenerated by setup.py\n'
384 b'version = b"%s"\n' % versionb,
384 b'version = b"%s"\n' % versionb,
385 ]))
385 ]))
386
386
387 try:
387 try:
388 oldpolicy = os.environ.get('HGMODULEPOLICY', None)
388 oldpolicy = os.environ.get('HGMODULEPOLICY', None)
389 os.environ['HGMODULEPOLICY'] = 'py'
389 os.environ['HGMODULEPOLICY'] = 'py'
390 from mercurial import __version__
390 from mercurial import __version__
391 version = __version__.version
391 version = __version__.version
392 except ImportError:
392 except ImportError:
393 version = b'unknown'
393 version = b'unknown'
394 finally:
394 finally:
395 if oldpolicy is None:
395 if oldpolicy is None:
396 del os.environ['HGMODULEPOLICY']
396 del os.environ['HGMODULEPOLICY']
397 else:
397 else:
398 os.environ['HGMODULEPOLICY'] = oldpolicy
398 os.environ['HGMODULEPOLICY'] = oldpolicy
399
399
400 class hgbuild(build):
400 class hgbuild(build):
401 # Insert hgbuildmo first so that files in mercurial/locale/ are found
401 # Insert hgbuildmo first so that files in mercurial/locale/ are found
402 # when build_py is run next.
402 # when build_py is run next.
403 sub_commands = [('build_mo', None)] + build.sub_commands
403 sub_commands = [('build_mo', None)] + build.sub_commands
404
404
405 class hgbuildmo(build):
405 class hgbuildmo(build):
406
406
407 description = "build translations (.mo files)"
407 description = "build translations (.mo files)"
408
408
409 def run(self):
409 def run(self):
410 if not find_executable('msgfmt'):
410 if not find_executable('msgfmt'):
411 self.warn("could not find msgfmt executable, no translations "
411 self.warn("could not find msgfmt executable, no translations "
412 "will be built")
412 "will be built")
413 return
413 return
414
414
415 podir = 'i18n'
415 podir = 'i18n'
416 if not os.path.isdir(podir):
416 if not os.path.isdir(podir):
417 self.warn("could not find %s/ directory" % podir)
417 self.warn("could not find %s/ directory" % podir)
418 return
418 return
419
419
420 join = os.path.join
420 join = os.path.join
421 for po in os.listdir(podir):
421 for po in os.listdir(podir):
422 if not po.endswith('.po'):
422 if not po.endswith('.po'):
423 continue
423 continue
424 pofile = join(podir, po)
424 pofile = join(podir, po)
425 modir = join('locale', po[:-3], 'LC_MESSAGES')
425 modir = join('locale', po[:-3], 'LC_MESSAGES')
426 mofile = join(modir, 'hg.mo')
426 mofile = join(modir, 'hg.mo')
427 mobuildfile = join('mercurial', mofile)
427 mobuildfile = join('mercurial', mofile)
428 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
428 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
429 if sys.platform != 'sunos5':
429 if sys.platform != 'sunos5':
430 # msgfmt on Solaris does not know about -c
430 # msgfmt on Solaris does not know about -c
431 cmd.append('-c')
431 cmd.append('-c')
432 self.mkpath(join('mercurial', modir))
432 self.mkpath(join('mercurial', modir))
433 self.make_file([pofile], mobuildfile, spawn, (cmd,))
433 self.make_file([pofile], mobuildfile, spawn, (cmd,))
434
434
435
435
436 class hgdist(Distribution):
436 class hgdist(Distribution):
437 pure = False
437 pure = False
438 cffi = ispypy
438 cffi = ispypy
439
439
440 global_options = Distribution.global_options + [
440 global_options = Distribution.global_options + [
441 ('pure', None, "use pure (slow) Python code instead of C extensions"),
441 ('pure', None, "use pure (slow) Python code instead of C extensions"),
442 ]
442 ]
443
443
444 def has_ext_modules(self):
444 def has_ext_modules(self):
445 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
445 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
446 # too late for some cases
446 # too late for some cases
447 return not self.pure and Distribution.has_ext_modules(self)
447 return not self.pure and Distribution.has_ext_modules(self)
448
448
449 # This is ugly as a one-liner. So use a variable.
449 # This is ugly as a one-liner. So use a variable.
450 buildextnegops = dict(getattr(build_ext, 'negative_options', {}))
450 buildextnegops = dict(getattr(build_ext, 'negative_options', {}))
451 buildextnegops['no-zstd'] = 'zstd'
451 buildextnegops['no-zstd'] = 'zstd'
452
452
453 class hgbuildext(build_ext):
453 class hgbuildext(build_ext):
454 user_options = build_ext.user_options + [
454 user_options = build_ext.user_options + [
455 ('zstd', None, 'compile zstd bindings [default]'),
455 ('zstd', None, 'compile zstd bindings [default]'),
456 ('no-zstd', None, 'do not compile zstd bindings'),
456 ('no-zstd', None, 'do not compile zstd bindings'),
457 ]
457 ]
458
458
459 boolean_options = build_ext.boolean_options + ['zstd']
459 boolean_options = build_ext.boolean_options + ['zstd']
460 negative_opt = buildextnegops
460 negative_opt = buildextnegops
461
461
462 def initialize_options(self):
462 def initialize_options(self):
463 self.zstd = True
463 self.zstd = True
464 return build_ext.initialize_options(self)
464 return build_ext.initialize_options(self)
465
465
466 def build_extensions(self):
466 def build_extensions(self):
467 ruststandalones = [e for e in self.extensions
467 ruststandalones = [e for e in self.extensions
468 if isinstance(e, RustStandaloneExtension)]
468 if isinstance(e, RustStandaloneExtension)]
469 self.extensions = [e for e in self.extensions
469 self.extensions = [e for e in self.extensions
470 if e not in ruststandalones]
470 if e not in ruststandalones]
471 # Filter out zstd if disabled via argument.
471 # Filter out zstd if disabled via argument.
472 if not self.zstd:
472 if not self.zstd:
473 self.extensions = [e for e in self.extensions
473 self.extensions = [e for e in self.extensions
474 if e.name != 'mercurial.zstd']
474 if e.name != 'mercurial.zstd']
475
475
476 for rustext in ruststandalones:
476 for rustext in ruststandalones:
477 rustext.build('' if self.inplace else self.build_lib)
477 rustext.build('' if self.inplace else self.build_lib)
478
478
479 return build_ext.build_extensions(self)
479 return build_ext.build_extensions(self)
480
480
481 def build_extension(self, ext):
481 def build_extension(self, ext):
482 if isinstance(ext, RustExtension):
482 if isinstance(ext, RustExtension):
483 ext.rustbuild()
483 ext.rustbuild()
484 try:
484 try:
485 build_ext.build_extension(self, ext)
485 build_ext.build_extension(self, ext)
486 except CCompilerError:
486 except CCompilerError:
487 if not getattr(ext, 'optional', False):
487 if not getattr(ext, 'optional', False):
488 raise
488 raise
489 log.warn("Failed to build optional extension '%s' (skipping)",
489 log.warn("Failed to build optional extension '%s' (skipping)",
490 ext.name)
490 ext.name)
491
491
492 class hgbuildscripts(build_scripts):
492 class hgbuildscripts(build_scripts):
493 def run(self):
493 def run(self):
494 if os.name != 'nt' or self.distribution.pure:
494 if os.name != 'nt' or self.distribution.pure:
495 return build_scripts.run(self)
495 return build_scripts.run(self)
496
496
497 exebuilt = False
497 exebuilt = False
498 try:
498 try:
499 self.run_command('build_hgexe')
499 self.run_command('build_hgexe')
500 exebuilt = True
500 exebuilt = True
501 except (DistutilsError, CCompilerError):
501 except (DistutilsError, CCompilerError):
502 log.warn('failed to build optional hg.exe')
502 log.warn('failed to build optional hg.exe')
503
503
504 if exebuilt:
504 if exebuilt:
505 # Copying hg.exe to the scripts build directory ensures it is
505 # Copying hg.exe to the scripts build directory ensures it is
506 # installed by the install_scripts command.
506 # installed by the install_scripts command.
507 hgexecommand = self.get_finalized_command('build_hgexe')
507 hgexecommand = self.get_finalized_command('build_hgexe')
508 dest = os.path.join(self.build_dir, 'hg.exe')
508 dest = os.path.join(self.build_dir, 'hg.exe')
509 self.mkpath(self.build_dir)
509 self.mkpath(self.build_dir)
510 self.copy_file(hgexecommand.hgexepath, dest)
510 self.copy_file(hgexecommand.hgexepath, dest)
511
511
512 # Remove hg.bat because it is redundant with hg.exe.
512 # Remove hg.bat because it is redundant with hg.exe.
513 self.scripts.remove('contrib/win32/hg.bat')
513 self.scripts.remove('contrib/win32/hg.bat')
514
514
515 return build_scripts.run(self)
515 return build_scripts.run(self)
516
516
517 class hgbuildpy(build_py):
517 class hgbuildpy(build_py):
518 def finalize_options(self):
518 def finalize_options(self):
519 build_py.finalize_options(self)
519 build_py.finalize_options(self)
520
520
521 if self.distribution.pure:
521 if self.distribution.pure:
522 self.distribution.ext_modules = []
522 self.distribution.ext_modules = []
523 elif self.distribution.cffi:
523 elif self.distribution.cffi:
524 from mercurial.cffi import (
524 from mercurial.cffi import (
525 bdiffbuild,
525 bdiffbuild,
526 mpatchbuild,
526 mpatchbuild,
527 )
527 )
528 exts = [mpatchbuild.ffi.distutils_extension(),
528 exts = [mpatchbuild.ffi.distutils_extension(),
529 bdiffbuild.ffi.distutils_extension()]
529 bdiffbuild.ffi.distutils_extension()]
530 # cffi modules go here
530 # cffi modules go here
531 if sys.platform == 'darwin':
531 if sys.platform == 'darwin':
532 from mercurial.cffi import osutilbuild
532 from mercurial.cffi import osutilbuild
533 exts.append(osutilbuild.ffi.distutils_extension())
533 exts.append(osutilbuild.ffi.distutils_extension())
534 self.distribution.ext_modules = exts
534 self.distribution.ext_modules = exts
535 else:
535 else:
536 h = os.path.join(get_python_inc(), 'Python.h')
536 h = os.path.join(get_python_inc(), 'Python.h')
537 if not os.path.exists(h):
537 if not os.path.exists(h):
538 raise SystemExit('Python headers are required to build '
538 raise SystemExit('Python headers are required to build '
539 'Mercurial but weren\'t found in %s' % h)
539 'Mercurial but weren\'t found in %s' % h)
540
540
541 def run(self):
541 def run(self):
542 basepath = os.path.join(self.build_lib, 'mercurial')
542 basepath = os.path.join(self.build_lib, 'mercurial')
543 self.mkpath(basepath)
543 self.mkpath(basepath)
544
544
545 if self.distribution.pure:
545 if self.distribution.pure:
546 modulepolicy = 'py'
546 modulepolicy = 'py'
547 elif self.build_lib == '.':
547 elif self.build_lib == '.':
548 # in-place build should run without rebuilding C extensions
548 # in-place build should run without rebuilding C extensions
549 modulepolicy = 'allow'
549 modulepolicy = 'allow'
550 else:
550 else:
551 modulepolicy = 'c'
551 modulepolicy = 'c'
552
552
553 content = b''.join([
553 content = b''.join([
554 b'# this file is autogenerated by setup.py\n',
554 b'# this file is autogenerated by setup.py\n',
555 b'modulepolicy = b"%s"\n' % modulepolicy.encode('ascii'),
555 b'modulepolicy = b"%s"\n' % modulepolicy.encode('ascii'),
556 ])
556 ])
557 write_if_changed(os.path.join(basepath, '__modulepolicy__.py'),
557 write_if_changed(os.path.join(basepath, '__modulepolicy__.py'),
558 content)
558 content)
559
559
560 build_py.run(self)
560 build_py.run(self)
561
561
562 class buildhgextindex(Command):
562 class buildhgextindex(Command):
563 description = 'generate prebuilt index of hgext (for frozen package)'
563 description = 'generate prebuilt index of hgext (for frozen package)'
564 user_options = []
564 user_options = []
565 _indexfilename = 'hgext/__index__.py'
565 _indexfilename = 'hgext/__index__.py'
566
566
567 def initialize_options(self):
567 def initialize_options(self):
568 pass
568 pass
569
569
570 def finalize_options(self):
570 def finalize_options(self):
571 pass
571 pass
572
572
573 def run(self):
573 def run(self):
574 if os.path.exists(self._indexfilename):
574 if os.path.exists(self._indexfilename):
575 with open(self._indexfilename, 'w') as f:
575 with open(self._indexfilename, 'w') as f:
576 f.write('# empty\n')
576 f.write('# empty\n')
577
577
578 # here no extension enabled, disabled() lists up everything
578 # here no extension enabled, disabled() lists up everything
579 code = ('import pprint; from mercurial import extensions; '
579 code = ('import pprint; from mercurial import extensions; '
580 'pprint.pprint(extensions.disabled())')
580 'pprint.pprint(extensions.disabled())')
581 returncode, out, err = runcmd([sys.executable, '-c', code],
581 returncode, out, err = runcmd([sys.executable, '-c', code],
582 localhgenv())
582 localhgenv())
583 if err or returncode != 0:
583 if err or returncode != 0:
584 raise DistutilsExecError(err)
584 raise DistutilsExecError(err)
585
585
586 with open(self._indexfilename, 'w') as f:
586 with open(self._indexfilename, 'w') as f:
587 f.write('# this file is autogenerated by setup.py\n')
587 f.write('# this file is autogenerated by setup.py\n')
588 f.write('docs = ')
588 f.write('docs = ')
589 f.write(out)
589 f.write(out)
590
590
591 class buildhgexe(build_ext):
591 class buildhgexe(build_ext):
592 description = 'compile hg.exe from mercurial/exewrapper.c'
592 description = 'compile hg.exe from mercurial/exewrapper.c'
593 user_options = build_ext.user_options + [
593 user_options = build_ext.user_options + [
594 ('long-paths-support', None, 'enable support for long paths on '
594 ('long-paths-support', None, 'enable support for long paths on '
595 'Windows (off by default and '
595 'Windows (off by default and '
596 'experimental)'),
596 'experimental)'),
597 ]
597 ]
598
598
599 LONG_PATHS_MANIFEST = """
599 LONG_PATHS_MANIFEST = """
600 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
600 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
601 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
601 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
602 <application>
602 <application>
603 <windowsSettings
603 <windowsSettings
604 xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
604 xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
605 <ws2:longPathAware>true</ws2:longPathAware>
605 <ws2:longPathAware>true</ws2:longPathAware>
606 </windowsSettings>
606 </windowsSettings>
607 </application>
607 </application>
608 </assembly>"""
608 </assembly>"""
609
609
610 def initialize_options(self):
610 def initialize_options(self):
611 build_ext.initialize_options(self)
611 build_ext.initialize_options(self)
612 self.long_paths_support = False
612 self.long_paths_support = False
613
613
614 def build_extensions(self):
614 def build_extensions(self):
615 if os.name != 'nt':
615 if os.name != 'nt':
616 return
616 return
617 if isinstance(self.compiler, HackedMingw32CCompiler):
617 if isinstance(self.compiler, HackedMingw32CCompiler):
618 self.compiler.compiler_so = self.compiler.compiler # no -mdll
618 self.compiler.compiler_so = self.compiler.compiler # no -mdll
619 self.compiler.dll_libraries = [] # no -lmsrvc90
619 self.compiler.dll_libraries = [] # no -lmsrvc90
620
620
621 # Different Python installs can have different Python library
621 # Different Python installs can have different Python library
622 # names. e.g. the official CPython distribution uses pythonXY.dll
622 # names. e.g. the official CPython distribution uses pythonXY.dll
623 # and MinGW uses libpythonX.Y.dll.
623 # and MinGW uses libpythonX.Y.dll.
624 _kernel32 = ctypes.windll.kernel32
624 _kernel32 = ctypes.windll.kernel32
625 _kernel32.GetModuleFileNameA.argtypes = [ctypes.c_void_p,
625 _kernel32.GetModuleFileNameA.argtypes = [ctypes.c_void_p,
626 ctypes.c_void_p,
626 ctypes.c_void_p,
627 ctypes.c_ulong]
627 ctypes.c_ulong]
628 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
628 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
629 size = 1000
629 size = 1000
630 buf = ctypes.create_string_buffer(size + 1)
630 buf = ctypes.create_string_buffer(size + 1)
631 filelen = _kernel32.GetModuleFileNameA(sys.dllhandle, ctypes.byref(buf),
631 filelen = _kernel32.GetModuleFileNameA(sys.dllhandle, ctypes.byref(buf),
632 size)
632 size)
633
633
634 if filelen > 0 and filelen != size:
634 if filelen > 0 and filelen != size:
635 dllbasename = os.path.basename(buf.value)
635 dllbasename = os.path.basename(buf.value)
636 if not dllbasename.lower().endswith(b'.dll'):
636 if not dllbasename.lower().endswith(b'.dll'):
637 raise SystemExit('Python DLL does not end with .dll: %s' %
637 raise SystemExit('Python DLL does not end with .dll: %s' %
638 dllbasename)
638 dllbasename)
639 pythonlib = dllbasename[:-4]
639 pythonlib = dllbasename[:-4]
640 else:
640 else:
641 log.warn('could not determine Python DLL filename; '
641 log.warn('could not determine Python DLL filename; '
642 'assuming pythonXY')
642 'assuming pythonXY')
643
643
644 hv = sys.hexversion
644 hv = sys.hexversion
645 pythonlib = 'python%d%d' % (hv >> 24, (hv >> 16) & 0xff)
645 pythonlib = 'python%d%d' % (hv >> 24, (hv >> 16) & 0xff)
646
646
647 log.info('using %s as Python library name' % pythonlib)
647 log.info('using %s as Python library name' % pythonlib)
648 with open('mercurial/hgpythonlib.h', 'wb') as f:
648 with open('mercurial/hgpythonlib.h', 'wb') as f:
649 f.write(b'/* this file is autogenerated by setup.py */\n')
649 f.write(b'/* this file is autogenerated by setup.py */\n')
650 f.write(b'#define HGPYTHONLIB "%s"\n' % pythonlib)
650 f.write(b'#define HGPYTHONLIB "%s"\n' % pythonlib)
651
651
652 macros = None
652 macros = None
653 if sys.version_info[0] >= 3:
653 if sys.version_info[0] >= 3:
654 macros = [('_UNICODE', None), ('UNICODE', None)]
654 macros = [('_UNICODE', None), ('UNICODE', None)]
655
655
656 objects = self.compiler.compile(['mercurial/exewrapper.c'],
656 objects = self.compiler.compile(['mercurial/exewrapper.c'],
657 output_dir=self.build_temp,
657 output_dir=self.build_temp,
658 macros=macros)
658 macros=macros)
659 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
659 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
660 self.hgtarget = os.path.join(dir, 'hg')
660 self.hgtarget = os.path.join(dir, 'hg')
661 self.compiler.link_executable(objects, self.hgtarget,
661 self.compiler.link_executable(objects, self.hgtarget,
662 libraries=[],
662 libraries=[],
663 output_dir=self.build_temp)
663 output_dir=self.build_temp)
664 if self.long_paths_support:
664 if self.long_paths_support:
665 self.addlongpathsmanifest()
665 self.addlongpathsmanifest()
666
666
667 def addlongpathsmanifest(self):
667 def addlongpathsmanifest(self):
668 r"""Add manifest pieces so that hg.exe understands long paths
668 r"""Add manifest pieces so that hg.exe understands long paths
669
669
670 This is an EXPERIMENTAL feature, use with care.
670 This is an EXPERIMENTAL feature, use with care.
671 To enable long paths support, one needs to do two things:
671 To enable long paths support, one needs to do two things:
672 - build Mercurial with --long-paths-support option
672 - build Mercurial with --long-paths-support option
673 - change HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\
673 - change HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\
674 LongPathsEnabled to have value 1.
674 LongPathsEnabled to have value 1.
675
675
676 Please ignore 'warning 81010002: Unrecognized Element "longPathAware"';
676 Please ignore 'warning 81010002: Unrecognized Element "longPathAware"';
677 it happens because Mercurial uses mt.exe circa 2008, which is not
677 it happens because Mercurial uses mt.exe circa 2008, which is not
678 yet aware of long paths support in the manifest (I think so at least).
678 yet aware of long paths support in the manifest (I think so at least).
679 This does not stop mt.exe from embedding/merging the XML properly.
679 This does not stop mt.exe from embedding/merging the XML properly.
680
680
681 Why resource #1 should be used for .exe manifests? I don't know and
681 Why resource #1 should be used for .exe manifests? I don't know and
682 wasn't able to find an explanation for mortals. But it seems to work.
682 wasn't able to find an explanation for mortals. But it seems to work.
683 """
683 """
684 exefname = self.compiler.executable_filename(self.hgtarget)
684 exefname = self.compiler.executable_filename(self.hgtarget)
685 fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
685 fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
686 os.close(fdauto)
686 os.close(fdauto)
687 with open(manfname, 'w') as f:
687 with open(manfname, 'w') as f:
688 f.write(self.LONG_PATHS_MANIFEST)
688 f.write(self.LONG_PATHS_MANIFEST)
689 log.info("long paths manifest is written to '%s'" % manfname)
689 log.info("long paths manifest is written to '%s'" % manfname)
690 inputresource = '-inputresource:%s;#1' % exefname
690 inputresource = '-inputresource:%s;#1' % exefname
691 outputresource = '-outputresource:%s;#1' % exefname
691 outputresource = '-outputresource:%s;#1' % exefname
692 log.info("running mt.exe to update hg.exe's manifest in-place")
692 log.info("running mt.exe to update hg.exe's manifest in-place")
693 # supplying both -manifest and -inputresource to mt.exe makes
693 # supplying both -manifest and -inputresource to mt.exe makes
694 # it merge the embedded and supplied manifests in the -outputresource
694 # it merge the embedded and supplied manifests in the -outputresource
695 self.spawn(['mt.exe', '-nologo', '-manifest', manfname,
695 self.spawn(['mt.exe', '-nologo', '-manifest', manfname,
696 inputresource, outputresource])
696 inputresource, outputresource])
697 log.info("done updating hg.exe's manifest")
697 log.info("done updating hg.exe's manifest")
698 os.remove(manfname)
698 os.remove(manfname)
699
699
700 @property
700 @property
701 def hgexepath(self):
701 def hgexepath(self):
702 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
702 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
703 return os.path.join(self.build_temp, dir, 'hg.exe')
703 return os.path.join(self.build_temp, dir, 'hg.exe')
704
704
705 class hgbuilddoc(Command):
705 class hgbuilddoc(Command):
706 description = 'build documentation'
706 description = 'build documentation'
707 user_options = [
707 user_options = [
708 ('man', None, 'generate man pages'),
708 ('man', None, 'generate man pages'),
709 ('html', None, 'generate html pages'),
709 ('html', None, 'generate html pages'),
710 ]
710 ]
711
711
712 def initialize_options(self):
712 def initialize_options(self):
713 self.man = None
713 self.man = None
714 self.html = None
714 self.html = None
715
715
716 def finalize_options(self):
716 def finalize_options(self):
717 # If --man or --html are set, only generate what we're told to.
717 # If --man or --html are set, only generate what we're told to.
718 # Otherwise generate everything.
718 # Otherwise generate everything.
719 have_subset = self.man is not None or self.html is not None
719 have_subset = self.man is not None or self.html is not None
720
720
721 if have_subset:
721 if have_subset:
722 self.man = True if self.man else False
722 self.man = True if self.man else False
723 self.html = True if self.html else False
723 self.html = True if self.html else False
724 else:
724 else:
725 self.man = True
725 self.man = True
726 self.html = True
726 self.html = True
727
727
728 def run(self):
728 def run(self):
729 def normalizecrlf(p):
729 def normalizecrlf(p):
730 with open(p, 'rb') as fh:
730 with open(p, 'rb') as fh:
731 orig = fh.read()
731 orig = fh.read()
732
732
733 if b'\r\n' not in orig:
733 if b'\r\n' not in orig:
734 return
734 return
735
735
736 log.info('normalizing %s to LF line endings' % p)
736 log.info('normalizing %s to LF line endings' % p)
737 with open(p, 'wb') as fh:
737 with open(p, 'wb') as fh:
738 fh.write(orig.replace(b'\r\n', b'\n'))
738 fh.write(orig.replace(b'\r\n', b'\n'))
739
739
740 def gentxt(root):
740 def gentxt(root):
741 txt = 'doc/%s.txt' % root
741 txt = 'doc/%s.txt' % root
742 log.info('generating %s' % txt)
742 log.info('generating %s' % txt)
743 res, out, err = runcmd(
743 res, out, err = runcmd(
744 [sys.executable, 'gendoc.py', root],
744 [sys.executable, 'gendoc.py', root],
745 os.environ,
745 os.environ,
746 cwd='doc')
746 cwd='doc')
747 if res:
747 if res:
748 raise SystemExit('error running gendoc.py: %s' %
748 raise SystemExit('error running gendoc.py: %s' %
749 '\n'.join([out, err]))
749 '\n'.join([out, err]))
750
750
751 with open(txt, 'wb') as fh:
751 with open(txt, 'wb') as fh:
752 fh.write(out)
752 fh.write(out)
753
753
754 def gengendoc(root):
754 def gengendoc(root):
755 gendoc = 'doc/%s.gendoc.txt' % root
755 gendoc = 'doc/%s.gendoc.txt' % root
756
756
757 log.info('generating %s' % gendoc)
757 log.info('generating %s' % gendoc)
758 res, out, err = runcmd(
758 res, out, err = runcmd(
759 [sys.executable, 'gendoc.py', '%s.gendoc' % root],
759 [sys.executable, 'gendoc.py', '%s.gendoc' % root],
760 os.environ,
760 os.environ,
761 cwd='doc')
761 cwd='doc')
762 if res:
762 if res:
763 raise SystemExit('error running gendoc: %s' %
763 raise SystemExit('error running gendoc: %s' %
764 '\n'.join([out, err]))
764 '\n'.join([out, err]))
765
765
766 with open(gendoc, 'wb') as fh:
766 with open(gendoc, 'wb') as fh:
767 fh.write(out)
767 fh.write(out)
768
768
769 def genman(root):
769 def genman(root):
770 log.info('generating doc/%s' % root)
770 log.info('generating doc/%s' % root)
771 res, out, err = runcmd(
771 res, out, err = runcmd(
772 [sys.executable, 'runrst', 'hgmanpage', '--halt', 'warning',
772 [sys.executable, 'runrst', 'hgmanpage', '--halt', 'warning',
773 '--strip-elements-with-class', 'htmlonly',
773 '--strip-elements-with-class', 'htmlonly',
774 '%s.txt' % root, root],
774 '%s.txt' % root, root],
775 os.environ,
775 os.environ,
776 cwd='doc')
776 cwd='doc')
777 if res:
777 if res:
778 raise SystemExit('error running runrst: %s' %
778 raise SystemExit('error running runrst: %s' %
779 '\n'.join([out, err]))
779 '\n'.join([out, err]))
780
780
781 normalizecrlf('doc/%s' % root)
781 normalizecrlf('doc/%s' % root)
782
782
783 def genhtml(root):
783 def genhtml(root):
784 log.info('generating doc/%s.html' % root)
784 log.info('generating doc/%s.html' % root)
785 res, out, err = runcmd(
785 res, out, err = runcmd(
786 [sys.executable, 'runrst', 'html', '--halt', 'warning',
786 [sys.executable, 'runrst', 'html', '--halt', 'warning',
787 '--link-stylesheet', '--stylesheet-path', 'style.css',
787 '--link-stylesheet', '--stylesheet-path', 'style.css',
788 '%s.txt' % root, '%s.html' % root],
788 '%s.txt' % root, '%s.html' % root],
789 os.environ,
789 os.environ,
790 cwd='doc')
790 cwd='doc')
791 if res:
791 if res:
792 raise SystemExit('error running runrst: %s' %
792 raise SystemExit('error running runrst: %s' %
793 '\n'.join([out, err]))
793 '\n'.join([out, err]))
794
794
795 normalizecrlf('doc/%s.html' % root)
795 normalizecrlf('doc/%s.html' % root)
796
796
797 # This logic is duplicated in doc/Makefile.
797 # This logic is duplicated in doc/Makefile.
798 sources = {f for f in os.listdir('mercurial/help')
798 sources = {f for f in os.listdir('mercurial/help')
799 if re.search('[0-9]\.txt$', f)}
799 if re.search('[0-9]\.txt$', f)}
800
800
801 # common.txt is a one-off.
801 # common.txt is a one-off.
802 gentxt('common')
802 gentxt('common')
803
803
804 for source in sorted(sources):
804 for source in sorted(sources):
805 assert source[-4:] == '.txt'
805 assert source[-4:] == '.txt'
806 root = source[:-4]
806 root = source[:-4]
807
807
808 gentxt(root)
808 gentxt(root)
809 gengendoc(root)
809 gengendoc(root)
810
810
811 if self.man:
811 if self.man:
812 genman(root)
812 genman(root)
813 if self.html:
813 if self.html:
814 genhtml(root)
814 genhtml(root)
815
815
816 class hginstall(install):
816 class hginstall(install):
817
817
818 user_options = install.user_options + [
818 user_options = install.user_options + [
819 ('old-and-unmanageable', None,
819 ('old-and-unmanageable', None,
820 'noop, present for eggless setuptools compat'),
820 'noop, present for eggless setuptools compat'),
821 ('single-version-externally-managed', None,
821 ('single-version-externally-managed', None,
822 'noop, present for eggless setuptools compat'),
822 'noop, present for eggless setuptools compat'),
823 ]
823 ]
824
824
825 # Also helps setuptools not be sad while we refuse to create eggs.
825 # Also helps setuptools not be sad while we refuse to create eggs.
826 single_version_externally_managed = True
826 single_version_externally_managed = True
827
827
828 def get_sub_commands(self):
828 def get_sub_commands(self):
829 # Screen out egg related commands to prevent egg generation. But allow
829 # Screen out egg related commands to prevent egg generation. But allow
830 # mercurial.egg-info generation, since that is part of modern
830 # mercurial.egg-info generation, since that is part of modern
831 # packaging.
831 # packaging.
832 excl = set(['bdist_egg'])
832 excl = set(['bdist_egg'])
833 return filter(lambda x: x not in excl, install.get_sub_commands(self))
833 return filter(lambda x: x not in excl, install.get_sub_commands(self))
834
834
835 class hginstalllib(install_lib):
835 class hginstalllib(install_lib):
836 '''
836 '''
837 This is a specialization of install_lib that replaces the copy_file used
837 This is a specialization of install_lib that replaces the copy_file used
838 there so that it supports setting the mode of files after copying them,
838 there so that it supports setting the mode of files after copying them,
839 instead of just preserving the mode that the files originally had. If your
839 instead of just preserving the mode that the files originally had. If your
840 system has a umask of something like 027, preserving the permissions when
840 system has a umask of something like 027, preserving the permissions when
841 copying will lead to a broken install.
841 copying will lead to a broken install.
842
842
843 Note that just passing keep_permissions=False to copy_file would be
843 Note that just passing keep_permissions=False to copy_file would be
844 insufficient, as it might still be applying a umask.
844 insufficient, as it might still be applying a umask.
845 '''
845 '''
846
846
847 def run(self):
847 def run(self):
848 realcopyfile = file_util.copy_file
848 realcopyfile = file_util.copy_file
849 def copyfileandsetmode(*args, **kwargs):
849 def copyfileandsetmode(*args, **kwargs):
850 src, dst = args[0], args[1]
850 src, dst = args[0], args[1]
851 dst, copied = realcopyfile(*args, **kwargs)
851 dst, copied = realcopyfile(*args, **kwargs)
852 if copied:
852 if copied:
853 st = os.stat(src)
853 st = os.stat(src)
854 # Persist executable bit (apply it to group and other if user
854 # Persist executable bit (apply it to group and other if user
855 # has it)
855 # has it)
856 if st[stat.ST_MODE] & stat.S_IXUSR:
856 if st[stat.ST_MODE] & stat.S_IXUSR:
857 setmode = int('0755', 8)
857 setmode = int('0755', 8)
858 else:
858 else:
859 setmode = int('0644', 8)
859 setmode = int('0644', 8)
860 m = stat.S_IMODE(st[stat.ST_MODE])
860 m = stat.S_IMODE(st[stat.ST_MODE])
861 m = (m & ~int('0777', 8)) | setmode
861 m = (m & ~int('0777', 8)) | setmode
862 os.chmod(dst, m)
862 os.chmod(dst, m)
863 file_util.copy_file = copyfileandsetmode
863 file_util.copy_file = copyfileandsetmode
864 try:
864 try:
865 install_lib.run(self)
865 install_lib.run(self)
866 finally:
866 finally:
867 file_util.copy_file = realcopyfile
867 file_util.copy_file = realcopyfile
868
868
869 class hginstallscripts(install_scripts):
869 class hginstallscripts(install_scripts):
870 '''
870 '''
871 This is a specialization of install_scripts that replaces the @LIBDIR@ with
871 This is a specialization of install_scripts that replaces the @LIBDIR@ with
872 the configured directory for modules. If possible, the path is made relative
872 the configured directory for modules. If possible, the path is made relative
873 to the directory for scripts.
873 to the directory for scripts.
874 '''
874 '''
875
875
876 def initialize_options(self):
876 def initialize_options(self):
877 install_scripts.initialize_options(self)
877 install_scripts.initialize_options(self)
878
878
879 self.install_lib = None
879 self.install_lib = None
880
880
881 def finalize_options(self):
881 def finalize_options(self):
882 install_scripts.finalize_options(self)
882 install_scripts.finalize_options(self)
883 self.set_undefined_options('install',
883 self.set_undefined_options('install',
884 ('install_lib', 'install_lib'))
884 ('install_lib', 'install_lib'))
885
885
886 def run(self):
886 def run(self):
887 install_scripts.run(self)
887 install_scripts.run(self)
888
888
889 # It only makes sense to replace @LIBDIR@ with the install path if
889 # It only makes sense to replace @LIBDIR@ with the install path if
890 # the install path is known. For wheels, the logic below calculates
890 # the install path is known. For wheels, the logic below calculates
891 # the libdir to be "../..". This is because the internal layout of a
891 # the libdir to be "../..". This is because the internal layout of a
892 # wheel archive looks like:
892 # wheel archive looks like:
893 #
893 #
894 # mercurial-3.6.1.data/scripts/hg
894 # mercurial-3.6.1.data/scripts/hg
895 # mercurial/__init__.py
895 # mercurial/__init__.py
896 #
896 #
897 # When installing wheels, the subdirectories of the "<pkg>.data"
897 # When installing wheels, the subdirectories of the "<pkg>.data"
898 # directory are translated to system local paths and files therein
898 # directory are translated to system local paths and files therein
899 # are copied in place. The mercurial/* files are installed into the
899 # are copied in place. The mercurial/* files are installed into the
900 # site-packages directory. However, the site-packages directory
900 # site-packages directory. However, the site-packages directory
901 # isn't known until wheel install time. This means we have no clue
901 # isn't known until wheel install time. This means we have no clue
902 # at wheel generation time what the installed site-packages directory
902 # at wheel generation time what the installed site-packages directory
903 # will be. And, wheels don't appear to provide the ability to register
903 # will be. And, wheels don't appear to provide the ability to register
904 # custom code to run during wheel installation. This all means that
904 # custom code to run during wheel installation. This all means that
905 # we can't reliably set the libdir in wheels: the default behavior
905 # we can't reliably set the libdir in wheels: the default behavior
906 # of looking in sys.path must do.
906 # of looking in sys.path must do.
907
907
908 if (os.path.splitdrive(self.install_dir)[0] !=
908 if (os.path.splitdrive(self.install_dir)[0] !=
909 os.path.splitdrive(self.install_lib)[0]):
909 os.path.splitdrive(self.install_lib)[0]):
910 # can't make relative paths from one drive to another, so use an
910 # can't make relative paths from one drive to another, so use an
911 # absolute path instead
911 # absolute path instead
912 libdir = self.install_lib
912 libdir = self.install_lib
913 else:
913 else:
914 common = os.path.commonprefix((self.install_dir, self.install_lib))
914 common = os.path.commonprefix((self.install_dir, self.install_lib))
915 rest = self.install_dir[len(common):]
915 rest = self.install_dir[len(common):]
916 uplevel = len([n for n in os.path.split(rest) if n])
916 uplevel = len([n for n in os.path.split(rest) if n])
917
917
918 libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common):]
918 libdir = uplevel * ('..' + os.sep) + self.install_lib[len(common):]
919
919
920 for outfile in self.outfiles:
920 for outfile in self.outfiles:
921 with open(outfile, 'rb') as fp:
921 with open(outfile, 'rb') as fp:
922 data = fp.read()
922 data = fp.read()
923
923
924 # skip binary files
924 # skip binary files
925 if b'\0' in data:
925 if b'\0' in data:
926 continue
926 continue
927
927
928 # During local installs, the shebang will be rewritten to the final
928 # During local installs, the shebang will be rewritten to the final
929 # install path. During wheel packaging, the shebang has a special
929 # install path. During wheel packaging, the shebang has a special
930 # value.
930 # value.
931 if data.startswith(b'#!python'):
931 if data.startswith(b'#!python'):
932 log.info('not rewriting @LIBDIR@ in %s because install path '
932 log.info('not rewriting @LIBDIR@ in %s because install path '
933 'not known' % outfile)
933 'not known' % outfile)
934 continue
934 continue
935
935
936 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
936 data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
937 with open(outfile, 'wb') as fp:
937 with open(outfile, 'wb') as fp:
938 fp.write(data)
938 fp.write(data)
939
939
940 cmdclass = {'build': hgbuild,
940 cmdclass = {'build': hgbuild,
941 'build_doc': hgbuilddoc,
941 'build_doc': hgbuilddoc,
942 'build_mo': hgbuildmo,
942 'build_mo': hgbuildmo,
943 'build_ext': hgbuildext,
943 'build_ext': hgbuildext,
944 'build_py': hgbuildpy,
944 'build_py': hgbuildpy,
945 'build_scripts': hgbuildscripts,
945 'build_scripts': hgbuildscripts,
946 'build_hgextindex': buildhgextindex,
946 'build_hgextindex': buildhgextindex,
947 'install': hginstall,
947 'install': hginstall,
948 'install_lib': hginstalllib,
948 'install_lib': hginstalllib,
949 'install_scripts': hginstallscripts,
949 'install_scripts': hginstallscripts,
950 'build_hgexe': buildhgexe,
950 'build_hgexe': buildhgexe,
951 }
951 }
952
952
953 packages = ['mercurial',
953 packages = ['mercurial',
954 'mercurial.cext',
954 'mercurial.cext',
955 'mercurial.cffi',
955 'mercurial.cffi',
956 'mercurial.hgweb',
956 'mercurial.hgweb',
957 'mercurial.pure',
957 'mercurial.pure',
958 'mercurial.thirdparty',
958 'mercurial.thirdparty',
959 'mercurial.thirdparty.attr',
959 'mercurial.thirdparty.attr',
960 'mercurial.thirdparty.zope',
960 'mercurial.thirdparty.zope',
961 'mercurial.thirdparty.zope.interface',
961 'mercurial.thirdparty.zope.interface',
962 'mercurial.utils',
962 'mercurial.utils',
963 'mercurial.revlogutils',
963 'mercurial.revlogutils',
964 'mercurial.testing',
964 'mercurial.testing',
965 'hgext', 'hgext.convert', 'hgext.fsmonitor',
965 'hgext', 'hgext.convert', 'hgext.fsmonitor',
966 'hgext.fastannotate',
966 'hgext.fastannotate',
967 'hgext.fsmonitor.pywatchman',
967 'hgext.fsmonitor.pywatchman',
968 'hgext.infinitepush',
968 'hgext.infinitepush',
969 'hgext.highlight',
969 'hgext.highlight',
970 'hgext.largefiles', 'hgext.lfs', 'hgext.narrow',
970 'hgext.largefiles', 'hgext.lfs', 'hgext.narrow',
971 'hgext.remotefilelog',
971 'hgext.remotefilelog',
972 'hgext.zeroconf', 'hgext3rd',
972 'hgext.zeroconf', 'hgext3rd',
973 'hgdemandimport']
973 'hgdemandimport']
974 if sys.version_info[0] == 2:
974 if sys.version_info[0] == 2:
975 packages.extend(['mercurial.thirdparty.concurrent',
975 packages.extend(['mercurial.thirdparty.concurrent',
976 'mercurial.thirdparty.concurrent.futures'])
976 'mercurial.thirdparty.concurrent.futures'])
977
977
978 if 'HG_PY2EXE_EXTRA_INSTALL_PACKAGES' in os.environ:
979 # py2exe can't cope with namespace packages very well, so we have to
980 # install any hgext3rd.* extensions that we want in the final py2exe
981 # image here. This is gross, but you gotta do what you gotta do.
982 packages.extend(os.environ['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'].split(' '))
983
978 common_depends = ['mercurial/bitmanipulation.h',
984 common_depends = ['mercurial/bitmanipulation.h',
979 'mercurial/compat.h',
985 'mercurial/compat.h',
980 'mercurial/cext/util.h']
986 'mercurial/cext/util.h']
981 common_include_dirs = ['mercurial']
987 common_include_dirs = ['mercurial']
982
988
983 osutil_cflags = []
989 osutil_cflags = []
984 osutil_ldflags = []
990 osutil_ldflags = []
985
991
986 # platform specific macros
992 # platform specific macros
987 for plat, func in [('bsd', 'setproctitle')]:
993 for plat, func in [('bsd', 'setproctitle')]:
988 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
994 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
989 osutil_cflags.append('-DHAVE_%s' % func.upper())
995 osutil_cflags.append('-DHAVE_%s' % func.upper())
990
996
991 for plat, macro, code in [
997 for plat, macro, code in [
992 ('bsd|darwin', 'BSD_STATFS', '''
998 ('bsd|darwin', 'BSD_STATFS', '''
993 #include <sys/param.h>
999 #include <sys/param.h>
994 #include <sys/mount.h>
1000 #include <sys/mount.h>
995 int main() { struct statfs s; return sizeof(s.f_fstypename); }
1001 int main() { struct statfs s; return sizeof(s.f_fstypename); }
996 '''),
1002 '''),
997 ('linux', 'LINUX_STATFS', '''
1003 ('linux', 'LINUX_STATFS', '''
998 #include <linux/magic.h>
1004 #include <linux/magic.h>
999 #include <sys/vfs.h>
1005 #include <sys/vfs.h>
1000 int main() { struct statfs s; return sizeof(s.f_type); }
1006 int main() { struct statfs s; return sizeof(s.f_type); }
1001 '''),
1007 '''),
1002 ]:
1008 ]:
1003 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
1009 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
1004 osutil_cflags.append('-DHAVE_%s' % macro)
1010 osutil_cflags.append('-DHAVE_%s' % macro)
1005
1011
1006 if sys.platform == 'darwin':
1012 if sys.platform == 'darwin':
1007 osutil_ldflags += ['-framework', 'ApplicationServices']
1013 osutil_ldflags += ['-framework', 'ApplicationServices']
1008
1014
1009 xdiff_srcs = [
1015 xdiff_srcs = [
1010 'mercurial/thirdparty/xdiff/xdiffi.c',
1016 'mercurial/thirdparty/xdiff/xdiffi.c',
1011 'mercurial/thirdparty/xdiff/xprepare.c',
1017 'mercurial/thirdparty/xdiff/xprepare.c',
1012 'mercurial/thirdparty/xdiff/xutils.c',
1018 'mercurial/thirdparty/xdiff/xutils.c',
1013 ]
1019 ]
1014
1020
1015 xdiff_headers = [
1021 xdiff_headers = [
1016 'mercurial/thirdparty/xdiff/xdiff.h',
1022 'mercurial/thirdparty/xdiff/xdiff.h',
1017 'mercurial/thirdparty/xdiff/xdiffi.h',
1023 'mercurial/thirdparty/xdiff/xdiffi.h',
1018 'mercurial/thirdparty/xdiff/xinclude.h',
1024 'mercurial/thirdparty/xdiff/xinclude.h',
1019 'mercurial/thirdparty/xdiff/xmacros.h',
1025 'mercurial/thirdparty/xdiff/xmacros.h',
1020 'mercurial/thirdparty/xdiff/xprepare.h',
1026 'mercurial/thirdparty/xdiff/xprepare.h',
1021 'mercurial/thirdparty/xdiff/xtypes.h',
1027 'mercurial/thirdparty/xdiff/xtypes.h',
1022 'mercurial/thirdparty/xdiff/xutils.h',
1028 'mercurial/thirdparty/xdiff/xutils.h',
1023 ]
1029 ]
1024
1030
1025 class RustCompilationError(CCompilerError):
1031 class RustCompilationError(CCompilerError):
1026 """Exception class for Rust compilation errors."""
1032 """Exception class for Rust compilation errors."""
1027
1033
1028 class RustExtension(Extension):
1034 class RustExtension(Extension):
1029 """Base classes for concrete Rust Extension classes.
1035 """Base classes for concrete Rust Extension classes.
1030 """
1036 """
1031
1037
1032 rusttargetdir = os.path.join('rust', 'target', 'release')
1038 rusttargetdir = os.path.join('rust', 'target', 'release')
1033
1039
1034 def __init__(self, mpath, sources, rustlibname, subcrate,
1040 def __init__(self, mpath, sources, rustlibname, subcrate,
1035 py3_features=None, **kw):
1041 py3_features=None, **kw):
1036 Extension.__init__(self, mpath, sources, **kw)
1042 Extension.__init__(self, mpath, sources, **kw)
1037 if hgrustext is None:
1043 if hgrustext is None:
1038 return
1044 return
1039 srcdir = self.rustsrcdir = os.path.join('rust', subcrate)
1045 srcdir = self.rustsrcdir = os.path.join('rust', subcrate)
1040 self.py3_features = py3_features
1046 self.py3_features = py3_features
1041
1047
1042 # adding Rust source and control files to depends so that the extension
1048 # adding Rust source and control files to depends so that the extension
1043 # gets rebuilt if they've changed
1049 # gets rebuilt if they've changed
1044 self.depends.append(os.path.join(srcdir, 'Cargo.toml'))
1050 self.depends.append(os.path.join(srcdir, 'Cargo.toml'))
1045 cargo_lock = os.path.join(srcdir, 'Cargo.lock')
1051 cargo_lock = os.path.join(srcdir, 'Cargo.lock')
1046 if os.path.exists(cargo_lock):
1052 if os.path.exists(cargo_lock):
1047 self.depends.append(cargo_lock)
1053 self.depends.append(cargo_lock)
1048 for dirpath, subdir, fnames in os.walk(os.path.join(srcdir, 'src')):
1054 for dirpath, subdir, fnames in os.walk(os.path.join(srcdir, 'src')):
1049 self.depends.extend(os.path.join(dirpath, fname)
1055 self.depends.extend(os.path.join(dirpath, fname)
1050 for fname in fnames
1056 for fname in fnames
1051 if os.path.splitext(fname)[1] == '.rs')
1057 if os.path.splitext(fname)[1] == '.rs')
1052
1058
1053 def rustbuild(self):
1059 def rustbuild(self):
1054 if hgrustext is None:
1060 if hgrustext is None:
1055 return
1061 return
1056 env = os.environ.copy()
1062 env = os.environ.copy()
1057 if 'HGTEST_RESTOREENV' in env:
1063 if 'HGTEST_RESTOREENV' in env:
1058 # Mercurial tests change HOME to a temporary directory,
1064 # Mercurial tests change HOME to a temporary directory,
1059 # but, if installed with rustup, the Rust toolchain needs
1065 # but, if installed with rustup, the Rust toolchain needs
1060 # HOME to be correct (otherwise the 'no default toolchain'
1066 # HOME to be correct (otherwise the 'no default toolchain'
1061 # error message is issued and the build fails).
1067 # error message is issued and the build fails).
1062 # This happens currently with test-hghave.t, which does
1068 # This happens currently with test-hghave.t, which does
1063 # invoke this build.
1069 # invoke this build.
1064
1070
1065 # Unix only fix (os.path.expanduser not really reliable if
1071 # Unix only fix (os.path.expanduser not really reliable if
1066 # HOME is shadowed like this)
1072 # HOME is shadowed like this)
1067 import pwd
1073 import pwd
1068 env['HOME'] = pwd.getpwuid(os.getuid()).pw_dir
1074 env['HOME'] = pwd.getpwuid(os.getuid()).pw_dir
1069
1075
1070 cargocmd = ['cargo', 'build', '-vv', '--release']
1076 cargocmd = ['cargo', 'build', '-vv', '--release']
1071 if sys.version_info[0] == 3 and self.py3_features is not None:
1077 if sys.version_info[0] == 3 and self.py3_features is not None:
1072 cargocmd.extend(('--features', self.py3_features,
1078 cargocmd.extend(('--features', self.py3_features,
1073 '--no-default-features'))
1079 '--no-default-features'))
1074 try:
1080 try:
1075 subprocess.check_call(cargocmd, env=env, cwd=self.rustsrcdir)
1081 subprocess.check_call(cargocmd, env=env, cwd=self.rustsrcdir)
1076 except OSError as exc:
1082 except OSError as exc:
1077 if exc.errno == errno.ENOENT:
1083 if exc.errno == errno.ENOENT:
1078 raise RustCompilationError("Cargo not found")
1084 raise RustCompilationError("Cargo not found")
1079 elif exc.errno == errno.EACCES:
1085 elif exc.errno == errno.EACCES:
1080 raise RustCompilationError(
1086 raise RustCompilationError(
1081 "Cargo found, but permisssion to execute it is denied")
1087 "Cargo found, but permisssion to execute it is denied")
1082 else:
1088 else:
1083 raise
1089 raise
1084 except subprocess.CalledProcessError:
1090 except subprocess.CalledProcessError:
1085 raise RustCompilationError(
1091 raise RustCompilationError(
1086 "Cargo failed. Working directory: %r, "
1092 "Cargo failed. Working directory: %r, "
1087 "command: %r, environment: %r" % (self.rustsrcdir, cmd, env))
1093 "command: %r, environment: %r" % (self.rustsrcdir, cmd, env))
1088
1094
1089 class RustEnhancedExtension(RustExtension):
1095 class RustEnhancedExtension(RustExtension):
1090 """A C Extension, conditionally enhanced with Rust code.
1096 """A C Extension, conditionally enhanced with Rust code.
1091
1097
1092 If the HGRUSTEXT environment variable is set to something else
1098 If the HGRUSTEXT environment variable is set to something else
1093 than 'cpython', the Rust sources get compiled and linked within the
1099 than 'cpython', the Rust sources get compiled and linked within the
1094 C target shared library object.
1100 C target shared library object.
1095 """
1101 """
1096
1102
1097 def __init__(self, mpath, sources, rustlibname, subcrate, **kw):
1103 def __init__(self, mpath, sources, rustlibname, subcrate, **kw):
1098 RustExtension.__init__(self, mpath, sources, rustlibname, subcrate,
1104 RustExtension.__init__(self, mpath, sources, rustlibname, subcrate,
1099 **kw)
1105 **kw)
1100 if hgrustext != 'direct-ffi':
1106 if hgrustext != 'direct-ffi':
1101 return
1107 return
1102 self.extra_compile_args.append('-DWITH_RUST')
1108 self.extra_compile_args.append('-DWITH_RUST')
1103 self.libraries.append(rustlibname)
1109 self.libraries.append(rustlibname)
1104 self.library_dirs.append(self.rusttargetdir)
1110 self.library_dirs.append(self.rusttargetdir)
1105
1111
1106 class RustStandaloneExtension(RustExtension):
1112 class RustStandaloneExtension(RustExtension):
1107
1113
1108 def __init__(self, pydottedname, rustcrate, dylibname, **kw):
1114 def __init__(self, pydottedname, rustcrate, dylibname, **kw):
1109 RustExtension.__init__(self, pydottedname, [], dylibname, rustcrate,
1115 RustExtension.__init__(self, pydottedname, [], dylibname, rustcrate,
1110 **kw)
1116 **kw)
1111 self.dylibname = dylibname
1117 self.dylibname = dylibname
1112
1118
1113 def build(self, target_dir):
1119 def build(self, target_dir):
1114 self.rustbuild()
1120 self.rustbuild()
1115 target = [target_dir]
1121 target = [target_dir]
1116 target.extend(self.name.split('.'))
1122 target.extend(self.name.split('.'))
1117 ext = '.so' # TODO Unix only
1123 ext = '.so' # TODO Unix only
1118 target[-1] += ext
1124 target[-1] += ext
1119 shutil.copy2(os.path.join(self.rusttargetdir, self.dylibname + ext),
1125 shutil.copy2(os.path.join(self.rusttargetdir, self.dylibname + ext),
1120 os.path.join(*target))
1126 os.path.join(*target))
1121
1127
1122
1128
1123 extmodules = [
1129 extmodules = [
1124 Extension('mercurial.cext.base85', ['mercurial/cext/base85.c'],
1130 Extension('mercurial.cext.base85', ['mercurial/cext/base85.c'],
1125 include_dirs=common_include_dirs,
1131 include_dirs=common_include_dirs,
1126 depends=common_depends),
1132 depends=common_depends),
1127 Extension('mercurial.cext.bdiff', ['mercurial/bdiff.c',
1133 Extension('mercurial.cext.bdiff', ['mercurial/bdiff.c',
1128 'mercurial/cext/bdiff.c'] + xdiff_srcs,
1134 'mercurial/cext/bdiff.c'] + xdiff_srcs,
1129 include_dirs=common_include_dirs,
1135 include_dirs=common_include_dirs,
1130 depends=common_depends + ['mercurial/bdiff.h'] + xdiff_headers),
1136 depends=common_depends + ['mercurial/bdiff.h'] + xdiff_headers),
1131 Extension('mercurial.cext.mpatch', ['mercurial/mpatch.c',
1137 Extension('mercurial.cext.mpatch', ['mercurial/mpatch.c',
1132 'mercurial/cext/mpatch.c'],
1138 'mercurial/cext/mpatch.c'],
1133 include_dirs=common_include_dirs,
1139 include_dirs=common_include_dirs,
1134 depends=common_depends),
1140 depends=common_depends),
1135 RustEnhancedExtension(
1141 RustEnhancedExtension(
1136 'mercurial.cext.parsers', ['mercurial/cext/charencode.c',
1142 'mercurial.cext.parsers', ['mercurial/cext/charencode.c',
1137 'mercurial/cext/dirs.c',
1143 'mercurial/cext/dirs.c',
1138 'mercurial/cext/manifest.c',
1144 'mercurial/cext/manifest.c',
1139 'mercurial/cext/parsers.c',
1145 'mercurial/cext/parsers.c',
1140 'mercurial/cext/pathencode.c',
1146 'mercurial/cext/pathencode.c',
1141 'mercurial/cext/revlog.c'],
1147 'mercurial/cext/revlog.c'],
1142 'hgdirectffi',
1148 'hgdirectffi',
1143 'hg-direct-ffi',
1149 'hg-direct-ffi',
1144 include_dirs=common_include_dirs,
1150 include_dirs=common_include_dirs,
1145 depends=common_depends + ['mercurial/cext/charencode.h',
1151 depends=common_depends + ['mercurial/cext/charencode.h',
1146 'mercurial/cext/revlog.h',
1152 'mercurial/cext/revlog.h',
1147 'rust/hg-core/src/ancestors.rs',
1153 'rust/hg-core/src/ancestors.rs',
1148 'rust/hg-core/src/lib.rs']),
1154 'rust/hg-core/src/lib.rs']),
1149 Extension('mercurial.cext.osutil', ['mercurial/cext/osutil.c'],
1155 Extension('mercurial.cext.osutil', ['mercurial/cext/osutil.c'],
1150 include_dirs=common_include_dirs,
1156 include_dirs=common_include_dirs,
1151 extra_compile_args=osutil_cflags,
1157 extra_compile_args=osutil_cflags,
1152 extra_link_args=osutil_ldflags,
1158 extra_link_args=osutil_ldflags,
1153 depends=common_depends),
1159 depends=common_depends),
1154 Extension(
1160 Extension(
1155 'mercurial.thirdparty.zope.interface._zope_interface_coptimizations', [
1161 'mercurial.thirdparty.zope.interface._zope_interface_coptimizations', [
1156 'mercurial/thirdparty/zope/interface/_zope_interface_coptimizations.c',
1162 'mercurial/thirdparty/zope/interface/_zope_interface_coptimizations.c',
1157 ]),
1163 ]),
1158 Extension('hgext.fsmonitor.pywatchman.bser',
1164 Extension('hgext.fsmonitor.pywatchman.bser',
1159 ['hgext/fsmonitor/pywatchman/bser.c']),
1165 ['hgext/fsmonitor/pywatchman/bser.c']),
1160 ]
1166 ]
1161
1167
1162 if hgrustext == 'cpython':
1168 if hgrustext == 'cpython':
1163 extmodules.append(
1169 extmodules.append(
1164 RustStandaloneExtension('mercurial.rustext', 'hg-cpython', 'librusthg',
1170 RustStandaloneExtension('mercurial.rustext', 'hg-cpython', 'librusthg',
1165 py3_features='python3')
1171 py3_features='python3')
1166 )
1172 )
1167
1173
1168
1174
1169 sys.path.insert(0, 'contrib/python-zstandard')
1175 sys.path.insert(0, 'contrib/python-zstandard')
1170 import setup_zstd
1176 import setup_zstd
1171 extmodules.append(setup_zstd.get_c_extension(
1177 extmodules.append(setup_zstd.get_c_extension(
1172 name='mercurial.zstd',
1178 name='mercurial.zstd',
1173 root=os.path.abspath(os.path.dirname(__file__))))
1179 root=os.path.abspath(os.path.dirname(__file__))))
1174
1180
1175 try:
1181 try:
1176 from distutils import cygwinccompiler
1182 from distutils import cygwinccompiler
1177
1183
1178 # the -mno-cygwin option has been deprecated for years
1184 # the -mno-cygwin option has been deprecated for years
1179 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
1185 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
1180
1186
1181 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
1187 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
1182 def __init__(self, *args, **kwargs):
1188 def __init__(self, *args, **kwargs):
1183 mingw32compilerclass.__init__(self, *args, **kwargs)
1189 mingw32compilerclass.__init__(self, *args, **kwargs)
1184 for i in 'compiler compiler_so linker_exe linker_so'.split():
1190 for i in 'compiler compiler_so linker_exe linker_so'.split():
1185 try:
1191 try:
1186 getattr(self, i).remove('-mno-cygwin')
1192 getattr(self, i).remove('-mno-cygwin')
1187 except ValueError:
1193 except ValueError:
1188 pass
1194 pass
1189
1195
1190 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
1196 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
1191 except ImportError:
1197 except ImportError:
1192 # the cygwinccompiler package is not available on some Python
1198 # the cygwinccompiler package is not available on some Python
1193 # distributions like the ones from the optware project for Synology
1199 # distributions like the ones from the optware project for Synology
1194 # DiskStation boxes
1200 # DiskStation boxes
1195 class HackedMingw32CCompiler(object):
1201 class HackedMingw32CCompiler(object):
1196 pass
1202 pass
1197
1203
1198 if os.name == 'nt':
1204 if os.name == 'nt':
1199 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
1205 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
1200 # extra_link_args to distutils.extensions.Extension() doesn't have any
1206 # extra_link_args to distutils.extensions.Extension() doesn't have any
1201 # effect.
1207 # effect.
1202 from distutils import msvccompiler
1208 from distutils import msvccompiler
1203
1209
1204 msvccompilerclass = msvccompiler.MSVCCompiler
1210 msvccompilerclass = msvccompiler.MSVCCompiler
1205
1211
1206 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
1212 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
1207 def initialize(self):
1213 def initialize(self):
1208 msvccompilerclass.initialize(self)
1214 msvccompilerclass.initialize(self)
1209 # "warning LNK4197: export 'func' specified multiple times"
1215 # "warning LNK4197: export 'func' specified multiple times"
1210 self.ldflags_shared.append('/ignore:4197')
1216 self.ldflags_shared.append('/ignore:4197')
1211 self.ldflags_shared_debug.append('/ignore:4197')
1217 self.ldflags_shared_debug.append('/ignore:4197')
1212
1218
1213 msvccompiler.MSVCCompiler = HackedMSVCCompiler
1219 msvccompiler.MSVCCompiler = HackedMSVCCompiler
1214
1220
1215 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
1221 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
1216 'help/*.txt',
1222 'help/*.txt',
1217 'help/internals/*.txt',
1223 'help/internals/*.txt',
1218 'default.d/*.rc',
1224 'default.d/*.rc',
1219 'dummycert.pem']}
1225 'dummycert.pem']}
1220
1226
1221 def ordinarypath(p):
1227 def ordinarypath(p):
1222 return p and p[0] != '.' and p[-1] != '~'
1228 return p and p[0] != '.' and p[-1] != '~'
1223
1229
1224 for root in ('templates',):
1230 for root in ('templates',):
1225 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
1231 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
1226 curdir = curdir.split(os.sep, 1)[1]
1232 curdir = curdir.split(os.sep, 1)[1]
1227 dirs[:] = filter(ordinarypath, dirs)
1233 dirs[:] = filter(ordinarypath, dirs)
1228 for f in filter(ordinarypath, files):
1234 for f in filter(ordinarypath, files):
1229 f = os.path.join(curdir, f)
1235 f = os.path.join(curdir, f)
1230 packagedata['mercurial'].append(f)
1236 packagedata['mercurial'].append(f)
1231
1237
1232 datafiles = []
1238 datafiles = []
1233
1239
1234 # distutils expects version to be str/unicode. Converting it to
1240 # distutils expects version to be str/unicode. Converting it to
1235 # unicode on Python 2 still works because it won't contain any
1241 # unicode on Python 2 still works because it won't contain any
1236 # non-ascii bytes and will be implicitly converted back to bytes
1242 # non-ascii bytes and will be implicitly converted back to bytes
1237 # when operated on.
1243 # when operated on.
1238 assert isinstance(version, bytes)
1244 assert isinstance(version, bytes)
1239 setupversion = version.decode('ascii')
1245 setupversion = version.decode('ascii')
1240
1246
1241 extra = {}
1247 extra = {}
1242
1248
1243 py2exepackages = [
1249 py2exepackages = [
1244 'hgdemandimport',
1250 'hgdemandimport',
1245 'hgext3rd',
1251 'hgext3rd',
1246 'hgext',
1252 'hgext',
1247 'email',
1253 'email',
1248 # implicitly imported per module policy
1254 # implicitly imported per module policy
1249 # (cffi wouldn't be used as a frozen exe)
1255 # (cffi wouldn't be used as a frozen exe)
1250 'mercurial.cext',
1256 'mercurial.cext',
1251 #'mercurial.cffi',
1257 #'mercurial.cffi',
1252 'mercurial.pure',
1258 'mercurial.pure',
1253 ]
1259 ]
1254
1260
1255 py2exeexcludes = []
1261 py2exeexcludes = []
1256 py2exedllexcludes = ['crypt32.dll']
1262 py2exedllexcludes = ['crypt32.dll']
1257
1263
1258 if issetuptools:
1264 if issetuptools:
1259 extra['python_requires'] = supportedpy
1265 extra['python_requires'] = supportedpy
1260
1266
1261 if py2exeloaded:
1267 if py2exeloaded:
1262 extra['console'] = [
1268 extra['console'] = [
1263 {'script':'hg',
1269 {'script':'hg',
1264 'copyright':'Copyright (C) 2005-2019 Matt Mackall and others',
1270 'copyright':'Copyright (C) 2005-2019 Matt Mackall and others',
1265 'product_version':version}]
1271 'product_version':version}]
1266 # Sub command of 'build' because 'py2exe' does not handle sub_commands.
1272 # Sub command of 'build' because 'py2exe' does not handle sub_commands.
1267 # Need to override hgbuild because it has a private copy of
1273 # Need to override hgbuild because it has a private copy of
1268 # build.sub_commands.
1274 # build.sub_commands.
1269 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1275 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1270 # put dlls in sub directory so that they won't pollute PATH
1276 # put dlls in sub directory so that they won't pollute PATH
1271 extra['zipfile'] = 'lib/library.zip'
1277 extra['zipfile'] = 'lib/library.zip'
1272
1278
1273 # We allow some configuration to be supplemented via environment
1279 # We allow some configuration to be supplemented via environment
1274 # variables. This is better than setup.cfg files because it allows
1280 # variables. This is better than setup.cfg files because it allows
1275 # supplementing configs instead of replacing them.
1281 # supplementing configs instead of replacing them.
1276 extrapackages = os.environ.get('HG_PY2EXE_EXTRA_PACKAGES')
1282 extrapackages = os.environ.get('HG_PY2EXE_EXTRA_PACKAGES')
1277 if extrapackages:
1283 if extrapackages:
1278 py2exepackages.extend(extrapackages.split(' '))
1284 py2exepackages.extend(extrapackages.split(' '))
1279
1285
1280 excludes = os.environ.get('HG_PY2EXE_EXTRA_EXCLUDES')
1286 excludes = os.environ.get('HG_PY2EXE_EXTRA_EXCLUDES')
1281 if excludes:
1287 if excludes:
1282 py2exeexcludes.extend(excludes.split(' '))
1288 py2exeexcludes.extend(excludes.split(' '))
1283
1289
1284 dllexcludes = os.environ.get('HG_PY2EXE_EXTRA_DLL_EXCLUDES')
1290 dllexcludes = os.environ.get('HG_PY2EXE_EXTRA_DLL_EXCLUDES')
1285 if dllexcludes:
1291 if dllexcludes:
1286 py2exedllexcludes.extend(dllexcludes.split(' '))
1292 py2exedllexcludes.extend(dllexcludes.split(' '))
1287
1293
1288 if os.name == 'nt':
1294 if os.name == 'nt':
1289 # Windows binary file versions for exe/dll files must have the
1295 # Windows binary file versions for exe/dll files must have the
1290 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
1296 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
1291 setupversion = setupversion.split(r'+', 1)[0]
1297 setupversion = setupversion.split(r'+', 1)[0]
1292
1298
1293 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
1299 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
1294 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
1300 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
1295 if version:
1301 if version:
1296 version = version[0]
1302 version = version[0]
1297 if sys.version_info[0] == 3:
1303 if sys.version_info[0] == 3:
1298 version = version.decode('utf-8')
1304 version = version.decode('utf-8')
1299 xcode4 = (version.startswith('Xcode') and
1305 xcode4 = (version.startswith('Xcode') and
1300 StrictVersion(version.split()[1]) >= StrictVersion('4.0'))
1306 StrictVersion(version.split()[1]) >= StrictVersion('4.0'))
1301 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
1307 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
1302 else:
1308 else:
1303 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
1309 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
1304 # installed, but instead with only command-line tools. Assume
1310 # installed, but instead with only command-line tools. Assume
1305 # that only happens on >= Lion, thus no PPC support.
1311 # that only happens on >= Lion, thus no PPC support.
1306 xcode4 = True
1312 xcode4 = True
1307 xcode51 = False
1313 xcode51 = False
1308
1314
1309 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
1315 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
1310 # distutils.sysconfig
1316 # distutils.sysconfig
1311 if xcode4:
1317 if xcode4:
1312 os.environ['ARCHFLAGS'] = ''
1318 os.environ['ARCHFLAGS'] = ''
1313
1319
1314 # XCode 5.1 changes clang such that it now fails to compile if the
1320 # XCode 5.1 changes clang such that it now fails to compile if the
1315 # -mno-fused-madd flag is passed, but the version of Python shipped with
1321 # -mno-fused-madd flag is passed, but the version of Python shipped with
1316 # OS X 10.9 Mavericks includes this flag. This causes problems in all
1322 # OS X 10.9 Mavericks includes this flag. This causes problems in all
1317 # C extension modules, and a bug has been filed upstream at
1323 # C extension modules, and a bug has been filed upstream at
1318 # http://bugs.python.org/issue21244. We also need to patch this here
1324 # http://bugs.python.org/issue21244. We also need to patch this here
1319 # so Mercurial can continue to compile in the meantime.
1325 # so Mercurial can continue to compile in the meantime.
1320 if xcode51:
1326 if xcode51:
1321 cflags = get_config_var('CFLAGS')
1327 cflags = get_config_var('CFLAGS')
1322 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
1328 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
1323 os.environ['CFLAGS'] = (
1329 os.environ['CFLAGS'] = (
1324 os.environ.get('CFLAGS', '') + ' -Qunused-arguments')
1330 os.environ.get('CFLAGS', '') + ' -Qunused-arguments')
1325
1331
1326 setup(name='mercurial',
1332 setup(name='mercurial',
1327 version=setupversion,
1333 version=setupversion,
1328 author='Matt Mackall and many others',
1334 author='Matt Mackall and many others',
1329 author_email='mercurial@mercurial-scm.org',
1335 author_email='mercurial@mercurial-scm.org',
1330 url='https://mercurial-scm.org/',
1336 url='https://mercurial-scm.org/',
1331 download_url='https://mercurial-scm.org/release/',
1337 download_url='https://mercurial-scm.org/release/',
1332 description=('Fast scalable distributed SCM (revision control, version '
1338 description=('Fast scalable distributed SCM (revision control, version '
1333 'control) system'),
1339 'control) system'),
1334 long_description=('Mercurial is a distributed SCM tool written in Python.'
1340 long_description=('Mercurial is a distributed SCM tool written in Python.'
1335 ' It is used by a number of large projects that require'
1341 ' It is used by a number of large projects that require'
1336 ' fast, reliable distributed revision control, such as '
1342 ' fast, reliable distributed revision control, such as '
1337 'Mozilla.'),
1343 'Mozilla.'),
1338 license='GNU GPLv2 or any later version',
1344 license='GNU GPLv2 or any later version',
1339 classifiers=[
1345 classifiers=[
1340 'Development Status :: 6 - Mature',
1346 'Development Status :: 6 - Mature',
1341 'Environment :: Console',
1347 'Environment :: Console',
1342 'Intended Audience :: Developers',
1348 'Intended Audience :: Developers',
1343 'Intended Audience :: System Administrators',
1349 'Intended Audience :: System Administrators',
1344 'License :: OSI Approved :: GNU General Public License (GPL)',
1350 'License :: OSI Approved :: GNU General Public License (GPL)',
1345 'Natural Language :: Danish',
1351 'Natural Language :: Danish',
1346 'Natural Language :: English',
1352 'Natural Language :: English',
1347 'Natural Language :: German',
1353 'Natural Language :: German',
1348 'Natural Language :: Italian',
1354 'Natural Language :: Italian',
1349 'Natural Language :: Japanese',
1355 'Natural Language :: Japanese',
1350 'Natural Language :: Portuguese (Brazilian)',
1356 'Natural Language :: Portuguese (Brazilian)',
1351 'Operating System :: Microsoft :: Windows',
1357 'Operating System :: Microsoft :: Windows',
1352 'Operating System :: OS Independent',
1358 'Operating System :: OS Independent',
1353 'Operating System :: POSIX',
1359 'Operating System :: POSIX',
1354 'Programming Language :: C',
1360 'Programming Language :: C',
1355 'Programming Language :: Python',
1361 'Programming Language :: Python',
1356 'Topic :: Software Development :: Version Control',
1362 'Topic :: Software Development :: Version Control',
1357 ],
1363 ],
1358 scripts=scripts,
1364 scripts=scripts,
1359 packages=packages,
1365 packages=packages,
1360 ext_modules=extmodules,
1366 ext_modules=extmodules,
1361 data_files=datafiles,
1367 data_files=datafiles,
1362 package_data=packagedata,
1368 package_data=packagedata,
1363 cmdclass=cmdclass,
1369 cmdclass=cmdclass,
1364 distclass=hgdist,
1370 distclass=hgdist,
1365 options={
1371 options={
1366 'py2exe': {
1372 'py2exe': {
1367 'bundle_files': 3,
1373 'bundle_files': 3,
1368 'dll_excludes': py2exedllexcludes,
1374 'dll_excludes': py2exedllexcludes,
1369 'excludes': py2exeexcludes,
1375 'excludes': py2exeexcludes,
1370 'packages': py2exepackages,
1376 'packages': py2exepackages,
1371 },
1377 },
1372 'bdist_mpkg': {
1378 'bdist_mpkg': {
1373 'zipdist': False,
1379 'zipdist': False,
1374 'license': 'COPYING',
1380 'license': 'COPYING',
1375 'readme': 'contrib/packaging/macosx/Readme.html',
1381 'readme': 'contrib/packaging/macosx/Readme.html',
1376 'welcome': 'contrib/packaging/macosx/Welcome.html',
1382 'welcome': 'contrib/packaging/macosx/Welcome.html',
1377 },
1383 },
1378 },
1384 },
1379 **extra)
1385 **extra)
General Comments 0
You need to be logged in to leave comments. Login now