##// END OF EJS Templates
setup: configure py2exe config via environment variables...
Gregory Szorc -
r42082:260305e8 default
parent child Browse files
Show More
@@ -1,70 +1,78 b''
1 # inno.py - Inno Setup functionality.
1 # inno.py - Inno Setup functionality.
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 shutil
12 import shutil
13 import subprocess
13 import subprocess
14
14
15 from .py2exe import (
15 from .py2exe import (
16 build_py2exe,
16 build_py2exe,
17 )
17 )
18 from .util import (
18 from .util import (
19 find_vc_runtime_files,
19 find_vc_runtime_files,
20 )
20 )
21
21
22
22
23 EXTRA_PACKAGES = {
24 'dulwich',
25 'keyring',
26 'pygments',
27 'win32ctypes',
28 }
29
30
23 def build(source_dir: pathlib.Path, build_dir: pathlib.Path,
31 def build(source_dir: pathlib.Path, build_dir: pathlib.Path,
24 python_exe: pathlib.Path, iscc_exe: pathlib.Path,
32 python_exe: pathlib.Path, iscc_exe: pathlib.Path,
25 version=None):
33 version=None):
26 """Build the Inno installer.
34 """Build the Inno installer.
27
35
28 Build files will be placed in ``build_dir``.
36 Build files will be placed in ``build_dir``.
29
37
30 py2exe's setup.py doesn't use setuptools. It doesn't have modern logic
38 py2exe's setup.py doesn't use setuptools. It doesn't have modern logic
31 for finding the Python 2.7 toolchain. So, we require the environment
39 for finding the Python 2.7 toolchain. So, we require the environment
32 to already be configured with an active toolchain.
40 to already be configured with an active toolchain.
33 """
41 """
34 if not iscc_exe.exists():
42 if not iscc_exe.exists():
35 raise Exception('%s does not exist' % iscc_exe)
43 raise Exception('%s does not exist' % iscc_exe)
36
44
37 vc_x64 = r'\x64' in os.environ.get('LIB', '')
45 vc_x64 = r'\x64' in os.environ.get('LIB', '')
38
46
39 requirements_txt = (source_dir / 'contrib' / 'packaging' /
47 requirements_txt = (source_dir / 'contrib' / 'packaging' /
40 'inno' / 'requirements.txt')
48 'inno' / 'requirements.txt')
41
49
42 build_py2exe(source_dir, build_dir, python_exe, 'inno',
50 build_py2exe(source_dir, build_dir, python_exe, 'inno',
43 requirements_txt)
51 requirements_txt, extra_packages=EXTRA_PACKAGES)
44
52
45 # hg.exe depends on VC9 runtime DLLs. Copy those into place.
53 # hg.exe depends on VC9 runtime DLLs. Copy those into place.
46 for f in find_vc_runtime_files(vc_x64):
54 for f in find_vc_runtime_files(vc_x64):
47 if f.name.endswith('.manifest'):
55 if f.name.endswith('.manifest'):
48 basename = 'Microsoft.VC90.CRT.manifest'
56 basename = 'Microsoft.VC90.CRT.manifest'
49 else:
57 else:
50 basename = f.name
58 basename = f.name
51
59
52 dest_path = source_dir / 'dist' / basename
60 dest_path = source_dir / 'dist' / basename
53
61
54 print('copying %s to %s' % (f, dest_path))
62 print('copying %s to %s' % (f, dest_path))
55 shutil.copyfile(f, dest_path)
63 shutil.copyfile(f, dest_path)
56
64
57 print('creating installer')
65 print('creating installer')
58
66
59 args = [str(iscc_exe)]
67 args = [str(iscc_exe)]
60
68
61 if vc_x64:
69 if vc_x64:
62 args.append('/dARCH=x64')
70 args.append('/dARCH=x64')
63
71
64 if version:
72 if version:
65 args.append('/dVERSION=%s' % version)
73 args.append('/dVERSION=%s' % version)
66
74
67 args.append('/Odist')
75 args.append('/Odist')
68 args.append('contrib/packaging/inno/mercurial.iss')
76 args.append('contrib/packaging/inno/mercurial.iss')
69
77
70 subprocess.run(args, cwd=str(source_dir), check=True)
78 subprocess.run(args, cwd=str(source_dir), check=True)
@@ -1,125 +1,135 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,
28 extra_dll_excludes=None):
27 """Build Mercurial with py2exe.
29 """Build Mercurial with py2exe.
28
30
29 Build files will be placed in ``build_dir``.
31 Build files will be placed in ``build_dir``.
30
32
31 py2exe's setup.py doesn't use setuptools. It doesn't have modern logic
33 py2exe's setup.py doesn't use setuptools. It doesn't have modern logic
32 for finding the Python 2.7 toolchain. So, we require the environment
34 for finding the Python 2.7 toolchain. So, we require the environment
33 to already be configured with an active toolchain.
35 to already be configured with an active toolchain.
34 """
36 """
35 if 'VCINSTALLDIR' not in os.environ:
37 if 'VCINSTALLDIR' not in os.environ:
36 raise Exception('not running from a Visual C++ build environment; '
38 raise Exception('not running from a Visual C++ build environment; '
37 'execute the "Visual C++ <version> Command Prompt" '
39 'execute the "Visual C++ <version> Command Prompt" '
38 'application shortcut or a vcsvarsall.bat file')
40 'application shortcut or a vcsvarsall.bat file')
39
41
40 # Identity x86/x64 and validate the environment matches the Python
42 # Identity x86/x64 and validate the environment matches the Python
41 # architecture.
43 # architecture.
42 vc_x64 = r'\x64' in os.environ['LIB']
44 vc_x64 = r'\x64' in os.environ['LIB']
43
45
44 py_info = python_exe_info(python_exe)
46 py_info = python_exe_info(python_exe)
45
47
46 if vc_x64:
48 if vc_x64:
47 if py_info['arch'] != '64bit':
49 if py_info['arch'] != '64bit':
48 raise Exception('architecture mismatch: Visual C++ environment '
50 raise Exception('architecture mismatch: Visual C++ environment '
49 'is configured for 64-bit but Python is 32-bit')
51 'is configured for 64-bit but Python is 32-bit')
50 else:
52 else:
51 if py_info['arch'] != '32bit':
53 if py_info['arch'] != '32bit':
52 raise Exception('architecture mismatch: Visual C++ environment '
54 raise Exception('architecture mismatch: Visual C++ environment '
53 'is configured for 32-bit but Python is 64-bit')
55 'is configured for 32-bit but Python is 64-bit')
54
56
55 if py_info['py3']:
57 if py_info['py3']:
56 raise Exception('Only Python 2 is currently supported')
58 raise Exception('Only Python 2 is currently supported')
57
59
58 build_dir.mkdir(exist_ok=True)
60 build_dir.mkdir(exist_ok=True)
59
61
60 gettext_pkg, gettext_entry = download_entry('gettext', build_dir)
62 gettext_pkg, gettext_entry = download_entry('gettext', build_dir)
61 gettext_dep_pkg = download_entry('gettext-dep', build_dir)[0]
63 gettext_dep_pkg = download_entry('gettext-dep', build_dir)[0]
62 virtualenv_pkg, virtualenv_entry = download_entry('virtualenv', build_dir)
64 virtualenv_pkg, virtualenv_entry = download_entry('virtualenv', build_dir)
63 py2exe_pkg, py2exe_entry = download_entry('py2exe', build_dir)
65 py2exe_pkg, py2exe_entry = download_entry('py2exe', build_dir)
64
66
65 venv_path = build_dir / ('venv-%s-%s' % (build_name,
67 venv_path = build_dir / ('venv-%s-%s' % (build_name,
66 'x64' if vc_x64 else 'x86'))
68 'x64' if vc_x64 else 'x86'))
67
69
68 gettext_root = build_dir / (
70 gettext_root = build_dir / (
69 'gettext-win-%s' % gettext_entry['version'])
71 'gettext-win-%s' % gettext_entry['version'])
70
72
71 if not gettext_root.exists():
73 if not gettext_root.exists():
72 extract_zip_to_directory(gettext_pkg, gettext_root)
74 extract_zip_to_directory(gettext_pkg, gettext_root)
73 extract_zip_to_directory(gettext_dep_pkg, gettext_root)
75 extract_zip_to_directory(gettext_dep_pkg, gettext_root)
74
76
75 # This assumes Python 2. We don't need virtualenv on Python 3.
77 # This assumes Python 2. We don't need virtualenv on Python 3.
76 virtualenv_src_path = build_dir / (
78 virtualenv_src_path = build_dir / (
77 'virtualenv-%s' % virtualenv_entry['version'])
79 'virtualenv-%s' % virtualenv_entry['version'])
78 virtualenv_py = virtualenv_src_path / 'virtualenv.py'
80 virtualenv_py = virtualenv_src_path / 'virtualenv.py'
79
81
80 if not virtualenv_src_path.exists():
82 if not virtualenv_src_path.exists():
81 extract_tar_to_directory(virtualenv_pkg, build_dir)
83 extract_tar_to_directory(virtualenv_pkg, build_dir)
82
84
83 py2exe_source_path = build_dir / ('py2exe-%s' % py2exe_entry['version'])
85 py2exe_source_path = build_dir / ('py2exe-%s' % py2exe_entry['version'])
84
86
85 if not py2exe_source_path.exists():
87 if not py2exe_source_path.exists():
86 extract_zip_to_directory(py2exe_pkg, build_dir)
88 extract_zip_to_directory(py2exe_pkg, build_dir)
87
89
88 if not venv_path.exists():
90 if not venv_path.exists():
89 print('creating virtualenv with dependencies')
91 print('creating virtualenv with dependencies')
90 subprocess.run(
92 subprocess.run(
91 [str(python_exe), str(virtualenv_py), str(venv_path)],
93 [str(python_exe), str(virtualenv_py), str(venv_path)],
92 check=True)
94 check=True)
93
95
94 venv_python = venv_path / 'Scripts' / 'python.exe'
96 venv_python = venv_path / 'Scripts' / 'python.exe'
95 venv_pip = venv_path / 'Scripts' / 'pip.exe'
97 venv_pip = venv_path / 'Scripts' / 'pip.exe'
96
98
97 subprocess.run([str(venv_pip), 'install', '-r', str(venv_requirements_txt)],
99 subprocess.run([str(venv_pip), 'install', '-r', str(venv_requirements_txt)],
98 check=True)
100 check=True)
99
101
100 # Force distutils to use VC++ settings from environment, which was
102 # Force distutils to use VC++ settings from environment, which was
101 # validated above.
103 # validated above.
102 env = dict(os.environ)
104 env = dict(os.environ)
103 env['DISTUTILS_USE_SDK'] = '1'
105 env['DISTUTILS_USE_SDK'] = '1'
104 env['MSSdk'] = '1'
106 env['MSSdk'] = '1'
105
107
108 if extra_packages:
109 env['HG_PY2EXE_EXTRA_PACKAGES'] = ' '.join(sorted(extra_packages))
110 if extra_excludes:
111 env['HG_PY2EXE_EXTRA_EXCLUDES'] = ' '.join(sorted(extra_excludes))
112 if extra_dll_excludes:
113 env['HG_PY2EXE_EXTRA_DLL_EXCLUDES'] = ' '.join(
114 sorted(extra_dll_excludes))
115
106 py2exe_py_path = venv_path / 'Lib' / 'site-packages' / 'py2exe'
116 py2exe_py_path = venv_path / 'Lib' / 'site-packages' / 'py2exe'
107 if not py2exe_py_path.exists():
117 if not py2exe_py_path.exists():
108 print('building py2exe')
118 print('building py2exe')
109 subprocess.run([str(venv_python), 'setup.py', 'install'],
119 subprocess.run([str(venv_python), 'setup.py', 'install'],
110 cwd=py2exe_source_path,
120 cwd=py2exe_source_path,
111 env=env,
121 env=env,
112 check=True)
122 check=True)
113
123
114 # Register location of msgfmt and other binaries.
124 # Register location of msgfmt and other binaries.
115 env['PATH'] = '%s%s%s' % (
125 env['PATH'] = '%s%s%s' % (
116 env['PATH'], os.pathsep, str(gettext_root / 'bin'))
126 env['PATH'], os.pathsep, str(gettext_root / 'bin'))
117
127
118 print('building Mercurial')
128 print('building Mercurial')
119 subprocess.run(
129 subprocess.run(
120 [str(venv_python), 'setup.py',
130 [str(venv_python), 'setup.py',
121 'py2exe', '-b', '3' if vc_x64 else '2',
131 'py2exe', '-b', '3' if vc_x64 else '2',
122 'build_doc', '--html'],
132 'build_doc', '--html'],
123 cwd=str(source_dir),
133 cwd=str(source_dir),
124 env=env,
134 env=env,
125 check=True)
135 check=True)
@@ -1,1383 +1,1375 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 common_depends = ['mercurial/bitmanipulation.h',
978 common_depends = ['mercurial/bitmanipulation.h',
979 'mercurial/compat.h',
979 'mercurial/compat.h',
980 'mercurial/cext/util.h']
980 'mercurial/cext/util.h']
981 common_include_dirs = ['mercurial']
981 common_include_dirs = ['mercurial']
982
982
983 osutil_cflags = []
983 osutil_cflags = []
984 osutil_ldflags = []
984 osutil_ldflags = []
985
985
986 # platform specific macros
986 # platform specific macros
987 for plat, func in [('bsd', 'setproctitle')]:
987 for plat, func in [('bsd', 'setproctitle')]:
988 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
988 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
989 osutil_cflags.append('-DHAVE_%s' % func.upper())
989 osutil_cflags.append('-DHAVE_%s' % func.upper())
990
990
991 for plat, macro, code in [
991 for plat, macro, code in [
992 ('bsd|darwin', 'BSD_STATFS', '''
992 ('bsd|darwin', 'BSD_STATFS', '''
993 #include <sys/param.h>
993 #include <sys/param.h>
994 #include <sys/mount.h>
994 #include <sys/mount.h>
995 int main() { struct statfs s; return sizeof(s.f_fstypename); }
995 int main() { struct statfs s; return sizeof(s.f_fstypename); }
996 '''),
996 '''),
997 ('linux', 'LINUX_STATFS', '''
997 ('linux', 'LINUX_STATFS', '''
998 #include <linux/magic.h>
998 #include <linux/magic.h>
999 #include <sys/vfs.h>
999 #include <sys/vfs.h>
1000 int main() { struct statfs s; return sizeof(s.f_type); }
1000 int main() { struct statfs s; return sizeof(s.f_type); }
1001 '''),
1001 '''),
1002 ]:
1002 ]:
1003 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
1003 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
1004 osutil_cflags.append('-DHAVE_%s' % macro)
1004 osutil_cflags.append('-DHAVE_%s' % macro)
1005
1005
1006 if sys.platform == 'darwin':
1006 if sys.platform == 'darwin':
1007 osutil_ldflags += ['-framework', 'ApplicationServices']
1007 osutil_ldflags += ['-framework', 'ApplicationServices']
1008
1008
1009 xdiff_srcs = [
1009 xdiff_srcs = [
1010 'mercurial/thirdparty/xdiff/xdiffi.c',
1010 'mercurial/thirdparty/xdiff/xdiffi.c',
1011 'mercurial/thirdparty/xdiff/xprepare.c',
1011 'mercurial/thirdparty/xdiff/xprepare.c',
1012 'mercurial/thirdparty/xdiff/xutils.c',
1012 'mercurial/thirdparty/xdiff/xutils.c',
1013 ]
1013 ]
1014
1014
1015 xdiff_headers = [
1015 xdiff_headers = [
1016 'mercurial/thirdparty/xdiff/xdiff.h',
1016 'mercurial/thirdparty/xdiff/xdiff.h',
1017 'mercurial/thirdparty/xdiff/xdiffi.h',
1017 'mercurial/thirdparty/xdiff/xdiffi.h',
1018 'mercurial/thirdparty/xdiff/xinclude.h',
1018 'mercurial/thirdparty/xdiff/xinclude.h',
1019 'mercurial/thirdparty/xdiff/xmacros.h',
1019 'mercurial/thirdparty/xdiff/xmacros.h',
1020 'mercurial/thirdparty/xdiff/xprepare.h',
1020 'mercurial/thirdparty/xdiff/xprepare.h',
1021 'mercurial/thirdparty/xdiff/xtypes.h',
1021 'mercurial/thirdparty/xdiff/xtypes.h',
1022 'mercurial/thirdparty/xdiff/xutils.h',
1022 'mercurial/thirdparty/xdiff/xutils.h',
1023 ]
1023 ]
1024
1024
1025 class RustCompilationError(CCompilerError):
1025 class RustCompilationError(CCompilerError):
1026 """Exception class for Rust compilation errors."""
1026 """Exception class for Rust compilation errors."""
1027
1027
1028 class RustExtension(Extension):
1028 class RustExtension(Extension):
1029 """Base classes for concrete Rust Extension classes.
1029 """Base classes for concrete Rust Extension classes.
1030 """
1030 """
1031
1031
1032 rusttargetdir = os.path.join('rust', 'target', 'release')
1032 rusttargetdir = os.path.join('rust', 'target', 'release')
1033
1033
1034 def __init__(self, mpath, sources, rustlibname, subcrate,
1034 def __init__(self, mpath, sources, rustlibname, subcrate,
1035 py3_features=None, **kw):
1035 py3_features=None, **kw):
1036 Extension.__init__(self, mpath, sources, **kw)
1036 Extension.__init__(self, mpath, sources, **kw)
1037 if hgrustext is None:
1037 if hgrustext is None:
1038 return
1038 return
1039 srcdir = self.rustsrcdir = os.path.join('rust', subcrate)
1039 srcdir = self.rustsrcdir = os.path.join('rust', subcrate)
1040 self.py3_features = py3_features
1040 self.py3_features = py3_features
1041
1041
1042 # adding Rust source and control files to depends so that the extension
1042 # adding Rust source and control files to depends so that the extension
1043 # gets rebuilt if they've changed
1043 # gets rebuilt if they've changed
1044 self.depends.append(os.path.join(srcdir, 'Cargo.toml'))
1044 self.depends.append(os.path.join(srcdir, 'Cargo.toml'))
1045 cargo_lock = os.path.join(srcdir, 'Cargo.lock')
1045 cargo_lock = os.path.join(srcdir, 'Cargo.lock')
1046 if os.path.exists(cargo_lock):
1046 if os.path.exists(cargo_lock):
1047 self.depends.append(cargo_lock)
1047 self.depends.append(cargo_lock)
1048 for dirpath, subdir, fnames in os.walk(os.path.join(srcdir, 'src')):
1048 for dirpath, subdir, fnames in os.walk(os.path.join(srcdir, 'src')):
1049 self.depends.extend(os.path.join(dirpath, fname)
1049 self.depends.extend(os.path.join(dirpath, fname)
1050 for fname in fnames
1050 for fname in fnames
1051 if os.path.splitext(fname)[1] == '.rs')
1051 if os.path.splitext(fname)[1] == '.rs')
1052
1052
1053 def rustbuild(self):
1053 def rustbuild(self):
1054 if hgrustext is None:
1054 if hgrustext is None:
1055 return
1055 return
1056 env = os.environ.copy()
1056 env = os.environ.copy()
1057 if 'HGTEST_RESTOREENV' in env:
1057 if 'HGTEST_RESTOREENV' in env:
1058 # Mercurial tests change HOME to a temporary directory,
1058 # Mercurial tests change HOME to a temporary directory,
1059 # but, if installed with rustup, the Rust toolchain needs
1059 # but, if installed with rustup, the Rust toolchain needs
1060 # HOME to be correct (otherwise the 'no default toolchain'
1060 # HOME to be correct (otherwise the 'no default toolchain'
1061 # error message is issued and the build fails).
1061 # error message is issued and the build fails).
1062 # This happens currently with test-hghave.t, which does
1062 # This happens currently with test-hghave.t, which does
1063 # invoke this build.
1063 # invoke this build.
1064
1064
1065 # Unix only fix (os.path.expanduser not really reliable if
1065 # Unix only fix (os.path.expanduser not really reliable if
1066 # HOME is shadowed like this)
1066 # HOME is shadowed like this)
1067 import pwd
1067 import pwd
1068 env['HOME'] = pwd.getpwuid(os.getuid()).pw_dir
1068 env['HOME'] = pwd.getpwuid(os.getuid()).pw_dir
1069
1069
1070 cargocmd = ['cargo', 'build', '-vv', '--release']
1070 cargocmd = ['cargo', 'build', '-vv', '--release']
1071 if sys.version_info[0] == 3 and self.py3_features is not None:
1071 if sys.version_info[0] == 3 and self.py3_features is not None:
1072 cargocmd.extend(('--features', self.py3_features,
1072 cargocmd.extend(('--features', self.py3_features,
1073 '--no-default-features'))
1073 '--no-default-features'))
1074 try:
1074 try:
1075 subprocess.check_call(cargocmd, env=env, cwd=self.rustsrcdir)
1075 subprocess.check_call(cargocmd, env=env, cwd=self.rustsrcdir)
1076 except OSError as exc:
1076 except OSError as exc:
1077 if exc.errno == errno.ENOENT:
1077 if exc.errno == errno.ENOENT:
1078 raise RustCompilationError("Cargo not found")
1078 raise RustCompilationError("Cargo not found")
1079 elif exc.errno == errno.EACCES:
1079 elif exc.errno == errno.EACCES:
1080 raise RustCompilationError(
1080 raise RustCompilationError(
1081 "Cargo found, but permisssion to execute it is denied")
1081 "Cargo found, but permisssion to execute it is denied")
1082 else:
1082 else:
1083 raise
1083 raise
1084 except subprocess.CalledProcessError:
1084 except subprocess.CalledProcessError:
1085 raise RustCompilationError(
1085 raise RustCompilationError(
1086 "Cargo failed. Working directory: %r, "
1086 "Cargo failed. Working directory: %r, "
1087 "command: %r, environment: %r" % (self.rustsrcdir, cmd, env))
1087 "command: %r, environment: %r" % (self.rustsrcdir, cmd, env))
1088
1088
1089 class RustEnhancedExtension(RustExtension):
1089 class RustEnhancedExtension(RustExtension):
1090 """A C Extension, conditionally enhanced with Rust code.
1090 """A C Extension, conditionally enhanced with Rust code.
1091
1091
1092 If the HGRUSTEXT environment variable is set to something else
1092 If the HGRUSTEXT environment variable is set to something else
1093 than 'cpython', the Rust sources get compiled and linked within the
1093 than 'cpython', the Rust sources get compiled and linked within the
1094 C target shared library object.
1094 C target shared library object.
1095 """
1095 """
1096
1096
1097 def __init__(self, mpath, sources, rustlibname, subcrate, **kw):
1097 def __init__(self, mpath, sources, rustlibname, subcrate, **kw):
1098 RustExtension.__init__(self, mpath, sources, rustlibname, subcrate,
1098 RustExtension.__init__(self, mpath, sources, rustlibname, subcrate,
1099 **kw)
1099 **kw)
1100 if hgrustext != 'direct-ffi':
1100 if hgrustext != 'direct-ffi':
1101 return
1101 return
1102 self.extra_compile_args.append('-DWITH_RUST')
1102 self.extra_compile_args.append('-DWITH_RUST')
1103 self.libraries.append(rustlibname)
1103 self.libraries.append(rustlibname)
1104 self.library_dirs.append(self.rusttargetdir)
1104 self.library_dirs.append(self.rusttargetdir)
1105
1105
1106 class RustStandaloneExtension(RustExtension):
1106 class RustStandaloneExtension(RustExtension):
1107
1107
1108 def __init__(self, pydottedname, rustcrate, dylibname, **kw):
1108 def __init__(self, pydottedname, rustcrate, dylibname, **kw):
1109 RustExtension.__init__(self, pydottedname, [], dylibname, rustcrate,
1109 RustExtension.__init__(self, pydottedname, [], dylibname, rustcrate,
1110 **kw)
1110 **kw)
1111 self.dylibname = dylibname
1111 self.dylibname = dylibname
1112
1112
1113 def build(self, target_dir):
1113 def build(self, target_dir):
1114 self.rustbuild()
1114 self.rustbuild()
1115 target = [target_dir]
1115 target = [target_dir]
1116 target.extend(self.name.split('.'))
1116 target.extend(self.name.split('.'))
1117 ext = '.so' # TODO Unix only
1117 ext = '.so' # TODO Unix only
1118 target[-1] += ext
1118 target[-1] += ext
1119 shutil.copy2(os.path.join(self.rusttargetdir, self.dylibname + ext),
1119 shutil.copy2(os.path.join(self.rusttargetdir, self.dylibname + ext),
1120 os.path.join(*target))
1120 os.path.join(*target))
1121
1121
1122
1122
1123 extmodules = [
1123 extmodules = [
1124 Extension('mercurial.cext.base85', ['mercurial/cext/base85.c'],
1124 Extension('mercurial.cext.base85', ['mercurial/cext/base85.c'],
1125 include_dirs=common_include_dirs,
1125 include_dirs=common_include_dirs,
1126 depends=common_depends),
1126 depends=common_depends),
1127 Extension('mercurial.cext.bdiff', ['mercurial/bdiff.c',
1127 Extension('mercurial.cext.bdiff', ['mercurial/bdiff.c',
1128 'mercurial/cext/bdiff.c'] + xdiff_srcs,
1128 'mercurial/cext/bdiff.c'] + xdiff_srcs,
1129 include_dirs=common_include_dirs,
1129 include_dirs=common_include_dirs,
1130 depends=common_depends + ['mercurial/bdiff.h'] + xdiff_headers),
1130 depends=common_depends + ['mercurial/bdiff.h'] + xdiff_headers),
1131 Extension('mercurial.cext.mpatch', ['mercurial/mpatch.c',
1131 Extension('mercurial.cext.mpatch', ['mercurial/mpatch.c',
1132 'mercurial/cext/mpatch.c'],
1132 'mercurial/cext/mpatch.c'],
1133 include_dirs=common_include_dirs,
1133 include_dirs=common_include_dirs,
1134 depends=common_depends),
1134 depends=common_depends),
1135 RustEnhancedExtension(
1135 RustEnhancedExtension(
1136 'mercurial.cext.parsers', ['mercurial/cext/charencode.c',
1136 'mercurial.cext.parsers', ['mercurial/cext/charencode.c',
1137 'mercurial/cext/dirs.c',
1137 'mercurial/cext/dirs.c',
1138 'mercurial/cext/manifest.c',
1138 'mercurial/cext/manifest.c',
1139 'mercurial/cext/parsers.c',
1139 'mercurial/cext/parsers.c',
1140 'mercurial/cext/pathencode.c',
1140 'mercurial/cext/pathencode.c',
1141 'mercurial/cext/revlog.c'],
1141 'mercurial/cext/revlog.c'],
1142 'hgdirectffi',
1142 'hgdirectffi',
1143 'hg-direct-ffi',
1143 'hg-direct-ffi',
1144 include_dirs=common_include_dirs,
1144 include_dirs=common_include_dirs,
1145 depends=common_depends + ['mercurial/cext/charencode.h',
1145 depends=common_depends + ['mercurial/cext/charencode.h',
1146 'mercurial/cext/revlog.h',
1146 'mercurial/cext/revlog.h',
1147 'rust/hg-core/src/ancestors.rs',
1147 'rust/hg-core/src/ancestors.rs',
1148 'rust/hg-core/src/lib.rs']),
1148 'rust/hg-core/src/lib.rs']),
1149 Extension('mercurial.cext.osutil', ['mercurial/cext/osutil.c'],
1149 Extension('mercurial.cext.osutil', ['mercurial/cext/osutil.c'],
1150 include_dirs=common_include_dirs,
1150 include_dirs=common_include_dirs,
1151 extra_compile_args=osutil_cflags,
1151 extra_compile_args=osutil_cflags,
1152 extra_link_args=osutil_ldflags,
1152 extra_link_args=osutil_ldflags,
1153 depends=common_depends),
1153 depends=common_depends),
1154 Extension(
1154 Extension(
1155 'mercurial.thirdparty.zope.interface._zope_interface_coptimizations', [
1155 'mercurial.thirdparty.zope.interface._zope_interface_coptimizations', [
1156 'mercurial/thirdparty/zope/interface/_zope_interface_coptimizations.c',
1156 'mercurial/thirdparty/zope/interface/_zope_interface_coptimizations.c',
1157 ]),
1157 ]),
1158 Extension('hgext.fsmonitor.pywatchman.bser',
1158 Extension('hgext.fsmonitor.pywatchman.bser',
1159 ['hgext/fsmonitor/pywatchman/bser.c']),
1159 ['hgext/fsmonitor/pywatchman/bser.c']),
1160 ]
1160 ]
1161
1161
1162 if hgrustext == 'cpython':
1162 if hgrustext == 'cpython':
1163 extmodules.append(
1163 extmodules.append(
1164 RustStandaloneExtension('mercurial.rustext', 'hg-cpython', 'librusthg',
1164 RustStandaloneExtension('mercurial.rustext', 'hg-cpython', 'librusthg',
1165 py3_features='python3')
1165 py3_features='python3')
1166 )
1166 )
1167
1167
1168
1168
1169 sys.path.insert(0, 'contrib/python-zstandard')
1169 sys.path.insert(0, 'contrib/python-zstandard')
1170 import setup_zstd
1170 import setup_zstd
1171 extmodules.append(setup_zstd.get_c_extension(
1171 extmodules.append(setup_zstd.get_c_extension(
1172 name='mercurial.zstd',
1172 name='mercurial.zstd',
1173 root=os.path.abspath(os.path.dirname(__file__))))
1173 root=os.path.abspath(os.path.dirname(__file__))))
1174
1174
1175 try:
1175 try:
1176 from distutils import cygwinccompiler
1176 from distutils import cygwinccompiler
1177
1177
1178 # the -mno-cygwin option has been deprecated for years
1178 # the -mno-cygwin option has been deprecated for years
1179 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
1179 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
1180
1180
1181 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
1181 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
1182 def __init__(self, *args, **kwargs):
1182 def __init__(self, *args, **kwargs):
1183 mingw32compilerclass.__init__(self, *args, **kwargs)
1183 mingw32compilerclass.__init__(self, *args, **kwargs)
1184 for i in 'compiler compiler_so linker_exe linker_so'.split():
1184 for i in 'compiler compiler_so linker_exe linker_so'.split():
1185 try:
1185 try:
1186 getattr(self, i).remove('-mno-cygwin')
1186 getattr(self, i).remove('-mno-cygwin')
1187 except ValueError:
1187 except ValueError:
1188 pass
1188 pass
1189
1189
1190 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
1190 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
1191 except ImportError:
1191 except ImportError:
1192 # the cygwinccompiler package is not available on some Python
1192 # the cygwinccompiler package is not available on some Python
1193 # distributions like the ones from the optware project for Synology
1193 # distributions like the ones from the optware project for Synology
1194 # DiskStation boxes
1194 # DiskStation boxes
1195 class HackedMingw32CCompiler(object):
1195 class HackedMingw32CCompiler(object):
1196 pass
1196 pass
1197
1197
1198 if os.name == 'nt':
1198 if os.name == 'nt':
1199 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
1199 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
1200 # extra_link_args to distutils.extensions.Extension() doesn't have any
1200 # extra_link_args to distutils.extensions.Extension() doesn't have any
1201 # effect.
1201 # effect.
1202 from distutils import msvccompiler
1202 from distutils import msvccompiler
1203
1203
1204 msvccompilerclass = msvccompiler.MSVCCompiler
1204 msvccompilerclass = msvccompiler.MSVCCompiler
1205
1205
1206 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
1206 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
1207 def initialize(self):
1207 def initialize(self):
1208 msvccompilerclass.initialize(self)
1208 msvccompilerclass.initialize(self)
1209 # "warning LNK4197: export 'func' specified multiple times"
1209 # "warning LNK4197: export 'func' specified multiple times"
1210 self.ldflags_shared.append('/ignore:4197')
1210 self.ldflags_shared.append('/ignore:4197')
1211 self.ldflags_shared_debug.append('/ignore:4197')
1211 self.ldflags_shared_debug.append('/ignore:4197')
1212
1212
1213 msvccompiler.MSVCCompiler = HackedMSVCCompiler
1213 msvccompiler.MSVCCompiler = HackedMSVCCompiler
1214
1214
1215 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
1215 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
1216 'help/*.txt',
1216 'help/*.txt',
1217 'help/internals/*.txt',
1217 'help/internals/*.txt',
1218 'default.d/*.rc',
1218 'default.d/*.rc',
1219 'dummycert.pem']}
1219 'dummycert.pem']}
1220
1220
1221 def ordinarypath(p):
1221 def ordinarypath(p):
1222 return p and p[0] != '.' and p[-1] != '~'
1222 return p and p[0] != '.' and p[-1] != '~'
1223
1223
1224 for root in ('templates',):
1224 for root in ('templates',):
1225 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
1225 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
1226 curdir = curdir.split(os.sep, 1)[1]
1226 curdir = curdir.split(os.sep, 1)[1]
1227 dirs[:] = filter(ordinarypath, dirs)
1227 dirs[:] = filter(ordinarypath, dirs)
1228 for f in filter(ordinarypath, files):
1228 for f in filter(ordinarypath, files):
1229 f = os.path.join(curdir, f)
1229 f = os.path.join(curdir, f)
1230 packagedata['mercurial'].append(f)
1230 packagedata['mercurial'].append(f)
1231
1231
1232 datafiles = []
1232 datafiles = []
1233
1233
1234 # distutils expects version to be str/unicode. Converting it to
1234 # distutils expects version to be str/unicode. Converting it to
1235 # unicode on Python 2 still works because it won't contain any
1235 # unicode on Python 2 still works because it won't contain any
1236 # non-ascii bytes and will be implicitly converted back to bytes
1236 # non-ascii bytes and will be implicitly converted back to bytes
1237 # when operated on.
1237 # when operated on.
1238 assert isinstance(version, bytes)
1238 assert isinstance(version, bytes)
1239 setupversion = version.decode('ascii')
1239 setupversion = version.decode('ascii')
1240
1240
1241 extra = {}
1241 extra = {}
1242
1242
1243 py2exepackages = [
1243 py2exepackages = [
1244 'hgdemandimport',
1244 'hgdemandimport',
1245 'hgext',
1245 'hgext',
1246 'email',
1246 'email',
1247 # implicitly imported per module policy
1247 # implicitly imported per module policy
1248 # (cffi wouldn't be used as a frozen exe)
1248 # (cffi wouldn't be used as a frozen exe)
1249 'mercurial.cext',
1249 'mercurial.cext',
1250 #'mercurial.cffi',
1250 #'mercurial.cffi',
1251 'mercurial.pure',
1251 'mercurial.pure',
1252 ]
1252 ]
1253
1253
1254 py2exeexcludes = []
1255 py2exedllexcludes = []
1256
1254 if issetuptools:
1257 if issetuptools:
1255 extra['python_requires'] = supportedpy
1258 extra['python_requires'] = supportedpy
1256
1259
1257 if py2exeloaded:
1260 if py2exeloaded:
1258 extra['console'] = [
1261 extra['console'] = [
1259 {'script':'hg',
1262 {'script':'hg',
1260 'copyright':'Copyright (C) 2005-2019 Matt Mackall and others',
1263 'copyright':'Copyright (C) 2005-2019 Matt Mackall and others',
1261 'product_version':version}]
1264 'product_version':version}]
1262 # sub command of 'build' because 'py2exe' does not handle sub_commands
1265 # sub command of 'build' because 'py2exe' does not handle sub_commands
1263 build.sub_commands.insert(0, ('build_hgextindex', None))
1266 build.sub_commands.insert(0, ('build_hgextindex', None))
1264 # put dlls in sub directory so that they won't pollute PATH
1267 # put dlls in sub directory so that they won't pollute PATH
1265 extra['zipfile'] = 'lib/library.zip'
1268 extra['zipfile'] = 'lib/library.zip'
1266
1269
1267 try:
1270 # We allow some configuration to be supplemented via environment
1268 import dulwich
1271 # variables. This is better than setup.cfg files because it allows
1269 dulwich.__version__
1272 # supplementing configs instead of replacing them.
1270 py2exepackages.append('dulwich')
1273 extrapackages = os.environ.get('HG_PY2EXE_EXTRA_PACKAGES')
1271 except ImportError:
1274 if extrapackages:
1272 pass
1275 py2exepackages.extend(extrapackages.split(' '))
1273
1274 try:
1275 import keyring
1276 keyring.util
1277 py2exepackages.append('keyring')
1278 except ImportError:
1279 pass
1280
1276
1281 try:
1277 excludes = os.environ.get('HG_PY2EXE_EXTRA_EXCLUDES')
1282 import pygments
1278 if excludes:
1283 pygments.__version__
1279 py2exeexcludes.extend(excludes.split(' '))
1284 py2exepackages.append('pygments')
1285 except ImportError:
1286 pass
1287
1280
1288 try:
1281 dllexcludes = os.environ.get('HG_PY2EXE_EXTRA_DLL_EXCLUDES')
1289 import win32ctypes
1282 if dllexcludes:
1290 win32ctypes.__version__
1283 py2exedllexcludes.extend(dllexcludes.split(' '))
1291 py2exepackages.append('win32ctypes')
1292 except ImportError:
1293 pass
1294
1284
1295 if os.name == 'nt':
1285 if os.name == 'nt':
1296 # Windows binary file versions for exe/dll files must have the
1286 # Windows binary file versions for exe/dll files must have the
1297 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
1287 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
1298 setupversion = setupversion.split(r'+', 1)[0]
1288 setupversion = setupversion.split(r'+', 1)[0]
1299
1289
1300 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
1290 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
1301 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
1291 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
1302 if version:
1292 if version:
1303 version = version[0]
1293 version = version[0]
1304 if sys.version_info[0] == 3:
1294 if sys.version_info[0] == 3:
1305 version = version.decode('utf-8')
1295 version = version.decode('utf-8')
1306 xcode4 = (version.startswith('Xcode') and
1296 xcode4 = (version.startswith('Xcode') and
1307 StrictVersion(version.split()[1]) >= StrictVersion('4.0'))
1297 StrictVersion(version.split()[1]) >= StrictVersion('4.0'))
1308 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
1298 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
1309 else:
1299 else:
1310 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
1300 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
1311 # installed, but instead with only command-line tools. Assume
1301 # installed, but instead with only command-line tools. Assume
1312 # that only happens on >= Lion, thus no PPC support.
1302 # that only happens on >= Lion, thus no PPC support.
1313 xcode4 = True
1303 xcode4 = True
1314 xcode51 = False
1304 xcode51 = False
1315
1305
1316 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
1306 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
1317 # distutils.sysconfig
1307 # distutils.sysconfig
1318 if xcode4:
1308 if xcode4:
1319 os.environ['ARCHFLAGS'] = ''
1309 os.environ['ARCHFLAGS'] = ''
1320
1310
1321 # XCode 5.1 changes clang such that it now fails to compile if the
1311 # XCode 5.1 changes clang such that it now fails to compile if the
1322 # -mno-fused-madd flag is passed, but the version of Python shipped with
1312 # -mno-fused-madd flag is passed, but the version of Python shipped with
1323 # OS X 10.9 Mavericks includes this flag. This causes problems in all
1313 # OS X 10.9 Mavericks includes this flag. This causes problems in all
1324 # C extension modules, and a bug has been filed upstream at
1314 # C extension modules, and a bug has been filed upstream at
1325 # http://bugs.python.org/issue21244. We also need to patch this here
1315 # http://bugs.python.org/issue21244. We also need to patch this here
1326 # so Mercurial can continue to compile in the meantime.
1316 # so Mercurial can continue to compile in the meantime.
1327 if xcode51:
1317 if xcode51:
1328 cflags = get_config_var('CFLAGS')
1318 cflags = get_config_var('CFLAGS')
1329 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
1319 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
1330 os.environ['CFLAGS'] = (
1320 os.environ['CFLAGS'] = (
1331 os.environ.get('CFLAGS', '') + ' -Qunused-arguments')
1321 os.environ.get('CFLAGS', '') + ' -Qunused-arguments')
1332
1322
1333 setup(name='mercurial',
1323 setup(name='mercurial',
1334 version=setupversion,
1324 version=setupversion,
1335 author='Matt Mackall and many others',
1325 author='Matt Mackall and many others',
1336 author_email='mercurial@mercurial-scm.org',
1326 author_email='mercurial@mercurial-scm.org',
1337 url='https://mercurial-scm.org/',
1327 url='https://mercurial-scm.org/',
1338 download_url='https://mercurial-scm.org/release/',
1328 download_url='https://mercurial-scm.org/release/',
1339 description=('Fast scalable distributed SCM (revision control, version '
1329 description=('Fast scalable distributed SCM (revision control, version '
1340 'control) system'),
1330 'control) system'),
1341 long_description=('Mercurial is a distributed SCM tool written in Python.'
1331 long_description=('Mercurial is a distributed SCM tool written in Python.'
1342 ' It is used by a number of large projects that require'
1332 ' It is used by a number of large projects that require'
1343 ' fast, reliable distributed revision control, such as '
1333 ' fast, reliable distributed revision control, such as '
1344 'Mozilla.'),
1334 'Mozilla.'),
1345 license='GNU GPLv2 or any later version',
1335 license='GNU GPLv2 or any later version',
1346 classifiers=[
1336 classifiers=[
1347 'Development Status :: 6 - Mature',
1337 'Development Status :: 6 - Mature',
1348 'Environment :: Console',
1338 'Environment :: Console',
1349 'Intended Audience :: Developers',
1339 'Intended Audience :: Developers',
1350 'Intended Audience :: System Administrators',
1340 'Intended Audience :: System Administrators',
1351 'License :: OSI Approved :: GNU General Public License (GPL)',
1341 'License :: OSI Approved :: GNU General Public License (GPL)',
1352 'Natural Language :: Danish',
1342 'Natural Language :: Danish',
1353 'Natural Language :: English',
1343 'Natural Language :: English',
1354 'Natural Language :: German',
1344 'Natural Language :: German',
1355 'Natural Language :: Italian',
1345 'Natural Language :: Italian',
1356 'Natural Language :: Japanese',
1346 'Natural Language :: Japanese',
1357 'Natural Language :: Portuguese (Brazilian)',
1347 'Natural Language :: Portuguese (Brazilian)',
1358 'Operating System :: Microsoft :: Windows',
1348 'Operating System :: Microsoft :: Windows',
1359 'Operating System :: OS Independent',
1349 'Operating System :: OS Independent',
1360 'Operating System :: POSIX',
1350 'Operating System :: POSIX',
1361 'Programming Language :: C',
1351 'Programming Language :: C',
1362 'Programming Language :: Python',
1352 'Programming Language :: Python',
1363 'Topic :: Software Development :: Version Control',
1353 'Topic :: Software Development :: Version Control',
1364 ],
1354 ],
1365 scripts=scripts,
1355 scripts=scripts,
1366 packages=packages,
1356 packages=packages,
1367 ext_modules=extmodules,
1357 ext_modules=extmodules,
1368 data_files=datafiles,
1358 data_files=datafiles,
1369 package_data=packagedata,
1359 package_data=packagedata,
1370 cmdclass=cmdclass,
1360 cmdclass=cmdclass,
1371 distclass=hgdist,
1361 distclass=hgdist,
1372 options={
1362 options={
1373 'py2exe': {
1363 'py2exe': {
1364 'dll_excludes': py2exedllexcludes,
1365 'excludes': py2exeexcludes,
1374 'packages': py2exepackages,
1366 'packages': py2exepackages,
1375 },
1367 },
1376 'bdist_mpkg': {
1368 'bdist_mpkg': {
1377 'zipdist': False,
1369 'zipdist': False,
1378 'license': 'COPYING',
1370 'license': 'COPYING',
1379 'readme': 'contrib/packaging/macosx/Readme.html',
1371 'readme': 'contrib/packaging/macosx/Readme.html',
1380 'welcome': 'contrib/packaging/macosx/Welcome.html',
1372 'welcome': 'contrib/packaging/macosx/Welcome.html',
1381 },
1373 },
1382 },
1374 },
1383 **extra)
1375 **extra)
General Comments 0
You need to be logged in to leave comments. Login now