##// END OF EJS Templates
wix: functionality to automate building WiX installers...
Gregory Szorc -
r42087:4371f543 default
parent child Browse files
Show More
@@ -0,0 +1,248 b''
1 # wix.py - WiX installer functionality
2 #
3 # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com>
4 #
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.
7
8 # no-check-code because Python 3 native.
9
10 import os
11 import pathlib
12 import re
13 import subprocess
14
15 from .downloads import (
16 download_entry,
17 )
18 from .py2exe import (
19 build_py2exe,
20 )
21 from .util import (
22 extract_zip_to_directory,
23 sign_with_signtool,
24 )
25
26
27 SUPPORT_WXS = [
28 ('contrib.wxs', r'contrib'),
29 ('dist.wxs', r'dist'),
30 ('doc.wxs', r'doc'),
31 ('help.wxs', r'mercurial\help'),
32 ('i18n.wxs', r'i18n'),
33 ('locale.wxs', r'mercurial\locale'),
34 ('templates.wxs', r'mercurial\templates'),
35 ]
36
37
38 EXTRA_PACKAGES = {
39 'distutils',
40 'enum',
41 'imagesize',
42 'pygments',
43 'sphinx',
44 }
45
46
47 EXCLUDES = {
48 # Python 3 only.
49 'jinja2.asyncsupport',
50 }
51
52
53 def find_version(source_dir: pathlib.Path):
54 version_py = source_dir / 'mercurial' / '__version__.py'
55
56 with version_py.open('r', encoding='utf-8') as fh:
57 source = fh.read().strip()
58
59 m = re.search('version = b"(.*)"', source)
60 return m.group(1)
61
62
63 def normalize_version(version):
64 """Normalize Mercurial version string so WiX accepts it.
65
66 Version strings have to be numeric X.Y.Z.
67 """
68
69 if '+' in version:
70 version, extra = version.split('+', 1)
71 else:
72 extra = None
73
74 # 4.9rc0
75 if version[:-1].endswith('rc'):
76 version = version[:-3]
77
78 versions = [int(v) for v in version.split('.')]
79 while len(versions) < 3:
80 versions.append(0)
81
82 major, minor, build = versions[:3]
83
84 if extra:
85 # <commit count>-<hash>+<date>
86 build = int(extra.split('-')[0])
87
88 return '.'.join('%d' % x for x in (major, minor, build))
89
90
91 def ensure_vc90_merge_modules(build_dir):
92 x86 = (
93 download_entry('vc9-crt-x86-msm', build_dir,
94 local_name='microsoft.vcxx.crt.x86_msm.msm')[0],
95 download_entry('vc9-crt-x86-msm-policy', build_dir,
96 local_name='policy.x.xx.microsoft.vcxx.crt.x86_msm.msm')[0]
97 )
98
99 x64 = (
100 download_entry('vc9-crt-x64-msm', build_dir,
101 local_name='microsoft.vcxx.crt.x64_msm.msm')[0],
102 download_entry('vc9-crt-x64-msm-policy', build_dir,
103 local_name='policy.x.xx.microsoft.vcxx.crt.x64_msm.msm')[0]
104 )
105 return {
106 'x86': x86,
107 'x64': x64,
108 }
109
110
111 def run_candle(wix, cwd, wxs, source_dir, defines=None):
112 args = [
113 str(wix / 'candle.exe'),
114 '-nologo',
115 str(wxs),
116 '-dSourceDir=%s' % source_dir,
117 ]
118
119 if defines:
120 args.extend('-d%s=%s' % define for define in sorted(defines.items()))
121
122 subprocess.run(args, cwd=str(cwd), check=True)
123
124
125 def make_post_build_signing_fn(name, subject_name=None, cert_path=None,
126 cert_password=None, timestamp_url=None):
127 """Create a callable that will use signtool to sign hg.exe."""
128
129 def post_build_sign(source_dir, build_dir, dist_dir, version):
130 description = '%s %s' % (name, version)
131
132 sign_with_signtool(dist_dir / 'hg.exe', description,
133 subject_name=subject_name, cert_path=cert_path,
134 cert_password=cert_password,
135 timestamp_url=timestamp_url)
136
137 return post_build_sign
138
139
140 def build_installer(source_dir: pathlib.Path, python_exe: pathlib.Path,
141 msi_name='mercurial', version=None, post_build_fn=None):
142 """Build a WiX MSI installer.
143
144 ``source_dir`` is the path to the Mercurial source tree to use.
145 ``arch`` is the target architecture. either ``x86`` or ``x64``.
146 ``python_exe`` is the path to the Python executable to use/bundle.
147 ``version`` is the Mercurial version string. If not defined,
148 ``mercurial/__version__.py`` will be consulted.
149 ``post_build_fn`` is a callable that will be called after building
150 Mercurial but before invoking WiX. It can be used to e.g. facilitate
151 signing. It is passed the paths to the Mercurial source, build, and
152 dist directories and the resolved Mercurial version.
153 """
154 arch = 'x64' if r'\x64' in os.environ.get('LIB', '') else 'x86'
155
156 hg_build_dir = source_dir / 'build'
157 dist_dir = source_dir / 'dist'
158
159 requirements_txt = (source_dir / 'contrib' / 'packaging' /
160 'wix' / 'requirements.txt')
161
162 build_py2exe(source_dir, hg_build_dir,
163 python_exe, 'wix', requirements_txt,
164 extra_packages=EXTRA_PACKAGES, extra_excludes=EXCLUDES)
165
166 version = version or normalize_version(find_version(source_dir))
167 print('using version string: %s' % version)
168
169 if post_build_fn:
170 post_build_fn(source_dir, hg_build_dir, dist_dir, version)
171
172 build_dir = hg_build_dir / ('wix-%s' % arch)
173
174 build_dir.mkdir(exist_ok=True)
175
176 wix_pkg, wix_entry = download_entry('wix', hg_build_dir)
177 wix_path = hg_build_dir / ('wix-%s' % wix_entry['version'])
178
179 if not wix_path.exists():
180 extract_zip_to_directory(wix_pkg, wix_path)
181
182 ensure_vc90_merge_modules(hg_build_dir)
183
184 source_build_rel = pathlib.Path(os.path.relpath(source_dir, build_dir))
185
186 defines = {'Platform': arch}
187
188 for wxs, rel_path in SUPPORT_WXS:
189 wxs = source_dir / 'contrib' / 'packaging' / 'wix' / wxs
190 wxs_source_dir = source_dir / rel_path
191 run_candle(wix_path, build_dir, wxs, wxs_source_dir, defines=defines)
192
193 source = source_dir / 'contrib' / 'packaging' / 'wix' / 'mercurial.wxs'
194 defines['Version'] = version
195 defines['Comments'] = 'Installs Mercurial version %s' % version
196 defines['VCRedistSrcDir'] = str(hg_build_dir)
197
198 run_candle(wix_path, build_dir, source, source_build_rel, defines=defines)
199
200 msi_path = source_dir / 'dist' / (
201 '%s-%s-%s.msi' % (msi_name, version, arch))
202
203 args = [
204 str(wix_path / 'light.exe'),
205 '-nologo',
206 '-ext', 'WixUIExtension',
207 '-sw1076',
208 '-spdb',
209 '-o', str(msi_path),
210 ]
211
212 for source, rel_path in SUPPORT_WXS:
213 assert source.endswith('.wxs')
214 args.append(str(build_dir / ('%s.wixobj' % source[:-4])))
215
216 args.append(str(build_dir / 'mercurial.wixobj'))
217
218 subprocess.run(args, cwd=str(source_dir), check=True)
219
220 print('%s created' % msi_path)
221
222 return {
223 'msi_path': msi_path,
224 }
225
226
227 def build_signed_installer(source_dir: pathlib.Path, python_exe: pathlib.Path,
228 name: str, version=None, subject_name=None,
229 cert_path=None, cert_password=None,
230 timestamp_url=None):
231 """Build an installer with signed executables."""
232
233 post_build_fn = make_post_build_signing_fn(
234 name,
235 subject_name=subject_name,
236 cert_path=cert_path,
237 cert_password=cert_password,
238 timestamp_url=timestamp_url)
239
240 info = build_installer(source_dir, python_exe=python_exe,
241 msi_name=name.lower(), version=version,
242 post_build_fn=post_build_fn)
243
244 description = '%s %s' % (name, version)
245
246 sign_with_signtool(info['msi_path'], description,
247 subject_name=subject_name, cert_path=cert_path,
248 cert_password=cert_password, timestamp_url=timestamp_url)
@@ -0,0 +1,65 b''
1 #!/usr/bin/env python3
2 # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com>
3 #
4 # This software may be used and distributed according to the terms of the
5 # GNU General Public License version 2 or any later version.
6
7 # no-check-code because Python 3 native.
8
9 """Code to build Mercurial WiX installer."""
10
11 import argparse
12 import os
13 import pathlib
14 import sys
15
16
17 if __name__ == '__main__':
18 parser = argparse.ArgumentParser()
19
20 parser.add_argument('--name',
21 help='Application name',
22 default='Mercurial')
23 parser.add_argument('--python',
24 help='Path to Python executable to use',
25 required=True)
26 parser.add_argument('--sign-sn',
27 help='Subject name (or fragment thereof) of certificate '
28 'to use for signing')
29 parser.add_argument('--sign-cert',
30 help='Path to certificate to use for signing')
31 parser.add_argument('--sign-password',
32 help='Password for signing certificate')
33 parser.add_argument('--sign-timestamp-url',
34 help='URL of timestamp server to use for signing')
35 parser.add_argument('--version',
36 help='Version string to use')
37
38 args = parser.parse_args()
39
40 here = pathlib.Path(os.path.abspath(os.path.dirname(__file__)))
41 source_dir = here.parent.parent.parent
42
43 sys.path.insert(0, str(source_dir / 'contrib' / 'packaging'))
44
45 from hgpackaging.wix import (
46 build_installer,
47 build_signed_installer,
48 )
49
50 fn = build_installer
51 kwargs = {
52 'source_dir': source_dir,
53 'python_exe': pathlib.Path(args.python),
54 'version': args.version,
55 }
56
57 if args.sign_sn or args.sign_cert:
58 fn = build_signed_installer
59 kwargs['name'] = args.name
60 kwargs['subject_name'] = args.sign_sn
61 kwargs['cert_path'] = args.sign_cert
62 kwargs['cert_password'] = args.sign_password
63 kwargs['timestamp_url'] = args.sign_timestamp_url
64
65 fn(**kwargs)
@@ -0,0 +1,132 b''
1 #
2 # This file is autogenerated by pip-compile
3 # To update, run:
4 #
5 # pip-compile --generate-hashes contrib/packaging/wix/requirements.txt.in -o contrib/packaging/wix/requirements.txt -U
6 #
7 alabaster==0.7.12 \
8 --hash=sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359 \
9 --hash=sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02 \
10 # via sphinx
11 babel==2.6.0 \
12 --hash=sha256:6778d85147d5d85345c14a26aada5e478ab04e39b078b0745ee6870c2b5cf669 \
13 --hash=sha256:8cba50f48c529ca3fa18cf81fa9403be176d374ac4d60738b839122dfaaa3d23 \
14 # via sphinx
15 certifi==2018.11.29 \
16 --hash=sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7 \
17 --hash=sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033 \
18 # via requests
19 chardet==3.0.4 \
20 --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \
21 --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 \
22 # via requests
23 colorama==0.4.1 \
24 --hash=sha256:05eed71e2e327246ad6b38c540c4a3117230b19679b875190486ddd2d721422d \
25 --hash=sha256:f8ac84de7840f5b9c4e3347b3c1eaa50f7e49c2b07596221daec5edaabbd7c48 \
26 # via sphinx
27 docutils==0.14 \
28 --hash=sha256:02aec4bd92ab067f6ff27a38a38a41173bf01bed8f89157768c1573f53e474a6 \
29 --hash=sha256:51e64ef2ebfb29cae1faa133b3710143496eca21c530f3f71424d77687764274 \
30 --hash=sha256:7a4bd47eaf6596e1295ecb11361139febe29b084a87bf005bf899f9a42edc3c6
31 enum==0.4.7 \
32 --hash=sha256:8c7cf3587eda51008bcc1eed99ea2c331ccd265c231dbaa95ec5258d3dc03100
33 future==0.17.1 \
34 --hash=sha256:67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8
35 idna==2.8 \
36 --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \
37 --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c \
38 # via requests
39 imagesize==1.1.0 \
40 --hash=sha256:3f349de3eb99145973fefb7dbe38554414e5c30abd0c8e4b970a7c9d09f3a1d8 \
41 --hash=sha256:f3832918bc3c66617f92e35f5d70729187676313caa60c187eb0f28b8fe5e3b5 \
42 # via sphinx
43 jinja2==2.10 \
44 --hash=sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd \
45 --hash=sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4 \
46 # via sphinx
47 markupsafe==1.1.1 \
48 --hash=sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473 \
49 --hash=sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161 \
50 --hash=sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235 \
51 --hash=sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5 \
52 --hash=sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff \
53 --hash=sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b \
54 --hash=sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1 \
55 --hash=sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e \
56 --hash=sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183 \
57 --hash=sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66 \
58 --hash=sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1 \
59 --hash=sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1 \
60 --hash=sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e \
61 --hash=sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b \
62 --hash=sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905 \
63 --hash=sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735 \
64 --hash=sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d \
65 --hash=sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e \
66 --hash=sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d \
67 --hash=sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c \
68 --hash=sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21 \
69 --hash=sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2 \
70 --hash=sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5 \
71 --hash=sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b \
72 --hash=sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6 \
73 --hash=sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f \
74 --hash=sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f \
75 --hash=sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7 \
76 # via jinja2
77 packaging==19.0 \
78 --hash=sha256:0c98a5d0be38ed775798ece1b9727178c4469d9c3b4ada66e8e6b7849f8732af \
79 --hash=sha256:9e1cbf8c12b1f1ce0bb5344b8d7ecf66a6f8a6e91bcb0c84593ed6d3ab5c4ab3 \
80 # via sphinx
81 pygments==2.3.1 \
82 --hash=sha256:5ffada19f6203563680669ee7f53b64dabbeb100eb51b61996085e99c03b284a \
83 --hash=sha256:e8218dd399a61674745138520d0d4cf2621d7e032439341bc3f647bff125818d
84 pyparsing==2.3.1 \
85 --hash=sha256:66c9268862641abcac4a96ba74506e594c884e3f57690a696d21ad8210ed667a \
86 --hash=sha256:f6c5ef0d7480ad048c054c37632c67fca55299990fff127850181659eea33fc3 \
87 # via packaging
88 pypiwin32==223 \
89 --hash=sha256:67adf399debc1d5d14dffc1ab5acacb800da569754fafdc576b2a039485aa775 \
90 --hash=sha256:71be40c1fbd28594214ecaecb58e7aa8b708eabfa0125c8a109ebd51edbd776a
91 pytz==2018.9 \
92 --hash=sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9 \
93 --hash=sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c \
94 # via babel
95 pywin32==224 \
96 --hash=sha256:22e218832a54ed206452c8f3ca9eff07ef327f8e597569a4c2828be5eaa09a77 \
97 --hash=sha256:32b37abafbfeddb0fe718008d6aada5a71efa2874f068bee1f9e703983dcc49a \
98 --hash=sha256:35451edb44162d2f603b5b18bd427bc88fcbc74849eaa7a7e7cfe0f507e5c0c8 \
99 --hash=sha256:4eda2e1e50faa706ff8226195b84fbcbd542b08c842a9b15e303589f85bfb41c \
100 --hash=sha256:5f265d72588806e134c8e1ede8561739071626ea4cc25c12d526aa7b82416ae5 \
101 --hash=sha256:6852ceac5fdd7a146b570655c37d9eacd520ed1eaeec051ff41c6fc94243d8bf \
102 --hash=sha256:6dbc4219fe45ece6a0cc6baafe0105604fdee551b5e876dc475d3955b77190ec \
103 --hash=sha256:9bd07746ce7f2198021a9fa187fa80df7b221ec5e4c234ab6f00ea355a3baf99 \
104 # via pypiwin32
105 requests==2.21.0 \
106 --hash=sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e \
107 --hash=sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b \
108 # via sphinx
109 six==1.12.0 \
110 --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \
111 --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 \
112 # via packaging, sphinx
113 snowballstemmer==1.2.1 \
114 --hash=sha256:919f26a68b2c17a7634da993d91339e288964f93c274f1343e3bbbe2096e1128 \
115 --hash=sha256:9f3bcd3c401c3e862ec0ebe6d2c069ebc012ce142cce209c098ccb5b09136e89 \
116 # via sphinx
117 sphinx==1.8.4 \
118 --hash=sha256:b53904fa7cb4b06a39409a492b949193a1b68cc7241a1a8ce9974f86f0d24287 \
119 --hash=sha256:c1c00fc4f6e8b101a0d037065043460dffc2d507257f2f11acaed71fd2b0c83c
120 sphinxcontrib-websupport==1.1.0 \
121 --hash=sha256:68ca7ff70785cbe1e7bccc71a48b5b6d965d79ca50629606c7861a21b206d9dd \
122 --hash=sha256:9de47f375baf1ea07cdb3436ff39d7a9c76042c10a769c52353ec46e4e8fc3b9 \
123 # via sphinx
124 typing==3.6.6 \
125 --hash=sha256:4027c5f6127a6267a435201981ba156de91ad0d1d98e9ddc2aa173453453492d \
126 --hash=sha256:57dcf675a99b74d64dacf6fba08fb17cf7e3d5fdff53d4a30ea2a5e7e52543d4 \
127 --hash=sha256:a4c8473ce11a65999c8f59cb093e70686b6c84c98df58c1dae9b3b196089858a \
128 # via sphinx
129 urllib3==1.24.1 \
130 --hash=sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39 \
131 --hash=sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22 \
132 # via requests
@@ -0,0 +1,6 b''
1 docutils
2 enum
3 future
4 pygments
5 pypiwin32
6 sphinx
@@ -1,145 +1,175 b''
1 # downloads.py - Code for downloading dependencies.
1 # downloads.py - Code for downloading dependencies.
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 gzip
10 import gzip
11 import hashlib
11 import hashlib
12 import pathlib
12 import pathlib
13 import urllib.request
13 import urllib.request
14
14
15
15
16 DOWNLOADS = {
16 DOWNLOADS = {
17 'gettext': {
17 'gettext': {
18 'url': 'https://versaweb.dl.sourceforge.net/project/gnuwin32/gettext/0.14.4/gettext-0.14.4-bin.zip',
18 'url': 'https://versaweb.dl.sourceforge.net/project/gnuwin32/gettext/0.14.4/gettext-0.14.4-bin.zip',
19 'size': 1606131,
19 'size': 1606131,
20 'sha256': '60b9ef26bc5cceef036f0424e542106cf158352b2677f43a01affd6d82a1d641',
20 'sha256': '60b9ef26bc5cceef036f0424e542106cf158352b2677f43a01affd6d82a1d641',
21 'version': '0.14.4',
21 'version': '0.14.4',
22 },
22 },
23 'gettext-dep': {
23 'gettext-dep': {
24 'url': 'https://versaweb.dl.sourceforge.net/project/gnuwin32/gettext/0.14.4/gettext-0.14.4-dep.zip',
24 'url': 'https://versaweb.dl.sourceforge.net/project/gnuwin32/gettext/0.14.4/gettext-0.14.4-dep.zip',
25 'size': 715086,
25 'size': 715086,
26 'sha256': '411f94974492fd2ecf52590cb05b1023530aec67e64154a88b1e4ebcd9c28588',
26 'sha256': '411f94974492fd2ecf52590cb05b1023530aec67e64154a88b1e4ebcd9c28588',
27 },
27 },
28 'py2exe': {
28 'py2exe': {
29 'url': 'https://versaweb.dl.sourceforge.net/project/py2exe/py2exe/0.6.9/py2exe-0.6.9.zip',
29 'url': 'https://versaweb.dl.sourceforge.net/project/py2exe/py2exe/0.6.9/py2exe-0.6.9.zip',
30 'size': 149687,
30 'size': 149687,
31 'sha256': '6bd383312e7d33eef2e43a5f236f9445e4f3e0f6b16333c6f183ed445c44ddbd',
31 'sha256': '6bd383312e7d33eef2e43a5f236f9445e4f3e0f6b16333c6f183ed445c44ddbd',
32 'version': '0.6.9',
32 'version': '0.6.9',
33 },
33 },
34 # The VC9 CRT merge modules aren't readily available on most systems because
35 # they are only installed as part of a full Visual Studio 2008 install.
36 # While we could potentially extract them from a Visual Studio 2008
37 # installer, it is easier to just fetch them from a known URL.
38 'vc9-crt-x86-msm': {
39 'url': 'https://github.com/indygreg/vc90-merge-modules/raw/9232f8f0b2135df619bf7946eaa176b4ac35ccff/Microsoft_VC90_CRT_x86.msm',
40 'size': 615424,
41 'sha256': '837e887ef31b332feb58156f429389de345cb94504228bb9a523c25a9dd3d75e',
42 },
43 'vc9-crt-x86-msm-policy': {
44 'url': 'https://github.com/indygreg/vc90-merge-modules/raw/9232f8f0b2135df619bf7946eaa176b4ac35ccff/policy_9_0_Microsoft_VC90_CRT_x86.msm',
45 'size': 71168,
46 'sha256': '3fbcf92e3801a0757f36c5e8d304e134a68d5cafd197a6df7734ae3e8825c940',
47 },
48 'vc9-crt-x64-msm': {
49 'url': 'https://github.com/indygreg/vc90-merge-modules/raw/9232f8f0b2135df619bf7946eaa176b4ac35ccff/Microsoft_VC90_CRT_x86_x64.msm',
50 'size': 662528,
51 'sha256': '50d9639b5ad4844a2285269c7551bf5157ec636e32396ddcc6f7ec5bce487a7c',
52 },
53 'vc9-crt-x64-msm-policy': {
54 'url': 'https://github.com/indygreg/vc90-merge-modules/raw/9232f8f0b2135df619bf7946eaa176b4ac35ccff/policy_9_0_Microsoft_VC90_CRT_x86_x64.msm',
55 'size': 71168,
56 'sha256': '0550ea1929b21239134ad3a678c944ba0f05f11087117b6cf0833e7110686486',
57 },
34 'virtualenv': {
58 'virtualenv': {
35 'url': 'https://files.pythonhosted.org/packages/37/db/89d6b043b22052109da35416abc3c397655e4bd3cff031446ba02b9654fa/virtualenv-16.4.3.tar.gz',
59 'url': 'https://files.pythonhosted.org/packages/37/db/89d6b043b22052109da35416abc3c397655e4bd3cff031446ba02b9654fa/virtualenv-16.4.3.tar.gz',
36 'size': 3713208,
60 'size': 3713208,
37 'sha256': '984d7e607b0a5d1329425dd8845bd971b957424b5ba664729fab51ab8c11bc39',
61 'sha256': '984d7e607b0a5d1329425dd8845bd971b957424b5ba664729fab51ab8c11bc39',
38 'version': '16.4.3',
62 'version': '16.4.3',
39 },
63 },
64 'wix': {
65 'url': 'https://github.com/wixtoolset/wix3/releases/download/wix3111rtm/wix311-binaries.zip',
66 'size': 34358269,
67 'sha256': '37f0a533b0978a454efb5dc3bd3598becf9660aaf4287e55bf68ca6b527d051d',
68 'version': '3.11.1',
69 },
40 }
70 }
41
71
42
72
43 def hash_path(p: pathlib.Path):
73 def hash_path(p: pathlib.Path):
44 h = hashlib.sha256()
74 h = hashlib.sha256()
45
75
46 with p.open('rb') as fh:
76 with p.open('rb') as fh:
47 while True:
77 while True:
48 chunk = fh.read(65536)
78 chunk = fh.read(65536)
49 if not chunk:
79 if not chunk:
50 break
80 break
51
81
52 h.update(chunk)
82 h.update(chunk)
53
83
54 return h.hexdigest()
84 return h.hexdigest()
55
85
56
86
57 class IntegrityError(Exception):
87 class IntegrityError(Exception):
58 """Represents an integrity error when downloading a URL."""
88 """Represents an integrity error when downloading a URL."""
59
89
60
90
61 def secure_download_stream(url, size, sha256):
91 def secure_download_stream(url, size, sha256):
62 """Securely download a URL to a stream of chunks.
92 """Securely download a URL to a stream of chunks.
63
93
64 If the integrity of the download fails, an IntegrityError is
94 If the integrity of the download fails, an IntegrityError is
65 raised.
95 raised.
66 """
96 """
67 h = hashlib.sha256()
97 h = hashlib.sha256()
68 length = 0
98 length = 0
69
99
70 with urllib.request.urlopen(url) as fh:
100 with urllib.request.urlopen(url) as fh:
71 if not url.endswith('.gz') and fh.info().get('Content-Encoding') == 'gzip':
101 if not url.endswith('.gz') and fh.info().get('Content-Encoding') == 'gzip':
72 fh = gzip.GzipFile(fileobj=fh)
102 fh = gzip.GzipFile(fileobj=fh)
73
103
74 while True:
104 while True:
75 chunk = fh.read(65536)
105 chunk = fh.read(65536)
76 if not chunk:
106 if not chunk:
77 break
107 break
78
108
79 h.update(chunk)
109 h.update(chunk)
80 length += len(chunk)
110 length += len(chunk)
81
111
82 yield chunk
112 yield chunk
83
113
84 digest = h.hexdigest()
114 digest = h.hexdigest()
85
115
86 if length != size:
116 if length != size:
87 raise IntegrityError('size mismatch on %s: wanted %d; got %d' % (
117 raise IntegrityError('size mismatch on %s: wanted %d; got %d' % (
88 url, size, length))
118 url, size, length))
89
119
90 if digest != sha256:
120 if digest != sha256:
91 raise IntegrityError('sha256 mismatch on %s: wanted %s; got %s' % (
121 raise IntegrityError('sha256 mismatch on %s: wanted %s; got %s' % (
92 url, sha256, digest))
122 url, sha256, digest))
93
123
94
124
95 def download_to_path(url: str, path: pathlib.Path, size: int, sha256: str):
125 def download_to_path(url: str, path: pathlib.Path, size: int, sha256: str):
96 """Download a URL to a filesystem path, possibly with verification."""
126 """Download a URL to a filesystem path, possibly with verification."""
97
127
98 # We download to a temporary file and rename at the end so there's
128 # We download to a temporary file and rename at the end so there's
99 # no chance of the final file being partially written or containing
129 # no chance of the final file being partially written or containing
100 # bad data.
130 # bad data.
101 print('downloading %s to %s' % (url, path))
131 print('downloading %s to %s' % (url, path))
102
132
103 if path.exists():
133 if path.exists():
104 good = True
134 good = True
105
135
106 if path.stat().st_size != size:
136 if path.stat().st_size != size:
107 print('existing file size is wrong; removing')
137 print('existing file size is wrong; removing')
108 good = False
138 good = False
109
139
110 if good:
140 if good:
111 if hash_path(path) != sha256:
141 if hash_path(path) != sha256:
112 print('existing file hash is wrong; removing')
142 print('existing file hash is wrong; removing')
113 good = False
143 good = False
114
144
115 if good:
145 if good:
116 print('%s exists and passes integrity checks' % path)
146 print('%s exists and passes integrity checks' % path)
117 return
147 return
118
148
119 path.unlink()
149 path.unlink()
120
150
121 tmp = path.with_name('%s.tmp' % path.name)
151 tmp = path.with_name('%s.tmp' % path.name)
122
152
123 try:
153 try:
124 with tmp.open('wb') as fh:
154 with tmp.open('wb') as fh:
125 for chunk in secure_download_stream(url, size, sha256):
155 for chunk in secure_download_stream(url, size, sha256):
126 fh.write(chunk)
156 fh.write(chunk)
127 except IntegrityError:
157 except IntegrityError:
128 tmp.unlink()
158 tmp.unlink()
129 raise
159 raise
130
160
131 tmp.rename(path)
161 tmp.rename(path)
132 print('successfully downloaded %s' % url)
162 print('successfully downloaded %s' % url)
133
163
134
164
135 def download_entry(name: dict, dest_path: pathlib.Path, local_name=None) -> pathlib.Path:
165 def download_entry(name: dict, dest_path: pathlib.Path, local_name=None) -> pathlib.Path:
136 entry = DOWNLOADS[name]
166 entry = DOWNLOADS[name]
137
167
138 url = entry['url']
168 url = entry['url']
139
169
140 local_name = local_name or url[url.rindex('/') + 1:]
170 local_name = local_name or url[url.rindex('/') + 1:]
141
171
142 local_path = dest_path / local_name
172 local_path = dest_path / local_name
143 download_to_path(url, local_path, entry['size'], entry['sha256'])
173 download_to_path(url, local_path, entry['size'], entry['sha256'])
144
174
145 return local_path, entry
175 return local_path, entry
@@ -1,73 +1,157 b''
1 # util.py - Common packaging utility code.
1 # util.py - Common packaging utility code.
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 distutils.version
10 import distutils.version
11 import getpass
11 import os
12 import os
12 import pathlib
13 import pathlib
13 import subprocess
14 import subprocess
14 import tarfile
15 import tarfile
15 import zipfile
16 import zipfile
16
17
17
18
18 def extract_tar_to_directory(source: pathlib.Path, dest: pathlib.Path):
19 def extract_tar_to_directory(source: pathlib.Path, dest: pathlib.Path):
19 with tarfile.open(source, 'r') as tf:
20 with tarfile.open(source, 'r') as tf:
20 tf.extractall(dest)
21 tf.extractall(dest)
21
22
22
23
23 def extract_zip_to_directory(source: pathlib.Path, dest: pathlib.Path):
24 def extract_zip_to_directory(source: pathlib.Path, dest: pathlib.Path):
24 with zipfile.ZipFile(source, 'r') as zf:
25 with zipfile.ZipFile(source, 'r') as zf:
25 zf.extractall(dest)
26 zf.extractall(dest)
26
27
27
28
28 def find_vc_runtime_files(x64=False):
29 def find_vc_runtime_files(x64=False):
29 """Finds Visual C++ Runtime DLLs to include in distribution."""
30 """Finds Visual C++ Runtime DLLs to include in distribution."""
30 winsxs = pathlib.Path(os.environ['SYSTEMROOT']) / 'WinSxS'
31 winsxs = pathlib.Path(os.environ['SYSTEMROOT']) / 'WinSxS'
31
32
32 prefix = 'amd64' if x64 else 'x86'
33 prefix = 'amd64' if x64 else 'x86'
33
34
34 candidates = sorted(p for p in os.listdir(winsxs)
35 candidates = sorted(p for p in os.listdir(winsxs)
35 if p.lower().startswith('%s_microsoft.vc90.crt_' % prefix))
36 if p.lower().startswith('%s_microsoft.vc90.crt_' % prefix))
36
37
37 for p in candidates:
38 for p in candidates:
38 print('found candidate VC runtime: %s' % p)
39 print('found candidate VC runtime: %s' % p)
39
40
40 # Take the newest version.
41 # Take the newest version.
41 version = candidates[-1]
42 version = candidates[-1]
42
43
43 d = winsxs / version
44 d = winsxs / version
44
45
45 return [
46 return [
46 d / 'msvcm90.dll',
47 d / 'msvcm90.dll',
47 d / 'msvcp90.dll',
48 d / 'msvcp90.dll',
48 d / 'msvcr90.dll',
49 d / 'msvcr90.dll',
49 winsxs / 'Manifests' / ('%s.manifest' % version),
50 winsxs / 'Manifests' / ('%s.manifest' % version),
50 ]
51 ]
51
52
52
53
54 def windows_10_sdk_info():
55 """Resolves information about the Windows 10 SDK."""
56
57 base = pathlib.Path(os.environ['ProgramFiles(x86)']) / 'Windows Kits' / '10'
58
59 if not base.is_dir():
60 raise Exception('unable to find Windows 10 SDK at %s' % base)
61
62 # Find the latest version.
63 bin_base = base / 'bin'
64
65 versions = [v for v in os.listdir(bin_base) if v.startswith('10.')]
66 version = sorted(versions, reverse=True)[0]
67
68 bin_version = bin_base / version
69
70 return {
71 'root': base,
72 'version': version,
73 'bin_root': bin_version,
74 'bin_x86': bin_version / 'x86',
75 'bin_x64': bin_version / 'x64'
76 }
77
78
79 def find_signtool():
80 """Find signtool.exe from the Windows SDK."""
81 sdk = windows_10_sdk_info()
82
83 for key in ('bin_x64', 'bin_x86'):
84 p = sdk[key] / 'signtool.exe'
85
86 if p.exists():
87 return p
88
89 raise Exception('could not find signtool.exe in Windows 10 SDK')
90
91
92 def sign_with_signtool(file_path, description, subject_name=None,
93 cert_path=None, cert_password=None,
94 timestamp_url=None):
95 """Digitally sign a file with signtool.exe.
96
97 ``file_path`` is file to sign.
98 ``description`` is text that goes in the signature.
99
100 The signing certificate can be specified by ``cert_path`` or
101 ``subject_name``. These correspond to the ``/f`` and ``/n`` arguments
102 to signtool.exe, respectively.
103
104 The certificate password can be specified via ``cert_password``. If
105 not provided, you will be prompted for the password.
106
107 ``timestamp_url`` is the URL of a RFC 3161 timestamp server (``/tr``
108 argument to signtool.exe).
109 """
110 if cert_path and subject_name:
111 raise ValueError('cannot specify both cert_path and subject_name')
112
113 while cert_path and not cert_password:
114 cert_password = getpass.getpass('password for %s: ' % cert_path)
115
116 args = [
117 str(find_signtool()), 'sign',
118 '/v',
119 '/fd', 'sha256',
120 '/d', description,
121 ]
122
123 if cert_path:
124 args.extend(['/f', str(cert_path), '/p', cert_password])
125 elif subject_name:
126 args.extend(['/n', subject_name])
127
128 if timestamp_url:
129 args.extend(['/tr', timestamp_url, '/td', 'sha256'])
130
131 args.append(str(file_path))
132
133 print('signing %s' % file_path)
134 subprocess.run(args, check=True)
135
136
53 PRINT_PYTHON_INFO = '''
137 PRINT_PYTHON_INFO = '''
54 import platform; print("%s:%s" % (platform.architecture()[0], platform.python_version()))
138 import platform; print("%s:%s" % (platform.architecture()[0], platform.python_version()))
55 '''.strip()
139 '''.strip()
56
140
57
141
58 def python_exe_info(python_exe: pathlib.Path):
142 def python_exe_info(python_exe: pathlib.Path):
59 """Obtain information about a Python executable."""
143 """Obtain information about a Python executable."""
60
144
61 res = subprocess.run(
145 res = subprocess.run(
62 [str(python_exe), '-c', PRINT_PYTHON_INFO],
146 [str(python_exe), '-c', PRINT_PYTHON_INFO],
63 capture_output=True, check=True)
147 capture_output=True, check=True)
64
148
65 arch, version = res.stdout.decode('utf-8').split(':')
149 arch, version = res.stdout.decode('utf-8').split(':')
66
150
67 version = distutils.version.LooseVersion(version)
151 version = distutils.version.LooseVersion(version)
68
152
69 return {
153 return {
70 'arch': arch,
154 'arch': arch,
71 'version': version,
155 'version': version,
72 'py3': version >= distutils.version.LooseVersion('3'),
156 'py3': version >= distutils.version.LooseVersion('3'),
73 }
157 }
@@ -1,29 +1,71 b''
1 WiX installer source files
1 WiX Installer
2 =============
3
4 The files in this directory are used to produce an MSI installer using
5 the WiX Toolset (http://wixtoolset.org/).
6
7 The MSI installers require elevated (admin) privileges due to the
8 installation of MSVC CRT libraries into the Windows system store. See
9 the Inno Setup installers in the ``inno`` sibling directory for installers
10 that do not have this requirement.
11
12 Requirements
13 ============
14
15 Building the WiX installers requires a Windows machine. The following
16 dependencies must be installed:
17
18 * Python 2.7 (download from https://www.python.org/downloads/)
19 * Microsoft Visual C++ Compiler for Python 2.7
20 (https://www.microsoft.com/en-us/download/details.aspx?id=44266)
21 * Python 3.5+ (to run the ``build.py`` script)
22
23 Building
24 ========
25
26 The ``build.py`` script automates the process of producing an MSI
27 installer. It manages fetching and configuring non-system dependencies
28 (such as py2exe, gettext, and various Python packages).
29
30 The script requires an activated ``Visual C++ 2008`` command prompt.
31 A shortcut to such a prompt was installed with ``Microsoft Visual
32 C++ Compiler for Python 2.7``. From your Start Menu, look for
33 ``Microsoft Visual C++ Compiler Package for Python 2.7`` then
34 launch either ``Visual C++ 2008 32-bit Command Prompt`` or
35 ``Visual C++ 2008 64-bit Command Prompt``.
36
37 From the prompt, change to the Mercurial source directory. e.g.
38 ``cd c:\src\hg``.
39
40 Next, invoke ``build.py`` to produce an MSI installer. You will need
41 to supply the path to the Python interpreter to use.::
42
43 $ python3 contrib\packaging\wix\build.py \
44 --python c:\python27\python.exe
45
46 .. note::
47
48 The script validates that the Visual C++ environment is active and
49 that the architecture of the specified Python interpreter matches the
50 Visual C++ environment. An error is raised otherwise.
51
52 If everything runs as intended, dependencies will be fetched and
53 configured into the ``build`` sub-directory, Mercurial will be built,
54 and an installer placed in the ``dist`` sub-directory. The final line
55 of output should print the name of the generated installer.
56
57 Additional options may be configured. Run ``build.py --help`` to see
58 a list of program flags.
59
60 Relationship to TortoiseHG
2 ==========================
61 ==========================
3
62
4 The files in this folder are used by the thg-winbuild [1] package
63 TortoiseHG uses the WiX files in this directory.
5 building architecture to create a Mercurial MSI installer. These files
6 are versioned within the Mercurial source tree because the WXS files
7 must kept up to date with distribution changes within their branch. In
8 other words, the default branch WXS files are expected to diverge from
9 the stable branch WXS files. Storing them within the same repository is
10 the only sane way to keep the source tree and the installer in sync.
11
12 The MSI installer builder uses only the mercurial.ini file from the
13 contrib/win32 folder.
14
64
15 The MSI packages built by thg-winbuild require elevated (admin)
65 The code for building TortoiseHG installers lives at
16 privileges to be installed due to the installation of MSVC CRT libraries
66 https://bitbucket.org/tortoisehg/thg-winbuild and is maintained by
17 under the C:\WINDOWS\WinSxS folder. Thus the InnoSetup installers may
67 Steve Borho (steve@borho.org).
18 still be useful to some users.
19
68
20 To build your own MSI packages, clone the thg-winbuild [1] repository
69 When changing behavior of the WiX installer, be sure to notify
21 and follow the README.txt [2] instructions closely. There are fewer
70 the TortoiseHG Project of the changes so they have ample time
22 prerequisites for a WiX [3] installer than an InnoSetup installer, but
71 provide feedback and react to those changes.
23 they are more specific.
24
25 Direct questions or comments to Steve Borho <steve@borho.org>
26
27 [1] http://bitbucket.org/tortoisehg/thg-winbuild
28 [2] http://bitbucket.org/tortoisehg/thg-winbuild/src/tip/README.txt
29 [3] http://wix.sourceforge.net/
@@ -1,74 +1,76 b''
1 #require test-repo
1 #require test-repo
2
2
3 $ . "$TESTDIR/helpers-testrepo.sh"
3 $ . "$TESTDIR/helpers-testrepo.sh"
4 $ check_code="$TESTDIR"/../contrib/check-code.py
4 $ check_code="$TESTDIR"/../contrib/check-code.py
5 $ cd "$TESTDIR"/..
5 $ cd "$TESTDIR"/..
6
6
7 New errors are not allowed. Warnings are strongly discouraged.
7 New errors are not allowed. Warnings are strongly discouraged.
8 (The writing "no-che?k-code" is for not skipping this file when checking.)
8 (The writing "no-che?k-code" is for not skipping this file when checking.)
9
9
10 $ testrepohg locate \
10 $ testrepohg locate \
11 > -X contrib/python-zstandard \
11 > -X contrib/python-zstandard \
12 > -X hgext/fsmonitor/pywatchman \
12 > -X hgext/fsmonitor/pywatchman \
13 > -X mercurial/thirdparty \
13 > -X mercurial/thirdparty \
14 > | sed 's-\\-/-g' | "$check_code" --warnings --per-file=0 - || false
14 > | sed 's-\\-/-g' | "$check_code" --warnings --per-file=0 - || false
15 Skipping contrib/packaging/hgpackaging/downloads.py it has no-che?k-code (glob)
15 Skipping contrib/packaging/hgpackaging/downloads.py it has no-che?k-code (glob)
16 Skipping contrib/packaging/hgpackaging/inno.py it has no-che?k-code (glob)
16 Skipping contrib/packaging/hgpackaging/inno.py it has no-che?k-code (glob)
17 Skipping contrib/packaging/hgpackaging/py2exe.py it has no-che?k-code (glob)
17 Skipping contrib/packaging/hgpackaging/py2exe.py it has no-che?k-code (glob)
18 Skipping contrib/packaging/hgpackaging/util.py it has no-che?k-code (glob)
18 Skipping contrib/packaging/hgpackaging/util.py it has no-che?k-code (glob)
19 Skipping contrib/packaging/hgpackaging/wix.py it has no-che?k-code (glob)
19 Skipping contrib/packaging/inno/build.py it has no-che?k-code (glob)
20 Skipping contrib/packaging/inno/build.py it has no-che?k-code (glob)
21 Skipping contrib/packaging/wix/build.py it has no-che?k-code (glob)
20 Skipping i18n/polib.py it has no-che?k-code (glob)
22 Skipping i18n/polib.py it has no-che?k-code (glob)
21 Skipping mercurial/statprof.py it has no-che?k-code (glob)
23 Skipping mercurial/statprof.py it has no-che?k-code (glob)
22 Skipping tests/badserverext.py it has no-che?k-code (glob)
24 Skipping tests/badserverext.py it has no-che?k-code (glob)
23
25
24 @commands in debugcommands.py should be in alphabetical order.
26 @commands in debugcommands.py should be in alphabetical order.
25
27
26 >>> import re
28 >>> import re
27 >>> commands = []
29 >>> commands = []
28 >>> with open('mercurial/debugcommands.py', 'rb') as fh:
30 >>> with open('mercurial/debugcommands.py', 'rb') as fh:
29 ... for line in fh:
31 ... for line in fh:
30 ... m = re.match(br"^@command\('([a-z]+)", line)
32 ... m = re.match(br"^@command\('([a-z]+)", line)
31 ... if m:
33 ... if m:
32 ... commands.append(m.group(1))
34 ... commands.append(m.group(1))
33 >>> scommands = list(sorted(commands))
35 >>> scommands = list(sorted(commands))
34 >>> for i, command in enumerate(scommands):
36 >>> for i, command in enumerate(scommands):
35 ... if command != commands[i]:
37 ... if command != commands[i]:
36 ... print('commands in debugcommands.py not sorted; first differing '
38 ... print('commands in debugcommands.py not sorted; first differing '
37 ... 'command is %s; expected %s' % (commands[i], command))
39 ... 'command is %s; expected %s' % (commands[i], command))
38 ... break
40 ... break
39
41
40 Prevent adding new files in the root directory accidentally.
42 Prevent adding new files in the root directory accidentally.
41
43
42 $ testrepohg files 'glob:*'
44 $ testrepohg files 'glob:*'
43 .arcconfig
45 .arcconfig
44 .clang-format
46 .clang-format
45 .editorconfig
47 .editorconfig
46 .hgignore
48 .hgignore
47 .hgsigs
49 .hgsigs
48 .hgtags
50 .hgtags
49 .jshintrc
51 .jshintrc
50 CONTRIBUTING
52 CONTRIBUTING
51 CONTRIBUTORS
53 CONTRIBUTORS
52 COPYING
54 COPYING
53 Makefile
55 Makefile
54 README.rst
56 README.rst
55 hg
57 hg
56 hgeditor
58 hgeditor
57 hgweb.cgi
59 hgweb.cgi
58 setup.py
60 setup.py
59
61
60 Prevent adding modules which could be shadowed by ancient .so/.dylib.
62 Prevent adding modules which could be shadowed by ancient .so/.dylib.
61
63
62 $ testrepohg files \
64 $ testrepohg files \
63 > mercurial/base85.py \
65 > mercurial/base85.py \
64 > mercurial/bdiff.py \
66 > mercurial/bdiff.py \
65 > mercurial/diffhelpers.py \
67 > mercurial/diffhelpers.py \
66 > mercurial/mpatch.py \
68 > mercurial/mpatch.py \
67 > mercurial/osutil.py \
69 > mercurial/osutil.py \
68 > mercurial/parsers.py \
70 > mercurial/parsers.py \
69 > mercurial/zstd.py
71 > mercurial/zstd.py
70 [1]
72 [1]
71
73
72 Keep python3 tests sorted:
74 Keep python3 tests sorted:
73 $ sort < contrib/python3-whitelist > $TESTTMP/py3sorted
75 $ sort < contrib/python3-whitelist > $TESTTMP/py3sorted
74 $ cmp contrib/python3-whitelist $TESTTMP/py3sorted || echo 'Please sort passing tests!'
76 $ cmp contrib/python3-whitelist $TESTTMP/py3sorted || echo 'Please sort passing tests!'
@@ -1,58 +1,59 b''
1 #require test-repo
1 #require test-repo
2
2
3 $ . "$TESTDIR/helpers-testrepo.sh"
3 $ . "$TESTDIR/helpers-testrepo.sh"
4 $ cd "$TESTDIR"/..
4 $ cd "$TESTDIR"/..
5
5
6 #if no-py3
6 #if no-py3
7 $ testrepohg files 'set:(**.py)' \
7 $ testrepohg files 'set:(**.py)' \
8 > -X contrib/packaging/hgpackaging/ \
8 > -X contrib/packaging/hgpackaging/ \
9 > -X contrib/packaging/inno/ \
9 > -X contrib/packaging/inno/ \
10 > -X contrib/packaging/wix/ \
10 > -X hgdemandimport/demandimportpy2.py \
11 > -X hgdemandimport/demandimportpy2.py \
11 > -X mercurial/thirdparty/cbor \
12 > -X mercurial/thirdparty/cbor \
12 > | sed 's|\\|/|g' | xargs "$PYTHON" contrib/check-py3-compat.py
13 > | sed 's|\\|/|g' | xargs "$PYTHON" contrib/check-py3-compat.py
13 contrib/python-zstandard/setup.py not using absolute_import
14 contrib/python-zstandard/setup.py not using absolute_import
14 contrib/python-zstandard/setup_zstd.py not using absolute_import
15 contrib/python-zstandard/setup_zstd.py not using absolute_import
15 contrib/python-zstandard/tests/common.py not using absolute_import
16 contrib/python-zstandard/tests/common.py not using absolute_import
16 contrib/python-zstandard/tests/test_buffer_util.py not using absolute_import
17 contrib/python-zstandard/tests/test_buffer_util.py not using absolute_import
17 contrib/python-zstandard/tests/test_compressor.py not using absolute_import
18 contrib/python-zstandard/tests/test_compressor.py not using absolute_import
18 contrib/python-zstandard/tests/test_compressor_fuzzing.py not using absolute_import
19 contrib/python-zstandard/tests/test_compressor_fuzzing.py not using absolute_import
19 contrib/python-zstandard/tests/test_data_structures.py not using absolute_import
20 contrib/python-zstandard/tests/test_data_structures.py not using absolute_import
20 contrib/python-zstandard/tests/test_data_structures_fuzzing.py not using absolute_import
21 contrib/python-zstandard/tests/test_data_structures_fuzzing.py not using absolute_import
21 contrib/python-zstandard/tests/test_decompressor.py not using absolute_import
22 contrib/python-zstandard/tests/test_decompressor.py not using absolute_import
22 contrib/python-zstandard/tests/test_decompressor_fuzzing.py not using absolute_import
23 contrib/python-zstandard/tests/test_decompressor_fuzzing.py not using absolute_import
23 contrib/python-zstandard/tests/test_estimate_sizes.py not using absolute_import
24 contrib/python-zstandard/tests/test_estimate_sizes.py not using absolute_import
24 contrib/python-zstandard/tests/test_module_attributes.py not using absolute_import
25 contrib/python-zstandard/tests/test_module_attributes.py not using absolute_import
25 contrib/python-zstandard/tests/test_train_dictionary.py not using absolute_import
26 contrib/python-zstandard/tests/test_train_dictionary.py not using absolute_import
26 setup.py not using absolute_import
27 setup.py not using absolute_import
27 #endif
28 #endif
28
29
29 #if py3
30 #if py3
30 $ testrepohg files 'set:(**.py) - grep(pygments)' \
31 $ testrepohg files 'set:(**.py) - grep(pygments)' \
31 > -X hgdemandimport/demandimportpy2.py \
32 > -X hgdemandimport/demandimportpy2.py \
32 > -X hgext/fsmonitor/pywatchman \
33 > -X hgext/fsmonitor/pywatchman \
33 > -X mercurial/cffi \
34 > -X mercurial/cffi \
34 > -X mercurial/thirdparty \
35 > -X mercurial/thirdparty \
35 > | sed 's|\\|/|g' | xargs "$PYTHON" contrib/check-py3-compat.py \
36 > | sed 's|\\|/|g' | xargs "$PYTHON" contrib/check-py3-compat.py \
36 > | sed 's/[0-9][0-9]*)$/*)/'
37 > | sed 's/[0-9][0-9]*)$/*)/'
37 contrib/python-zstandard/tests/test_compressor.py:324: SyntaxWarning: invalid escape sequence \( (py38 !)
38 contrib/python-zstandard/tests/test_compressor.py:324: SyntaxWarning: invalid escape sequence \( (py38 !)
38 with self.assertRaisesRegexp(zstd.ZstdError, 'cannot call compress\(\) after compressor'): (py38 !)
39 with self.assertRaisesRegexp(zstd.ZstdError, 'cannot call compress\(\) after compressor'): (py38 !)
39 contrib/python-zstandard/tests/test_compressor.py:1329: SyntaxWarning: invalid escape sequence \( (py38 !)
40 contrib/python-zstandard/tests/test_compressor.py:1329: SyntaxWarning: invalid escape sequence \( (py38 !)
40 'cannot call compress\(\) after compression finished'): (py38 !)
41 'cannot call compress\(\) after compression finished'): (py38 !)
41 contrib/python-zstandard/tests/test_compressor.py:1341: SyntaxWarning: invalid escape sequence \( (py38 !)
42 contrib/python-zstandard/tests/test_compressor.py:1341: SyntaxWarning: invalid escape sequence \( (py38 !)
42 'cannot call flush\(\) after compression finished'): (py38 !)
43 'cannot call flush\(\) after compression finished'): (py38 !)
43 contrib/python-zstandard/tests/test_compressor.py:1353: SyntaxWarning: invalid escape sequence \( (py38 !)
44 contrib/python-zstandard/tests/test_compressor.py:1353: SyntaxWarning: invalid escape sequence \( (py38 !)
44 'cannot call finish\(\) after compression finished'): (py38 !)
45 'cannot call finish\(\) after compression finished'): (py38 !)
45 hgext/convert/transport.py: error importing: <*Error> No module named 'svn.client' (error at transport.py:*) (glob) (?)
46 hgext/convert/transport.py: error importing: <*Error> No module named 'svn.client' (error at transport.py:*) (glob) (?)
46 hgext/infinitepush/sqlindexapi.py: error importing: <*Error> No module named 'mysql' (error at sqlindexapi.py:*) (glob) (?)
47 hgext/infinitepush/sqlindexapi.py: error importing: <*Error> No module named 'mysql' (error at sqlindexapi.py:*) (glob) (?)
47 mercurial/scmwindows.py: error importing: <ValueError> _type_ 'v' not supported (error at win32.py:*) (no-windows !)
48 mercurial/scmwindows.py: error importing: <ValueError> _type_ 'v' not supported (error at win32.py:*) (no-windows !)
48 mercurial/win32.py: error importing: <ValueError> _type_ 'v' not supported (error at win32.py:*) (no-windows !)
49 mercurial/win32.py: error importing: <ValueError> _type_ 'v' not supported (error at win32.py:*) (no-windows !)
49 mercurial/windows.py: error importing: <ModuleNotFoundError> No module named 'msvcrt' (error at windows.py:*) (no-windows !)
50 mercurial/windows.py: error importing: <ModuleNotFoundError> No module named 'msvcrt' (error at windows.py:*) (no-windows !)
50 mercurial/posix.py: error importing: <ModuleNotFoundError> No module named 'fcntl' (error at posix.py:*) (windows !)
51 mercurial/posix.py: error importing: <ModuleNotFoundError> No module named 'fcntl' (error at posix.py:*) (windows !)
51 mercurial/scmposix.py: error importing: <ModuleNotFoundError> No module named 'fcntl' (error at scmposix.py:*) (windows !)
52 mercurial/scmposix.py: error importing: <ModuleNotFoundError> No module named 'fcntl' (error at scmposix.py:*) (windows !)
52 #endif
53 #endif
53
54
54 #if py3 pygments
55 #if py3 pygments
55 $ testrepohg files 'set:(**.py) and grep(pygments)' | sed 's|\\|/|g' \
56 $ testrepohg files 'set:(**.py) and grep(pygments)' | sed 's|\\|/|g' \
56 > | xargs "$PYTHON" contrib/check-py3-compat.py \
57 > | xargs "$PYTHON" contrib/check-py3-compat.py \
57 > | sed 's/[0-9][0-9]*)$/*)/'
58 > | sed 's/[0-9][0-9]*)$/*)/'
58 #endif
59 #endif
General Comments 0
You need to be logged in to leave comments. Login now