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