##// END OF EJS Templates
packaging: stage installed files for Inno...
Gregory Szorc -
r43822:d2ea31a2 default draft
parent child Browse files
Show More
@@ -1,112 +1,159 b''
1 # inno.py - Inno Setup functionality.
1 # inno.py - Inno Setup functionality.
2 #
2 #
3 # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com>
3 # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 # no-check-code because Python 3 native.
8 # no-check-code because Python 3 native.
9
9
10 import os
10 import os
11 import pathlib
11 import pathlib
12 import shutil
12 import shutil
13 import subprocess
13 import subprocess
14
14
15 import jinja2
15 import jinja2
16
16
17 from .py2exe import build_py2exe
17 from .py2exe import (
18 build_py2exe,
19 stage_install,
20 )
18 from .util import find_vc_runtime_files
21 from .util import find_vc_runtime_files
19
22
20 EXTRA_PACKAGES = {
23 EXTRA_PACKAGES = {
21 'dulwich',
24 'dulwich',
22 'keyring',
25 'keyring',
23 'pygments',
26 'pygments',
24 'win32ctypes',
27 'win32ctypes',
25 }
28 }
26
29
30 PACKAGE_FILES_METADATA = {
31 'ReadMe.html': 'Flags: isreadme',
32 'hg.exe': "AfterInstall: Touch('{app}\\hg.exe.local')",
33 }
34
27
35
28 def build(
36 def build(
29 source_dir: pathlib.Path,
37 source_dir: pathlib.Path,
30 build_dir: pathlib.Path,
38 build_dir: pathlib.Path,
31 python_exe: pathlib.Path,
39 python_exe: pathlib.Path,
32 iscc_exe: pathlib.Path,
40 iscc_exe: pathlib.Path,
33 version=None,
41 version=None,
34 ):
42 ):
35 """Build the Inno installer.
43 """Build the Inno installer.
36
44
37 Build files will be placed in ``build_dir``.
45 Build files will be placed in ``build_dir``.
38
46
39 py2exe's setup.py doesn't use setuptools. It doesn't have modern logic
47 py2exe's setup.py doesn't use setuptools. It doesn't have modern logic
40 for finding the Python 2.7 toolchain. So, we require the environment
48 for finding the Python 2.7 toolchain. So, we require the environment
41 to already be configured with an active toolchain.
49 to already be configured with an active toolchain.
42 """
50 """
43 if not iscc_exe.exists():
51 if not iscc_exe.exists():
44 raise Exception('%s does not exist' % iscc_exe)
52 raise Exception('%s does not exist' % iscc_exe)
45
53
46 vc_x64 = r'\x64' in os.environ.get('LIB', '')
54 vc_x64 = r'\x64' in os.environ.get('LIB', '')
47 arch = 'x64' if vc_x64 else 'x86'
55 arch = 'x64' if vc_x64 else 'x86'
48 inno_source_dir = source_dir / 'contrib' / 'packaging' / 'inno'
56 inno_source_dir = source_dir / 'contrib' / 'packaging' / 'inno'
49 inno_build_dir = build_dir / ('inno-%s' % arch)
57 inno_build_dir = build_dir / ('inno-%s' % arch)
58 staging_dir = inno_build_dir / 'stage'
50
59
51 requirements_txt = (
60 requirements_txt = (
52 source_dir / 'contrib' / 'packaging' / 'inno' / 'requirements.txt'
61 source_dir / 'contrib' / 'packaging' / 'inno' / 'requirements.txt'
53 )
62 )
54
63
55 inno_build_dir.mkdir(parents=True, exist_ok=True)
64 inno_build_dir.mkdir(parents=True, exist_ok=True)
56
65
57 build_py2exe(
66 build_py2exe(
58 source_dir,
67 source_dir,
59 build_dir,
68 build_dir,
60 python_exe,
69 python_exe,
61 'inno',
70 'inno',
62 requirements_txt,
71 requirements_txt,
63 extra_packages=EXTRA_PACKAGES,
72 extra_packages=EXTRA_PACKAGES,
64 )
73 )
65
74
75 # Purge the staging directory for every build so packaging is
76 # pristine.
77 if staging_dir.exists():
78 print('purging %s' % staging_dir)
79 shutil.rmtree(staging_dir)
80
81 # Now assemble all the packaged files into the staging directory.
82 stage_install(source_dir, staging_dir)
83
66 # hg.exe depends on VC9 runtime DLLs. Copy those into place.
84 # hg.exe depends on VC9 runtime DLLs. Copy those into place.
67 for f in find_vc_runtime_files(vc_x64):
85 for f in find_vc_runtime_files(vc_x64):
68 if f.name.endswith('.manifest'):
86 if f.name.endswith('.manifest'):
69 basename = 'Microsoft.VC90.CRT.manifest'
87 basename = 'Microsoft.VC90.CRT.manifest'
70 else:
88 else:
71 basename = f.name
89 basename = f.name
72
90
73 dest_path = source_dir / 'dist' / basename
91 dest_path = staging_dir / basename
74
92
75 print('copying %s to %s' % (f, dest_path))
93 print('copying %s to %s' % (f, dest_path))
76 shutil.copyfile(f, dest_path)
94 shutil.copyfile(f, dest_path)
77
95
96 # The final package layout is simply a mirror of the staging directory.
97 package_files = []
98 for root, dirs, files in os.walk(staging_dir):
99 dirs.sort()
100
101 root = pathlib.Path(root)
102
103 for f in sorted(files):
104 full = root / f
105 rel = full.relative_to(staging_dir)
106 if str(rel.parent) == '.':
107 dest_dir = '{app}'
108 else:
109 dest_dir = '{app}\\%s' % rel.parent
110
111 package_files.append(
112 {
113 'source': rel,
114 'dest_dir': dest_dir,
115 'metadata': PACKAGE_FILES_METADATA.get(str(rel), None),
116 }
117 )
118
78 print('creating installer')
119 print('creating installer')
79
120
80 # Install Inno files by rendering a template.
121 # Install Inno files by rendering a template.
81 jinja_env = jinja2.Environment(
122 jinja_env = jinja2.Environment(
82 loader=jinja2.FileSystemLoader(str(inno_source_dir)),
123 loader=jinja2.FileSystemLoader(str(inno_source_dir)),
83 # Need to change these to prevent conflict with Inno Setup.
124 # Need to change these to prevent conflict with Inno Setup.
84 comment_start_string='{##',
125 comment_start_string='{##',
85 comment_end_string='##}',
126 comment_end_string='##}',
86 )
127 )
87
128
88 try:
129 try:
89 template = jinja_env.get_template('mercurial.iss')
130 template = jinja_env.get_template('mercurial.iss')
90 except jinja2.TemplateSyntaxError as e:
131 except jinja2.TemplateSyntaxError as e:
91 raise Exception(
132 raise Exception(
92 'template syntax error at %s:%d: %s'
133 'template syntax error at %s:%d: %s'
93 % (e.name, e.lineno, e.message,)
134 % (e.name, e.lineno, e.message,)
94 )
135 )
95
136
96 content = template.render()
137 content = template.render(package_files=package_files)
97
138
98 with (inno_build_dir / 'mercurial.iss').open('w', encoding='utf-8') as fh:
139 with (inno_build_dir / 'mercurial.iss').open('w', encoding='utf-8') as fh:
99 fh.write(content)
140 fh.write(content)
100
141
142 # Copy additional files used by Inno.
143 for p in ('mercurial.ico', 'postinstall.txt'):
144 shutil.copyfile(
145 source_dir / 'contrib' / 'win32' / p, inno_build_dir / p
146 )
147
101 args = [str(iscc_exe)]
148 args = [str(iscc_exe)]
102
149
103 if vc_x64:
150 if vc_x64:
104 args.append('/dARCH=x64')
151 args.append('/dARCH=x64')
105
152
106 if version:
153 if version:
107 args.append('/dVERSION=%s' % version)
154 args.append('/dVERSION=%s' % version)
108
155
109 args.append('/Odist')
156 args.append('/Odist')
110 args.append(str(inno_build_dir / 'mercurial.iss'))
157 args.append(str(inno_build_dir / 'mercurial.iss'))
111
158
112 subprocess.run(args, cwd=str(source_dir), check=True)
159 subprocess.run(args, cwd=str(source_dir), check=True)
@@ -1,171 +1,213 b''
1 # py2exe.py - Functionality for performing py2exe builds.
1 # py2exe.py - Functionality for performing py2exe builds.
2 #
2 #
3 # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com>
3 # Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 # no-check-code because Python 3 native.
8 # no-check-code because Python 3 native.
9
9
10 import os
10 import os
11 import pathlib
11 import pathlib
12 import subprocess
12 import subprocess
13
13
14 from .downloads import download_entry
14 from .downloads import download_entry
15 from .util import (
15 from .util import (
16 extract_tar_to_directory,
16 extract_tar_to_directory,
17 extract_zip_to_directory,
17 extract_zip_to_directory,
18 process_install_rules,
18 python_exe_info,
19 python_exe_info,
19 )
20 )
20
21
21
22
23 STAGING_RULES = [
24 ('contrib/bash_completion', 'Contrib/'),
25 ('contrib/hgk', 'Contrib/hgk.tcl'),
26 ('contrib/hgweb.fcgi', 'Contrib/'),
27 ('contrib/hgweb.wsgi', 'Contrib/'),
28 ('contrib/mercurial.el', 'Contrib/'),
29 ('contrib/mq.el', 'Contrib/'),
30 ('contrib/tcsh_completion', 'Contrib/'),
31 ('contrib/tcsh_completion_build.sh', 'Contrib/'),
32 ('contrib/vim/*', 'Contrib/Vim/'),
33 ('contrib/win32/postinstall.txt', 'ReleaseNotes.txt'),
34 ('contrib/win32/ReadMe.html', 'ReadMe.html'),
35 ('contrib/xml.rnc', 'Contrib/'),
36 ('contrib/zsh_completion', 'Contrib/'),
37 ('dist/hg.exe', './'),
38 ('dist/lib/*.dll', 'lib/'),
39 ('dist/lib/*.pyd', 'lib/'),
40 ('dist/lib/library.zip', 'lib/'),
41 ('dist/Microsoft.VC*.CRT.manifest', './'),
42 ('dist/msvc*.dll', './'),
43 ('dist/python*.dll', './'),
44 ('doc/*.html', 'Docs/'),
45 ('doc/style.css', 'Docs/'),
46 ('mercurial/help/**/*.txt', 'help/'),
47 ('mercurial/default.d/*.rc', 'default.d/'),
48 ('mercurial/locale/**/*', 'locale/'),
49 ('mercurial/templates/**/*', 'Templates/'),
50 ('CONTRIBUTORS', 'Contributors.txt'),
51 ('COPYING', 'Copying.txt'),
52 ]
53
54
22 def build_py2exe(
55 def build_py2exe(
23 source_dir: pathlib.Path,
56 source_dir: pathlib.Path,
24 build_dir: pathlib.Path,
57 build_dir: pathlib.Path,
25 python_exe: pathlib.Path,
58 python_exe: pathlib.Path,
26 build_name: str,
59 build_name: str,
27 venv_requirements_txt: pathlib.Path,
60 venv_requirements_txt: pathlib.Path,
28 extra_packages=None,
61 extra_packages=None,
29 extra_excludes=None,
62 extra_excludes=None,
30 extra_dll_excludes=None,
63 extra_dll_excludes=None,
31 extra_packages_script=None,
64 extra_packages_script=None,
32 ):
65 ):
33 """Build Mercurial with py2exe.
66 """Build Mercurial with py2exe.
34
67
35 Build files will be placed in ``build_dir``.
68 Build files will be placed in ``build_dir``.
36
69
37 py2exe's setup.py doesn't use setuptools. It doesn't have modern logic
70 py2exe's setup.py doesn't use setuptools. It doesn't have modern logic
38 for finding the Python 2.7 toolchain. So, we require the environment
71 for finding the Python 2.7 toolchain. So, we require the environment
39 to already be configured with an active toolchain.
72 to already be configured with an active toolchain.
40 """
73 """
41 if 'VCINSTALLDIR' not in os.environ:
74 if 'VCINSTALLDIR' not in os.environ:
42 raise Exception(
75 raise Exception(
43 'not running from a Visual C++ build environment; '
76 'not running from a Visual C++ build environment; '
44 'execute the "Visual C++ <version> Command Prompt" '
77 'execute the "Visual C++ <version> Command Prompt" '
45 'application shortcut or a vcsvarsall.bat file'
78 'application shortcut or a vcsvarsall.bat file'
46 )
79 )
47
80
48 # Identity x86/x64 and validate the environment matches the Python
81 # Identity x86/x64 and validate the environment matches the Python
49 # architecture.
82 # architecture.
50 vc_x64 = r'\x64' in os.environ['LIB']
83 vc_x64 = r'\x64' in os.environ['LIB']
51
84
52 py_info = python_exe_info(python_exe)
85 py_info = python_exe_info(python_exe)
53
86
54 if vc_x64:
87 if vc_x64:
55 if py_info['arch'] != '64bit':
88 if py_info['arch'] != '64bit':
56 raise Exception(
89 raise Exception(
57 'architecture mismatch: Visual C++ environment '
90 'architecture mismatch: Visual C++ environment '
58 'is configured for 64-bit but Python is 32-bit'
91 'is configured for 64-bit but Python is 32-bit'
59 )
92 )
60 else:
93 else:
61 if py_info['arch'] != '32bit':
94 if py_info['arch'] != '32bit':
62 raise Exception(
95 raise Exception(
63 'architecture mismatch: Visual C++ environment '
96 'architecture mismatch: Visual C++ environment '
64 'is configured for 32-bit but Python is 64-bit'
97 'is configured for 32-bit but Python is 64-bit'
65 )
98 )
66
99
67 if py_info['py3']:
100 if py_info['py3']:
68 raise Exception('Only Python 2 is currently supported')
101 raise Exception('Only Python 2 is currently supported')
69
102
70 build_dir.mkdir(exist_ok=True)
103 build_dir.mkdir(exist_ok=True)
71
104
72 gettext_pkg, gettext_entry = download_entry('gettext', build_dir)
105 gettext_pkg, gettext_entry = download_entry('gettext', build_dir)
73 gettext_dep_pkg = download_entry('gettext-dep', build_dir)[0]
106 gettext_dep_pkg = download_entry('gettext-dep', build_dir)[0]
74 virtualenv_pkg, virtualenv_entry = download_entry('virtualenv', build_dir)
107 virtualenv_pkg, virtualenv_entry = download_entry('virtualenv', build_dir)
75 py2exe_pkg, py2exe_entry = download_entry('py2exe', build_dir)
108 py2exe_pkg, py2exe_entry = download_entry('py2exe', build_dir)
76
109
77 venv_path = build_dir / (
110 venv_path = build_dir / (
78 'venv-%s-%s' % (build_name, 'x64' if vc_x64 else 'x86')
111 'venv-%s-%s' % (build_name, 'x64' if vc_x64 else 'x86')
79 )
112 )
80
113
81 gettext_root = build_dir / ('gettext-win-%s' % gettext_entry['version'])
114 gettext_root = build_dir / ('gettext-win-%s' % gettext_entry['version'])
82
115
83 if not gettext_root.exists():
116 if not gettext_root.exists():
84 extract_zip_to_directory(gettext_pkg, gettext_root)
117 extract_zip_to_directory(gettext_pkg, gettext_root)
85 extract_zip_to_directory(gettext_dep_pkg, gettext_root)
118 extract_zip_to_directory(gettext_dep_pkg, gettext_root)
86
119
87 # This assumes Python 2. We don't need virtualenv on Python 3.
120 # This assumes Python 2. We don't need virtualenv on Python 3.
88 virtualenv_src_path = build_dir / (
121 virtualenv_src_path = build_dir / (
89 'virtualenv-%s' % virtualenv_entry['version']
122 'virtualenv-%s' % virtualenv_entry['version']
90 )
123 )
91 virtualenv_py = virtualenv_src_path / 'virtualenv.py'
124 virtualenv_py = virtualenv_src_path / 'virtualenv.py'
92
125
93 if not virtualenv_src_path.exists():
126 if not virtualenv_src_path.exists():
94 extract_tar_to_directory(virtualenv_pkg, build_dir)
127 extract_tar_to_directory(virtualenv_pkg, build_dir)
95
128
96 py2exe_source_path = build_dir / ('py2exe-%s' % py2exe_entry['version'])
129 py2exe_source_path = build_dir / ('py2exe-%s' % py2exe_entry['version'])
97
130
98 if not py2exe_source_path.exists():
131 if not py2exe_source_path.exists():
99 extract_zip_to_directory(py2exe_pkg, build_dir)
132 extract_zip_to_directory(py2exe_pkg, build_dir)
100
133
101 if not venv_path.exists():
134 if not venv_path.exists():
102 print('creating virtualenv with dependencies')
135 print('creating virtualenv with dependencies')
103 subprocess.run(
136 subprocess.run(
104 [str(python_exe), str(virtualenv_py), str(venv_path)], check=True
137 [str(python_exe), str(virtualenv_py), str(venv_path)], check=True
105 )
138 )
106
139
107 venv_python = venv_path / 'Scripts' / 'python.exe'
140 venv_python = venv_path / 'Scripts' / 'python.exe'
108 venv_pip = venv_path / 'Scripts' / 'pip.exe'
141 venv_pip = venv_path / 'Scripts' / 'pip.exe'
109
142
110 subprocess.run(
143 subprocess.run(
111 [str(venv_pip), 'install', '-r', str(venv_requirements_txt)], check=True
144 [str(venv_pip), 'install', '-r', str(venv_requirements_txt)], check=True
112 )
145 )
113
146
114 # Force distutils to use VC++ settings from environment, which was
147 # Force distutils to use VC++ settings from environment, which was
115 # validated above.
148 # validated above.
116 env = dict(os.environ)
149 env = dict(os.environ)
117 env['DISTUTILS_USE_SDK'] = '1'
150 env['DISTUTILS_USE_SDK'] = '1'
118 env['MSSdk'] = '1'
151 env['MSSdk'] = '1'
119
152
120 if extra_packages_script:
153 if extra_packages_script:
121 more_packages = set(
154 more_packages = set(
122 subprocess.check_output(extra_packages_script, cwd=build_dir)
155 subprocess.check_output(extra_packages_script, cwd=build_dir)
123 .split(b'\0')[-1]
156 .split(b'\0')[-1]
124 .strip()
157 .strip()
125 .decode('utf-8')
158 .decode('utf-8')
126 .splitlines()
159 .splitlines()
127 )
160 )
128 if more_packages:
161 if more_packages:
129 if not extra_packages:
162 if not extra_packages:
130 extra_packages = more_packages
163 extra_packages = more_packages
131 else:
164 else:
132 extra_packages |= more_packages
165 extra_packages |= more_packages
133
166
134 if extra_packages:
167 if extra_packages:
135 env['HG_PY2EXE_EXTRA_PACKAGES'] = ' '.join(sorted(extra_packages))
168 env['HG_PY2EXE_EXTRA_PACKAGES'] = ' '.join(sorted(extra_packages))
136 hgext3rd_extras = sorted(
169 hgext3rd_extras = sorted(
137 e for e in extra_packages if e.startswith('hgext3rd.')
170 e for e in extra_packages if e.startswith('hgext3rd.')
138 )
171 )
139 if hgext3rd_extras:
172 if hgext3rd_extras:
140 env['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'] = ' '.join(hgext3rd_extras)
173 env['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'] = ' '.join(hgext3rd_extras)
141 if extra_excludes:
174 if extra_excludes:
142 env['HG_PY2EXE_EXTRA_EXCLUDES'] = ' '.join(sorted(extra_excludes))
175 env['HG_PY2EXE_EXTRA_EXCLUDES'] = ' '.join(sorted(extra_excludes))
143 if extra_dll_excludes:
176 if extra_dll_excludes:
144 env['HG_PY2EXE_EXTRA_DLL_EXCLUDES'] = ' '.join(
177 env['HG_PY2EXE_EXTRA_DLL_EXCLUDES'] = ' '.join(
145 sorted(extra_dll_excludes)
178 sorted(extra_dll_excludes)
146 )
179 )
147
180
148 py2exe_py_path = venv_path / 'Lib' / 'site-packages' / 'py2exe'
181 py2exe_py_path = venv_path / 'Lib' / 'site-packages' / 'py2exe'
149 if not py2exe_py_path.exists():
182 if not py2exe_py_path.exists():
150 print('building py2exe')
183 print('building py2exe')
151 subprocess.run(
184 subprocess.run(
152 [str(venv_python), 'setup.py', 'install'],
185 [str(venv_python), 'setup.py', 'install'],
153 cwd=py2exe_source_path,
186 cwd=py2exe_source_path,
154 env=env,
187 env=env,
155 check=True,
188 check=True,
156 )
189 )
157
190
158 # Register location of msgfmt and other binaries.
191 # Register location of msgfmt and other binaries.
159 env['PATH'] = '%s%s%s' % (
192 env['PATH'] = '%s%s%s' % (
160 env['PATH'],
193 env['PATH'],
161 os.pathsep,
194 os.pathsep,
162 str(gettext_root / 'bin'),
195 str(gettext_root / 'bin'),
163 )
196 )
164
197
165 print('building Mercurial')
198 print('building Mercurial')
166 subprocess.run(
199 subprocess.run(
167 [str(venv_python), 'setup.py', 'py2exe', 'build_doc', '--html'],
200 [str(venv_python), 'setup.py', 'py2exe', 'build_doc', '--html'],
168 cwd=str(source_dir),
201 cwd=str(source_dir),
169 env=env,
202 env=env,
170 check=True,
203 check=True,
171 )
204 )
205
206
207 def stage_install(source_dir: pathlib.Path, staging_dir: pathlib.Path):
208 """Copy all files to be installed to a directory.
209
210 This allows packaging to simply walk a directory tree to find source
211 files.
212 """
213 process_install_rules(STAGING_RULES, source_dir, staging_dir)
@@ -1,166 +1,212 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 getpass
12 import glob
12 import os
13 import os
13 import pathlib
14 import pathlib
15 import shutil
14 import subprocess
16 import subprocess
15 import tarfile
17 import tarfile
16 import zipfile
18 import zipfile
17
19
18
20
19 def extract_tar_to_directory(source: pathlib.Path, dest: pathlib.Path):
21 def extract_tar_to_directory(source: pathlib.Path, dest: pathlib.Path):
20 with tarfile.open(source, 'r') as tf:
22 with tarfile.open(source, 'r') as tf:
21 tf.extractall(dest)
23 tf.extractall(dest)
22
24
23
25
24 def extract_zip_to_directory(source: pathlib.Path, dest: pathlib.Path):
26 def extract_zip_to_directory(source: pathlib.Path, dest: pathlib.Path):
25 with zipfile.ZipFile(source, 'r') as zf:
27 with zipfile.ZipFile(source, 'r') as zf:
26 zf.extractall(dest)
28 zf.extractall(dest)
27
29
28
30
29 def find_vc_runtime_files(x64=False):
31 def find_vc_runtime_files(x64=False):
30 """Finds Visual C++ Runtime DLLs to include in distribution."""
32 """Finds Visual C++ Runtime DLLs to include in distribution."""
31 winsxs = pathlib.Path(os.environ['SYSTEMROOT']) / 'WinSxS'
33 winsxs = pathlib.Path(os.environ['SYSTEMROOT']) / 'WinSxS'
32
34
33 prefix = 'amd64' if x64 else 'x86'
35 prefix = 'amd64' if x64 else 'x86'
34
36
35 candidates = sorted(
37 candidates = sorted(
36 p
38 p
37 for p in os.listdir(winsxs)
39 for p in os.listdir(winsxs)
38 if p.lower().startswith('%s_microsoft.vc90.crt_' % prefix)
40 if p.lower().startswith('%s_microsoft.vc90.crt_' % prefix)
39 )
41 )
40
42
41 for p in candidates:
43 for p in candidates:
42 print('found candidate VC runtime: %s' % p)
44 print('found candidate VC runtime: %s' % p)
43
45
44 # Take the newest version.
46 # Take the newest version.
45 version = candidates[-1]
47 version = candidates[-1]
46
48
47 d = winsxs / version
49 d = winsxs / version
48
50
49 return [
51 return [
50 d / 'msvcm90.dll',
52 d / 'msvcm90.dll',
51 d / 'msvcp90.dll',
53 d / 'msvcp90.dll',
52 d / 'msvcr90.dll',
54 d / 'msvcr90.dll',
53 winsxs / 'Manifests' / ('%s.manifest' % version),
55 winsxs / 'Manifests' / ('%s.manifest' % version),
54 ]
56 ]
55
57
56
58
57 def windows_10_sdk_info():
59 def windows_10_sdk_info():
58 """Resolves information about the Windows 10 SDK."""
60 """Resolves information about the Windows 10 SDK."""
59
61
60 base = pathlib.Path(os.environ['ProgramFiles(x86)']) / 'Windows Kits' / '10'
62 base = pathlib.Path(os.environ['ProgramFiles(x86)']) / 'Windows Kits' / '10'
61
63
62 if not base.is_dir():
64 if not base.is_dir():
63 raise Exception('unable to find Windows 10 SDK at %s' % base)
65 raise Exception('unable to find Windows 10 SDK at %s' % base)
64
66
65 # Find the latest version.
67 # Find the latest version.
66 bin_base = base / 'bin'
68 bin_base = base / 'bin'
67
69
68 versions = [v for v in os.listdir(bin_base) if v.startswith('10.')]
70 versions = [v for v in os.listdir(bin_base) if v.startswith('10.')]
69 version = sorted(versions, reverse=True)[0]
71 version = sorted(versions, reverse=True)[0]
70
72
71 bin_version = bin_base / version
73 bin_version = bin_base / version
72
74
73 return {
75 return {
74 'root': base,
76 'root': base,
75 'version': version,
77 'version': version,
76 'bin_root': bin_version,
78 'bin_root': bin_version,
77 'bin_x86': bin_version / 'x86',
79 'bin_x86': bin_version / 'x86',
78 'bin_x64': bin_version / 'x64',
80 'bin_x64': bin_version / 'x64',
79 }
81 }
80
82
81
83
82 def find_signtool():
84 def find_signtool():
83 """Find signtool.exe from the Windows SDK."""
85 """Find signtool.exe from the Windows SDK."""
84 sdk = windows_10_sdk_info()
86 sdk = windows_10_sdk_info()
85
87
86 for key in ('bin_x64', 'bin_x86'):
88 for key in ('bin_x64', 'bin_x86'):
87 p = sdk[key] / 'signtool.exe'
89 p = sdk[key] / 'signtool.exe'
88
90
89 if p.exists():
91 if p.exists():
90 return p
92 return p
91
93
92 raise Exception('could not find signtool.exe in Windows 10 SDK')
94 raise Exception('could not find signtool.exe in Windows 10 SDK')
93
95
94
96
95 def sign_with_signtool(
97 def sign_with_signtool(
96 file_path,
98 file_path,
97 description,
99 description,
98 subject_name=None,
100 subject_name=None,
99 cert_path=None,
101 cert_path=None,
100 cert_password=None,
102 cert_password=None,
101 timestamp_url=None,
103 timestamp_url=None,
102 ):
104 ):
103 """Digitally sign a file with signtool.exe.
105 """Digitally sign a file with signtool.exe.
104
106
105 ``file_path`` is file to sign.
107 ``file_path`` is file to sign.
106 ``description`` is text that goes in the signature.
108 ``description`` is text that goes in the signature.
107
109
108 The signing certificate can be specified by ``cert_path`` or
110 The signing certificate can be specified by ``cert_path`` or
109 ``subject_name``. These correspond to the ``/f`` and ``/n`` arguments
111 ``subject_name``. These correspond to the ``/f`` and ``/n`` arguments
110 to signtool.exe, respectively.
112 to signtool.exe, respectively.
111
113
112 The certificate password can be specified via ``cert_password``. If
114 The certificate password can be specified via ``cert_password``. If
113 not provided, you will be prompted for the password.
115 not provided, you will be prompted for the password.
114
116
115 ``timestamp_url`` is the URL of a RFC 3161 timestamp server (``/tr``
117 ``timestamp_url`` is the URL of a RFC 3161 timestamp server (``/tr``
116 argument to signtool.exe).
118 argument to signtool.exe).
117 """
119 """
118 if cert_path and subject_name:
120 if cert_path and subject_name:
119 raise ValueError('cannot specify both cert_path and subject_name')
121 raise ValueError('cannot specify both cert_path and subject_name')
120
122
121 while cert_path and not cert_password:
123 while cert_path and not cert_password:
122 cert_password = getpass.getpass('password for %s: ' % cert_path)
124 cert_password = getpass.getpass('password for %s: ' % cert_path)
123
125
124 args = [
126 args = [
125 str(find_signtool()),
127 str(find_signtool()),
126 'sign',
128 'sign',
127 '/v',
129 '/v',
128 '/fd',
130 '/fd',
129 'sha256',
131 'sha256',
130 '/d',
132 '/d',
131 description,
133 description,
132 ]
134 ]
133
135
134 if cert_path:
136 if cert_path:
135 args.extend(['/f', str(cert_path), '/p', cert_password])
137 args.extend(['/f', str(cert_path), '/p', cert_password])
136 elif subject_name:
138 elif subject_name:
137 args.extend(['/n', subject_name])
139 args.extend(['/n', subject_name])
138
140
139 if timestamp_url:
141 if timestamp_url:
140 args.extend(['/tr', timestamp_url, '/td', 'sha256'])
142 args.extend(['/tr', timestamp_url, '/td', 'sha256'])
141
143
142 args.append(str(file_path))
144 args.append(str(file_path))
143
145
144 print('signing %s' % file_path)
146 print('signing %s' % file_path)
145 subprocess.run(args, check=True)
147 subprocess.run(args, check=True)
146
148
147
149
148 PRINT_PYTHON_INFO = '''
150 PRINT_PYTHON_INFO = '''
149 import platform; print("%s:%s" % (platform.architecture()[0], platform.python_version()))
151 import platform; print("%s:%s" % (platform.architecture()[0], platform.python_version()))
150 '''.strip()
152 '''.strip()
151
153
152
154
153 def python_exe_info(python_exe: pathlib.Path):
155 def python_exe_info(python_exe: pathlib.Path):
154 """Obtain information about a Python executable."""
156 """Obtain information about a Python executable."""
155
157
156 res = subprocess.check_output([str(python_exe), '-c', PRINT_PYTHON_INFO])
158 res = subprocess.check_output([str(python_exe), '-c', PRINT_PYTHON_INFO])
157
159
158 arch, version = res.decode('utf-8').split(':')
160 arch, version = res.decode('utf-8').split(':')
159
161
160 version = distutils.version.LooseVersion(version)
162 version = distutils.version.LooseVersion(version)
161
163
162 return {
164 return {
163 'arch': arch,
165 'arch': arch,
164 'version': version,
166 'version': version,
165 'py3': version >= distutils.version.LooseVersion('3'),
167 'py3': version >= distutils.version.LooseVersion('3'),
166 }
168 }
169
170
171 def process_install_rules(
172 rules: list, source_dir: pathlib.Path, dest_dir: pathlib.Path
173 ):
174 for source, dest in rules:
175 if '*' in source:
176 if not dest.endswith('/'):
177 raise ValueError('destination must end in / when globbing')
178
179 # We strip off the source path component before the first glob
180 # character to construct the relative install path.
181 prefix_end_index = source[: source.index('*')].rindex('/')
182 relative_prefix = source_dir / source[0:prefix_end_index]
183
184 for res in glob.glob(str(source_dir / source), recursive=True):
185 source_path = pathlib.Path(res)
186
187 if source_path.is_dir():
188 continue
189
190 rel_path = source_path.relative_to(relative_prefix)
191
192 dest_path = dest_dir / dest[:-1] / rel_path
193
194 dest_path.parent.mkdir(parents=True, exist_ok=True)
195 print('copying %s to %s' % (source_path, dest_path))
196 shutil.copy(source_path, dest_path)
197
198 # Simple file case.
199 else:
200 source_path = pathlib.Path(source)
201
202 if dest.endswith('/'):
203 dest_path = pathlib.Path(dest) / source_path.name
204 else:
205 dest_path = pathlib.Path(dest)
206
207 full_source_path = source_dir / source_path
208 full_dest_path = dest_dir / dest_path
209
210 full_dest_path.parent.mkdir(parents=True, exist_ok=True)
211 shutil.copy(full_source_path, full_dest_path)
212 print('copying %s to %s' % (full_source_path, full_dest_path))
@@ -1,125 +1,99 b''
1 ; Script generated by the Inno Setup Script Wizard.
1 ; Script generated by the Inno Setup Script Wizard.
2 ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
2 ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
3
3
4 #ifndef VERSION
4 #ifndef VERSION
5 #define FileHandle
5 #define FileHandle
6 #define FileLine
6 #define FileLine
7 #define VERSION = "unknown"
7 #define VERSION = "unknown"
8 #if FileHandle = FileOpen(SourcePath + "\..\..\mercurial\__version__.py")
8 #if FileHandle = FileOpen(SourcePath + "\..\..\mercurial\__version__.py")
9 #expr FileLine = FileRead(FileHandle)
9 #expr FileLine = FileRead(FileHandle)
10 #expr FileLine = FileRead(FileHandle)
10 #expr FileLine = FileRead(FileHandle)
11 #define VERSION = Copy(FileLine, Pos('"', FileLine)+1, Len(FileLine)-Pos('"', FileLine)-1)
11 #define VERSION = Copy(FileLine, Pos('"', FileLine)+1, Len(FileLine)-Pos('"', FileLine)-1)
12 #endif
12 #endif
13 #if FileHandle
13 #if FileHandle
14 #expr FileClose(FileHandle)
14 #expr FileClose(FileHandle)
15 #endif
15 #endif
16 #pragma message "Detected Version: " + VERSION
16 #pragma message "Detected Version: " + VERSION
17 #endif
17 #endif
18
18
19 #ifndef ARCH
19 #ifndef ARCH
20 #define ARCH = "x86"
20 #define ARCH = "x86"
21 #endif
21 #endif
22
22
23 [Setup]
23 [Setup]
24 AppCopyright=Copyright 2005-2019 Matt Mackall and others
24 AppCopyright=Copyright 2005-2019 Matt Mackall and others
25 AppName=Mercurial
25 AppName=Mercurial
26 AppVersion={#VERSION}
26 AppVersion={#VERSION}
27 #if ARCH == "x64"
27 #if ARCH == "x64"
28 AppVerName=Mercurial {#VERSION} (64-bit)
28 AppVerName=Mercurial {#VERSION} (64-bit)
29 OutputBaseFilename=Mercurial-{#VERSION}-x64
29 OutputBaseFilename=Mercurial-{#VERSION}-x64
30 ArchitecturesAllowed=x64
30 ArchitecturesAllowed=x64
31 ArchitecturesInstallIn64BitMode=x64
31 ArchitecturesInstallIn64BitMode=x64
32 #else
32 #else
33 AppVerName=Mercurial {#VERSION}
33 AppVerName=Mercurial {#VERSION}
34 OutputBaseFilename=Mercurial-{#VERSION}
34 OutputBaseFilename=Mercurial-{#VERSION}
35 #endif
35 #endif
36 InfoAfterFile=contrib/win32/postinstall.txt
36 InfoAfterFile=../postinstall.txt
37 LicenseFile=COPYING
37 LicenseFile=Copying.txt
38 ShowLanguageDialog=yes
38 ShowLanguageDialog=yes
39 AppPublisher=Matt Mackall and others
39 AppPublisher=Matt Mackall and others
40 AppPublisherURL=https://mercurial-scm.org/
40 AppPublisherURL=https://mercurial-scm.org/
41 AppSupportURL=https://mercurial-scm.org/
41 AppSupportURL=https://mercurial-scm.org/
42 AppUpdatesURL=https://mercurial-scm.org/
42 AppUpdatesURL=https://mercurial-scm.org/
43 {{ 'AppID={{4B95A5F1-EF59-4B08-BED8-C891C46121B3}' }}
43 {{ 'AppID={{4B95A5F1-EF59-4B08-BED8-C891C46121B3}' }}
44 AppContact=mercurial@mercurial-scm.org
44 AppContact=mercurial@mercurial-scm.org
45 DefaultDirName={pf}\Mercurial
45 DefaultDirName={pf}\Mercurial
46 SourceDir=..\..
46 SourceDir=stage
47 VersionInfoDescription=Mercurial distributed SCM (version {#VERSION})
47 VersionInfoDescription=Mercurial distributed SCM (version {#VERSION})
48 VersionInfoCopyright=Copyright 2005-2019 Matt Mackall and others
48 VersionInfoCopyright=Copyright 2005-2019 Matt Mackall and others
49 VersionInfoCompany=Matt Mackall and others
49 VersionInfoCompany=Matt Mackall and others
50 InternalCompressLevel=max
50 InternalCompressLevel=max
51 SolidCompression=true
51 SolidCompression=true
52 SetupIconFile=contrib\win32\mercurial.ico
52 SetupIconFile=../mercurial.ico
53 AllowNoIcons=true
53 AllowNoIcons=true
54 DefaultGroupName=Mercurial
54 DefaultGroupName=Mercurial
55 PrivilegesRequired=none
55 PrivilegesRequired=none
56 ChangesEnvironment=true
56 ChangesEnvironment=true
57
57
58 [Files]
58 [Files]
59 Source: contrib\mercurial.el; DestDir: {app}/Contrib
59 {% for entry in package_files -%}
60 Source: contrib\vim\*.*; DestDir: {app}/Contrib/Vim
60 Source: {{ entry.source }}; DestDir: {{ entry.dest_dir }}
61 Source: contrib\zsh_completion; DestDir: {app}/Contrib
61 {%- if entry.metadata %}; {{ entry.metadata }}{% endif %}
62 Source: contrib\bash_completion; DestDir: {app}/Contrib
62 {% endfor %}
63 Source: contrib\tcsh_completion; DestDir: {app}/Contrib
64 Source: contrib\tcsh_completion_build.sh; DestDir: {app}/Contrib
65 Source: contrib\hgk; DestDir: {app}/Contrib; DestName: hgk.tcl
66 Source: contrib\xml.rnc; DestDir: {app}/Contrib
67 Source: contrib\mercurial.el; DestDir: {app}/Contrib
68 Source: contrib\mq.el; DestDir: {app}/Contrib
69 Source: contrib\hgweb.fcgi; DestDir: {app}/Contrib
70 Source: contrib\hgweb.wsgi; DestDir: {app}/Contrib
71 Source: contrib\win32\ReadMe.html; DestDir: {app}; Flags: isreadme
72 Source: contrib\win32\postinstall.txt; DestDir: {app}; DestName: ReleaseNotes.txt
73 Source: dist\hg.exe; DestDir: {app}; AfterInstall: Touch('{app}\hg.exe.local')
74 Source: dist\lib\*.dll; Destdir: {app}\lib
75 Source: dist\lib\*.pyd; Destdir: {app}\lib
76 Source: dist\python*.dll; Destdir: {app}; Flags: skipifsourcedoesntexist
77 Source: dist\msvc*.dll; DestDir: {app}; Flags: skipifsourcedoesntexist
78 Source: dist\Microsoft.VC*.CRT.manifest; DestDir: {app}; Flags: skipifsourcedoesntexist
79 Source: dist\lib\library.zip; DestDir: {app}\lib
80 Source: doc\*.html; DestDir: {app}\Docs
81 Source: doc\style.css; DestDir: {app}\Docs
82 Source: mercurial\help\*.txt; DestDir: {app}\help
83 Source: mercurial\help\internals\*.txt; DestDir: {app}\help\internals
84 Source: mercurial\default.d\*.rc; DestDir: {app}\default.d
85 Source: mercurial\locale\*.*; DestDir: {app}\locale; Flags: recursesubdirs createallsubdirs skipifsourcedoesntexist
86 Source: mercurial\templates\*.*; DestDir: {app}\Templates; Flags: recursesubdirs createallsubdirs
87 Source: CONTRIBUTORS; DestDir: {app}; DestName: Contributors.txt
88 Source: COPYING; DestDir: {app}; DestName: Copying.txt
89
63
90 [INI]
64 [INI]
91 Filename: {app}\Mercurial.url; Section: InternetShortcut; Key: URL; String: https://mercurial-scm.org/
65 Filename: {app}\Mercurial.url; Section: InternetShortcut; Key: URL; String: https://mercurial-scm.org/
92 Filename: {app}\default.d\editor.rc; Section: ui; Key: editor; String: notepad
66 Filename: {app}\default.d\editor.rc; Section: ui; Key: editor; String: notepad
93
67
94 [UninstallDelete]
68 [UninstallDelete]
95 Type: files; Name: {app}\Mercurial.url
69 Type: files; Name: {app}\Mercurial.url
96 Type: filesandordirs; Name: {app}\default.d
70 Type: filesandordirs; Name: {app}\default.d
97 Type: files; Name: "{app}\hg.exe.local"
71 Type: files; Name: "{app}\hg.exe.local"
98
72
99 [Icons]
73 [Icons]
100 Name: {group}\Uninstall Mercurial; Filename: {uninstallexe}
74 Name: {group}\Uninstall Mercurial; Filename: {uninstallexe}
101 Name: {group}\Mercurial Command Reference; Filename: {app}\Docs\hg.1.html
75 Name: {group}\Mercurial Command Reference; Filename: {app}\Docs\hg.1.html
102 Name: {group}\Mercurial Configuration Files; Filename: {app}\Docs\hgrc.5.html
76 Name: {group}\Mercurial Configuration Files; Filename: {app}\Docs\hgrc.5.html
103 Name: {group}\Mercurial Ignore Files; Filename: {app}\Docs\hgignore.5.html
77 Name: {group}\Mercurial Ignore Files; Filename: {app}\Docs\hgignore.5.html
104 Name: {group}\Mercurial Web Site; Filename: {app}\Mercurial.url
78 Name: {group}\Mercurial Web Site; Filename: {app}\Mercurial.url
105
79
106 [Tasks]
80 [Tasks]
107 Name: modifypath; Description: Add the installation path to the search path; Flags: unchecked
81 Name: modifypath; Description: Add the installation path to the search path; Flags: unchecked
108
82
109 [Code]
83 [Code]
110 procedure Touch(fn: String);
84 procedure Touch(fn: String);
111 begin
85 begin
112 SaveStringToFile(ExpandConstant(fn), '', False);
86 SaveStringToFile(ExpandConstant(fn), '', False);
113 end;
87 end;
114
88
115 const
89 const
116 ModPathName = 'modifypath';
90 ModPathName = 'modifypath';
117 ModPathType = 'user';
91 ModPathType = 'user';
118
92
119 function ModPathDir(): TArrayOfString;
93 function ModPathDir(): TArrayOfString;
120 begin
94 begin
121 setArrayLength(Result, 1)
95 setArrayLength(Result, 1)
122 Result[0] := ExpandConstant('{app}');
96 Result[0] := ExpandConstant('{app}');
123 end;
97 end;
124
98
125 {% include 'modpath.iss' %}
99 {% include 'modpath.iss' %}
General Comments 0
You need to be logged in to leave comments. Login now