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