Show More
@@ -1,258 +1,258 | |||||
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 | PYTHON?=python |
|
8 | PYTHON?=python | |
9 | $(eval HGROOT := $(shell pwd)) |
|
9 | $(eval HGROOT := $(shell pwd)) | |
10 | HGPYTHONS ?= $(HGROOT)/build/pythons |
|
10 | HGPYTHONS ?= $(HGROOT)/build/pythons | |
11 | PURE= |
|
11 | PURE= | |
12 | PYFILESCMD=find mercurial hgext doc -name '*.py' |
|
12 | PYFILESCMD=find mercurial hgext doc -name '*.py' | |
13 | PYFILES:=$(shell $(PYFILESCMD)) |
|
13 | PYFILES:=$(shell $(PYFILESCMD)) | |
14 | DOCFILES=mercurial/help/*.txt |
|
14 | DOCFILES=mercurial/helptext/*.txt | |
15 | export LANGUAGE=C |
|
15 | export LANGUAGE=C | |
16 | export LC_ALL=C |
|
16 | export LC_ALL=C | |
17 | TESTFLAGS ?= $(shell echo $$HGTESTFLAGS) |
|
17 | TESTFLAGS ?= $(shell echo $$HGTESTFLAGS) | |
18 | OSXVERSIONFLAGS ?= $(shell echo $$OSXVERSIONFLAGS) |
|
18 | OSXVERSIONFLAGS ?= $(shell echo $$OSXVERSIONFLAGS) | |
19 | CARGO = cargo |
|
19 | CARGO = cargo | |
20 |
|
20 | |||
21 | # Set this to e.g. "mingw32" to use a non-default compiler. |
|
21 | # Set this to e.g. "mingw32" to use a non-default compiler. | |
22 | COMPILER= |
|
22 | COMPILER= | |
23 |
|
23 | |||
24 | COMPILERFLAG_tmp_ = |
|
24 | COMPILERFLAG_tmp_ = | |
25 | COMPILERFLAG_tmp_${COMPILER} ?= -c $(COMPILER) |
|
25 | COMPILERFLAG_tmp_${COMPILER} ?= -c $(COMPILER) | |
26 | COMPILERFLAG=${COMPILERFLAG_tmp_${COMPILER}} |
|
26 | COMPILERFLAG=${COMPILERFLAG_tmp_${COMPILER}} | |
27 |
|
27 | |||
28 | help: |
|
28 | help: | |
29 | @echo 'Commonly used make targets:' |
|
29 | @echo 'Commonly used make targets:' | |
30 | @echo ' all - build program and documentation' |
|
30 | @echo ' all - build program and documentation' | |
31 | @echo ' install - install program and man pages to $$PREFIX ($(PREFIX))' |
|
31 | @echo ' install - install program and man pages to $$PREFIX ($(PREFIX))' | |
32 | @echo ' install-home - install with setup.py install --home=$$HOME ($(HOME))' |
|
32 | @echo ' install-home - install with setup.py install --home=$$HOME ($(HOME))' | |
33 | @echo ' local - build for inplace usage' |
|
33 | @echo ' local - build for inplace usage' | |
34 | @echo ' tests - run all tests in the automatic test suite' |
|
34 | @echo ' tests - run all tests in the automatic test suite' | |
35 | @echo ' test-foo - run only specified tests (e.g. test-merge1.t)' |
|
35 | @echo ' test-foo - run only specified tests (e.g. test-merge1.t)' | |
36 | @echo ' dist - run all tests and create a source tarball in dist/' |
|
36 | @echo ' dist - run all tests and create a source tarball in dist/' | |
37 | @echo ' clean - remove files created by other targets' |
|
37 | @echo ' clean - remove files created by other targets' | |
38 | @echo ' (except installed files or dist source tarball)' |
|
38 | @echo ' (except installed files or dist source tarball)' | |
39 | @echo ' update-pot - update i18n/hg.pot' |
|
39 | @echo ' update-pot - update i18n/hg.pot' | |
40 | @echo |
|
40 | @echo | |
41 | @echo 'Example for a system-wide installation under /usr/local:' |
|
41 | @echo 'Example for a system-wide installation under /usr/local:' | |
42 | @echo ' make all && su -c "make install" && hg version' |
|
42 | @echo ' make all && su -c "make install" && hg version' | |
43 | @echo |
|
43 | @echo | |
44 | @echo 'Example for a local installation (usable in this directory):' |
|
44 | @echo 'Example for a local installation (usable in this directory):' | |
45 | @echo ' make local && ./hg version' |
|
45 | @echo ' make local && ./hg version' | |
46 |
|
46 | |||
47 | all: build doc |
|
47 | all: build doc | |
48 |
|
48 | |||
49 | local: |
|
49 | local: | |
50 | $(PYTHON) setup.py $(PURE) \ |
|
50 | $(PYTHON) setup.py $(PURE) \ | |
51 | build_py -c -d . \ |
|
51 | build_py -c -d . \ | |
52 | build_ext $(COMPILERFLAG) -i \ |
|
52 | build_ext $(COMPILERFLAG) -i \ | |
53 | build_hgexe $(COMPILERFLAG) -i \ |
|
53 | build_hgexe $(COMPILERFLAG) -i \ | |
54 | build_mo |
|
54 | build_mo | |
55 | env HGRCPATH= $(PYTHON) hg version |
|
55 | env HGRCPATH= $(PYTHON) hg version | |
56 |
|
56 | |||
57 | build: |
|
57 | build: | |
58 | $(PYTHON) setup.py $(PURE) build $(COMPILERFLAG) |
|
58 | $(PYTHON) setup.py $(PURE) build $(COMPILERFLAG) | |
59 |
|
59 | |||
60 | wheel: |
|
60 | wheel: | |
61 | FORCE_SETUPTOOLS=1 $(PYTHON) setup.py $(PURE) bdist_wheel $(COMPILERFLAG) |
|
61 | FORCE_SETUPTOOLS=1 $(PYTHON) setup.py $(PURE) bdist_wheel $(COMPILERFLAG) | |
62 |
|
62 | |||
63 | doc: |
|
63 | doc: | |
64 | $(MAKE) -C doc |
|
64 | $(MAKE) -C doc | |
65 |
|
65 | |||
66 | cleanbutpackages: |
|
66 | cleanbutpackages: | |
67 | -$(PYTHON) setup.py clean --all # ignore errors from this command |
|
67 | -$(PYTHON) setup.py clean --all # ignore errors from this command | |
68 | find contrib doc hgext hgext3rd i18n mercurial tests hgdemandimport \ |
|
68 | find contrib doc hgext hgext3rd i18n mercurial tests hgdemandimport \ | |
69 | \( -name '*.py[cdo]' -o -name '*.so' \) -exec rm -f '{}' ';' |
|
69 | \( -name '*.py[cdo]' -o -name '*.so' \) -exec rm -f '{}' ';' | |
70 | rm -f MANIFEST MANIFEST.in hgext/__index__.py tests/*.err |
|
70 | rm -f MANIFEST MANIFEST.in hgext/__index__.py tests/*.err | |
71 | rm -f mercurial/__modulepolicy__.py |
|
71 | rm -f mercurial/__modulepolicy__.py | |
72 | if test -d .hg; then rm -f mercurial/__version__.py; fi |
|
72 | if test -d .hg; then rm -f mercurial/__version__.py; fi | |
73 | rm -rf build mercurial/locale |
|
73 | rm -rf build mercurial/locale | |
74 | $(MAKE) -C doc clean |
|
74 | $(MAKE) -C doc clean | |
75 | $(MAKE) -C contrib/chg distclean |
|
75 | $(MAKE) -C contrib/chg distclean | |
76 | rm -rf rust/target |
|
76 | rm -rf rust/target | |
77 | rm -f mercurial/rustext.so |
|
77 | rm -f mercurial/rustext.so | |
78 |
|
78 | |||
79 | clean: cleanbutpackages |
|
79 | clean: cleanbutpackages | |
80 | rm -rf packages |
|
80 | rm -rf packages | |
81 |
|
81 | |||
82 | install: install-bin install-doc |
|
82 | install: install-bin install-doc | |
83 |
|
83 | |||
84 | install-bin: build |
|
84 | install-bin: build | |
85 | $(PYTHON) setup.py $(PURE) install --root="$(DESTDIR)/" --prefix="$(PREFIX)" --force |
|
85 | $(PYTHON) setup.py $(PURE) install --root="$(DESTDIR)/" --prefix="$(PREFIX)" --force | |
86 |
|
86 | |||
87 | install-doc: doc |
|
87 | install-doc: doc | |
88 | cd doc && $(MAKE) $(MFLAGS) install |
|
88 | cd doc && $(MAKE) $(MFLAGS) install | |
89 |
|
89 | |||
90 | install-home: install-home-bin install-home-doc |
|
90 | install-home: install-home-bin install-home-doc | |
91 |
|
91 | |||
92 | install-home-bin: build |
|
92 | install-home-bin: build | |
93 | $(PYTHON) setup.py $(PURE) install --home="$(HOME)" --prefix="" --force |
|
93 | $(PYTHON) setup.py $(PURE) install --home="$(HOME)" --prefix="" --force | |
94 |
|
94 | |||
95 | install-home-doc: doc |
|
95 | install-home-doc: doc | |
96 | cd doc && $(MAKE) $(MFLAGS) PREFIX="$(HOME)" install |
|
96 | cd doc && $(MAKE) $(MFLAGS) PREFIX="$(HOME)" install | |
97 |
|
97 | |||
98 | MANIFEST-doc: |
|
98 | MANIFEST-doc: | |
99 | $(MAKE) -C doc MANIFEST |
|
99 | $(MAKE) -C doc MANIFEST | |
100 |
|
100 | |||
101 | MANIFEST.in: MANIFEST-doc |
|
101 | MANIFEST.in: MANIFEST-doc | |
102 | hg manifest | sed -e 's/^/include /' > MANIFEST.in |
|
102 | hg manifest | sed -e 's/^/include /' > MANIFEST.in | |
103 | echo include mercurial/__version__.py >> MANIFEST.in |
|
103 | echo include mercurial/__version__.py >> MANIFEST.in | |
104 | sed -e 's/^/include /' < doc/MANIFEST >> MANIFEST.in |
|
104 | sed -e 's/^/include /' < doc/MANIFEST >> MANIFEST.in | |
105 |
|
105 | |||
106 | dist: tests dist-notests |
|
106 | dist: tests dist-notests | |
107 |
|
107 | |||
108 | dist-notests: doc MANIFEST.in |
|
108 | dist-notests: doc MANIFEST.in | |
109 | TAR_OPTIONS="--owner=root --group=root --mode=u+w,go-w,a+rX-s" $(PYTHON) setup.py -q sdist |
|
109 | TAR_OPTIONS="--owner=root --group=root --mode=u+w,go-w,a+rX-s" $(PYTHON) setup.py -q sdist | |
110 |
|
110 | |||
111 | check: tests |
|
111 | check: tests | |
112 |
|
112 | |||
113 | tests: |
|
113 | tests: | |
114 | # Run Rust tests if cargo is installed |
|
114 | # Run Rust tests if cargo is installed | |
115 | if command -v $(CARGO) >/dev/null 2>&1; then \ |
|
115 | if command -v $(CARGO) >/dev/null 2>&1; then \ | |
116 | $(MAKE) rust-tests; \ |
|
116 | $(MAKE) rust-tests; \ | |
117 | fi |
|
117 | fi | |
118 | cd tests && $(PYTHON) run-tests.py $(TESTFLAGS) |
|
118 | cd tests && $(PYTHON) run-tests.py $(TESTFLAGS) | |
119 |
|
119 | |||
120 | test-%: |
|
120 | test-%: | |
121 | cd tests && $(PYTHON) run-tests.py $(TESTFLAGS) $@ |
|
121 | cd tests && $(PYTHON) run-tests.py $(TESTFLAGS) $@ | |
122 |
|
122 | |||
123 | testpy-%: |
|
123 | testpy-%: | |
124 | @echo Looking for Python $* in $(HGPYTHONS) |
|
124 | @echo Looking for Python $* in $(HGPYTHONS) | |
125 | [ -e $(HGPYTHONS)/$*/bin/python ] || ( \ |
|
125 | [ -e $(HGPYTHONS)/$*/bin/python ] || ( \ | |
126 | cd $$(mktemp --directory --tmpdir) && \ |
|
126 | cd $$(mktemp --directory --tmpdir) && \ | |
127 | $(MAKE) -f $(HGROOT)/contrib/Makefile.python PYTHONVER=$* PREFIX=$(HGPYTHONS)/$* python ) |
|
127 | $(MAKE) -f $(HGROOT)/contrib/Makefile.python PYTHONVER=$* PREFIX=$(HGPYTHONS)/$* python ) | |
128 | cd tests && $(HGPYTHONS)/$*/bin/python run-tests.py $(TESTFLAGS) |
|
128 | cd tests && $(HGPYTHONS)/$*/bin/python run-tests.py $(TESTFLAGS) | |
129 |
|
129 | |||
130 | rust-tests: py_feature = $(shell $(PYTHON) -c \ |
|
130 | rust-tests: py_feature = $(shell $(PYTHON) -c \ | |
131 | 'import sys; print(["python27-bin", "python3-bin"][sys.version_info[0] >= 3])') |
|
131 | 'import sys; print(["python27-bin", "python3-bin"][sys.version_info[0] >= 3])') | |
132 | rust-tests: |
|
132 | rust-tests: | |
133 | cd $(HGROOT)/rust/hg-cpython \ |
|
133 | cd $(HGROOT)/rust/hg-cpython \ | |
134 | && $(CARGO) test --quiet --all \ |
|
134 | && $(CARGO) test --quiet --all \ | |
135 | --no-default-features --features "$(py_feature)" |
|
135 | --no-default-features --features "$(py_feature)" | |
136 |
|
136 | |||
137 | check-code: |
|
137 | check-code: | |
138 | hg manifest | xargs python contrib/check-code.py |
|
138 | hg manifest | xargs python contrib/check-code.py | |
139 |
|
139 | |||
140 | format-c: |
|
140 | format-c: | |
141 | clang-format --style file -i \ |
|
141 | clang-format --style file -i \ | |
142 | `hg files 'set:(**.c or **.cc or **.h) and not "listfile:contrib/clang-format-ignorelist"'` |
|
142 | `hg files 'set:(**.c or **.cc or **.h) and not "listfile:contrib/clang-format-ignorelist"'` | |
143 |
|
143 | |||
144 | update-pot: i18n/hg.pot |
|
144 | update-pot: i18n/hg.pot | |
145 |
|
145 | |||
146 | i18n/hg.pot: $(PYFILES) $(DOCFILES) i18n/posplit i18n/hggettext |
|
146 | i18n/hg.pot: $(PYFILES) $(DOCFILES) i18n/posplit i18n/hggettext | |
147 | $(PYTHON) i18n/hggettext mercurial/commands.py \ |
|
147 | $(PYTHON) i18n/hggettext mercurial/commands.py \ | |
148 | hgext/*.py hgext/*/__init__.py \ |
|
148 | hgext/*.py hgext/*/__init__.py \ | |
149 | mercurial/fileset.py mercurial/revset.py \ |
|
149 | mercurial/fileset.py mercurial/revset.py \ | |
150 | mercurial/templatefilters.py \ |
|
150 | mercurial/templatefilters.py \ | |
151 | mercurial/templatefuncs.py \ |
|
151 | mercurial/templatefuncs.py \ | |
152 | mercurial/templatekw.py \ |
|
152 | mercurial/templatekw.py \ | |
153 | mercurial/filemerge.py \ |
|
153 | mercurial/filemerge.py \ | |
154 | mercurial/hgweb/webcommands.py \ |
|
154 | mercurial/hgweb/webcommands.py \ | |
155 | mercurial/util.py \ |
|
155 | mercurial/util.py \ | |
156 | $(DOCFILES) > i18n/hg.pot.tmp |
|
156 | $(DOCFILES) > i18n/hg.pot.tmp | |
157 | # All strings marked for translation in Mercurial contain |
|
157 | # All strings marked for translation in Mercurial contain | |
158 | # ASCII characters only. But some files contain string |
|
158 | # ASCII characters only. But some files contain string | |
159 | # literals like this '\037\213'. xgettext thinks it has to |
|
159 | # literals like this '\037\213'. xgettext thinks it has to | |
160 | # parse them even though they are not marked for translation. |
|
160 | # parse them even though they are not marked for translation. | |
161 | # Extracting with an explicit encoding of ISO-8859-1 will make |
|
161 | # Extracting with an explicit encoding of ISO-8859-1 will make | |
162 | # xgettext "parse" and ignore them. |
|
162 | # xgettext "parse" and ignore them. | |
163 | $(PYFILESCMD) | xargs \ |
|
163 | $(PYFILESCMD) | xargs \ | |
164 | xgettext --package-name "Mercurial" \ |
|
164 | xgettext --package-name "Mercurial" \ | |
165 | --msgid-bugs-address "<mercurial-devel@mercurial-scm.org>" \ |
|
165 | --msgid-bugs-address "<mercurial-devel@mercurial-scm.org>" \ | |
166 | --copyright-holder "Matt Mackall <mpm@selenic.com> and others" \ |
|
166 | --copyright-holder "Matt Mackall <mpm@selenic.com> and others" \ | |
167 | --from-code ISO-8859-1 --join --sort-by-file --add-comments=i18n: \ |
|
167 | --from-code ISO-8859-1 --join --sort-by-file --add-comments=i18n: \ | |
168 | -d hg -p i18n -o hg.pot.tmp |
|
168 | -d hg -p i18n -o hg.pot.tmp | |
169 | $(PYTHON) i18n/posplit i18n/hg.pot.tmp |
|
169 | $(PYTHON) i18n/posplit i18n/hg.pot.tmp | |
170 | # The target file is not created before the last step. So it never is in |
|
170 | # The target file is not created before the last step. So it never is in | |
171 | # an intermediate state. |
|
171 | # an intermediate state. | |
172 | mv -f i18n/hg.pot.tmp i18n/hg.pot |
|
172 | mv -f i18n/hg.pot.tmp i18n/hg.pot | |
173 |
|
173 | |||
174 | %.po: i18n/hg.pot |
|
174 | %.po: i18n/hg.pot | |
175 | # work on a temporary copy for never having a half completed target |
|
175 | # work on a temporary copy for never having a half completed target | |
176 | cp $@ $@.tmp |
|
176 | cp $@ $@.tmp | |
177 | msgmerge --no-location --update $@.tmp $^ |
|
177 | msgmerge --no-location --update $@.tmp $^ | |
178 | mv -f $@.tmp $@ |
|
178 | mv -f $@.tmp $@ | |
179 |
|
179 | |||
180 | # Packaging targets |
|
180 | # Packaging targets | |
181 |
|
181 | |||
182 | packaging_targets := \ |
|
182 | packaging_targets := \ | |
183 | centos5 \ |
|
183 | centos5 \ | |
184 | centos6 \ |
|
184 | centos6 \ | |
185 | centos7 \ |
|
185 | centos7 \ | |
186 | centos8 \ |
|
186 | centos8 \ | |
187 | deb \ |
|
187 | deb \ | |
188 | docker-centos5 \ |
|
188 | docker-centos5 \ | |
189 | docker-centos6 \ |
|
189 | docker-centos6 \ | |
190 | docker-centos7 \ |
|
190 | docker-centos7 \ | |
191 | docker-centos8 \ |
|
191 | docker-centos8 \ | |
192 | docker-debian-bullseye \ |
|
192 | docker-debian-bullseye \ | |
193 | docker-debian-buster \ |
|
193 | docker-debian-buster \ | |
194 | docker-debian-stretch \ |
|
194 | docker-debian-stretch \ | |
195 | docker-fedora \ |
|
195 | docker-fedora \ | |
196 | docker-ubuntu-trusty \ |
|
196 | docker-ubuntu-trusty \ | |
197 | docker-ubuntu-trusty-ppa \ |
|
197 | docker-ubuntu-trusty-ppa \ | |
198 | docker-ubuntu-xenial \ |
|
198 | docker-ubuntu-xenial \ | |
199 | docker-ubuntu-xenial-ppa \ |
|
199 | docker-ubuntu-xenial-ppa \ | |
200 | docker-ubuntu-artful \ |
|
200 | docker-ubuntu-artful \ | |
201 | docker-ubuntu-artful-ppa \ |
|
201 | docker-ubuntu-artful-ppa \ | |
202 | docker-ubuntu-bionic \ |
|
202 | docker-ubuntu-bionic \ | |
203 | docker-ubuntu-bionic-ppa \ |
|
203 | docker-ubuntu-bionic-ppa \ | |
204 | fedora \ |
|
204 | fedora \ | |
205 | linux-wheels \ |
|
205 | linux-wheels \ | |
206 | linux-wheels-x86_64 \ |
|
206 | linux-wheels-x86_64 \ | |
207 | linux-wheels-i686 \ |
|
207 | linux-wheels-i686 \ | |
208 | ppa |
|
208 | ppa | |
209 |
|
209 | |||
210 | # Forward packaging targets for convenience. |
|
210 | # Forward packaging targets for convenience. | |
211 | $(packaging_targets): |
|
211 | $(packaging_targets): | |
212 | $(MAKE) -C contrib/packaging $@ |
|
212 | $(MAKE) -C contrib/packaging $@ | |
213 |
|
213 | |||
214 | osx: |
|
214 | osx: | |
215 | rm -rf build/mercurial |
|
215 | rm -rf build/mercurial | |
216 | /usr/bin/python2.7 setup.py install --optimize=1 \ |
|
216 | /usr/bin/python2.7 setup.py install --optimize=1 \ | |
217 | --root=build/mercurial/ --prefix=/usr/local/ \ |
|
217 | --root=build/mercurial/ --prefix=/usr/local/ \ | |
218 | --install-lib=/Library/Python/2.7/site-packages/ |
|
218 | --install-lib=/Library/Python/2.7/site-packages/ | |
219 | make -C doc all install DESTDIR="$(PWD)/build/mercurial/" |
|
219 | make -C doc all install DESTDIR="$(PWD)/build/mercurial/" | |
220 | # Place a bogon .DS_Store file in the target dir so we can be |
|
220 | # Place a bogon .DS_Store file in the target dir so we can be | |
221 | # sure it doesn't get included in the final package. |
|
221 | # sure it doesn't get included in the final package. | |
222 | touch build/mercurial/.DS_Store |
|
222 | touch build/mercurial/.DS_Store | |
223 | # install zsh completions - this location appears to be |
|
223 | # install zsh completions - this location appears to be | |
224 | # searched by default as of macOS Sierra. |
|
224 | # searched by default as of macOS Sierra. | |
225 | install -d build/mercurial/usr/local/share/zsh/site-functions/ |
|
225 | install -d build/mercurial/usr/local/share/zsh/site-functions/ | |
226 | install -m 0644 contrib/zsh_completion build/mercurial/usr/local/share/zsh/site-functions/_hg |
|
226 | install -m 0644 contrib/zsh_completion build/mercurial/usr/local/share/zsh/site-functions/_hg | |
227 | # install bash completions - there doesn't appear to be a |
|
227 | # install bash completions - there doesn't appear to be a | |
228 | # place that's searched by default for bash, so we'll follow |
|
228 | # place that's searched by default for bash, so we'll follow | |
229 | # the lead of Apple's git install and just put it in a |
|
229 | # the lead of Apple's git install and just put it in a | |
230 | # location of our own. |
|
230 | # location of our own. | |
231 | install -d build/mercurial/usr/local/hg/contrib/ |
|
231 | install -d build/mercurial/usr/local/hg/contrib/ | |
232 | install -m 0644 contrib/bash_completion build/mercurial/usr/local/hg/contrib/hg-completion.bash |
|
232 | install -m 0644 contrib/bash_completion build/mercurial/usr/local/hg/contrib/hg-completion.bash | |
233 | make -C contrib/chg \ |
|
233 | make -C contrib/chg \ | |
234 | HGPATH=/usr/local/bin/hg \ |
|
234 | HGPATH=/usr/local/bin/hg \ | |
235 | PYTHON=/usr/bin/python2.7 \ |
|
235 | PYTHON=/usr/bin/python2.7 \ | |
236 | HGEXTDIR=/Library/Python/2.7/site-packages/hgext \ |
|
236 | HGEXTDIR=/Library/Python/2.7/site-packages/hgext \ | |
237 | DESTDIR=../../build/mercurial \ |
|
237 | DESTDIR=../../build/mercurial \ | |
238 | PREFIX=/usr/local \ |
|
238 | PREFIX=/usr/local \ | |
239 | clean install |
|
239 | clean install | |
240 | mkdir -p $${OUTPUTDIR:-dist} |
|
240 | mkdir -p $${OUTPUTDIR:-dist} | |
241 | HGVER=$$(python contrib/genosxversion.py $(OSXVERSIONFLAGS) build/mercurial/Library/Python/2.7/site-packages/mercurial/__version__.py) && \ |
|
241 | HGVER=$$(python contrib/genosxversion.py $(OSXVERSIONFLAGS) build/mercurial/Library/Python/2.7/site-packages/mercurial/__version__.py) && \ | |
242 | OSXVER=$$(sw_vers -productVersion | cut -d. -f1,2) && \ |
|
242 | OSXVER=$$(sw_vers -productVersion | cut -d. -f1,2) && \ | |
243 | pkgbuild --filter \\.DS_Store --root build/mercurial/ \ |
|
243 | pkgbuild --filter \\.DS_Store --root build/mercurial/ \ | |
244 | --identifier org.mercurial-scm.mercurial \ |
|
244 | --identifier org.mercurial-scm.mercurial \ | |
245 | --version "$${HGVER}" \ |
|
245 | --version "$${HGVER}" \ | |
246 | build/mercurial.pkg && \ |
|
246 | build/mercurial.pkg && \ | |
247 | productbuild --distribution contrib/packaging/macosx/distribution.xml \ |
|
247 | productbuild --distribution contrib/packaging/macosx/distribution.xml \ | |
248 | --package-path build/ \ |
|
248 | --package-path build/ \ | |
249 | --version "$${HGVER}" \ |
|
249 | --version "$${HGVER}" \ | |
250 | --resources contrib/packaging/macosx/ \ |
|
250 | --resources contrib/packaging/macosx/ \ | |
251 | "$${OUTPUTDIR:-dist/}"/Mercurial-"$${HGVER}"-macosx"$${OSXVER}".pkg |
|
251 | "$${OUTPUTDIR:-dist/}"/Mercurial-"$${HGVER}"-macosx"$${OSXVER}".pkg | |
252 |
|
252 | |||
253 | .PHONY: help all local build doc cleanbutpackages clean install install-bin \ |
|
253 | .PHONY: help all local build doc cleanbutpackages clean install install-bin \ | |
254 | install-doc install-home install-home-bin install-home-doc \ |
|
254 | install-doc install-home install-home-bin install-home-doc \ | |
255 | dist dist-notests check tests rust-tests check-code format-c \ |
|
255 | dist dist-notests check tests rust-tests check-code format-c \ | |
256 | update-pot \ |
|
256 | update-pot \ | |
257 | $(packaging_targets) \ |
|
257 | $(packaging_targets) \ | |
258 | osx |
|
258 | osx |
@@ -1,245 +1,245 | |||||
1 | # py2exe.py - Functionality for performing py2exe builds. |
|
1 | # py2exe.py - Functionality for performing py2exe builds. | |
2 | # |
|
2 | # | |
3 | # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com> |
|
3 | # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com> | |
4 | # |
|
4 | # | |
5 | # This software may be used and distributed according to the terms of the |
|
5 | # This software may be used and distributed according to the terms of the | |
6 | # GNU General Public License version 2 or any later version. |
|
6 | # GNU General Public License version 2 or any later version. | |
7 |
|
7 | |||
8 | # no-check-code because Python 3 native. |
|
8 | # no-check-code because Python 3 native. | |
9 |
|
9 | |||
10 | import os |
|
10 | import os | |
11 | import pathlib |
|
11 | import pathlib | |
12 | import subprocess |
|
12 | import subprocess | |
13 |
|
13 | |||
14 | from .downloads import download_entry |
|
14 | from .downloads import download_entry | |
15 | from .util import ( |
|
15 | from .util import ( | |
16 | extract_tar_to_directory, |
|
16 | extract_tar_to_directory, | |
17 | extract_zip_to_directory, |
|
17 | extract_zip_to_directory, | |
18 | process_install_rules, |
|
18 | process_install_rules, | |
19 | python_exe_info, |
|
19 | python_exe_info, | |
20 | ) |
|
20 | ) | |
21 |
|
21 | |||
22 |
|
22 | |||
23 | STAGING_RULES = [ |
|
23 | STAGING_RULES = [ | |
24 | ('contrib/bash_completion', 'Contrib/'), |
|
24 | ('contrib/bash_completion', 'Contrib/'), | |
25 | ('contrib/hgk', 'Contrib/hgk.tcl'), |
|
25 | ('contrib/hgk', 'Contrib/hgk.tcl'), | |
26 | ('contrib/hgweb.fcgi', 'Contrib/'), |
|
26 | ('contrib/hgweb.fcgi', 'Contrib/'), | |
27 | ('contrib/hgweb.wsgi', 'Contrib/'), |
|
27 | ('contrib/hgweb.wsgi', 'Contrib/'), | |
28 | ('contrib/logo-droplets.svg', 'Contrib/'), |
|
28 | ('contrib/logo-droplets.svg', 'Contrib/'), | |
29 | ('contrib/mercurial.el', 'Contrib/'), |
|
29 | ('contrib/mercurial.el', 'Contrib/'), | |
30 | ('contrib/mq.el', 'Contrib/'), |
|
30 | ('contrib/mq.el', 'Contrib/'), | |
31 | ('contrib/tcsh_completion', 'Contrib/'), |
|
31 | ('contrib/tcsh_completion', 'Contrib/'), | |
32 | ('contrib/tcsh_completion_build.sh', 'Contrib/'), |
|
32 | ('contrib/tcsh_completion_build.sh', 'Contrib/'), | |
33 | ('contrib/vim/*', 'Contrib/Vim/'), |
|
33 | ('contrib/vim/*', 'Contrib/Vim/'), | |
34 | ('contrib/win32/postinstall.txt', 'ReleaseNotes.txt'), |
|
34 | ('contrib/win32/postinstall.txt', 'ReleaseNotes.txt'), | |
35 | ('contrib/win32/ReadMe.html', 'ReadMe.html'), |
|
35 | ('contrib/win32/ReadMe.html', 'ReadMe.html'), | |
36 | ('contrib/xml.rnc', 'Contrib/'), |
|
36 | ('contrib/xml.rnc', 'Contrib/'), | |
37 | ('contrib/zsh_completion', 'Contrib/'), |
|
37 | ('contrib/zsh_completion', 'Contrib/'), | |
38 | ('dist/hg.exe', './'), |
|
38 | ('dist/hg.exe', './'), | |
39 | ('dist/lib/*.dll', 'lib/'), |
|
39 | ('dist/lib/*.dll', 'lib/'), | |
40 | ('dist/lib/*.pyd', 'lib/'), |
|
40 | ('dist/lib/*.pyd', 'lib/'), | |
41 | ('dist/lib/library.zip', 'lib/'), |
|
41 | ('dist/lib/library.zip', 'lib/'), | |
42 | ('dist/Microsoft.VC*.CRT.manifest', './'), |
|
42 | ('dist/Microsoft.VC*.CRT.manifest', './'), | |
43 | ('dist/msvc*.dll', './'), |
|
43 | ('dist/msvc*.dll', './'), | |
44 | ('dist/python*.dll', './'), |
|
44 | ('dist/python*.dll', './'), | |
45 | ('doc/*.html', 'doc/'), |
|
45 | ('doc/*.html', 'doc/'), | |
46 | ('doc/style.css', 'doc/'), |
|
46 | ('doc/style.css', 'doc/'), | |
47 | ('mercurial/help/**/*.txt', 'help/'), |
|
47 | ('mercurial/helptext/**/*.txt', 'helptext/'), | |
48 | ('mercurial/default.d/*.rc', 'hgrc.d/'), |
|
48 | ('mercurial/default.d/*.rc', 'hgrc.d/'), | |
49 | ('mercurial/locale/**/*', 'locale/'), |
|
49 | ('mercurial/locale/**/*', 'locale/'), | |
50 | ('mercurial/templates/**/*', 'Templates/'), |
|
50 | ('mercurial/templates/**/*', 'Templates/'), | |
51 | ('COPYING', 'Copying.txt'), |
|
51 | ('COPYING', 'Copying.txt'), | |
52 | ] |
|
52 | ] | |
53 |
|
53 | |||
54 | # List of paths to exclude from the staging area. |
|
54 | # List of paths to exclude from the staging area. | |
55 | STAGING_EXCLUDES = [ |
|
55 | STAGING_EXCLUDES = [ | |
56 | 'doc/hg-ssh.8.html', |
|
56 | 'doc/hg-ssh.8.html', | |
57 | ] |
|
57 | ] | |
58 |
|
58 | |||
59 |
|
59 | |||
60 | def build_py2exe( |
|
60 | def build_py2exe( | |
61 | source_dir: pathlib.Path, |
|
61 | source_dir: pathlib.Path, | |
62 | build_dir: pathlib.Path, |
|
62 | build_dir: pathlib.Path, | |
63 | python_exe: pathlib.Path, |
|
63 | python_exe: pathlib.Path, | |
64 | build_name: str, |
|
64 | build_name: str, | |
65 | venv_requirements_txt: pathlib.Path, |
|
65 | venv_requirements_txt: pathlib.Path, | |
66 | extra_packages=None, |
|
66 | extra_packages=None, | |
67 | extra_excludes=None, |
|
67 | extra_excludes=None, | |
68 | extra_dll_excludes=None, |
|
68 | extra_dll_excludes=None, | |
69 | extra_packages_script=None, |
|
69 | extra_packages_script=None, | |
70 | ): |
|
70 | ): | |
71 | """Build Mercurial with py2exe. |
|
71 | """Build Mercurial with py2exe. | |
72 |
|
72 | |||
73 | Build files will be placed in ``build_dir``. |
|
73 | Build files will be placed in ``build_dir``. | |
74 |
|
74 | |||
75 | py2exe's setup.py doesn't use setuptools. It doesn't have modern logic |
|
75 | py2exe's setup.py doesn't use setuptools. It doesn't have modern logic | |
76 | for finding the Python 2.7 toolchain. So, we require the environment |
|
76 | for finding the Python 2.7 toolchain. So, we require the environment | |
77 | to already be configured with an active toolchain. |
|
77 | to already be configured with an active toolchain. | |
78 | """ |
|
78 | """ | |
79 | if 'VCINSTALLDIR' not in os.environ: |
|
79 | if 'VCINSTALLDIR' not in os.environ: | |
80 | raise Exception( |
|
80 | raise Exception( | |
81 | 'not running from a Visual C++ build environment; ' |
|
81 | 'not running from a Visual C++ build environment; ' | |
82 | 'execute the "Visual C++ <version> Command Prompt" ' |
|
82 | 'execute the "Visual C++ <version> Command Prompt" ' | |
83 | 'application shortcut or a vcsvarsall.bat file' |
|
83 | 'application shortcut or a vcsvarsall.bat file' | |
84 | ) |
|
84 | ) | |
85 |
|
85 | |||
86 | # Identity x86/x64 and validate the environment matches the Python |
|
86 | # Identity x86/x64 and validate the environment matches the Python | |
87 | # architecture. |
|
87 | # architecture. | |
88 | vc_x64 = r'\x64' in os.environ['LIB'] |
|
88 | vc_x64 = r'\x64' in os.environ['LIB'] | |
89 |
|
89 | |||
90 | py_info = python_exe_info(python_exe) |
|
90 | py_info = python_exe_info(python_exe) | |
91 |
|
91 | |||
92 | if vc_x64: |
|
92 | if vc_x64: | |
93 | if py_info['arch'] != '64bit': |
|
93 | if py_info['arch'] != '64bit': | |
94 | raise Exception( |
|
94 | raise Exception( | |
95 | 'architecture mismatch: Visual C++ environment ' |
|
95 | 'architecture mismatch: Visual C++ environment ' | |
96 | 'is configured for 64-bit but Python is 32-bit' |
|
96 | 'is configured for 64-bit but Python is 32-bit' | |
97 | ) |
|
97 | ) | |
98 | else: |
|
98 | else: | |
99 | if py_info['arch'] != '32bit': |
|
99 | if py_info['arch'] != '32bit': | |
100 | raise Exception( |
|
100 | raise Exception( | |
101 | 'architecture mismatch: Visual C++ environment ' |
|
101 | 'architecture mismatch: Visual C++ environment ' | |
102 | 'is configured for 32-bit but Python is 64-bit' |
|
102 | 'is configured for 32-bit but Python is 64-bit' | |
103 | ) |
|
103 | ) | |
104 |
|
104 | |||
105 | if py_info['py3']: |
|
105 | if py_info['py3']: | |
106 | raise Exception('Only Python 2 is currently supported') |
|
106 | raise Exception('Only Python 2 is currently supported') | |
107 |
|
107 | |||
108 | build_dir.mkdir(exist_ok=True) |
|
108 | build_dir.mkdir(exist_ok=True) | |
109 |
|
109 | |||
110 | gettext_pkg, gettext_entry = download_entry('gettext', build_dir) |
|
110 | gettext_pkg, gettext_entry = download_entry('gettext', build_dir) | |
111 | gettext_dep_pkg = download_entry('gettext-dep', build_dir)[0] |
|
111 | gettext_dep_pkg = download_entry('gettext-dep', build_dir)[0] | |
112 | virtualenv_pkg, virtualenv_entry = download_entry('virtualenv', build_dir) |
|
112 | virtualenv_pkg, virtualenv_entry = download_entry('virtualenv', build_dir) | |
113 | py2exe_pkg, py2exe_entry = download_entry('py2exe', build_dir) |
|
113 | py2exe_pkg, py2exe_entry = download_entry('py2exe', build_dir) | |
114 |
|
114 | |||
115 | venv_path = build_dir / ( |
|
115 | venv_path = build_dir / ( | |
116 | 'venv-%s-%s' % (build_name, 'x64' if vc_x64 else 'x86') |
|
116 | 'venv-%s-%s' % (build_name, 'x64' if vc_x64 else 'x86') | |
117 | ) |
|
117 | ) | |
118 |
|
118 | |||
119 | gettext_root = build_dir / ('gettext-win-%s' % gettext_entry['version']) |
|
119 | gettext_root = build_dir / ('gettext-win-%s' % gettext_entry['version']) | |
120 |
|
120 | |||
121 | if not gettext_root.exists(): |
|
121 | if not gettext_root.exists(): | |
122 | extract_zip_to_directory(gettext_pkg, gettext_root) |
|
122 | extract_zip_to_directory(gettext_pkg, gettext_root) | |
123 | extract_zip_to_directory(gettext_dep_pkg, gettext_root) |
|
123 | extract_zip_to_directory(gettext_dep_pkg, gettext_root) | |
124 |
|
124 | |||
125 | # This assumes Python 2. We don't need virtualenv on Python 3. |
|
125 | # This assumes Python 2. We don't need virtualenv on Python 3. | |
126 | virtualenv_src_path = build_dir / ( |
|
126 | virtualenv_src_path = build_dir / ( | |
127 | 'virtualenv-%s' % virtualenv_entry['version'] |
|
127 | 'virtualenv-%s' % virtualenv_entry['version'] | |
128 | ) |
|
128 | ) | |
129 | virtualenv_py = virtualenv_src_path / 'virtualenv.py' |
|
129 | virtualenv_py = virtualenv_src_path / 'virtualenv.py' | |
130 |
|
130 | |||
131 | if not virtualenv_src_path.exists(): |
|
131 | if not virtualenv_src_path.exists(): | |
132 | extract_tar_to_directory(virtualenv_pkg, build_dir) |
|
132 | extract_tar_to_directory(virtualenv_pkg, build_dir) | |
133 |
|
133 | |||
134 | py2exe_source_path = build_dir / ('py2exe-%s' % py2exe_entry['version']) |
|
134 | py2exe_source_path = build_dir / ('py2exe-%s' % py2exe_entry['version']) | |
135 |
|
135 | |||
136 | if not py2exe_source_path.exists(): |
|
136 | if not py2exe_source_path.exists(): | |
137 | extract_zip_to_directory(py2exe_pkg, build_dir) |
|
137 | extract_zip_to_directory(py2exe_pkg, build_dir) | |
138 |
|
138 | |||
139 | if not venv_path.exists(): |
|
139 | if not venv_path.exists(): | |
140 | print('creating virtualenv with dependencies') |
|
140 | print('creating virtualenv with dependencies') | |
141 | subprocess.run( |
|
141 | subprocess.run( | |
142 | [str(python_exe), str(virtualenv_py), str(venv_path)], check=True |
|
142 | [str(python_exe), str(virtualenv_py), str(venv_path)], check=True | |
143 | ) |
|
143 | ) | |
144 |
|
144 | |||
145 | venv_python = venv_path / 'Scripts' / 'python.exe' |
|
145 | venv_python = venv_path / 'Scripts' / 'python.exe' | |
146 | venv_pip = venv_path / 'Scripts' / 'pip.exe' |
|
146 | venv_pip = venv_path / 'Scripts' / 'pip.exe' | |
147 |
|
147 | |||
148 | subprocess.run( |
|
148 | subprocess.run( | |
149 | [str(venv_pip), 'install', '-r', str(venv_requirements_txt)], check=True |
|
149 | [str(venv_pip), 'install', '-r', str(venv_requirements_txt)], check=True | |
150 | ) |
|
150 | ) | |
151 |
|
151 | |||
152 | # Force distutils to use VC++ settings from environment, which was |
|
152 | # Force distutils to use VC++ settings from environment, which was | |
153 | # validated above. |
|
153 | # validated above. | |
154 | env = dict(os.environ) |
|
154 | env = dict(os.environ) | |
155 | env['DISTUTILS_USE_SDK'] = '1' |
|
155 | env['DISTUTILS_USE_SDK'] = '1' | |
156 | env['MSSdk'] = '1' |
|
156 | env['MSSdk'] = '1' | |
157 |
|
157 | |||
158 | if extra_packages_script: |
|
158 | if extra_packages_script: | |
159 | more_packages = set( |
|
159 | more_packages = set( | |
160 | subprocess.check_output(extra_packages_script, cwd=build_dir) |
|
160 | subprocess.check_output(extra_packages_script, cwd=build_dir) | |
161 | .split(b'\0')[-1] |
|
161 | .split(b'\0')[-1] | |
162 | .strip() |
|
162 | .strip() | |
163 | .decode('utf-8') |
|
163 | .decode('utf-8') | |
164 | .splitlines() |
|
164 | .splitlines() | |
165 | ) |
|
165 | ) | |
166 | if more_packages: |
|
166 | if more_packages: | |
167 | if not extra_packages: |
|
167 | if not extra_packages: | |
168 | extra_packages = more_packages |
|
168 | extra_packages = more_packages | |
169 | else: |
|
169 | else: | |
170 | extra_packages |= more_packages |
|
170 | extra_packages |= more_packages | |
171 |
|
171 | |||
172 | if extra_packages: |
|
172 | if extra_packages: | |
173 | env['HG_PY2EXE_EXTRA_PACKAGES'] = ' '.join(sorted(extra_packages)) |
|
173 | env['HG_PY2EXE_EXTRA_PACKAGES'] = ' '.join(sorted(extra_packages)) | |
174 | hgext3rd_extras = sorted( |
|
174 | hgext3rd_extras = sorted( | |
175 | e for e in extra_packages if e.startswith('hgext3rd.') |
|
175 | e for e in extra_packages if e.startswith('hgext3rd.') | |
176 | ) |
|
176 | ) | |
177 | if hgext3rd_extras: |
|
177 | if hgext3rd_extras: | |
178 | env['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'] = ' '.join(hgext3rd_extras) |
|
178 | env['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'] = ' '.join(hgext3rd_extras) | |
179 | if extra_excludes: |
|
179 | if extra_excludes: | |
180 | env['HG_PY2EXE_EXTRA_EXCLUDES'] = ' '.join(sorted(extra_excludes)) |
|
180 | env['HG_PY2EXE_EXTRA_EXCLUDES'] = ' '.join(sorted(extra_excludes)) | |
181 | if extra_dll_excludes: |
|
181 | if extra_dll_excludes: | |
182 | env['HG_PY2EXE_EXTRA_DLL_EXCLUDES'] = ' '.join( |
|
182 | env['HG_PY2EXE_EXTRA_DLL_EXCLUDES'] = ' '.join( | |
183 | sorted(extra_dll_excludes) |
|
183 | sorted(extra_dll_excludes) | |
184 | ) |
|
184 | ) | |
185 |
|
185 | |||
186 | py2exe_py_path = venv_path / 'Lib' / 'site-packages' / 'py2exe' |
|
186 | py2exe_py_path = venv_path / 'Lib' / 'site-packages' / 'py2exe' | |
187 | if not py2exe_py_path.exists(): |
|
187 | if not py2exe_py_path.exists(): | |
188 | print('building py2exe') |
|
188 | print('building py2exe') | |
189 | subprocess.run( |
|
189 | subprocess.run( | |
190 | [str(venv_python), 'setup.py', 'install'], |
|
190 | [str(venv_python), 'setup.py', 'install'], | |
191 | cwd=py2exe_source_path, |
|
191 | cwd=py2exe_source_path, | |
192 | env=env, |
|
192 | env=env, | |
193 | check=True, |
|
193 | check=True, | |
194 | ) |
|
194 | ) | |
195 |
|
195 | |||
196 | # Register location of msgfmt and other binaries. |
|
196 | # Register location of msgfmt and other binaries. | |
197 | env['PATH'] = '%s%s%s' % ( |
|
197 | env['PATH'] = '%s%s%s' % ( | |
198 | env['PATH'], |
|
198 | env['PATH'], | |
199 | os.pathsep, |
|
199 | os.pathsep, | |
200 | str(gettext_root / 'bin'), |
|
200 | str(gettext_root / 'bin'), | |
201 | ) |
|
201 | ) | |
202 |
|
202 | |||
203 | print('building Mercurial') |
|
203 | print('building Mercurial') | |
204 | subprocess.run( |
|
204 | subprocess.run( | |
205 | [str(venv_python), 'setup.py', 'py2exe', 'build_doc', '--html'], |
|
205 | [str(venv_python), 'setup.py', 'py2exe', 'build_doc', '--html'], | |
206 | cwd=str(source_dir), |
|
206 | cwd=str(source_dir), | |
207 | env=env, |
|
207 | env=env, | |
208 | check=True, |
|
208 | check=True, | |
209 | ) |
|
209 | ) | |
210 |
|
210 | |||
211 |
|
211 | |||
212 | def stage_install( |
|
212 | def stage_install( | |
213 | source_dir: pathlib.Path, staging_dir: pathlib.Path, lower_case=False |
|
213 | source_dir: pathlib.Path, staging_dir: pathlib.Path, lower_case=False | |
214 | ): |
|
214 | ): | |
215 | """Copy all files to be installed to a directory. |
|
215 | """Copy all files to be installed to a directory. | |
216 |
|
216 | |||
217 | This allows packaging to simply walk a directory tree to find source |
|
217 | This allows packaging to simply walk a directory tree to find source | |
218 | files. |
|
218 | files. | |
219 | """ |
|
219 | """ | |
220 | if lower_case: |
|
220 | if lower_case: | |
221 | rules = [] |
|
221 | rules = [] | |
222 | for source, dest in STAGING_RULES: |
|
222 | for source, dest in STAGING_RULES: | |
223 | # Only lower directory names. |
|
223 | # Only lower directory names. | |
224 | if '/' in dest: |
|
224 | if '/' in dest: | |
225 | parent, leaf = dest.rsplit('/', 1) |
|
225 | parent, leaf = dest.rsplit('/', 1) | |
226 | dest = '%s/%s' % (parent.lower(), leaf) |
|
226 | dest = '%s/%s' % (parent.lower(), leaf) | |
227 | rules.append((source, dest)) |
|
227 | rules.append((source, dest)) | |
228 | else: |
|
228 | else: | |
229 | rules = STAGING_RULES |
|
229 | rules = STAGING_RULES | |
230 |
|
230 | |||
231 | process_install_rules(rules, source_dir, staging_dir) |
|
231 | process_install_rules(rules, source_dir, staging_dir) | |
232 |
|
232 | |||
233 | # Write out a default editor.rc file to configure notepad as the |
|
233 | # Write out a default editor.rc file to configure notepad as the | |
234 | # default editor. |
|
234 | # default editor. | |
235 | with (staging_dir / 'hgrc.d' / 'editor.rc').open( |
|
235 | with (staging_dir / 'hgrc.d' / 'editor.rc').open( | |
236 | 'w', encoding='utf-8' |
|
236 | 'w', encoding='utf-8' | |
237 | ) as fh: |
|
237 | ) as fh: | |
238 | fh.write('[ui]\neditor = notepad\n') |
|
238 | fh.write('[ui]\neditor = notepad\n') | |
239 |
|
239 | |||
240 | # Purge any files we don't want to be there. |
|
240 | # Purge any files we don't want to be there. | |
241 | for f in STAGING_EXCLUDES: |
|
241 | for f in STAGING_EXCLUDES: | |
242 | p = staging_dir / f |
|
242 | p = staging_dir / f | |
243 | if p.exists(): |
|
243 | if p.exists(): | |
244 | print('removing %s' % p) |
|
244 | print('removing %s' % p) | |
245 | p.unlink() |
|
245 | p.unlink() |
@@ -1,48 +1,48 | |||||
1 | SOURCES=$(notdir $(wildcard ../mercurial/help/*.[0-9].txt)) |
|
1 | SOURCES=$(notdir $(wildcard ../mercurial/helptext/*.[0-9].txt)) | |
2 | MAN=$(SOURCES:%.txt=%) |
|
2 | MAN=$(SOURCES:%.txt=%) | |
3 | HTML=$(SOURCES:%.txt=%.html) |
|
3 | HTML=$(SOURCES:%.txt=%.html) | |
4 | GENDOC=gendoc.py ../mercurial/commands.py ../mercurial/help.py \ |
|
4 | GENDOC=gendoc.py ../mercurial/commands.py ../mercurial/help.py \ | |
5 | ../mercurial/help/*.txt ../hgext/*.py ../hgext/*/__init__.py |
|
5 | ../mercurial/helptext/*.txt ../hgext/*.py ../hgext/*/__init__.py | |
6 | PREFIX=/usr/local |
|
6 | PREFIX=/usr/local | |
7 | MANDIR=$(PREFIX)/share/man |
|
7 | MANDIR=$(PREFIX)/share/man | |
8 | INSTALL=install -c -m 644 |
|
8 | INSTALL=install -c -m 644 | |
9 | PYTHON?=python |
|
9 | PYTHON?=python | |
10 | RSTARGS= |
|
10 | RSTARGS= | |
11 |
|
11 | |||
12 | export HGENCODING=UTF-8 |
|
12 | export HGENCODING=UTF-8 | |
13 |
|
13 | |||
14 | all: man html |
|
14 | all: man html | |
15 |
|
15 | |||
16 | man: $(MAN) |
|
16 | man: $(MAN) | |
17 |
|
17 | |||
18 | html: $(HTML) |
|
18 | html: $(HTML) | |
19 |
|
19 | |||
20 | # This logic is duplicated in setup.py:hgbuilddoc() |
|
20 | # This logic is duplicated in setup.py:hgbuilddoc() | |
21 | common.txt $(SOURCES) $(SOURCES:%.txt=%.gendoc.txt): $(GENDOC) |
|
21 | common.txt $(SOURCES) $(SOURCES:%.txt=%.gendoc.txt): $(GENDOC) | |
22 | ${PYTHON} gendoc.py "$(basename $@)" > $@.tmp |
|
22 | ${PYTHON} gendoc.py "$(basename $@)" > $@.tmp | |
23 | mv $@.tmp $@ |
|
23 | mv $@.tmp $@ | |
24 |
|
24 | |||
25 | %: %.txt %.gendoc.txt common.txt |
|
25 | %: %.txt %.gendoc.txt common.txt | |
26 | $(PYTHON) runrst hgmanpage $(RSTARGS) --halt warning \ |
|
26 | $(PYTHON) runrst hgmanpage $(RSTARGS) --halt warning \ | |
27 | --strip-elements-with-class htmlonly $*.txt $* |
|
27 | --strip-elements-with-class htmlonly $*.txt $* | |
28 |
|
28 | |||
29 | %.html: %.txt %.gendoc.txt common.txt |
|
29 | %.html: %.txt %.gendoc.txt common.txt | |
30 | $(PYTHON) runrst html $(RSTARGS) --halt warning \ |
|
30 | $(PYTHON) runrst html $(RSTARGS) --halt warning \ | |
31 | --link-stylesheet --stylesheet-path style.css $*.txt $*.html |
|
31 | --link-stylesheet --stylesheet-path style.css $*.txt $*.html | |
32 |
|
32 | |||
33 | MANIFEST: man html |
|
33 | MANIFEST: man html | |
34 | # tracked files are already in the main MANIFEST |
|
34 | # tracked files are already in the main MANIFEST | |
35 | $(RM) $@ |
|
35 | $(RM) $@ | |
36 | for i in $(MAN) $(HTML); do \ |
|
36 | for i in $(MAN) $(HTML); do \ | |
37 | echo "doc/$$i" >> $@ ; \ |
|
37 | echo "doc/$$i" >> $@ ; \ | |
38 | done |
|
38 | done | |
39 |
|
39 | |||
40 | install: man |
|
40 | install: man | |
41 | for i in $(MAN) ; do \ |
|
41 | for i in $(MAN) ; do \ | |
42 | subdir=`echo $$i | sed -n 's/^.*\.\([0-9]\)$$/man\1/p'` ; \ |
|
42 | subdir=`echo $$i | sed -n 's/^.*\.\([0-9]\)$$/man\1/p'` ; \ | |
43 | mkdir -p "$(DESTDIR)$(MANDIR)"/$$subdir ; \ |
|
43 | mkdir -p "$(DESTDIR)$(MANDIR)"/$$subdir ; \ | |
44 | $(INSTALL) $$i "$(DESTDIR)$(MANDIR)"/$$subdir ; \ |
|
44 | $(INSTALL) $$i "$(DESTDIR)$(MANDIR)"/$$subdir ; \ | |
45 | done |
|
45 | done | |
46 |
|
46 | |||
47 | clean: |
|
47 | clean: | |
48 | $(RM) $(MAN) $(HTML) common.txt $(SOURCES) $(SOURCES:%.txt=%.gendoc.txt) MANIFEST |
|
48 | $(RM) $(MAN) $(HTML) common.txt $(SOURCES) $(SOURCES:%.txt=%.gendoc.txt) MANIFEST |
@@ -1,467 +1,467 | |||||
1 | # linelog - efficient cache for annotate data |
|
1 | # linelog - efficient cache for annotate data | |
2 | # |
|
2 | # | |
3 | # Copyright 2018 Google LLC. |
|
3 | # Copyright 2018 Google LLC. | |
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 | """linelog is an efficient cache for annotate data inspired by SCCS Weaves. |
|
7 | """linelog is an efficient cache for annotate data inspired by SCCS Weaves. | |
8 |
|
8 | |||
9 | SCCS Weaves are an implementation of |
|
9 | SCCS Weaves are an implementation of | |
10 | https://en.wikipedia.org/wiki/Interleaved_deltas. See |
|
10 | https://en.wikipedia.org/wiki/Interleaved_deltas. See | |
11 | mercurial/help/internals/linelog.txt for an exploration of SCCS weaves |
|
11 | mercurial/helptext/internals/linelog.txt for an exploration of SCCS weaves | |
12 | and how linelog works in detail. |
|
12 | and how linelog works in detail. | |
13 |
|
13 | |||
14 | Here's a hacker's summary: a linelog is a program which is executed in |
|
14 | Here's a hacker's summary: a linelog is a program which is executed in | |
15 | the context of a revision. Executing the program emits information |
|
15 | the context of a revision. Executing the program emits information | |
16 | about lines, including the revision that introduced them and the line |
|
16 | about lines, including the revision that introduced them and the line | |
17 | number in the file at the introducing revision. When an insertion or |
|
17 | number in the file at the introducing revision. When an insertion or | |
18 | deletion is performed on the file, a jump instruction is used to patch |
|
18 | deletion is performed on the file, a jump instruction is used to patch | |
19 | in a new body of annotate information. |
|
19 | in a new body of annotate information. | |
20 | """ |
|
20 | """ | |
21 | from __future__ import absolute_import, print_function |
|
21 | from __future__ import absolute_import, print_function | |
22 |
|
22 | |||
23 | import abc |
|
23 | import abc | |
24 | import struct |
|
24 | import struct | |
25 |
|
25 | |||
26 | from .thirdparty import attr |
|
26 | from .thirdparty import attr | |
27 | from . import pycompat |
|
27 | from . import pycompat | |
28 |
|
28 | |||
29 | _llentry = struct.Struct(b'>II') |
|
29 | _llentry = struct.Struct(b'>II') | |
30 |
|
30 | |||
31 |
|
31 | |||
32 | class LineLogError(Exception): |
|
32 | class LineLogError(Exception): | |
33 | """Error raised when something bad happens internally in linelog.""" |
|
33 | """Error raised when something bad happens internally in linelog.""" | |
34 |
|
34 | |||
35 |
|
35 | |||
36 | @attr.s |
|
36 | @attr.s | |
37 | class lineinfo(object): |
|
37 | class lineinfo(object): | |
38 | # Introducing revision of this line. |
|
38 | # Introducing revision of this line. | |
39 | rev = attr.ib() |
|
39 | rev = attr.ib() | |
40 | # Line number for this line in its introducing revision. |
|
40 | # Line number for this line in its introducing revision. | |
41 | linenum = attr.ib() |
|
41 | linenum = attr.ib() | |
42 | # Private. Offset in the linelog program of this line. Used internally. |
|
42 | # Private. Offset in the linelog program of this line. Used internally. | |
43 | _offset = attr.ib() |
|
43 | _offset = attr.ib() | |
44 |
|
44 | |||
45 |
|
45 | |||
46 | @attr.s |
|
46 | @attr.s | |
47 | class annotateresult(object): |
|
47 | class annotateresult(object): | |
48 | rev = attr.ib() |
|
48 | rev = attr.ib() | |
49 | lines = attr.ib() |
|
49 | lines = attr.ib() | |
50 | _eof = attr.ib() |
|
50 | _eof = attr.ib() | |
51 |
|
51 | |||
52 | def __iter__(self): |
|
52 | def __iter__(self): | |
53 | return iter(self.lines) |
|
53 | return iter(self.lines) | |
54 |
|
54 | |||
55 |
|
55 | |||
56 | class _llinstruction(object): # pytype: disable=ignored-metaclass |
|
56 | class _llinstruction(object): # pytype: disable=ignored-metaclass | |
57 |
|
57 | |||
58 | __metaclass__ = abc.ABCMeta |
|
58 | __metaclass__ = abc.ABCMeta | |
59 |
|
59 | |||
60 | @abc.abstractmethod |
|
60 | @abc.abstractmethod | |
61 | def __init__(self, op1, op2): |
|
61 | def __init__(self, op1, op2): | |
62 | pass |
|
62 | pass | |
63 |
|
63 | |||
64 | @abc.abstractmethod |
|
64 | @abc.abstractmethod | |
65 | def __str__(self): |
|
65 | def __str__(self): | |
66 | pass |
|
66 | pass | |
67 |
|
67 | |||
68 | def __repr__(self): |
|
68 | def __repr__(self): | |
69 | return str(self) |
|
69 | return str(self) | |
70 |
|
70 | |||
71 | @abc.abstractmethod |
|
71 | @abc.abstractmethod | |
72 | def __eq__(self, other): |
|
72 | def __eq__(self, other): | |
73 | pass |
|
73 | pass | |
74 |
|
74 | |||
75 | @abc.abstractmethod |
|
75 | @abc.abstractmethod | |
76 | def encode(self): |
|
76 | def encode(self): | |
77 | """Encode this instruction to the binary linelog format.""" |
|
77 | """Encode this instruction to the binary linelog format.""" | |
78 |
|
78 | |||
79 | @abc.abstractmethod |
|
79 | @abc.abstractmethod | |
80 | def execute(self, rev, pc, emit): |
|
80 | def execute(self, rev, pc, emit): | |
81 | """Execute this instruction. |
|
81 | """Execute this instruction. | |
82 |
|
82 | |||
83 | Args: |
|
83 | Args: | |
84 | rev: The revision we're annotating. |
|
84 | rev: The revision we're annotating. | |
85 | pc: The current offset in the linelog program. |
|
85 | pc: The current offset in the linelog program. | |
86 | emit: A function that accepts a single lineinfo object. |
|
86 | emit: A function that accepts a single lineinfo object. | |
87 |
|
87 | |||
88 | Returns: |
|
88 | Returns: | |
89 | The new value of pc. Returns None if exeuction should stop |
|
89 | The new value of pc. Returns None if exeuction should stop | |
90 | (that is, we've found the end of the file.) |
|
90 | (that is, we've found the end of the file.) | |
91 | """ |
|
91 | """ | |
92 |
|
92 | |||
93 |
|
93 | |||
94 | class _jge(_llinstruction): |
|
94 | class _jge(_llinstruction): | |
95 | """If the current rev is greater than or equal to op1, jump to op2.""" |
|
95 | """If the current rev is greater than or equal to op1, jump to op2.""" | |
96 |
|
96 | |||
97 | def __init__(self, op1, op2): |
|
97 | def __init__(self, op1, op2): | |
98 | self._cmprev = op1 |
|
98 | self._cmprev = op1 | |
99 | self._target = op2 |
|
99 | self._target = op2 | |
100 |
|
100 | |||
101 | def __str__(self): |
|
101 | def __str__(self): | |
102 | return 'JGE %d %d' % (self._cmprev, self._target) |
|
102 | return 'JGE %d %d' % (self._cmprev, self._target) | |
103 |
|
103 | |||
104 | def __eq__(self, other): |
|
104 | def __eq__(self, other): | |
105 | return ( |
|
105 | return ( | |
106 | type(self) == type(other) |
|
106 | type(self) == type(other) | |
107 | and self._cmprev == other._cmprev |
|
107 | and self._cmprev == other._cmprev | |
108 | and self._target == other._target |
|
108 | and self._target == other._target | |
109 | ) |
|
109 | ) | |
110 |
|
110 | |||
111 | def encode(self): |
|
111 | def encode(self): | |
112 | return _llentry.pack(self._cmprev << 2, self._target) |
|
112 | return _llentry.pack(self._cmprev << 2, self._target) | |
113 |
|
113 | |||
114 | def execute(self, rev, pc, emit): |
|
114 | def execute(self, rev, pc, emit): | |
115 | if rev >= self._cmprev: |
|
115 | if rev >= self._cmprev: | |
116 | return self._target |
|
116 | return self._target | |
117 | return pc + 1 |
|
117 | return pc + 1 | |
118 |
|
118 | |||
119 |
|
119 | |||
120 | class _jump(_llinstruction): |
|
120 | class _jump(_llinstruction): | |
121 | """Unconditional jumps are expressed as a JGE with op1 set to 0.""" |
|
121 | """Unconditional jumps are expressed as a JGE with op1 set to 0.""" | |
122 |
|
122 | |||
123 | def __init__(self, op1, op2): |
|
123 | def __init__(self, op1, op2): | |
124 | if op1 != 0: |
|
124 | if op1 != 0: | |
125 | raise LineLogError(b"malformed JUMP, op1 must be 0, got %d" % op1) |
|
125 | raise LineLogError(b"malformed JUMP, op1 must be 0, got %d" % op1) | |
126 | self._target = op2 |
|
126 | self._target = op2 | |
127 |
|
127 | |||
128 | def __str__(self): |
|
128 | def __str__(self): | |
129 | return 'JUMP %d' % (self._target) |
|
129 | return 'JUMP %d' % (self._target) | |
130 |
|
130 | |||
131 | def __eq__(self, other): |
|
131 | def __eq__(self, other): | |
132 | return type(self) == type(other) and self._target == other._target |
|
132 | return type(self) == type(other) and self._target == other._target | |
133 |
|
133 | |||
134 | def encode(self): |
|
134 | def encode(self): | |
135 | return _llentry.pack(0, self._target) |
|
135 | return _llentry.pack(0, self._target) | |
136 |
|
136 | |||
137 | def execute(self, rev, pc, emit): |
|
137 | def execute(self, rev, pc, emit): | |
138 | return self._target |
|
138 | return self._target | |
139 |
|
139 | |||
140 |
|
140 | |||
141 | class _eof(_llinstruction): |
|
141 | class _eof(_llinstruction): | |
142 | """EOF is expressed as a JGE that always jumps to 0.""" |
|
142 | """EOF is expressed as a JGE that always jumps to 0.""" | |
143 |
|
143 | |||
144 | def __init__(self, op1, op2): |
|
144 | def __init__(self, op1, op2): | |
145 | if op1 != 0: |
|
145 | if op1 != 0: | |
146 | raise LineLogError(b"malformed EOF, op1 must be 0, got %d" % op1) |
|
146 | raise LineLogError(b"malformed EOF, op1 must be 0, got %d" % op1) | |
147 | if op2 != 0: |
|
147 | if op2 != 0: | |
148 | raise LineLogError(b"malformed EOF, op2 must be 0, got %d" % op2) |
|
148 | raise LineLogError(b"malformed EOF, op2 must be 0, got %d" % op2) | |
149 |
|
149 | |||
150 | def __str__(self): |
|
150 | def __str__(self): | |
151 | return r'EOF' |
|
151 | return r'EOF' | |
152 |
|
152 | |||
153 | def __eq__(self, other): |
|
153 | def __eq__(self, other): | |
154 | return type(self) == type(other) |
|
154 | return type(self) == type(other) | |
155 |
|
155 | |||
156 | def encode(self): |
|
156 | def encode(self): | |
157 | return _llentry.pack(0, 0) |
|
157 | return _llentry.pack(0, 0) | |
158 |
|
158 | |||
159 | def execute(self, rev, pc, emit): |
|
159 | def execute(self, rev, pc, emit): | |
160 | return None |
|
160 | return None | |
161 |
|
161 | |||
162 |
|
162 | |||
163 | class _jl(_llinstruction): |
|
163 | class _jl(_llinstruction): | |
164 | """If the current rev is less than op1, jump to op2.""" |
|
164 | """If the current rev is less than op1, jump to op2.""" | |
165 |
|
165 | |||
166 | def __init__(self, op1, op2): |
|
166 | def __init__(self, op1, op2): | |
167 | self._cmprev = op1 |
|
167 | self._cmprev = op1 | |
168 | self._target = op2 |
|
168 | self._target = op2 | |
169 |
|
169 | |||
170 | def __str__(self): |
|
170 | def __str__(self): | |
171 | return 'JL %d %d' % (self._cmprev, self._target) |
|
171 | return 'JL %d %d' % (self._cmprev, self._target) | |
172 |
|
172 | |||
173 | def __eq__(self, other): |
|
173 | def __eq__(self, other): | |
174 | return ( |
|
174 | return ( | |
175 | type(self) == type(other) |
|
175 | type(self) == type(other) | |
176 | and self._cmprev == other._cmprev |
|
176 | and self._cmprev == other._cmprev | |
177 | and self._target == other._target |
|
177 | and self._target == other._target | |
178 | ) |
|
178 | ) | |
179 |
|
179 | |||
180 | def encode(self): |
|
180 | def encode(self): | |
181 | return _llentry.pack(1 | (self._cmprev << 2), self._target) |
|
181 | return _llentry.pack(1 | (self._cmprev << 2), self._target) | |
182 |
|
182 | |||
183 | def execute(self, rev, pc, emit): |
|
183 | def execute(self, rev, pc, emit): | |
184 | if rev < self._cmprev: |
|
184 | if rev < self._cmprev: | |
185 | return self._target |
|
185 | return self._target | |
186 | return pc + 1 |
|
186 | return pc + 1 | |
187 |
|
187 | |||
188 |
|
188 | |||
189 | class _line(_llinstruction): |
|
189 | class _line(_llinstruction): | |
190 | """Emit a line.""" |
|
190 | """Emit a line.""" | |
191 |
|
191 | |||
192 | def __init__(self, op1, op2): |
|
192 | def __init__(self, op1, op2): | |
193 | # This line was introduced by this revision number. |
|
193 | # This line was introduced by this revision number. | |
194 | self._rev = op1 |
|
194 | self._rev = op1 | |
195 | # This line had the specified line number in the introducing revision. |
|
195 | # This line had the specified line number in the introducing revision. | |
196 | self._origlineno = op2 |
|
196 | self._origlineno = op2 | |
197 |
|
197 | |||
198 | def __str__(self): |
|
198 | def __str__(self): | |
199 | return 'LINE %d %d' % (self._rev, self._origlineno) |
|
199 | return 'LINE %d %d' % (self._rev, self._origlineno) | |
200 |
|
200 | |||
201 | def __eq__(self, other): |
|
201 | def __eq__(self, other): | |
202 | return ( |
|
202 | return ( | |
203 | type(self) == type(other) |
|
203 | type(self) == type(other) | |
204 | and self._rev == other._rev |
|
204 | and self._rev == other._rev | |
205 | and self._origlineno == other._origlineno |
|
205 | and self._origlineno == other._origlineno | |
206 | ) |
|
206 | ) | |
207 |
|
207 | |||
208 | def encode(self): |
|
208 | def encode(self): | |
209 | return _llentry.pack(2 | (self._rev << 2), self._origlineno) |
|
209 | return _llentry.pack(2 | (self._rev << 2), self._origlineno) | |
210 |
|
210 | |||
211 | def execute(self, rev, pc, emit): |
|
211 | def execute(self, rev, pc, emit): | |
212 | emit(lineinfo(self._rev, self._origlineno, pc)) |
|
212 | emit(lineinfo(self._rev, self._origlineno, pc)) | |
213 | return pc + 1 |
|
213 | return pc + 1 | |
214 |
|
214 | |||
215 |
|
215 | |||
216 | def _decodeone(data, offset): |
|
216 | def _decodeone(data, offset): | |
217 | """Decode a single linelog instruction from an offset in a buffer.""" |
|
217 | """Decode a single linelog instruction from an offset in a buffer.""" | |
218 | try: |
|
218 | try: | |
219 | op1, op2 = _llentry.unpack_from(data, offset) |
|
219 | op1, op2 = _llentry.unpack_from(data, offset) | |
220 | except struct.error as e: |
|
220 | except struct.error as e: | |
221 | raise LineLogError(b'reading an instruction failed: %r' % e) |
|
221 | raise LineLogError(b'reading an instruction failed: %r' % e) | |
222 | opcode = op1 & 0b11 |
|
222 | opcode = op1 & 0b11 | |
223 | op1 = op1 >> 2 |
|
223 | op1 = op1 >> 2 | |
224 | if opcode == 0: |
|
224 | if opcode == 0: | |
225 | if op1 == 0: |
|
225 | if op1 == 0: | |
226 | if op2 == 0: |
|
226 | if op2 == 0: | |
227 | return _eof(op1, op2) |
|
227 | return _eof(op1, op2) | |
228 | return _jump(op1, op2) |
|
228 | return _jump(op1, op2) | |
229 | return _jge(op1, op2) |
|
229 | return _jge(op1, op2) | |
230 | elif opcode == 1: |
|
230 | elif opcode == 1: | |
231 | return _jl(op1, op2) |
|
231 | return _jl(op1, op2) | |
232 | elif opcode == 2: |
|
232 | elif opcode == 2: | |
233 | return _line(op1, op2) |
|
233 | return _line(op1, op2) | |
234 | raise NotImplementedError(b'Unimplemented opcode %r' % opcode) |
|
234 | raise NotImplementedError(b'Unimplemented opcode %r' % opcode) | |
235 |
|
235 | |||
236 |
|
236 | |||
237 | class linelog(object): |
|
237 | class linelog(object): | |
238 | """Efficient cache for per-line history information.""" |
|
238 | """Efficient cache for per-line history information.""" | |
239 |
|
239 | |||
240 | def __init__(self, program=None, maxrev=0): |
|
240 | def __init__(self, program=None, maxrev=0): | |
241 | if program is None: |
|
241 | if program is None: | |
242 | # We pad the program with an extra leading EOF so that our |
|
242 | # We pad the program with an extra leading EOF so that our | |
243 | # offsets will match the C code exactly. This means we can |
|
243 | # offsets will match the C code exactly. This means we can | |
244 | # interoperate with the C code. |
|
244 | # interoperate with the C code. | |
245 | program = [_eof(0, 0), _eof(0, 0)] |
|
245 | program = [_eof(0, 0), _eof(0, 0)] | |
246 | self._program = program |
|
246 | self._program = program | |
247 | self._lastannotate = None |
|
247 | self._lastannotate = None | |
248 | self._maxrev = maxrev |
|
248 | self._maxrev = maxrev | |
249 |
|
249 | |||
250 | def __eq__(self, other): |
|
250 | def __eq__(self, other): | |
251 | return ( |
|
251 | return ( | |
252 | type(self) == type(other) |
|
252 | type(self) == type(other) | |
253 | and self._program == other._program |
|
253 | and self._program == other._program | |
254 | and self._maxrev == other._maxrev |
|
254 | and self._maxrev == other._maxrev | |
255 | ) |
|
255 | ) | |
256 |
|
256 | |||
257 | def __repr__(self): |
|
257 | def __repr__(self): | |
258 | return b'<linelog at %s: maxrev=%d size=%d>' % ( |
|
258 | return b'<linelog at %s: maxrev=%d size=%d>' % ( | |
259 | hex(id(self)), |
|
259 | hex(id(self)), | |
260 | self._maxrev, |
|
260 | self._maxrev, | |
261 | len(self._program), |
|
261 | len(self._program), | |
262 | ) |
|
262 | ) | |
263 |
|
263 | |||
264 | def debugstr(self): |
|
264 | def debugstr(self): | |
265 | fmt = '%%%dd %%s' % len(str(len(self._program))) |
|
265 | fmt = '%%%dd %%s' % len(str(len(self._program))) | |
266 | return pycompat.sysstr(b'\n').join( |
|
266 | return pycompat.sysstr(b'\n').join( | |
267 | fmt % (idx, i) for idx, i in enumerate(self._program[1:], 1) |
|
267 | fmt % (idx, i) for idx, i in enumerate(self._program[1:], 1) | |
268 | ) |
|
268 | ) | |
269 |
|
269 | |||
270 | @classmethod |
|
270 | @classmethod | |
271 | def fromdata(cls, buf): |
|
271 | def fromdata(cls, buf): | |
272 | if len(buf) % _llentry.size != 0: |
|
272 | if len(buf) % _llentry.size != 0: | |
273 | raise LineLogError( |
|
273 | raise LineLogError( | |
274 | b"invalid linelog buffer size %d (must be a multiple of %d)" |
|
274 | b"invalid linelog buffer size %d (must be a multiple of %d)" | |
275 | % (len(buf), _llentry.size) |
|
275 | % (len(buf), _llentry.size) | |
276 | ) |
|
276 | ) | |
277 | expected = len(buf) / _llentry.size |
|
277 | expected = len(buf) / _llentry.size | |
278 | fakejge = _decodeone(buf, 0) |
|
278 | fakejge = _decodeone(buf, 0) | |
279 | if isinstance(fakejge, _jump): |
|
279 | if isinstance(fakejge, _jump): | |
280 | maxrev = 0 |
|
280 | maxrev = 0 | |
281 | elif isinstance(fakejge, (_jge, _jl)): |
|
281 | elif isinstance(fakejge, (_jge, _jl)): | |
282 | maxrev = fakejge._cmprev |
|
282 | maxrev = fakejge._cmprev | |
283 | else: |
|
283 | else: | |
284 | raise LineLogError( |
|
284 | raise LineLogError( | |
285 | 'Expected one of _jump, _jge, or _jl. Got %s.' |
|
285 | 'Expected one of _jump, _jge, or _jl. Got %s.' | |
286 | % type(fakejge).__name__ |
|
286 | % type(fakejge).__name__ | |
287 | ) |
|
287 | ) | |
288 | assert isinstance(fakejge, (_jump, _jge, _jl)) # help pytype |
|
288 | assert isinstance(fakejge, (_jump, _jge, _jl)) # help pytype | |
289 | numentries = fakejge._target |
|
289 | numentries = fakejge._target | |
290 | if expected != numentries: |
|
290 | if expected != numentries: | |
291 | raise LineLogError( |
|
291 | raise LineLogError( | |
292 | b"corrupt linelog data: claimed" |
|
292 | b"corrupt linelog data: claimed" | |
293 | b" %d entries but given data for %d entries" |
|
293 | b" %d entries but given data for %d entries" | |
294 | % (expected, numentries) |
|
294 | % (expected, numentries) | |
295 | ) |
|
295 | ) | |
296 | instructions = [_eof(0, 0)] |
|
296 | instructions = [_eof(0, 0)] | |
297 | for offset in pycompat.xrange(1, numentries): |
|
297 | for offset in pycompat.xrange(1, numentries): | |
298 | instructions.append(_decodeone(buf, offset * _llentry.size)) |
|
298 | instructions.append(_decodeone(buf, offset * _llentry.size)) | |
299 | return cls(instructions, maxrev=maxrev) |
|
299 | return cls(instructions, maxrev=maxrev) | |
300 |
|
300 | |||
301 | def encode(self): |
|
301 | def encode(self): | |
302 | hdr = _jge(self._maxrev, len(self._program)).encode() |
|
302 | hdr = _jge(self._maxrev, len(self._program)).encode() | |
303 | return hdr + b''.join(i.encode() for i in self._program[1:]) |
|
303 | return hdr + b''.join(i.encode() for i in self._program[1:]) | |
304 |
|
304 | |||
305 | def clear(self): |
|
305 | def clear(self): | |
306 | self._program = [] |
|
306 | self._program = [] | |
307 | self._maxrev = 0 |
|
307 | self._maxrev = 0 | |
308 | self._lastannotate = None |
|
308 | self._lastannotate = None | |
309 |
|
309 | |||
310 | def replacelines_vec(self, rev, a1, a2, blines): |
|
310 | def replacelines_vec(self, rev, a1, a2, blines): | |
311 | return self.replacelines( |
|
311 | return self.replacelines( | |
312 | rev, a1, a2, 0, len(blines), _internal_blines=blines |
|
312 | rev, a1, a2, 0, len(blines), _internal_blines=blines | |
313 | ) |
|
313 | ) | |
314 |
|
314 | |||
315 | def replacelines(self, rev, a1, a2, b1, b2, _internal_blines=None): |
|
315 | def replacelines(self, rev, a1, a2, b1, b2, _internal_blines=None): | |
316 | """Replace lines [a1, a2) with lines [b1, b2).""" |
|
316 | """Replace lines [a1, a2) with lines [b1, b2).""" | |
317 | if self._lastannotate: |
|
317 | if self._lastannotate: | |
318 | # TODO(augie): make replacelines() accept a revision at |
|
318 | # TODO(augie): make replacelines() accept a revision at | |
319 | # which we're editing as well as a revision to mark |
|
319 | # which we're editing as well as a revision to mark | |
320 | # responsible for the edits. In hg-experimental it's |
|
320 | # responsible for the edits. In hg-experimental it's | |
321 | # stateful like this, so we're doing the same thing to |
|
321 | # stateful like this, so we're doing the same thing to | |
322 | # retain compatibility with absorb until that's imported. |
|
322 | # retain compatibility with absorb until that's imported. | |
323 | ar = self._lastannotate |
|
323 | ar = self._lastannotate | |
324 | else: |
|
324 | else: | |
325 | ar = self.annotate(rev) |
|
325 | ar = self.annotate(rev) | |
326 | # ar = self.annotate(self._maxrev) |
|
326 | # ar = self.annotate(self._maxrev) | |
327 | if a1 > len(ar.lines): |
|
327 | if a1 > len(ar.lines): | |
328 | raise LineLogError( |
|
328 | raise LineLogError( | |
329 | b'%d contains %d lines, tried to access line %d' |
|
329 | b'%d contains %d lines, tried to access line %d' | |
330 | % (rev, len(ar.lines), a1) |
|
330 | % (rev, len(ar.lines), a1) | |
331 | ) |
|
331 | ) | |
332 | elif a1 == len(ar.lines): |
|
332 | elif a1 == len(ar.lines): | |
333 | # Simulated EOF instruction since we're at EOF, which |
|
333 | # Simulated EOF instruction since we're at EOF, which | |
334 | # doesn't have a "real" line. |
|
334 | # doesn't have a "real" line. | |
335 | a1inst = _eof(0, 0) |
|
335 | a1inst = _eof(0, 0) | |
336 | a1info = lineinfo(0, 0, ar._eof) |
|
336 | a1info = lineinfo(0, 0, ar._eof) | |
337 | else: |
|
337 | else: | |
338 | a1info = ar.lines[a1] |
|
338 | a1info = ar.lines[a1] | |
339 | a1inst = self._program[a1info._offset] |
|
339 | a1inst = self._program[a1info._offset] | |
340 | programlen = self._program.__len__ |
|
340 | programlen = self._program.__len__ | |
341 | oldproglen = programlen() |
|
341 | oldproglen = programlen() | |
342 | appendinst = self._program.append |
|
342 | appendinst = self._program.append | |
343 |
|
343 | |||
344 | # insert |
|
344 | # insert | |
345 | blineinfos = [] |
|
345 | blineinfos = [] | |
346 | bappend = blineinfos.append |
|
346 | bappend = blineinfos.append | |
347 | if b1 < b2: |
|
347 | if b1 < b2: | |
348 | # Determine the jump target for the JGE at the start of |
|
348 | # Determine the jump target for the JGE at the start of | |
349 | # the new block. |
|
349 | # the new block. | |
350 | tgt = oldproglen + (b2 - b1 + 1) |
|
350 | tgt = oldproglen + (b2 - b1 + 1) | |
351 | # Jump to skip the insert if we're at an older revision. |
|
351 | # Jump to skip the insert if we're at an older revision. | |
352 | appendinst(_jl(rev, tgt)) |
|
352 | appendinst(_jl(rev, tgt)) | |
353 | for linenum in pycompat.xrange(b1, b2): |
|
353 | for linenum in pycompat.xrange(b1, b2): | |
354 | if _internal_blines is None: |
|
354 | if _internal_blines is None: | |
355 | bappend(lineinfo(rev, linenum, programlen())) |
|
355 | bappend(lineinfo(rev, linenum, programlen())) | |
356 | appendinst(_line(rev, linenum)) |
|
356 | appendinst(_line(rev, linenum)) | |
357 | else: |
|
357 | else: | |
358 | newrev, newlinenum = _internal_blines[linenum] |
|
358 | newrev, newlinenum = _internal_blines[linenum] | |
359 | bappend(lineinfo(newrev, newlinenum, programlen())) |
|
359 | bappend(lineinfo(newrev, newlinenum, programlen())) | |
360 | appendinst(_line(newrev, newlinenum)) |
|
360 | appendinst(_line(newrev, newlinenum)) | |
361 | # delete |
|
361 | # delete | |
362 | if a1 < a2: |
|
362 | if a1 < a2: | |
363 | if a2 > len(ar.lines): |
|
363 | if a2 > len(ar.lines): | |
364 | raise LineLogError( |
|
364 | raise LineLogError( | |
365 | b'%d contains %d lines, tried to access line %d' |
|
365 | b'%d contains %d lines, tried to access line %d' | |
366 | % (rev, len(ar.lines), a2) |
|
366 | % (rev, len(ar.lines), a2) | |
367 | ) |
|
367 | ) | |
368 | elif a2 == len(ar.lines): |
|
368 | elif a2 == len(ar.lines): | |
369 | endaddr = ar._eof |
|
369 | endaddr = ar._eof | |
370 | else: |
|
370 | else: | |
371 | endaddr = ar.lines[a2]._offset |
|
371 | endaddr = ar.lines[a2]._offset | |
372 | if a2 > 0 and rev < self._maxrev: |
|
372 | if a2 > 0 and rev < self._maxrev: | |
373 | # If we're here, we're deleting a chunk of an old |
|
373 | # If we're here, we're deleting a chunk of an old | |
374 | # commit, so we need to be careful and not touch |
|
374 | # commit, so we need to be careful and not touch | |
375 | # invisible lines between a2-1 and a2 (IOW, lines that |
|
375 | # invisible lines between a2-1 and a2 (IOW, lines that | |
376 | # are added later). |
|
376 | # are added later). | |
377 | endaddr = ar.lines[a2 - 1]._offset + 1 |
|
377 | endaddr = ar.lines[a2 - 1]._offset + 1 | |
378 | appendinst(_jge(rev, endaddr)) |
|
378 | appendinst(_jge(rev, endaddr)) | |
379 | # copy instruction from a1 |
|
379 | # copy instruction from a1 | |
380 | a1instpc = programlen() |
|
380 | a1instpc = programlen() | |
381 | appendinst(a1inst) |
|
381 | appendinst(a1inst) | |
382 | # if a1inst isn't a jump or EOF, then we need to add an unconditional |
|
382 | # if a1inst isn't a jump or EOF, then we need to add an unconditional | |
383 | # jump back into the program here. |
|
383 | # jump back into the program here. | |
384 | if not isinstance(a1inst, (_jump, _eof)): |
|
384 | if not isinstance(a1inst, (_jump, _eof)): | |
385 | appendinst(_jump(0, a1info._offset + 1)) |
|
385 | appendinst(_jump(0, a1info._offset + 1)) | |
386 | # Patch instruction at a1, which makes our patch live. |
|
386 | # Patch instruction at a1, which makes our patch live. | |
387 | self._program[a1info._offset] = _jump(0, oldproglen) |
|
387 | self._program[a1info._offset] = _jump(0, oldproglen) | |
388 |
|
388 | |||
389 | # Update self._lastannotate in place. This serves as a cache to avoid |
|
389 | # Update self._lastannotate in place. This serves as a cache to avoid | |
390 | # expensive "self.annotate" in this function, when "replacelines" is |
|
390 | # expensive "self.annotate" in this function, when "replacelines" is | |
391 | # used continuously. |
|
391 | # used continuously. | |
392 | if len(self._lastannotate.lines) > a1: |
|
392 | if len(self._lastannotate.lines) > a1: | |
393 | self._lastannotate.lines[a1]._offset = a1instpc |
|
393 | self._lastannotate.lines[a1]._offset = a1instpc | |
394 | else: |
|
394 | else: | |
395 | assert isinstance(a1inst, _eof) |
|
395 | assert isinstance(a1inst, _eof) | |
396 | self._lastannotate._eof = a1instpc |
|
396 | self._lastannotate._eof = a1instpc | |
397 | self._lastannotate.lines[a1:a2] = blineinfos |
|
397 | self._lastannotate.lines[a1:a2] = blineinfos | |
398 | self._lastannotate.rev = max(self._lastannotate.rev, rev) |
|
398 | self._lastannotate.rev = max(self._lastannotate.rev, rev) | |
399 |
|
399 | |||
400 | if rev > self._maxrev: |
|
400 | if rev > self._maxrev: | |
401 | self._maxrev = rev |
|
401 | self._maxrev = rev | |
402 |
|
402 | |||
403 | def annotate(self, rev): |
|
403 | def annotate(self, rev): | |
404 | pc = 1 |
|
404 | pc = 1 | |
405 | lines = [] |
|
405 | lines = [] | |
406 | executed = 0 |
|
406 | executed = 0 | |
407 | # Sanity check: if instructions executed exceeds len(program), we |
|
407 | # Sanity check: if instructions executed exceeds len(program), we | |
408 | # hit an infinite loop in the linelog program somehow and we |
|
408 | # hit an infinite loop in the linelog program somehow and we | |
409 | # should stop. |
|
409 | # should stop. | |
410 | while pc is not None and executed < len(self._program): |
|
410 | while pc is not None and executed < len(self._program): | |
411 | inst = self._program[pc] |
|
411 | inst = self._program[pc] | |
412 | lastpc = pc |
|
412 | lastpc = pc | |
413 | pc = inst.execute(rev, pc, lines.append) |
|
413 | pc = inst.execute(rev, pc, lines.append) | |
414 | executed += 1 |
|
414 | executed += 1 | |
415 | if pc is not None: |
|
415 | if pc is not None: | |
416 | raise LineLogError( |
|
416 | raise LineLogError( | |
417 | r'Probably hit an infinite loop in linelog. Program:\n' |
|
417 | r'Probably hit an infinite loop in linelog. Program:\n' | |
418 | + self.debugstr() |
|
418 | + self.debugstr() | |
419 | ) |
|
419 | ) | |
420 | ar = annotateresult(rev, lines, lastpc) |
|
420 | ar = annotateresult(rev, lines, lastpc) | |
421 | self._lastannotate = ar |
|
421 | self._lastannotate = ar | |
422 | return ar |
|
422 | return ar | |
423 |
|
423 | |||
424 | @property |
|
424 | @property | |
425 | def maxrev(self): |
|
425 | def maxrev(self): | |
426 | return self._maxrev |
|
426 | return self._maxrev | |
427 |
|
427 | |||
428 | # Stateful methods which depend on the value of the last |
|
428 | # Stateful methods which depend on the value of the last | |
429 | # annotation run. This API is for compatiblity with the original |
|
429 | # annotation run. This API is for compatiblity with the original | |
430 | # linelog, and we should probably consider refactoring it. |
|
430 | # linelog, and we should probably consider refactoring it. | |
431 | @property |
|
431 | @property | |
432 | def annotateresult(self): |
|
432 | def annotateresult(self): | |
433 | """Return the last annotation result. C linelog code exposed this.""" |
|
433 | """Return the last annotation result. C linelog code exposed this.""" | |
434 | return [(l.rev, l.linenum) for l in self._lastannotate.lines] |
|
434 | return [(l.rev, l.linenum) for l in self._lastannotate.lines] | |
435 |
|
435 | |||
436 | def getoffset(self, line): |
|
436 | def getoffset(self, line): | |
437 | return self._lastannotate.lines[line]._offset |
|
437 | return self._lastannotate.lines[line]._offset | |
438 |
|
438 | |||
439 | def getalllines(self, start=0, end=0): |
|
439 | def getalllines(self, start=0, end=0): | |
440 | """Get all lines that ever occurred in [start, end). |
|
440 | """Get all lines that ever occurred in [start, end). | |
441 |
|
441 | |||
442 | Passing start == end == 0 means "all lines ever". |
|
442 | Passing start == end == 0 means "all lines ever". | |
443 |
|
443 | |||
444 | This works in terms of *internal* program offsets, not line numbers. |
|
444 | This works in terms of *internal* program offsets, not line numbers. | |
445 | """ |
|
445 | """ | |
446 | pc = start or 1 |
|
446 | pc = start or 1 | |
447 | lines = [] |
|
447 | lines = [] | |
448 | # only take as many steps as there are instructions in the |
|
448 | # only take as many steps as there are instructions in the | |
449 | # program - if we don't find an EOF or our stop-line before |
|
449 | # program - if we don't find an EOF or our stop-line before | |
450 | # then, something is badly broken. |
|
450 | # then, something is badly broken. | |
451 | for step in pycompat.xrange(len(self._program)): |
|
451 | for step in pycompat.xrange(len(self._program)): | |
452 | inst = self._program[pc] |
|
452 | inst = self._program[pc] | |
453 | nextpc = pc + 1 |
|
453 | nextpc = pc + 1 | |
454 | if isinstance(inst, _jump): |
|
454 | if isinstance(inst, _jump): | |
455 | nextpc = inst._target |
|
455 | nextpc = inst._target | |
456 | elif isinstance(inst, _eof): |
|
456 | elif isinstance(inst, _eof): | |
457 | return lines |
|
457 | return lines | |
458 | elif isinstance(inst, (_jl, _jge)): |
|
458 | elif isinstance(inst, (_jl, _jge)): | |
459 | pass |
|
459 | pass | |
460 | elif isinstance(inst, _line): |
|
460 | elif isinstance(inst, _line): | |
461 | lines.append((inst._rev, inst._origlineno)) |
|
461 | lines.append((inst._rev, inst._origlineno)) | |
462 | else: |
|
462 | else: | |
463 | raise LineLogError(b"Illegal instruction %r" % inst) |
|
463 | raise LineLogError(b"Illegal instruction %r" % inst) | |
464 | if nextpc == end: |
|
464 | if nextpc == end: | |
465 | return lines |
|
465 | return lines | |
466 | pc = nextpc |
|
466 | pc = nextpc | |
467 | raise LineLogError(b"Failed to perform getalllines") |
|
467 | raise LineLogError(b"Failed to perform getalllines") |
General Comments 0
You need to be logged in to leave comments.
Login now