##// END OF EJS Templates
setup: further improve the error path for version retrieval...
marmoute -
r50988:010a1e73 stable
parent child Browse files
Show More
@@ -1,300 +1,300 b''
1 1 # If you want to change PREFIX, do not just edit it below. The changed
2 2 # value wont get passed on to recursive make calls. You should instead
3 3 # override the variable on the command like:
4 4 #
5 5 # % make PREFIX=/opt/ install
6 6
7 7 export PREFIX=/usr/local
8 8
9 9 # Default to Python 3.
10 10 #
11 11 # Windows ships Python 3 as `python.exe`, which may not be on PATH. py.exe is.
12 12 ifeq ($(OS),Windows_NT)
13 13 PYTHON?=py -3
14 14 else
15 15 PYTHON?=python3
16 16 endif
17 17
18 18 PYOXIDIZER?=pyoxidizer
19 19
20 20 $(eval HGROOT := $(shell pwd))
21 21 HGPYTHONS ?= $(HGROOT)/build/pythons
22 22 PURE=
23 23 PYFILESCMD=find mercurial hgext doc -name '*.py'
24 24 PYFILES:=$(shell $(PYFILESCMD))
25 25 DOCFILES=mercurial/helptext/*.txt
26 26 export LANGUAGE=C
27 27 export LC_ALL=C
28 28 TESTFLAGS ?= $(shell echo $$HGTESTFLAGS)
29 29 OSXVERSIONFLAGS ?= $(shell echo $$OSXVERSIONFLAGS)
30 30 CARGO = cargo
31 31
32 32 # Set this to e.g. "mingw32" to use a non-default compiler.
33 33 COMPILER=
34 34
35 35 COMPILERFLAG_tmp_ =
36 36 COMPILERFLAG_tmp_${COMPILER} ?= -c $(COMPILER)
37 37 COMPILERFLAG=${COMPILERFLAG_tmp_${COMPILER}}
38 38
39 39 help:
40 40 @echo 'Commonly used make targets:'
41 41 @echo ' all - build program and documentation'
42 42 @echo ' install - install program and man pages to $$PREFIX ($(PREFIX))'
43 43 @echo ' install-home - install with setup.py install --home=$$HOME ($(HOME))'
44 44 @echo ' local - build for inplace usage'
45 45 @echo ' tests - run all tests in the automatic test suite'
46 46 @echo ' test-foo - run only specified tests (e.g. test-merge1.t)'
47 47 @echo ' dist - run all tests and create a source tarball in dist/'
48 48 @echo ' clean - remove files created by other targets'
49 49 @echo ' (except installed files or dist source tarball)'
50 50 @echo ' update-pot - update i18n/hg.pot'
51 51 @echo
52 52 @echo 'Example for a system-wide installation under /usr/local:'
53 53 @echo ' make all && su -c "make install" && hg version'
54 54 @echo
55 55 @echo 'Example for a local installation (usable in this directory):'
56 56 @echo ' make local && ./hg version'
57 57
58 58 all: build doc
59 59
60 60 local:
61 $(PYTHON) setup.py $(PURE) \
61 MERCURIAL_SETUP_MAKE_LOCAL=1 $(PYTHON) setup.py $(PURE) \
62 62 build_py -c -d . \
63 63 build_ext $(COMPILERFLAG) -i \
64 64 build_hgexe $(COMPILERFLAG) -i \
65 65 build_mo
66 66 env HGRCPATH= $(PYTHON) hg version
67 67
68 68 build:
69 69 $(PYTHON) setup.py $(PURE) build $(COMPILERFLAG)
70 70
71 71 build-chg:
72 72 make -C contrib/chg
73 73
74 74 build-rhg:
75 75 (cd rust/rhg; cargo build --release)
76 76
77 77 wheel:
78 78 FORCE_SETUPTOOLS=1 $(PYTHON) setup.py $(PURE) bdist_wheel $(COMPILERFLAG)
79 79
80 80 doc:
81 81 $(MAKE) -C doc
82 82
83 83 cleanbutpackages:
84 84 rm -f hg.exe
85 85 -$(PYTHON) setup.py clean --all # ignore errors from this command
86 86 find contrib doc hgext hgext3rd i18n mercurial tests hgdemandimport \
87 87 \( -name '*.py[cdo]' -o -name '*.so' \) -exec rm -f '{}' ';'
88 88 rm -f MANIFEST MANIFEST.in hgext/__index__.py tests/*.err
89 89 rm -f mercurial/__modulepolicy__.py
90 90 if test -d .hg; then rm -f mercurial/__version__.py; fi
91 91 rm -rf build mercurial/locale
92 92 $(MAKE) -C doc clean
93 93 $(MAKE) -C contrib/chg distclean
94 94 rm -rf rust/target
95 95 rm -f mercurial/rustext.so
96 96
97 97 clean: cleanbutpackages
98 98 rm -rf packages
99 99
100 100 install: install-bin install-doc
101 101
102 102 install-bin: build
103 103 $(PYTHON) setup.py $(PURE) install --root="$(DESTDIR)/" --prefix="$(PREFIX)" --force
104 104
105 105 install-chg: build-chg
106 106 make -C contrib/chg install PREFIX="$(PREFIX)"
107 107
108 108 install-doc: doc
109 109 cd doc && $(MAKE) $(MFLAGS) install
110 110
111 111 install-home: install-home-bin install-home-doc
112 112
113 113 install-home-bin: build
114 114 $(PYTHON) setup.py $(PURE) install --home="$(HOME)" --prefix="" --force
115 115
116 116 install-home-doc: doc
117 117 cd doc && $(MAKE) $(MFLAGS) PREFIX="$(HOME)" install
118 118
119 119 install-rhg: build-rhg
120 120 install -m 755 rust/target/release/rhg "$(PREFIX)"/bin/
121 121
122 122 MANIFEST-doc:
123 123 $(MAKE) -C doc MANIFEST
124 124
125 125 MANIFEST.in: MANIFEST-doc
126 126 hg manifest | sed -e 's/^/include /' > MANIFEST.in
127 127 echo include mercurial/__version__.py >> MANIFEST.in
128 128 sed -e 's/^/include /' < doc/MANIFEST >> MANIFEST.in
129 129
130 130 dist: tests dist-notests
131 131
132 132 dist-notests: doc MANIFEST.in
133 133 TAR_OPTIONS="--owner=root --group=root --mode=u+w,go-w,a+rX-s" $(PYTHON) setup.py -q sdist
134 134
135 135 check: tests
136 136
137 137 tests:
138 138 # Run Rust tests if cargo is installed
139 139 if command -v $(CARGO) >/dev/null 2>&1; then \
140 140 $(MAKE) rust-tests; \
141 141 fi
142 142 cd tests && $(PYTHON) run-tests.py $(TESTFLAGS)
143 143
144 144 test-%:
145 145 cd tests && $(PYTHON) run-tests.py $(TESTFLAGS) $@
146 146
147 147 testpy-%:
148 148 @echo Looking for Python $* in $(HGPYTHONS)
149 149 [ -e $(HGPYTHONS)/$*/bin/python ] || ( \
150 150 cd $$(mktemp --directory --tmpdir) && \
151 151 $(MAKE) -f $(HGROOT)/contrib/Makefile.python PYTHONVER=$* PREFIX=$(HGPYTHONS)/$* python )
152 152 cd tests && $(HGPYTHONS)/$*/bin/python run-tests.py $(TESTFLAGS)
153 153
154 154 rust-tests:
155 155 cd $(HGROOT)/rust/hg-cpython \
156 156 && $(CARGO) test --quiet --all --features "$(HG_RUST_FEATURES)"
157 157
158 158 check-code:
159 159 hg manifest | xargs python contrib/check-code.py
160 160
161 161 format-c:
162 162 clang-format --style file -i \
163 163 `hg files 'set:(**.c or **.cc or **.h) and not "listfile:contrib/clang-format-ignorelist"'`
164 164
165 165 update-pot: i18n/hg.pot
166 166
167 167 i18n/hg.pot: $(PYFILES) $(DOCFILES) i18n/posplit i18n/hggettext
168 168 $(PYTHON) i18n/hggettext mercurial/commands.py \
169 169 hgext/*.py hgext/*/__init__.py \
170 170 mercurial/fileset.py mercurial/revset.py \
171 171 mercurial/templatefilters.py \
172 172 mercurial/templatefuncs.py \
173 173 mercurial/templatekw.py \
174 174 mercurial/filemerge.py \
175 175 mercurial/hgweb/webcommands.py \
176 176 mercurial/util.py \
177 177 $(DOCFILES) > i18n/hg.pot.tmp
178 178 # All strings marked for translation in Mercurial contain
179 179 # ASCII characters only. But some files contain string
180 180 # literals like this '\037\213'. xgettext thinks it has to
181 181 # parse them even though they are not marked for translation.
182 182 # Extracting with an explicit encoding of ISO-8859-1 will make
183 183 # xgettext "parse" and ignore them.
184 184 $(PYFILESCMD) | xargs \
185 185 xgettext --package-name "Mercurial" \
186 186 --msgid-bugs-address "<mercurial-devel@mercurial-scm.org>" \
187 187 --copyright-holder "Olivia Mackall <olivia@selenic.com> and others" \
188 188 --from-code ISO-8859-1 --join --sort-by-file --add-comments=i18n: \
189 189 -d hg -p i18n -o hg.pot.tmp
190 190 $(PYTHON) i18n/posplit i18n/hg.pot.tmp
191 191 # The target file is not created before the last step. So it never is in
192 192 # an intermediate state.
193 193 mv -f i18n/hg.pot.tmp i18n/hg.pot
194 194
195 195 %.po: i18n/hg.pot
196 196 # work on a temporary copy for never having a half completed target
197 197 cp $@ $@.tmp
198 198 msgmerge --no-location --update $@.tmp $^
199 199 mv -f $@.tmp $@
200 200
201 201 # Packaging targets
202 202
203 203 packaging_targets := \
204 204 rhel7 \
205 205 rhel8 \
206 206 rhel9 \
207 207 deb \
208 208 docker-rhel7 \
209 209 docker-rhel8 \
210 210 docker-rhel9 \
211 211 docker-debian-bullseye \
212 212 docker-debian-buster \
213 213 docker-debian-stretch \
214 214 docker-fedora \
215 215 docker-ubuntu-xenial \
216 216 docker-ubuntu-xenial-ppa \
217 217 docker-ubuntu-bionic \
218 218 docker-ubuntu-bionic-ppa \
219 219 docker-ubuntu-focal \
220 220 docker-ubuntu-focal-ppa \
221 221 fedora \
222 222 linux-wheels \
223 223 linux-wheels-x86_64 \
224 224 linux-wheels-i686 \
225 225 ppa
226 226
227 227 # Forward packaging targets for convenience.
228 228 $(packaging_targets):
229 229 $(MAKE) -C contrib/packaging $@
230 230
231 231 osx:
232 232 rm -rf build/mercurial
233 233 /usr/bin/python2.7 setup.py install --optimize=1 \
234 234 --root=build/mercurial/ --prefix=/usr/local/ \
235 235 --install-lib=/Library/Python/2.7/site-packages/
236 236 make -C doc all install DESTDIR="$(PWD)/build/mercurial/"
237 237 # Place a bogon .DS_Store file in the target dir so we can be
238 238 # sure it doesn't get included in the final package.
239 239 touch build/mercurial/.DS_Store
240 240 make -C contrib/chg \
241 241 HGPATH=/usr/local/bin/hg \
242 242 PYTHON=/usr/bin/python2.7 \
243 243 DESTDIR=../../build/mercurial \
244 244 PREFIX=/usr/local \
245 245 clean install
246 246 mkdir -p $${OUTPUTDIR:-dist}
247 247 HGVER=$$(python contrib/genosxversion.py $(OSXVERSIONFLAGS) build/mercurial/Library/Python/2.7/site-packages/mercurial/__version__.py) && \
248 248 OSXVER=$$(sw_vers -productVersion | cut -d. -f1,2) && \
249 249 pkgbuild --filter \\.DS_Store --root build/mercurial/ \
250 250 --identifier org.mercurial-scm.mercurial \
251 251 --version "$${HGVER}" \
252 252 build/mercurial.pkg && \
253 253 productbuild --distribution contrib/packaging/macosx/distribution.xml \
254 254 --package-path build/ \
255 255 --version "$${HGVER}" \
256 256 --resources contrib/packaging/macosx/ \
257 257 "$${OUTPUTDIR:-dist/}"/Mercurial-"$${HGVER}"-macosx"$${OSXVER}".pkg
258 258
259 259 pyoxidizer:
260 260 $(PYOXIDIZER) build --path ./rust/hgcli --release
261 261
262 262
263 263 # a temporary target to setup all we need for run-tests.py --pyoxidizer
264 264 # (should go away as the run-tests implementation improves
265 265 pyoxidizer-windows-tests: PYOX_DIR=build/pyoxidizer/x86_64-pc-windows-msvc/release/app
266 266 pyoxidizer-windows-tests: pyoxidizer
267 267 rm -rf $(PYOX_DIR)/templates
268 268 cp -ar $(PYOX_DIR)/lib/mercurial/templates $(PYOX_DIR)/templates
269 269 rm -rf $(PYOX_DIR)/helptext
270 270 cp -ar $(PYOX_DIR)/lib/mercurial/helptext $(PYOX_DIR)/helptext
271 271 rm -rf $(PYOX_DIR)/defaultrc
272 272 cp -ar $(PYOX_DIR)/lib/mercurial/defaultrc $(PYOX_DIR)/defaultrc
273 273 rm -rf $(PYOX_DIR)/contrib
274 274 cp -ar contrib $(PYOX_DIR)/contrib
275 275 rm -rf $(PYOX_DIR)/doc
276 276 cp -ar doc $(PYOX_DIR)/doc
277 277
278 278
279 279 # a temporary target to setup all we need for run-tests.py --pyoxidizer
280 280 # (should go away as the run-tests implementation improves
281 281 pyoxidizer-macos-tests: PYOX_DIR=build/pyoxidizer/x86_64-apple-darwin/release/app
282 282 pyoxidizer-macos-tests: pyoxidizer
283 283 rm -rf $(PYOX_DIR)/templates
284 284 cp -a mercurial/templates $(PYOX_DIR)/templates
285 285 rm -rf $(PYOX_DIR)/helptext
286 286 cp -a mercurial/helptext $(PYOX_DIR)/helptext
287 287 rm -rf $(PYOX_DIR)/defaultrc
288 288 cp -a mercurial/defaultrc $(PYOX_DIR)/defaultrc
289 289 rm -rf $(PYOX_DIR)/contrib
290 290 cp -a contrib $(PYOX_DIR)/contrib
291 291 rm -rf $(PYOX_DIR)/doc
292 292 cp -a doc $(PYOX_DIR)/doc
293 293
294 294
295 295 .PHONY: help all local build doc cleanbutpackages clean install install-bin \
296 296 install-doc install-home install-home-bin install-home-doc \
297 297 dist dist-notests check tests rust-tests check-code format-c \
298 298 update-pot pyoxidizer pyoxidizer-windows-tests pyoxidizer-macos-tests \
299 299 $(packaging_targets) \
300 300 osx
@@ -1,1793 +1,1826 b''
1 1 #
2 2 # This is the mercurial setup script.
3 3 #
4 4 # 'python setup.py install', or
5 5 # 'python setup.py --help' for more options
6 6 import os
7 7
8 8 # Mercurial can't work on 3.6.0 or 3.6.1 due to a bug in % formatting
9 9 # in bytestrings.
10 10 supportedpy = ','.join(
11 11 [
12 12 '>=3.6.2',
13 13 ]
14 14 )
15 15
16 16 import sys, platform
17 17 import sysconfig
18 18
19 19
20 20 def sysstr(s):
21 21 return s.decode('latin-1')
22 22
23 23
24 def eprint(*args, **kwargs):
25 kwargs['file'] = sys.stderr
26 print(*args, **kwargs)
27
28
24 29 import ssl
25 30
26 31 # ssl.HAS_TLSv1* are preferred to check support but they were added in Python
27 32 # 3.7. Prior to CPython commit 6e8cda91d92da72800d891b2fc2073ecbc134d98
28 33 # (backported to the 3.7 branch), ssl.PROTOCOL_TLSv1_1 / ssl.PROTOCOL_TLSv1_2
29 34 # were defined only if compiled against a OpenSSL version with TLS 1.1 / 1.2
30 35 # support. At the mentioned commit, they were unconditionally defined.
31 36 _notset = object()
32 37 has_tlsv1_1 = getattr(ssl, 'HAS_TLSv1_1', _notset)
33 38 if has_tlsv1_1 is _notset:
34 39 has_tlsv1_1 = getattr(ssl, 'PROTOCOL_TLSv1_1', _notset) is not _notset
35 40 has_tlsv1_2 = getattr(ssl, 'HAS_TLSv1_2', _notset)
36 41 if has_tlsv1_2 is _notset:
37 42 has_tlsv1_2 = getattr(ssl, 'PROTOCOL_TLSv1_2', _notset) is not _notset
38 43 if not (has_tlsv1_1 or has_tlsv1_2):
39 44 error = """
40 45 The `ssl` module does not advertise support for TLS 1.1 or TLS 1.2.
41 46 Please make sure that your Python installation was compiled against an OpenSSL
42 47 version enabling these features (likely this requires the OpenSSL version to
43 48 be at least 1.0.1).
44 49 """
45 50 print(error, file=sys.stderr)
46 51 sys.exit(1)
47 52
48 53 DYLIB_SUFFIX = sysconfig.get_config_vars()['EXT_SUFFIX']
49 54
50 55 # Solaris Python packaging brain damage
51 56 try:
52 57 import hashlib
53 58
54 59 sha = hashlib.sha1()
55 60 except ImportError:
56 61 try:
57 62 import sha
58 63
59 64 sha.sha # silence unused import warning
60 65 except ImportError:
61 66 raise SystemExit(
62 67 "Couldn't import standard hashlib (incomplete Python install)."
63 68 )
64 69
65 70 try:
66 71 import zlib
67 72
68 73 zlib.compressobj # silence unused import warning
69 74 except ImportError:
70 75 raise SystemExit(
71 76 "Couldn't import standard zlib (incomplete Python install)."
72 77 )
73 78
74 79 # The base IronPython distribution (as of 2.7.1) doesn't support bz2
75 80 isironpython = False
76 81 try:
77 82 isironpython = (
78 83 platform.python_implementation().lower().find("ironpython") != -1
79 84 )
80 85 except AttributeError:
81 86 pass
82 87
83 88 if isironpython:
84 89 sys.stderr.write("warning: IronPython detected (no bz2 support)\n")
85 90 else:
86 91 try:
87 92 import bz2
88 93
89 94 bz2.BZ2Compressor # silence unused import warning
90 95 except ImportError:
91 96 raise SystemExit(
92 97 "Couldn't import standard bz2 (incomplete Python install)."
93 98 )
94 99
95 100 ispypy = "PyPy" in sys.version
96 101
97 102 import ctypes
98 103 import stat, subprocess, time
99 104 import re
100 105 import shutil
101 106 import tempfile
102 107
103 108 # We have issues with setuptools on some platforms and builders. Until
104 109 # those are resolved, setuptools is opt-in except for platforms where
105 110 # we don't have issues.
106 111 issetuptools = os.name == 'nt' or 'FORCE_SETUPTOOLS' in os.environ
107 112 if issetuptools:
108 113 from setuptools import setup
109 114 else:
110 115 from distutils.core import setup
111 116 from distutils.ccompiler import new_compiler
112 117 from distutils.core import Command, Extension
113 118 from distutils.dist import Distribution
114 119 from distutils.command.build import build
115 120 from distutils.command.build_ext import build_ext
116 121 from distutils.command.build_py import build_py
117 122 from distutils.command.build_scripts import build_scripts
118 123 from distutils.command.install import install
119 124 from distutils.command.install_lib import install_lib
120 125 from distutils.command.install_scripts import install_scripts
121 126 from distutils import log
122 127 from distutils.spawn import spawn, find_executable
123 128 from distutils import file_util
124 129 from distutils.errors import (
125 130 CCompilerError,
126 131 DistutilsError,
127 132 DistutilsExecError,
128 133 )
129 134 from distutils.sysconfig import get_python_inc, get_config_var
130 135 from distutils.version import StrictVersion
131 136
132 137 # Explain to distutils.StrictVersion how our release candidates are versioned
133 138 StrictVersion.version_re = re.compile(r'^(\d+)\.(\d+)(\.(\d+))?-?(rc(\d+))?$')
134 139
135 140
136 141 def write_if_changed(path, content):
137 142 """Write content to a file iff the content hasn't changed."""
138 143 if os.path.exists(path):
139 144 with open(path, 'rb') as fh:
140 145 current = fh.read()
141 146 else:
142 147 current = b''
143 148
144 149 if current != content:
145 150 with open(path, 'wb') as fh:
146 151 fh.write(content)
147 152
148 153
149 154 scripts = ['hg']
150 155 if os.name == 'nt':
151 156 # We remove hg.bat if we are able to build hg.exe.
152 157 scripts.append('contrib/win32/hg.bat')
153 158
154 159
155 160 def cancompile(cc, code):
156 161 tmpdir = tempfile.mkdtemp(prefix='hg-install-')
157 162 devnull = oldstderr = None
158 163 try:
159 164 fname = os.path.join(tmpdir, 'testcomp.c')
160 165 f = open(fname, 'w')
161 166 f.write(code)
162 167 f.close()
163 168 # Redirect stderr to /dev/null to hide any error messages
164 169 # from the compiler.
165 170 # This will have to be changed if we ever have to check
166 171 # for a function on Windows.
167 172 devnull = open('/dev/null', 'w')
168 173 oldstderr = os.dup(sys.stderr.fileno())
169 174 os.dup2(devnull.fileno(), sys.stderr.fileno())
170 175 objects = cc.compile([fname], output_dir=tmpdir)
171 176 cc.link_executable(objects, os.path.join(tmpdir, "a.out"))
172 177 return True
173 178 except Exception:
174 179 return False
175 180 finally:
176 181 if oldstderr is not None:
177 182 os.dup2(oldstderr, sys.stderr.fileno())
178 183 if devnull is not None:
179 184 devnull.close()
180 185 shutil.rmtree(tmpdir)
181 186
182 187
183 188 # simplified version of distutils.ccompiler.CCompiler.has_function
184 189 # that actually removes its temporary files.
185 190 def hasfunction(cc, funcname):
186 191 code = 'int main(void) { %s(); }\n' % funcname
187 192 return cancompile(cc, code)
188 193
189 194
190 195 def hasheader(cc, headername):
191 196 code = '#include <%s>\nint main(void) { return 0; }\n' % headername
192 197 return cancompile(cc, code)
193 198
194 199
195 200 # py2exe needs to be installed to work
196 201 try:
197 202 import py2exe
198 203
199 204 py2exe.patch_distutils()
200 205 py2exeloaded = True
201 206 # import py2exe's patched Distribution class
202 207 from distutils.core import Distribution
203 208 except ImportError:
204 209 py2exeloaded = False
205 210
206 211
207 212 def runcmd(cmd, env, cwd=None):
208 213 p = subprocess.Popen(
209 214 cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, cwd=cwd
210 215 )
211 216 out, err = p.communicate()
212 217 return p.returncode, out, err
213 218
214 219
215 220 class hgcommand:
216 221 def __init__(self, cmd, env):
217 222 self.cmd = cmd
218 223 self.env = env
219 224
220 225 def run(self, args):
221 226 cmd = self.cmd + args
222 227 returncode, out, err = runcmd(cmd, self.env)
223 228 err = filterhgerr(err)
224 229 if err:
225 230 print("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
226 231 print(err, file=sys.stderr)
227 232 if returncode != 0:
228 233 return b''
229 234 return out
230 235
231 236
232 237 def filterhgerr(err):
233 238 # If root is executing setup.py, but the repository is owned by
234 239 # another user (as in "sudo python setup.py install") we will get
235 240 # trust warnings since the .hg/hgrc file is untrusted. That is
236 241 # fine, we don't want to load it anyway. Python may warn about
237 242 # a missing __init__.py in mercurial/locale, we also ignore that.
238 243 err = [
239 244 e
240 245 for e in err.splitlines()
241 246 if (
242 247 not e.startswith(b'not trusting file')
243 248 and not e.startswith(b'warning: Not importing')
244 249 and not e.startswith(b'obsolete feature not enabled')
245 250 and not e.startswith(b'*** failed to import extension')
246 251 and not e.startswith(b'devel-warn:')
247 252 and not (
248 253 e.startswith(b'(third party extension')
249 254 and e.endswith(b'or newer of Mercurial; disabling)')
250 255 )
251 256 )
252 257 ]
253 258 return b'\n'.join(b' ' + e for e in err)
254 259
255 260
256 261 def findhg():
257 262 """Try to figure out how we should invoke hg for examining the local
258 263 repository contents.
259 264
260 265 Returns an hgcommand object."""
261 266 # By default, prefer the "hg" command in the user's path. This was
262 267 # presumably the hg command that the user used to create this repository.
263 268 #
264 269 # This repository may require extensions or other settings that would not
265 270 # be enabled by running the hg script directly from this local repository.
266 271 hgenv = os.environ.copy()
267 272 # Use HGPLAIN to disable hgrc settings that would change output formatting,
268 273 # and disable localization for the same reasons.
269 274 hgenv['HGPLAIN'] = '1'
270 275 hgenv['LANGUAGE'] = 'C'
271 276 hgcmd = ['hg']
272 277 # Run a simple "hg log" command just to see if using hg from the user's
273 278 # path works and can successfully interact with this repository. Windows
274 279 # gives precedence to hg.exe in the current directory, so fall back to the
275 280 # python invocation of local hg, where pythonXY.dll can always be found.
276 281 check_cmd = ['log', '-r.', '-Ttest']
277 282 if os.name != 'nt' or not os.path.exists("hg.exe"):
278 283 try:
279 284 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
280 285 except EnvironmentError:
281 286 retcode = -1
282 287 if retcode == 0 and not filterhgerr(err):
283 288 return hgcommand(hgcmd, hgenv)
284 289
285 290 # Fall back to trying the local hg installation.
286 291 hgenv = localhgenv()
287 292 hgcmd = [sys.executable, 'hg']
288 293 try:
289 294 retcode, out, err = runcmd(hgcmd + check_cmd, hgenv)
290 295 except EnvironmentError:
291 296 retcode = -1
292 297 if retcode == 0 and not filterhgerr(err):
293 298 return hgcommand(hgcmd, hgenv)
294 299
295 raise SystemExit(
296 'Unable to find a working hg binary to extract the '
297 'version from the repository tags'
298 )
300 eprint("/!\\")
301 eprint(r"/!\ Unable to find a working hg binary")
302 eprint(r"/!\ Version cannot be extract from the repository")
303 eprint(r"/!\ Re-run the setup once a first version is built")
304 return None
299 305
300 306
301 307 def localhgenv():
302 308 """Get an environment dictionary to use for invoking or importing
303 309 mercurial from the local repository."""
304 310 # Execute hg out of this directory with a custom environment which takes
305 311 # care to not use any hgrc files and do no localization.
306 312 env = {
307 313 'HGMODULEPOLICY': 'py',
308 314 'HGRCPATH': '',
309 315 'LANGUAGE': 'C',
310 316 'PATH': '',
311 317 } # make pypi modules that use os.environ['PATH'] happy
312 318 if 'LD_LIBRARY_PATH' in os.environ:
313 319 env['LD_LIBRARY_PATH'] = os.environ['LD_LIBRARY_PATH']
314 320 if 'SystemRoot' in os.environ:
315 321 # SystemRoot is required by Windows to load various DLLs. See:
316 322 # https://bugs.python.org/issue13524#msg148850
317 323 env['SystemRoot'] = os.environ['SystemRoot']
318 324 return env
319 325
320 326
321 327 version = ''
322 328
323 if os.path.isdir('.hg'):
329
330 def _try_get_version():
324 331 hg = findhg()
332 if hg is None:
333 return ''
334 hgid = None
335 numerictags = []
325 336 cmd = ['log', '-r', '.', '--template', '{tags}\n']
326 numerictags = [t for t in sysstr(hg.run(cmd)).split() if t[0:1].isdigit()]
337 pieces = sysstr(hg.run(cmd)).split()
338 numerictags = [t for t in pieces if t[0:1].isdigit()]
327 339 hgid = sysstr(hg.run(['id', '-i'])).strip()
328 340 if not hgid:
329 # Bail out if hg is having problems interacting with this repository,
330 # rather than falling through and producing a bogus version number.
331 # Continuing with an invalid version number will break extensions
332 # that define minimumhgversion.
333 raise SystemExit('Unable to determine hg version from local repository')
341 eprint("/!\\")
342 eprint(r"/!\ Unable to determine hg version from local repository")
343 eprint(r"/!\ Failed to retrieve current revision tags")
344 return ''
334 345 if numerictags: # tag(s) found
335 346 version = numerictags[-1]
336 347 if hgid.endswith('+'): # propagate the dirty status to the tag
337 348 version += '+'
338 else: # no tag found
349 else: # no tag found on the checked out revision
339 350 ltagcmd = ['parents', '--template', '{latesttag}']
340 351 ltag = sysstr(hg.run(ltagcmd))
341 352 if not ltag:
342 ltag = 'null'
353 eprint("/!\\")
354 eprint(r"/!\ Unable to determine hg version from local repository")
355 eprint(
356 r"/!\ Failed to retrieve current revision distance to lated tag"
357 )
358 return ''
343 359 changessincecmd = ['log', '-T', 'x\n', '-r', "only(.,'%s')" % ltag]
344 360 changessince = len(hg.run(changessincecmd).splitlines())
345 if ltag == 'null':
346 ltag = '0.0'
347 361 version = '%s+hg%s.%s' % (ltag, changessince, hgid)
348 362 if version.endswith('+'):
349 363 version = version[:-1] + 'local' + time.strftime('%Y%m%d')
364 return version
365
366
367 if os.path.isdir('.hg'):
368 version = _try_get_version()
350 369 elif os.path.exists('.hg_archival.txt'):
351 370 kw = dict(
352 371 [[t.strip() for t in l.split(':', 1)] for l in open('.hg_archival.txt')]
353 372 )
354 373 if 'tag' in kw:
355 374 version = kw['tag']
356 375 elif 'latesttag' in kw:
357 376 if 'changessincelatesttag' in kw:
358 377 version = (
359 378 '%(latesttag)s+hg%(changessincelatesttag)s.%(node).12s' % kw
360 379 )
361 380 else:
362 381 version = '%(latesttag)s+hg%(latesttagdistance)s.%(node).12s' % kw
363 382 else:
364 383 version = '0+hg' + kw.get('node', '')[:12]
365 384 elif os.path.exists('mercurial/__version__.py'):
366 385 with open('mercurial/__version__.py') as f:
367 386 data = f.read()
368 387 version = re.search('version = b"(.*)"', data).group(1)
369
370 if version:
371 versionb = version
372 if not isinstance(versionb, bytes):
373 versionb = versionb.encode('ascii')
388 if not version:
389 if os.environ.get("MERCURIAL_SETUP_MAKE_LOCAL") == "1":
390 version = "0.0+0"
391 eprint("/!\\")
392 eprint(r"/!\ Using '0.0+0' as the default version")
393 eprint(r"/!\ Re-run make local once that first version is built")
394 eprint("/!\\")
395 else:
396 eprint("/!\\")
397 eprint(r"/!\ Could not determine the Mercurial version")
398 eprint(r"/!\ You need to build a local version first")
399 eprint(r"/!\ Run `make local` and try again")
400 eprint("/!\\")
401 msg = "Run `make local` first to get a working local version"
402 raise SystemExit(msg)
374 403
375 write_if_changed(
376 'mercurial/__version__.py',
377 b''.join(
378 [
379 b'# this file is autogenerated by setup.py\n'
380 b'version = b"%s"\n' % versionb,
381 ]
382 ),
383 )
404 versionb = version
405 if not isinstance(versionb, bytes):
406 versionb = versionb.encode('ascii')
407
408 write_if_changed(
409 'mercurial/__version__.py',
410 b''.join(
411 [
412 b'# this file is autogenerated by setup.py\n'
413 b'version = b"%s"\n' % versionb,
414 ]
415 ),
416 )
384 417
385 418
386 419 class hgbuild(build):
387 420 # Insert hgbuildmo first so that files in mercurial/locale/ are found
388 421 # when build_py is run next.
389 422 sub_commands = [('build_mo', None)] + build.sub_commands
390 423
391 424
392 425 class hgbuildmo(build):
393 426
394 427 description = "build translations (.mo files)"
395 428
396 429 def run(self):
397 430 if not find_executable('msgfmt'):
398 431 self.warn(
399 432 "could not find msgfmt executable, no translations "
400 433 "will be built"
401 434 )
402 435 return
403 436
404 437 podir = 'i18n'
405 438 if not os.path.isdir(podir):
406 439 self.warn("could not find %s/ directory" % podir)
407 440 return
408 441
409 442 join = os.path.join
410 443 for po in os.listdir(podir):
411 444 if not po.endswith('.po'):
412 445 continue
413 446 pofile = join(podir, po)
414 447 modir = join('locale', po[:-3], 'LC_MESSAGES')
415 448 mofile = join(modir, 'hg.mo')
416 449 mobuildfile = join('mercurial', mofile)
417 450 cmd = ['msgfmt', '-v', '-o', mobuildfile, pofile]
418 451 if sys.platform != 'sunos5':
419 452 # msgfmt on Solaris does not know about -c
420 453 cmd.append('-c')
421 454 self.mkpath(join('mercurial', modir))
422 455 self.make_file([pofile], mobuildfile, spawn, (cmd,))
423 456
424 457
425 458 class hgdist(Distribution):
426 459 pure = False
427 460 rust = False
428 461 no_rust = False
429 462 cffi = ispypy
430 463
431 464 global_options = Distribution.global_options + [
432 465 ('pure', None, "use pure (slow) Python code instead of C extensions"),
433 466 ('rust', None, "use Rust extensions additionally to C extensions"),
434 467 (
435 468 'no-rust',
436 469 None,
437 470 "do not use Rust extensions additionally to C extensions",
438 471 ),
439 472 ]
440 473
441 474 negative_opt = Distribution.negative_opt.copy()
442 475 boolean_options = ['pure', 'rust', 'no-rust']
443 476 negative_opt['no-rust'] = 'rust'
444 477
445 478 def _set_command_options(self, command_obj, option_dict=None):
446 479 # Not all distutils versions in the wild have boolean_options.
447 480 # This should be cleaned up when we're Python 3 only.
448 481 command_obj.boolean_options = (
449 482 getattr(command_obj, 'boolean_options', []) + self.boolean_options
450 483 )
451 484 return Distribution._set_command_options(
452 485 self, command_obj, option_dict=option_dict
453 486 )
454 487
455 488 def parse_command_line(self):
456 489 ret = Distribution.parse_command_line(self)
457 490 if not (self.rust or self.no_rust):
458 491 hgrustext = os.environ.get('HGWITHRUSTEXT')
459 492 # TODO record it for proper rebuild upon changes
460 493 # (see mercurial/__modulepolicy__.py)
461 494 if hgrustext != 'cpython' and hgrustext is not None:
462 495 if hgrustext:
463 496 msg = 'unknown HGWITHRUSTEXT value: %s' % hgrustext
464 497 print(msg, file=sys.stderr)
465 498 hgrustext = None
466 499 self.rust = hgrustext is not None
467 500 self.no_rust = not self.rust
468 501 return ret
469 502
470 503 def has_ext_modules(self):
471 504 # self.ext_modules is emptied in hgbuildpy.finalize_options which is
472 505 # too late for some cases
473 506 return not self.pure and Distribution.has_ext_modules(self)
474 507
475 508
476 509 # This is ugly as a one-liner. So use a variable.
477 510 buildextnegops = dict(getattr(build_ext, 'negative_options', {}))
478 511 buildextnegops['no-zstd'] = 'zstd'
479 512 buildextnegops['no-rust'] = 'rust'
480 513
481 514
482 515 class hgbuildext(build_ext):
483 516 user_options = build_ext.user_options + [
484 517 ('zstd', None, 'compile zstd bindings [default]'),
485 518 ('no-zstd', None, 'do not compile zstd bindings'),
486 519 (
487 520 'rust',
488 521 None,
489 522 'compile Rust extensions if they are in use '
490 523 '(requires Cargo) [default]',
491 524 ),
492 525 ('no-rust', None, 'do not compile Rust extensions'),
493 526 ]
494 527
495 528 boolean_options = build_ext.boolean_options + ['zstd', 'rust']
496 529 negative_opt = buildextnegops
497 530
498 531 def initialize_options(self):
499 532 self.zstd = True
500 533 self.rust = True
501 534
502 535 return build_ext.initialize_options(self)
503 536
504 537 def finalize_options(self):
505 538 # Unless overridden by the end user, build extensions in parallel.
506 539 # Only influences behavior on Python 3.5+.
507 540 if getattr(self, 'parallel', None) is None:
508 541 self.parallel = True
509 542
510 543 return build_ext.finalize_options(self)
511 544
512 545 def build_extensions(self):
513 546 ruststandalones = [
514 547 e for e in self.extensions if isinstance(e, RustStandaloneExtension)
515 548 ]
516 549 self.extensions = [
517 550 e for e in self.extensions if e not in ruststandalones
518 551 ]
519 552 # Filter out zstd if disabled via argument.
520 553 if not self.zstd:
521 554 self.extensions = [
522 555 e for e in self.extensions if e.name != 'mercurial.zstd'
523 556 ]
524 557
525 558 # Build Rust standalone extensions if it'll be used
526 559 # and its build is not explicitly disabled (for external build
527 560 # as Linux distributions would do)
528 561 if self.distribution.rust and self.rust:
529 562 if not sys.platform.startswith('linux'):
530 563 self.warn(
531 564 "rust extensions have only been tested on Linux "
532 565 "and may not behave correctly on other platforms"
533 566 )
534 567
535 568 for rustext in ruststandalones:
536 569 rustext.build('' if self.inplace else self.build_lib)
537 570
538 571 return build_ext.build_extensions(self)
539 572
540 573 def build_extension(self, ext):
541 574 if (
542 575 self.distribution.rust
543 576 and self.rust
544 577 and isinstance(ext, RustExtension)
545 578 ):
546 579 ext.rustbuild()
547 580 try:
548 581 build_ext.build_extension(self, ext)
549 582 except CCompilerError:
550 583 if not getattr(ext, 'optional', False):
551 584 raise
552 585 log.warn(
553 586 "Failed to build optional extension '%s' (skipping)", ext.name
554 587 )
555 588
556 589
557 590 class hgbuildscripts(build_scripts):
558 591 def run(self):
559 592 if os.name != 'nt' or self.distribution.pure:
560 593 return build_scripts.run(self)
561 594
562 595 exebuilt = False
563 596 try:
564 597 self.run_command('build_hgexe')
565 598 exebuilt = True
566 599 except (DistutilsError, CCompilerError):
567 600 log.warn('failed to build optional hg.exe')
568 601
569 602 if exebuilt:
570 603 # Copying hg.exe to the scripts build directory ensures it is
571 604 # installed by the install_scripts command.
572 605 hgexecommand = self.get_finalized_command('build_hgexe')
573 606 dest = os.path.join(self.build_dir, 'hg.exe')
574 607 self.mkpath(self.build_dir)
575 608 self.copy_file(hgexecommand.hgexepath, dest)
576 609
577 610 # Remove hg.bat because it is redundant with hg.exe.
578 611 self.scripts.remove('contrib/win32/hg.bat')
579 612
580 613 return build_scripts.run(self)
581 614
582 615
583 616 class hgbuildpy(build_py):
584 617 def finalize_options(self):
585 618 build_py.finalize_options(self)
586 619
587 620 if self.distribution.pure:
588 621 self.distribution.ext_modules = []
589 622 elif self.distribution.cffi:
590 623 from mercurial.cffi import (
591 624 bdiffbuild,
592 625 mpatchbuild,
593 626 )
594 627
595 628 exts = [
596 629 mpatchbuild.ffi.distutils_extension(),
597 630 bdiffbuild.ffi.distutils_extension(),
598 631 ]
599 632 # cffi modules go here
600 633 if sys.platform == 'darwin':
601 634 from mercurial.cffi import osutilbuild
602 635
603 636 exts.append(osutilbuild.ffi.distutils_extension())
604 637 self.distribution.ext_modules = exts
605 638 else:
606 639 h = os.path.join(get_python_inc(), 'Python.h')
607 640 if not os.path.exists(h):
608 641 raise SystemExit(
609 642 'Python headers are required to build '
610 643 'Mercurial but weren\'t found in %s' % h
611 644 )
612 645
613 646 def run(self):
614 647 basepath = os.path.join(self.build_lib, 'mercurial')
615 648 self.mkpath(basepath)
616 649
617 650 rust = self.distribution.rust
618 651 if self.distribution.pure:
619 652 modulepolicy = 'py'
620 653 elif self.build_lib == '.':
621 654 # in-place build should run without rebuilding and Rust extensions
622 655 modulepolicy = 'rust+c-allow' if rust else 'allow'
623 656 else:
624 657 modulepolicy = 'rust+c' if rust else 'c'
625 658
626 659 content = b''.join(
627 660 [
628 661 b'# this file is autogenerated by setup.py\n',
629 662 b'modulepolicy = b"%s"\n' % modulepolicy.encode('ascii'),
630 663 ]
631 664 )
632 665 write_if_changed(os.path.join(basepath, '__modulepolicy__.py'), content)
633 666
634 667 build_py.run(self)
635 668
636 669
637 670 class buildhgextindex(Command):
638 671 description = 'generate prebuilt index of hgext (for frozen package)'
639 672 user_options = []
640 673 _indexfilename = 'hgext/__index__.py'
641 674
642 675 def initialize_options(self):
643 676 pass
644 677
645 678 def finalize_options(self):
646 679 pass
647 680
648 681 def run(self):
649 682 if os.path.exists(self._indexfilename):
650 683 with open(self._indexfilename, 'w') as f:
651 684 f.write('# empty\n')
652 685
653 686 # here no extension enabled, disabled() lists up everything
654 687 code = (
655 688 'import pprint; from mercurial import extensions; '
656 689 'ext = extensions.disabled();'
657 690 'ext.pop("__index__", None);'
658 691 'pprint.pprint(ext)'
659 692 )
660 693 returncode, out, err = runcmd(
661 694 [sys.executable, '-c', code], localhgenv()
662 695 )
663 696 if err or returncode != 0:
664 697 raise DistutilsExecError(err)
665 698
666 699 with open(self._indexfilename, 'wb') as f:
667 700 f.write(b'# this file is autogenerated by setup.py\n')
668 701 f.write(b'docs = ')
669 702 f.write(out)
670 703
671 704
672 705 class buildhgexe(build_ext):
673 706 description = 'compile hg.exe from mercurial/exewrapper.c'
674 707
675 708 LONG_PATHS_MANIFEST = """\
676 709 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
677 710 <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
678 711 <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
679 712 <security>
680 713 <requestedPrivileges>
681 714 <requestedExecutionLevel
682 715 level="asInvoker"
683 716 uiAccess="false"
684 717 />
685 718 </requestedPrivileges>
686 719 </security>
687 720 </trustInfo>
688 721 <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
689 722 <application>
690 723 <!-- Windows Vista -->
691 724 <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
692 725 <!-- Windows 7 -->
693 726 <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
694 727 <!-- Windows 8 -->
695 728 <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
696 729 <!-- Windows 8.1 -->
697 730 <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
698 731 <!-- Windows 10 and Windows 11 -->
699 732 <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
700 733 </application>
701 734 </compatibility>
702 735 <application xmlns="urn:schemas-microsoft-com:asm.v3">
703 736 <windowsSettings
704 737 xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
705 738 <ws2:longPathAware>true</ws2:longPathAware>
706 739 </windowsSettings>
707 740 </application>
708 741 <dependency>
709 742 <dependentAssembly>
710 743 <assemblyIdentity type="win32"
711 744 name="Microsoft.Windows.Common-Controls"
712 745 version="6.0.0.0"
713 746 processorArchitecture="*"
714 747 publicKeyToken="6595b64144ccf1df"
715 748 language="*" />
716 749 </dependentAssembly>
717 750 </dependency>
718 751 </assembly>
719 752 """
720 753
721 754 def initialize_options(self):
722 755 build_ext.initialize_options(self)
723 756
724 757 def build_extensions(self):
725 758 if os.name != 'nt':
726 759 return
727 760 if isinstance(self.compiler, HackedMingw32CCompiler):
728 761 self.compiler.compiler_so = self.compiler.compiler # no -mdll
729 762 self.compiler.dll_libraries = [] # no -lmsrvc90
730 763
731 764 pythonlib = None
732 765
733 766 dirname = os.path.dirname(self.get_ext_fullpath('dummy'))
734 767 self.hgtarget = os.path.join(dirname, 'hg')
735 768
736 769 if getattr(sys, 'dllhandle', None):
737 770 # Different Python installs can have different Python library
738 771 # names. e.g. the official CPython distribution uses pythonXY.dll
739 772 # and MinGW uses libpythonX.Y.dll.
740 773 _kernel32 = ctypes.windll.kernel32
741 774 _kernel32.GetModuleFileNameA.argtypes = [
742 775 ctypes.c_void_p,
743 776 ctypes.c_void_p,
744 777 ctypes.c_ulong,
745 778 ]
746 779 _kernel32.GetModuleFileNameA.restype = ctypes.c_ulong
747 780 size = 1000
748 781 buf = ctypes.create_string_buffer(size + 1)
749 782 filelen = _kernel32.GetModuleFileNameA(
750 783 sys.dllhandle, ctypes.byref(buf), size
751 784 )
752 785
753 786 if filelen > 0 and filelen != size:
754 787 dllbasename = os.path.basename(buf.value)
755 788 if not dllbasename.lower().endswith(b'.dll'):
756 789 raise SystemExit(
757 790 'Python DLL does not end with .dll: %s' % dllbasename
758 791 )
759 792 pythonlib = dllbasename[:-4]
760 793
761 794 # Copy the pythonXY.dll next to the binary so that it runs
762 795 # without tampering with PATH.
763 796 dest = os.path.join(
764 797 os.path.dirname(self.hgtarget),
765 798 os.fsdecode(dllbasename),
766 799 )
767 800
768 801 if not os.path.exists(dest):
769 802 shutil.copy(buf.value, dest)
770 803
771 804 # Also overwrite python3.dll so that hgext.git is usable.
772 805 # TODO: also handle the MSYS flavor
773 806 python_x = os.path.join(
774 807 os.path.dirname(os.fsdecode(buf.value)),
775 808 "python3.dll",
776 809 )
777 810
778 811 if os.path.exists(python_x):
779 812 dest = os.path.join(
780 813 os.path.dirname(self.hgtarget),
781 814 os.path.basename(python_x),
782 815 )
783 816
784 817 shutil.copy(python_x, dest)
785 818
786 819 if not pythonlib:
787 820 log.warn(
788 821 'could not determine Python DLL filename; assuming pythonXY'
789 822 )
790 823
791 824 hv = sys.hexversion
792 825 pythonlib = b'python%d%d' % (hv >> 24, (hv >> 16) & 0xFF)
793 826
794 827 log.info('using %s as Python library name' % pythonlib)
795 828 with open('mercurial/hgpythonlib.h', 'wb') as f:
796 829 f.write(b'/* this file is autogenerated by setup.py */\n')
797 830 f.write(b'#define HGPYTHONLIB "%s"\n' % pythonlib)
798 831
799 832 objects = self.compiler.compile(
800 833 ['mercurial/exewrapper.c'],
801 834 output_dir=self.build_temp,
802 835 macros=[('_UNICODE', None), ('UNICODE', None)],
803 836 )
804 837 self.compiler.link_executable(
805 838 objects, self.hgtarget, libraries=[], output_dir=self.build_temp
806 839 )
807 840
808 841 self.addlongpathsmanifest()
809 842
810 843 def addlongpathsmanifest(self):
811 844 """Add manifest pieces so that hg.exe understands long paths
812 845
813 846 Why resource #1 should be used for .exe manifests? I don't know and
814 847 wasn't able to find an explanation for mortals. But it seems to work.
815 848 """
816 849 exefname = self.compiler.executable_filename(self.hgtarget)
817 850 fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
818 851 os.close(fdauto)
819 852 with open(manfname, 'w', encoding="UTF-8") as f:
820 853 f.write(self.LONG_PATHS_MANIFEST)
821 854 log.info("long paths manifest is written to '%s'" % manfname)
822 855 outputresource = '-outputresource:%s;#1' % exefname
823 856 log.info("running mt.exe to update hg.exe's manifest in-place")
824 857
825 858 self.spawn(
826 859 [
827 860 self.compiler.mt,
828 861 '-nologo',
829 862 '-manifest',
830 863 manfname,
831 864 outputresource,
832 865 ]
833 866 )
834 867 log.info("done updating hg.exe's manifest")
835 868 os.remove(manfname)
836 869
837 870 @property
838 871 def hgexepath(self):
839 872 dir = os.path.dirname(self.get_ext_fullpath('dummy'))
840 873 return os.path.join(self.build_temp, dir, 'hg.exe')
841 874
842 875
843 876 class hgbuilddoc(Command):
844 877 description = 'build documentation'
845 878 user_options = [
846 879 ('man', None, 'generate man pages'),
847 880 ('html', None, 'generate html pages'),
848 881 ]
849 882
850 883 def initialize_options(self):
851 884 self.man = None
852 885 self.html = None
853 886
854 887 def finalize_options(self):
855 888 # If --man or --html are set, only generate what we're told to.
856 889 # Otherwise generate everything.
857 890 have_subset = self.man is not None or self.html is not None
858 891
859 892 if have_subset:
860 893 self.man = True if self.man else False
861 894 self.html = True if self.html else False
862 895 else:
863 896 self.man = True
864 897 self.html = True
865 898
866 899 def run(self):
867 900 def normalizecrlf(p):
868 901 with open(p, 'rb') as fh:
869 902 orig = fh.read()
870 903
871 904 if b'\r\n' not in orig:
872 905 return
873 906
874 907 log.info('normalizing %s to LF line endings' % p)
875 908 with open(p, 'wb') as fh:
876 909 fh.write(orig.replace(b'\r\n', b'\n'))
877 910
878 911 def gentxt(root):
879 912 txt = 'doc/%s.txt' % root
880 913 log.info('generating %s' % txt)
881 914 res, out, err = runcmd(
882 915 [sys.executable, 'gendoc.py', root], os.environ, cwd='doc'
883 916 )
884 917 if res:
885 918 raise SystemExit(
886 919 'error running gendoc.py: %s'
887 920 % '\n'.join([sysstr(out), sysstr(err)])
888 921 )
889 922
890 923 with open(txt, 'wb') as fh:
891 924 fh.write(out)
892 925
893 926 def gengendoc(root):
894 927 gendoc = 'doc/%s.gendoc.txt' % root
895 928
896 929 log.info('generating %s' % gendoc)
897 930 res, out, err = runcmd(
898 931 [sys.executable, 'gendoc.py', '%s.gendoc' % root],
899 932 os.environ,
900 933 cwd='doc',
901 934 )
902 935 if res:
903 936 raise SystemExit(
904 937 'error running gendoc: %s'
905 938 % '\n'.join([sysstr(out), sysstr(err)])
906 939 )
907 940
908 941 with open(gendoc, 'wb') as fh:
909 942 fh.write(out)
910 943
911 944 def genman(root):
912 945 log.info('generating doc/%s' % root)
913 946 res, out, err = runcmd(
914 947 [
915 948 sys.executable,
916 949 'runrst',
917 950 'hgmanpage',
918 951 '--halt',
919 952 'warning',
920 953 '--strip-elements-with-class',
921 954 'htmlonly',
922 955 '%s.txt' % root,
923 956 root,
924 957 ],
925 958 os.environ,
926 959 cwd='doc',
927 960 )
928 961 if res:
929 962 raise SystemExit(
930 963 'error running runrst: %s'
931 964 % '\n'.join([sysstr(out), sysstr(err)])
932 965 )
933 966
934 967 normalizecrlf('doc/%s' % root)
935 968
936 969 def genhtml(root):
937 970 log.info('generating doc/%s.html' % root)
938 971 res, out, err = runcmd(
939 972 [
940 973 sys.executable,
941 974 'runrst',
942 975 'html',
943 976 '--halt',
944 977 'warning',
945 978 '--link-stylesheet',
946 979 '--stylesheet-path',
947 980 'style.css',
948 981 '%s.txt' % root,
949 982 '%s.html' % root,
950 983 ],
951 984 os.environ,
952 985 cwd='doc',
953 986 )
954 987 if res:
955 988 raise SystemExit(
956 989 'error running runrst: %s'
957 990 % '\n'.join([sysstr(out), sysstr(err)])
958 991 )
959 992
960 993 normalizecrlf('doc/%s.html' % root)
961 994
962 995 # This logic is duplicated in doc/Makefile.
963 996 sources = {
964 997 f
965 998 for f in os.listdir('mercurial/helptext')
966 999 if re.search(r'[0-9]\.txt$', f)
967 1000 }
968 1001
969 1002 # common.txt is a one-off.
970 1003 gentxt('common')
971 1004
972 1005 for source in sorted(sources):
973 1006 assert source[-4:] == '.txt'
974 1007 root = source[:-4]
975 1008
976 1009 gentxt(root)
977 1010 gengendoc(root)
978 1011
979 1012 if self.man:
980 1013 genman(root)
981 1014 if self.html:
982 1015 genhtml(root)
983 1016
984 1017
985 1018 class hginstall(install):
986 1019
987 1020 user_options = install.user_options + [
988 1021 (
989 1022 'old-and-unmanageable',
990 1023 None,
991 1024 'noop, present for eggless setuptools compat',
992 1025 ),
993 1026 (
994 1027 'single-version-externally-managed',
995 1028 None,
996 1029 'noop, present for eggless setuptools compat',
997 1030 ),
998 1031 ]
999 1032
1000 1033 sub_commands = install.sub_commands + [
1001 1034 ('install_completion', lambda self: True)
1002 1035 ]
1003 1036
1004 1037 # Also helps setuptools not be sad while we refuse to create eggs.
1005 1038 single_version_externally_managed = True
1006 1039
1007 1040 def get_sub_commands(self):
1008 1041 # Screen out egg related commands to prevent egg generation. But allow
1009 1042 # mercurial.egg-info generation, since that is part of modern
1010 1043 # packaging.
1011 1044 excl = {'bdist_egg'}
1012 1045 return filter(lambda x: x not in excl, install.get_sub_commands(self))
1013 1046
1014 1047
1015 1048 class hginstalllib(install_lib):
1016 1049 """
1017 1050 This is a specialization of install_lib that replaces the copy_file used
1018 1051 there so that it supports setting the mode of files after copying them,
1019 1052 instead of just preserving the mode that the files originally had. If your
1020 1053 system has a umask of something like 027, preserving the permissions when
1021 1054 copying will lead to a broken install.
1022 1055
1023 1056 Note that just passing keep_permissions=False to copy_file would be
1024 1057 insufficient, as it might still be applying a umask.
1025 1058 """
1026 1059
1027 1060 def run(self):
1028 1061 realcopyfile = file_util.copy_file
1029 1062
1030 1063 def copyfileandsetmode(*args, **kwargs):
1031 1064 src, dst = args[0], args[1]
1032 1065 dst, copied = realcopyfile(*args, **kwargs)
1033 1066 if copied:
1034 1067 st = os.stat(src)
1035 1068 # Persist executable bit (apply it to group and other if user
1036 1069 # has it)
1037 1070 if st[stat.ST_MODE] & stat.S_IXUSR:
1038 1071 setmode = int('0755', 8)
1039 1072 else:
1040 1073 setmode = int('0644', 8)
1041 1074 m = stat.S_IMODE(st[stat.ST_MODE])
1042 1075 m = (m & ~int('0777', 8)) | setmode
1043 1076 os.chmod(dst, m)
1044 1077
1045 1078 file_util.copy_file = copyfileandsetmode
1046 1079 try:
1047 1080 install_lib.run(self)
1048 1081 finally:
1049 1082 file_util.copy_file = realcopyfile
1050 1083
1051 1084
1052 1085 class hginstallscripts(install_scripts):
1053 1086 """
1054 1087 This is a specialization of install_scripts that replaces the @LIBDIR@ with
1055 1088 the configured directory for modules. If possible, the path is made relative
1056 1089 to the directory for scripts.
1057 1090 """
1058 1091
1059 1092 def initialize_options(self):
1060 1093 install_scripts.initialize_options(self)
1061 1094
1062 1095 self.install_lib = None
1063 1096
1064 1097 def finalize_options(self):
1065 1098 install_scripts.finalize_options(self)
1066 1099 self.set_undefined_options('install', ('install_lib', 'install_lib'))
1067 1100
1068 1101 def run(self):
1069 1102 install_scripts.run(self)
1070 1103
1071 1104 # It only makes sense to replace @LIBDIR@ with the install path if
1072 1105 # the install path is known. For wheels, the logic below calculates
1073 1106 # the libdir to be "../..". This is because the internal layout of a
1074 1107 # wheel archive looks like:
1075 1108 #
1076 1109 # mercurial-3.6.1.data/scripts/hg
1077 1110 # mercurial/__init__.py
1078 1111 #
1079 1112 # When installing wheels, the subdirectories of the "<pkg>.data"
1080 1113 # directory are translated to system local paths and files therein
1081 1114 # are copied in place. The mercurial/* files are installed into the
1082 1115 # site-packages directory. However, the site-packages directory
1083 1116 # isn't known until wheel install time. This means we have no clue
1084 1117 # at wheel generation time what the installed site-packages directory
1085 1118 # will be. And, wheels don't appear to provide the ability to register
1086 1119 # custom code to run during wheel installation. This all means that
1087 1120 # we can't reliably set the libdir in wheels: the default behavior
1088 1121 # of looking in sys.path must do.
1089 1122
1090 1123 if (
1091 1124 os.path.splitdrive(self.install_dir)[0]
1092 1125 != os.path.splitdrive(self.install_lib)[0]
1093 1126 ):
1094 1127 # can't make relative paths from one drive to another, so use an
1095 1128 # absolute path instead
1096 1129 libdir = self.install_lib
1097 1130 else:
1098 1131 libdir = os.path.relpath(self.install_lib, self.install_dir)
1099 1132
1100 1133 for outfile in self.outfiles:
1101 1134 with open(outfile, 'rb') as fp:
1102 1135 data = fp.read()
1103 1136
1104 1137 # skip binary files
1105 1138 if b'\0' in data:
1106 1139 continue
1107 1140
1108 1141 # During local installs, the shebang will be rewritten to the final
1109 1142 # install path. During wheel packaging, the shebang has a special
1110 1143 # value.
1111 1144 if data.startswith(b'#!python'):
1112 1145 log.info(
1113 1146 'not rewriting @LIBDIR@ in %s because install path '
1114 1147 'not known' % outfile
1115 1148 )
1116 1149 continue
1117 1150
1118 1151 data = data.replace(b'@LIBDIR@', libdir.encode('unicode_escape'))
1119 1152 with open(outfile, 'wb') as fp:
1120 1153 fp.write(data)
1121 1154
1122 1155
1123 1156 class hginstallcompletion(Command):
1124 1157 description = 'Install shell completion'
1125 1158
1126 1159 def initialize_options(self):
1127 1160 self.install_dir = None
1128 1161 self.outputs = []
1129 1162
1130 1163 def finalize_options(self):
1131 1164 self.set_undefined_options(
1132 1165 'install_data', ('install_dir', 'install_dir')
1133 1166 )
1134 1167
1135 1168 def get_outputs(self):
1136 1169 return self.outputs
1137 1170
1138 1171 def run(self):
1139 1172 for src, dir_path, dest in (
1140 1173 (
1141 1174 'bash_completion',
1142 1175 ('share', 'bash-completion', 'completions'),
1143 1176 'hg',
1144 1177 ),
1145 1178 ('zsh_completion', ('share', 'zsh', 'site-functions'), '_hg'),
1146 1179 ):
1147 1180 dir = os.path.join(self.install_dir, *dir_path)
1148 1181 self.mkpath(dir)
1149 1182
1150 1183 dest = os.path.join(dir, dest)
1151 1184 self.outputs.append(dest)
1152 1185 self.copy_file(os.path.join('contrib', src), dest)
1153 1186
1154 1187
1155 1188 # virtualenv installs custom distutils/__init__.py and
1156 1189 # distutils/distutils.cfg files which essentially proxy back to the
1157 1190 # "real" distutils in the main Python install. The presence of this
1158 1191 # directory causes py2exe to pick up the "hacked" distutils package
1159 1192 # from the virtualenv and "import distutils" will fail from the py2exe
1160 1193 # build because the "real" distutils files can't be located.
1161 1194 #
1162 1195 # We work around this by monkeypatching the py2exe code finding Python
1163 1196 # modules to replace the found virtualenv distutils modules with the
1164 1197 # original versions via filesystem scanning. This is a bit hacky. But
1165 1198 # it allows us to use virtualenvs for py2exe packaging, which is more
1166 1199 # deterministic and reproducible.
1167 1200 #
1168 1201 # It's worth noting that the common StackOverflow suggestions for this
1169 1202 # problem involve copying the original distutils files into the
1170 1203 # virtualenv or into the staging directory after setup() is invoked.
1171 1204 # The former is very brittle and can easily break setup(). Our hacking
1172 1205 # of the found modules routine has a similar result as copying the files
1173 1206 # manually. But it makes fewer assumptions about how py2exe works and
1174 1207 # is less brittle.
1175 1208
1176 1209 # This only catches virtualenvs made with virtualenv (as opposed to
1177 1210 # venv, which is likely what Python 3 uses).
1178 1211 py2exehacked = py2exeloaded and getattr(sys, 'real_prefix', None) is not None
1179 1212
1180 1213 if py2exehacked:
1181 1214 from distutils.command.py2exe import py2exe as buildpy2exe
1182 1215 from py2exe.mf import Module as py2exemodule
1183 1216
1184 1217 class hgbuildpy2exe(buildpy2exe):
1185 1218 def find_needed_modules(self, mf, files, modules):
1186 1219 res = buildpy2exe.find_needed_modules(self, mf, files, modules)
1187 1220
1188 1221 # Replace virtualenv's distutils modules with the real ones.
1189 1222 modules = {}
1190 1223 for k, v in res.modules.items():
1191 1224 if k != 'distutils' and not k.startswith('distutils.'):
1192 1225 modules[k] = v
1193 1226
1194 1227 res.modules = modules
1195 1228
1196 1229 import opcode
1197 1230
1198 1231 distutilsreal = os.path.join(
1199 1232 os.path.dirname(opcode.__file__), 'distutils'
1200 1233 )
1201 1234
1202 1235 for root, dirs, files in os.walk(distutilsreal):
1203 1236 for f in sorted(files):
1204 1237 if not f.endswith('.py'):
1205 1238 continue
1206 1239
1207 1240 full = os.path.join(root, f)
1208 1241
1209 1242 parents = ['distutils']
1210 1243
1211 1244 if root != distutilsreal:
1212 1245 rel = os.path.relpath(root, distutilsreal)
1213 1246 parents.extend(p for p in rel.split(os.sep))
1214 1247
1215 1248 modname = '%s.%s' % ('.'.join(parents), f[:-3])
1216 1249
1217 1250 if modname.startswith('distutils.tests.'):
1218 1251 continue
1219 1252
1220 1253 if modname.endswith('.__init__'):
1221 1254 modname = modname[: -len('.__init__')]
1222 1255 path = os.path.dirname(full)
1223 1256 else:
1224 1257 path = None
1225 1258
1226 1259 res.modules[modname] = py2exemodule(
1227 1260 modname, full, path=path
1228 1261 )
1229 1262
1230 1263 if 'distutils' not in res.modules:
1231 1264 raise SystemExit('could not find distutils modules')
1232 1265
1233 1266 return res
1234 1267
1235 1268
1236 1269 cmdclass = {
1237 1270 'build': hgbuild,
1238 1271 'build_doc': hgbuilddoc,
1239 1272 'build_mo': hgbuildmo,
1240 1273 'build_ext': hgbuildext,
1241 1274 'build_py': hgbuildpy,
1242 1275 'build_scripts': hgbuildscripts,
1243 1276 'build_hgextindex': buildhgextindex,
1244 1277 'install': hginstall,
1245 1278 'install_completion': hginstallcompletion,
1246 1279 'install_lib': hginstalllib,
1247 1280 'install_scripts': hginstallscripts,
1248 1281 'build_hgexe': buildhgexe,
1249 1282 }
1250 1283
1251 1284 if py2exehacked:
1252 1285 cmdclass['py2exe'] = hgbuildpy2exe
1253 1286
1254 1287 packages = [
1255 1288 'mercurial',
1256 1289 'mercurial.cext',
1257 1290 'mercurial.cffi',
1258 1291 'mercurial.defaultrc',
1259 1292 'mercurial.dirstateutils',
1260 1293 'mercurial.helptext',
1261 1294 'mercurial.helptext.internals',
1262 1295 'mercurial.hgweb',
1263 1296 'mercurial.interfaces',
1264 1297 'mercurial.pure',
1265 1298 'mercurial.templates',
1266 1299 'mercurial.thirdparty',
1267 1300 'mercurial.thirdparty.attr',
1268 1301 'mercurial.thirdparty.zope',
1269 1302 'mercurial.thirdparty.zope.interface',
1270 1303 'mercurial.upgrade_utils',
1271 1304 'mercurial.utils',
1272 1305 'mercurial.revlogutils',
1273 1306 'mercurial.testing',
1274 1307 'hgext',
1275 1308 'hgext.convert',
1276 1309 'hgext.fsmonitor',
1277 1310 'hgext.fastannotate',
1278 1311 'hgext.fsmonitor.pywatchman',
1279 1312 'hgext.git',
1280 1313 'hgext.highlight',
1281 1314 'hgext.hooklib',
1282 1315 'hgext.infinitepush',
1283 1316 'hgext.largefiles',
1284 1317 'hgext.lfs',
1285 1318 'hgext.narrow',
1286 1319 'hgext.remotefilelog',
1287 1320 'hgext.zeroconf',
1288 1321 'hgext3rd',
1289 1322 'hgdemandimport',
1290 1323 ]
1291 1324
1292 1325 for name in os.listdir(os.path.join('mercurial', 'templates')):
1293 1326 if name != '__pycache__' and os.path.isdir(
1294 1327 os.path.join('mercurial', 'templates', name)
1295 1328 ):
1296 1329 packages.append('mercurial.templates.%s' % name)
1297 1330
1298 1331 if 'HG_PY2EXE_EXTRA_INSTALL_PACKAGES' in os.environ:
1299 1332 # py2exe can't cope with namespace packages very well, so we have to
1300 1333 # install any hgext3rd.* extensions that we want in the final py2exe
1301 1334 # image here. This is gross, but you gotta do what you gotta do.
1302 1335 packages.extend(os.environ['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'].split(' '))
1303 1336
1304 1337 common_depends = [
1305 1338 'mercurial/bitmanipulation.h',
1306 1339 'mercurial/compat.h',
1307 1340 'mercurial/cext/util.h',
1308 1341 ]
1309 1342 common_include_dirs = ['mercurial']
1310 1343
1311 1344 common_cflags = []
1312 1345
1313 1346 # MSVC 2008 still needs declarations at the top of the scope, but Python 3.9
1314 1347 # makes declarations not at the top of a scope in the headers.
1315 1348 if os.name != 'nt' and sys.version_info[1] < 9:
1316 1349 common_cflags = ['-Werror=declaration-after-statement']
1317 1350
1318 1351 osutil_cflags = []
1319 1352 osutil_ldflags = []
1320 1353
1321 1354 # platform specific macros
1322 1355 for plat, func in [('bsd', 'setproctitle')]:
1323 1356 if re.search(plat, sys.platform) and hasfunction(new_compiler(), func):
1324 1357 osutil_cflags.append('-DHAVE_%s' % func.upper())
1325 1358
1326 1359 for plat, macro, code in [
1327 1360 (
1328 1361 'bsd|darwin',
1329 1362 'BSD_STATFS',
1330 1363 '''
1331 1364 #include <sys/param.h>
1332 1365 #include <sys/mount.h>
1333 1366 int main() { struct statfs s; return sizeof(s.f_fstypename); }
1334 1367 ''',
1335 1368 ),
1336 1369 (
1337 1370 'linux',
1338 1371 'LINUX_STATFS',
1339 1372 '''
1340 1373 #include <linux/magic.h>
1341 1374 #include <sys/vfs.h>
1342 1375 int main() { struct statfs s; return sizeof(s.f_type); }
1343 1376 ''',
1344 1377 ),
1345 1378 ]:
1346 1379 if re.search(plat, sys.platform) and cancompile(new_compiler(), code):
1347 1380 osutil_cflags.append('-DHAVE_%s' % macro)
1348 1381
1349 1382 if sys.platform == 'darwin':
1350 1383 osutil_ldflags += ['-framework', 'ApplicationServices']
1351 1384
1352 1385 if sys.platform == 'sunos5':
1353 1386 osutil_ldflags += ['-lsocket']
1354 1387
1355 1388 xdiff_srcs = [
1356 1389 'mercurial/thirdparty/xdiff/xdiffi.c',
1357 1390 'mercurial/thirdparty/xdiff/xprepare.c',
1358 1391 'mercurial/thirdparty/xdiff/xutils.c',
1359 1392 ]
1360 1393
1361 1394 xdiff_headers = [
1362 1395 'mercurial/thirdparty/xdiff/xdiff.h',
1363 1396 'mercurial/thirdparty/xdiff/xdiffi.h',
1364 1397 'mercurial/thirdparty/xdiff/xinclude.h',
1365 1398 'mercurial/thirdparty/xdiff/xmacros.h',
1366 1399 'mercurial/thirdparty/xdiff/xprepare.h',
1367 1400 'mercurial/thirdparty/xdiff/xtypes.h',
1368 1401 'mercurial/thirdparty/xdiff/xutils.h',
1369 1402 ]
1370 1403
1371 1404
1372 1405 class RustCompilationError(CCompilerError):
1373 1406 """Exception class for Rust compilation errors."""
1374 1407
1375 1408
1376 1409 class RustExtension(Extension):
1377 1410 """Base classes for concrete Rust Extension classes."""
1378 1411
1379 1412 rusttargetdir = os.path.join('rust', 'target', 'release')
1380 1413
1381 1414 def __init__(self, mpath, sources, rustlibname, subcrate, **kw):
1382 1415 Extension.__init__(self, mpath, sources, **kw)
1383 1416 srcdir = self.rustsrcdir = os.path.join('rust', subcrate)
1384 1417
1385 1418 # adding Rust source and control files to depends so that the extension
1386 1419 # gets rebuilt if they've changed
1387 1420 self.depends.append(os.path.join(srcdir, 'Cargo.toml'))
1388 1421 cargo_lock = os.path.join(srcdir, 'Cargo.lock')
1389 1422 if os.path.exists(cargo_lock):
1390 1423 self.depends.append(cargo_lock)
1391 1424 for dirpath, subdir, fnames in os.walk(os.path.join(srcdir, 'src')):
1392 1425 self.depends.extend(
1393 1426 os.path.join(dirpath, fname)
1394 1427 for fname in fnames
1395 1428 if os.path.splitext(fname)[1] == '.rs'
1396 1429 )
1397 1430
1398 1431 @staticmethod
1399 1432 def rustdylibsuffix():
1400 1433 """Return the suffix for shared libraries produced by rustc.
1401 1434
1402 1435 See also: https://doc.rust-lang.org/reference/linkage.html
1403 1436 """
1404 1437 if sys.platform == 'darwin':
1405 1438 return '.dylib'
1406 1439 elif os.name == 'nt':
1407 1440 return '.dll'
1408 1441 else:
1409 1442 return '.so'
1410 1443
1411 1444 def rustbuild(self):
1412 1445 env = os.environ.copy()
1413 1446 if 'HGTEST_RESTOREENV' in env:
1414 1447 # Mercurial tests change HOME to a temporary directory,
1415 1448 # but, if installed with rustup, the Rust toolchain needs
1416 1449 # HOME to be correct (otherwise the 'no default toolchain'
1417 1450 # error message is issued and the build fails).
1418 1451 # This happens currently with test-hghave.t, which does
1419 1452 # invoke this build.
1420 1453
1421 1454 # Unix only fix (os.path.expanduser not really reliable if
1422 1455 # HOME is shadowed like this)
1423 1456 import pwd
1424 1457
1425 1458 env['HOME'] = pwd.getpwuid(os.getuid()).pw_dir
1426 1459
1427 1460 cargocmd = ['cargo', 'rustc', '--release']
1428 1461
1429 1462 rust_features = env.get("HG_RUST_FEATURES")
1430 1463 if rust_features:
1431 1464 cargocmd.extend(('--features', rust_features))
1432 1465
1433 1466 cargocmd.append('--')
1434 1467 if sys.platform == 'darwin':
1435 1468 cargocmd.extend(
1436 1469 ("-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup")
1437 1470 )
1438 1471 try:
1439 1472 subprocess.check_call(cargocmd, env=env, cwd=self.rustsrcdir)
1440 1473 except FileNotFoundError:
1441 1474 raise RustCompilationError("Cargo not found")
1442 1475 except PermissionError:
1443 1476 raise RustCompilationError(
1444 1477 "Cargo found, but permission to execute it is denied"
1445 1478 )
1446 1479 except subprocess.CalledProcessError:
1447 1480 raise RustCompilationError(
1448 1481 "Cargo failed. Working directory: %r, "
1449 1482 "command: %r, environment: %r"
1450 1483 % (self.rustsrcdir, cargocmd, env)
1451 1484 )
1452 1485
1453 1486
1454 1487 class RustStandaloneExtension(RustExtension):
1455 1488 def __init__(self, pydottedname, rustcrate, dylibname, **kw):
1456 1489 RustExtension.__init__(
1457 1490 self, pydottedname, [], dylibname, rustcrate, **kw
1458 1491 )
1459 1492 self.dylibname = dylibname
1460 1493
1461 1494 def build(self, target_dir):
1462 1495 self.rustbuild()
1463 1496 target = [target_dir]
1464 1497 target.extend(self.name.split('.'))
1465 1498 target[-1] += DYLIB_SUFFIX
1466 1499 shutil.copy2(
1467 1500 os.path.join(
1468 1501 self.rusttargetdir, self.dylibname + self.rustdylibsuffix()
1469 1502 ),
1470 1503 os.path.join(*target),
1471 1504 )
1472 1505
1473 1506
1474 1507 extmodules = [
1475 1508 Extension(
1476 1509 'mercurial.cext.base85',
1477 1510 ['mercurial/cext/base85.c'],
1478 1511 include_dirs=common_include_dirs,
1479 1512 extra_compile_args=common_cflags,
1480 1513 depends=common_depends,
1481 1514 ),
1482 1515 Extension(
1483 1516 'mercurial.cext.bdiff',
1484 1517 ['mercurial/bdiff.c', 'mercurial/cext/bdiff.c'] + xdiff_srcs,
1485 1518 include_dirs=common_include_dirs,
1486 1519 extra_compile_args=common_cflags,
1487 1520 depends=common_depends + ['mercurial/bdiff.h'] + xdiff_headers,
1488 1521 ),
1489 1522 Extension(
1490 1523 'mercurial.cext.mpatch',
1491 1524 ['mercurial/mpatch.c', 'mercurial/cext/mpatch.c'],
1492 1525 include_dirs=common_include_dirs,
1493 1526 extra_compile_args=common_cflags,
1494 1527 depends=common_depends,
1495 1528 ),
1496 1529 Extension(
1497 1530 'mercurial.cext.parsers',
1498 1531 [
1499 1532 'mercurial/cext/charencode.c',
1500 1533 'mercurial/cext/dirs.c',
1501 1534 'mercurial/cext/manifest.c',
1502 1535 'mercurial/cext/parsers.c',
1503 1536 'mercurial/cext/pathencode.c',
1504 1537 'mercurial/cext/revlog.c',
1505 1538 ],
1506 1539 include_dirs=common_include_dirs,
1507 1540 extra_compile_args=common_cflags,
1508 1541 depends=common_depends
1509 1542 + [
1510 1543 'mercurial/cext/charencode.h',
1511 1544 'mercurial/cext/revlog.h',
1512 1545 ],
1513 1546 ),
1514 1547 Extension(
1515 1548 'mercurial.cext.osutil',
1516 1549 ['mercurial/cext/osutil.c'],
1517 1550 include_dirs=common_include_dirs,
1518 1551 extra_compile_args=common_cflags + osutil_cflags,
1519 1552 extra_link_args=osutil_ldflags,
1520 1553 depends=common_depends,
1521 1554 ),
1522 1555 Extension(
1523 1556 'mercurial.thirdparty.zope.interface._zope_interface_coptimizations',
1524 1557 [
1525 1558 'mercurial/thirdparty/zope/interface/_zope_interface_coptimizations.c',
1526 1559 ],
1527 1560 extra_compile_args=common_cflags,
1528 1561 ),
1529 1562 Extension(
1530 1563 'mercurial.thirdparty.sha1dc',
1531 1564 [
1532 1565 'mercurial/thirdparty/sha1dc/cext.c',
1533 1566 'mercurial/thirdparty/sha1dc/lib/sha1.c',
1534 1567 'mercurial/thirdparty/sha1dc/lib/ubc_check.c',
1535 1568 ],
1536 1569 extra_compile_args=common_cflags,
1537 1570 ),
1538 1571 Extension(
1539 1572 'hgext.fsmonitor.pywatchman.bser',
1540 1573 ['hgext/fsmonitor/pywatchman/bser.c'],
1541 1574 extra_compile_args=common_cflags,
1542 1575 ),
1543 1576 RustStandaloneExtension(
1544 1577 'mercurial.rustext',
1545 1578 'hg-cpython',
1546 1579 'librusthg',
1547 1580 ),
1548 1581 ]
1549 1582
1550 1583
1551 1584 sys.path.insert(0, 'contrib/python-zstandard')
1552 1585 import setup_zstd
1553 1586
1554 1587 zstd = setup_zstd.get_c_extension(
1555 1588 name='mercurial.zstd', root=os.path.abspath(os.path.dirname(__file__))
1556 1589 )
1557 1590 zstd.extra_compile_args += common_cflags
1558 1591 extmodules.append(zstd)
1559 1592
1560 1593 try:
1561 1594 from distutils import cygwinccompiler
1562 1595
1563 1596 # the -mno-cygwin option has been deprecated for years
1564 1597 mingw32compilerclass = cygwinccompiler.Mingw32CCompiler
1565 1598
1566 1599 class HackedMingw32CCompiler(cygwinccompiler.Mingw32CCompiler):
1567 1600 def __init__(self, *args, **kwargs):
1568 1601 mingw32compilerclass.__init__(self, *args, **kwargs)
1569 1602 for i in 'compiler compiler_so linker_exe linker_so'.split():
1570 1603 try:
1571 1604 getattr(self, i).remove('-mno-cygwin')
1572 1605 except ValueError:
1573 1606 pass
1574 1607
1575 1608 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
1576 1609 except ImportError:
1577 1610 # the cygwinccompiler package is not available on some Python
1578 1611 # distributions like the ones from the optware project for Synology
1579 1612 # DiskStation boxes
1580 1613 class HackedMingw32CCompiler:
1581 1614 pass
1582 1615
1583 1616
1584 1617 if os.name == 'nt':
1585 1618 # Allow compiler/linker flags to be added to Visual Studio builds. Passing
1586 1619 # extra_link_args to distutils.extensions.Extension() doesn't have any
1587 1620 # effect.
1588 1621 from distutils import msvccompiler
1589 1622
1590 1623 msvccompilerclass = msvccompiler.MSVCCompiler
1591 1624
1592 1625 class HackedMSVCCompiler(msvccompiler.MSVCCompiler):
1593 1626 def initialize(self):
1594 1627 msvccompilerclass.initialize(self)
1595 1628 # "warning LNK4197: export 'func' specified multiple times"
1596 1629 self.ldflags_shared.append('/ignore:4197')
1597 1630 self.ldflags_shared_debug.append('/ignore:4197')
1598 1631
1599 1632 msvccompiler.MSVCCompiler = HackedMSVCCompiler
1600 1633
1601 1634 packagedata = {
1602 1635 'mercurial': [
1603 1636 'locale/*/LC_MESSAGES/hg.mo',
1604 1637 'dummycert.pem',
1605 1638 ],
1606 1639 'mercurial.defaultrc': [
1607 1640 '*.rc',
1608 1641 ],
1609 1642 'mercurial.helptext': [
1610 1643 '*.txt',
1611 1644 ],
1612 1645 'mercurial.helptext.internals': [
1613 1646 '*.txt',
1614 1647 ],
1615 1648 }
1616 1649
1617 1650
1618 1651 def ordinarypath(p):
1619 1652 return p and p[0] != '.' and p[-1] != '~'
1620 1653
1621 1654
1622 1655 for root in ('templates',):
1623 1656 for curdir, dirs, files in os.walk(os.path.join('mercurial', root)):
1624 1657 packagename = curdir.replace(os.sep, '.')
1625 1658 packagedata[packagename] = list(filter(ordinarypath, files))
1626 1659
1627 1660 datafiles = []
1628 1661
1629 1662 # distutils expects version to be str/unicode. Converting it to
1630 1663 # unicode on Python 2 still works because it won't contain any
1631 1664 # non-ascii bytes and will be implicitly converted back to bytes
1632 1665 # when operated on.
1633 1666 assert isinstance(version, str)
1634 1667 setupversion = version
1635 1668
1636 1669 extra = {}
1637 1670
1638 1671 py2exepackages = [
1639 1672 'hgdemandimport',
1640 1673 'hgext3rd',
1641 1674 'hgext',
1642 1675 'email',
1643 1676 # implicitly imported per module policy
1644 1677 # (cffi wouldn't be used as a frozen exe)
1645 1678 'mercurial.cext',
1646 1679 #'mercurial.cffi',
1647 1680 'mercurial.pure',
1648 1681 ]
1649 1682
1650 1683 py2exe_includes = []
1651 1684
1652 1685 py2exeexcludes = []
1653 1686 py2exedllexcludes = ['crypt32.dll']
1654 1687
1655 1688 if issetuptools:
1656 1689 extra['python_requires'] = supportedpy
1657 1690
1658 1691 if py2exeloaded:
1659 1692 extra['console'] = [
1660 1693 {
1661 1694 'script': 'hg',
1662 1695 'copyright': 'Copyright (C) 2005-2023 Olivia Mackall and others',
1663 1696 'product_version': version,
1664 1697 }
1665 1698 ]
1666 1699 # Sub command of 'build' because 'py2exe' does not handle sub_commands.
1667 1700 # Need to override hgbuild because it has a private copy of
1668 1701 # build.sub_commands.
1669 1702 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1670 1703 # put dlls in sub directory so that they won't pollute PATH
1671 1704 extra['zipfile'] = 'lib/library.zip'
1672 1705
1673 1706 # We allow some configuration to be supplemented via environment
1674 1707 # variables. This is better than setup.cfg files because it allows
1675 1708 # supplementing configs instead of replacing them.
1676 1709 extrapackages = os.environ.get('HG_PY2EXE_EXTRA_PACKAGES')
1677 1710 if extrapackages:
1678 1711 py2exepackages.extend(extrapackages.split(' '))
1679 1712
1680 1713 extra_includes = os.environ.get('HG_PY2EXE_EXTRA_INCLUDES')
1681 1714 if extra_includes:
1682 1715 py2exe_includes.extend(extra_includes.split(' '))
1683 1716
1684 1717 excludes = os.environ.get('HG_PY2EXE_EXTRA_EXCLUDES')
1685 1718 if excludes:
1686 1719 py2exeexcludes.extend(excludes.split(' '))
1687 1720
1688 1721 dllexcludes = os.environ.get('HG_PY2EXE_EXTRA_DLL_EXCLUDES')
1689 1722 if dllexcludes:
1690 1723 py2exedllexcludes.extend(dllexcludes.split(' '))
1691 1724
1692 1725 if os.environ.get('PYOXIDIZER'):
1693 1726 hgbuild.sub_commands.insert(0, ('build_hgextindex', None))
1694 1727
1695 1728 if os.name == 'nt':
1696 1729 # Windows binary file versions for exe/dll files must have the
1697 1730 # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
1698 1731 setupversion = setupversion.split(r'+', 1)[0]
1699 1732
1700 1733 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
1701 1734 version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
1702 1735 if version:
1703 1736 version = version[0].decode('utf-8')
1704 1737 xcode4 = version.startswith('Xcode') and StrictVersion(
1705 1738 version.split()[1]
1706 1739 ) >= StrictVersion('4.0')
1707 1740 xcode51 = re.match(r'^Xcode\s+5\.1', version) is not None
1708 1741 else:
1709 1742 # xcodebuild returns empty on OS X Lion with XCode 4.3 not
1710 1743 # installed, but instead with only command-line tools. Assume
1711 1744 # that only happens on >= Lion, thus no PPC support.
1712 1745 xcode4 = True
1713 1746 xcode51 = False
1714 1747
1715 1748 # XCode 4.0 dropped support for ppc architecture, which is hardcoded in
1716 1749 # distutils.sysconfig
1717 1750 if xcode4:
1718 1751 os.environ['ARCHFLAGS'] = ''
1719 1752
1720 1753 # XCode 5.1 changes clang such that it now fails to compile if the
1721 1754 # -mno-fused-madd flag is passed, but the version of Python shipped with
1722 1755 # OS X 10.9 Mavericks includes this flag. This causes problems in all
1723 1756 # C extension modules, and a bug has been filed upstream at
1724 1757 # http://bugs.python.org/issue21244. We also need to patch this here
1725 1758 # so Mercurial can continue to compile in the meantime.
1726 1759 if xcode51:
1727 1760 cflags = get_config_var('CFLAGS')
1728 1761 if cflags and re.search(r'-mno-fused-madd\b', cflags) is not None:
1729 1762 os.environ['CFLAGS'] = (
1730 1763 os.environ.get('CFLAGS', '') + ' -Qunused-arguments'
1731 1764 )
1732 1765
1733 1766 setup(
1734 1767 name='mercurial',
1735 1768 version=setupversion,
1736 1769 author='Olivia Mackall and many others',
1737 1770 author_email='mercurial@mercurial-scm.org',
1738 1771 url='https://mercurial-scm.org/',
1739 1772 download_url='https://mercurial-scm.org/release/',
1740 1773 description=(
1741 1774 'Fast scalable distributed SCM (revision control, version '
1742 1775 'control) system'
1743 1776 ),
1744 1777 long_description=(
1745 1778 'Mercurial is a distributed SCM tool written in Python.'
1746 1779 ' It is used by a number of large projects that require'
1747 1780 ' fast, reliable distributed revision control, such as '
1748 1781 'Mozilla.'
1749 1782 ),
1750 1783 license='GNU GPLv2 or any later version',
1751 1784 classifiers=[
1752 1785 'Development Status :: 6 - Mature',
1753 1786 'Environment :: Console',
1754 1787 'Intended Audience :: Developers',
1755 1788 'Intended Audience :: System Administrators',
1756 1789 'License :: OSI Approved :: GNU General Public License (GPL)',
1757 1790 'Natural Language :: Danish',
1758 1791 'Natural Language :: English',
1759 1792 'Natural Language :: German',
1760 1793 'Natural Language :: Italian',
1761 1794 'Natural Language :: Japanese',
1762 1795 'Natural Language :: Portuguese (Brazilian)',
1763 1796 'Operating System :: Microsoft :: Windows',
1764 1797 'Operating System :: OS Independent',
1765 1798 'Operating System :: POSIX',
1766 1799 'Programming Language :: C',
1767 1800 'Programming Language :: Python',
1768 1801 'Topic :: Software Development :: Version Control',
1769 1802 ],
1770 1803 scripts=scripts,
1771 1804 packages=packages,
1772 1805 ext_modules=extmodules,
1773 1806 data_files=datafiles,
1774 1807 package_data=packagedata,
1775 1808 cmdclass=cmdclass,
1776 1809 distclass=hgdist,
1777 1810 options={
1778 1811 'py2exe': {
1779 1812 'bundle_files': 3,
1780 1813 'dll_excludes': py2exedllexcludes,
1781 1814 'includes': py2exe_includes,
1782 1815 'excludes': py2exeexcludes,
1783 1816 'packages': py2exepackages,
1784 1817 },
1785 1818 'bdist_mpkg': {
1786 1819 'zipdist': False,
1787 1820 'license': 'COPYING',
1788 1821 'readme': 'contrib/packaging/macosx/Readme.html',
1789 1822 'welcome': 'contrib/packaging/macosx/Welcome.html',
1790 1823 },
1791 1824 },
1792 1825 **extra
1793 1826 )
General Comments 0
You need to be logged in to leave comments. Login now