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