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