##// END OF EJS Templates
packaging: split Inno installer building from Mercurial building...
Gregory Szorc -
r45255:5760936e default draft
parent child Browse files
Show More
@@ -1,173 +1,197 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 (
17 from .py2exe import (
18 build_py2exe,
18 build_py2exe,
19 stage_install,
19 stage_install,
20 )
20 )
21 from .util import (
21 from .util import (
22 find_vc_runtime_files,
22 find_vc_runtime_files,
23 normalize_windows_version,
23 normalize_windows_version,
24 process_install_rules,
24 process_install_rules,
25 read_version_py,
25 read_version_py,
26 )
26 )
27
27
28 EXTRA_PACKAGES = {
28 EXTRA_PACKAGES = {
29 'dulwich',
29 'dulwich',
30 'keyring',
30 'keyring',
31 'pygments',
31 'pygments',
32 'win32ctypes',
32 'win32ctypes',
33 }
33 }
34
34
35 EXTRA_INSTALL_RULES = [
35 EXTRA_INSTALL_RULES = [
36 ('contrib/win32/mercurial.ini', 'defaultrc/mercurial.rc'),
36 ('contrib/win32/mercurial.ini', 'defaultrc/mercurial.rc'),
37 ]
37 ]
38
38
39 PACKAGE_FILES_METADATA = {
39 PACKAGE_FILES_METADATA = {
40 'ReadMe.html': 'Flags: isreadme',
40 'ReadMe.html': 'Flags: isreadme',
41 }
41 }
42
42
43
43
44 def build(
44 def build(
45 source_dir: pathlib.Path,
45 source_dir: pathlib.Path,
46 build_dir: pathlib.Path,
46 build_dir: pathlib.Path,
47 python_exe: pathlib.Path,
47 python_exe: pathlib.Path,
48 iscc_exe: pathlib.Path,
48 iscc_exe: pathlib.Path,
49 version=None,
49 version=None,
50 ):
50 ):
51 """Build the Inno installer.
51 """Build the Inno installer.
52
52
53 Build files will be placed in ``build_dir``.
53 Build files will be placed in ``build_dir``.
54
54
55 py2exe's setup.py doesn't use setuptools. It doesn't have modern logic
55 py2exe's setup.py doesn't use setuptools. It doesn't have modern logic
56 for finding the Python 2.7 toolchain. So, we require the environment
56 for finding the Python 2.7 toolchain. So, we require the environment
57 to already be configured with an active toolchain.
57 to already be configured with an active toolchain.
58 """
58 """
59 if not iscc_exe.exists():
59 if not iscc_exe.exists():
60 raise Exception('%s does not exist' % iscc_exe)
60 raise Exception('%s does not exist' % iscc_exe)
61
61
62 vc_x64 = r'\x64' in os.environ.get('LIB', '')
62 vc_x64 = r'\x64' in os.environ.get('LIB', '')
63 arch = 'x64' if vc_x64 else 'x86'
63 arch = 'x64' if vc_x64 else 'x86'
64 inno_source_dir = source_dir / 'contrib' / 'packaging' / 'inno'
64 inno_build_dir = build_dir / ('inno-py2exe-%s' % arch)
65 inno_build_dir = build_dir / ('inno-%s' % arch)
66 staging_dir = inno_build_dir / 'stage'
65 staging_dir = inno_build_dir / 'stage'
67
66
68 requirements_txt = (
67 requirements_txt = (
69 source_dir / 'contrib' / 'packaging' / 'requirements_win32.txt'
68 source_dir / 'contrib' / 'packaging' / 'requirements_win32.txt'
70 )
69 )
71
70
72 inno_build_dir.mkdir(parents=True, exist_ok=True)
71 inno_build_dir.mkdir(parents=True, exist_ok=True)
73
72
74 build_py2exe(
73 build_py2exe(
75 source_dir,
74 source_dir,
76 build_dir,
75 build_dir,
77 python_exe,
76 python_exe,
78 'inno',
77 'inno',
79 requirements_txt,
78 requirements_txt,
80 extra_packages=EXTRA_PACKAGES,
79 extra_packages=EXTRA_PACKAGES,
81 )
80 )
82
81
83 # Purge the staging directory for every build so packaging is
82 # Purge the staging directory for every build so packaging is
84 # pristine.
83 # pristine.
85 if staging_dir.exists():
84 if staging_dir.exists():
86 print('purging %s' % staging_dir)
85 print('purging %s' % staging_dir)
87 shutil.rmtree(staging_dir)
86 shutil.rmtree(staging_dir)
88
87
89 # Now assemble all the packaged files into the staging directory.
88 # Now assemble all the packaged files into the staging directory.
90 stage_install(source_dir, staging_dir)
89 stage_install(source_dir, staging_dir)
91
90
92 # We also install some extra files.
91 # We also install some extra files.
93 process_install_rules(EXTRA_INSTALL_RULES, source_dir, staging_dir)
92 process_install_rules(EXTRA_INSTALL_RULES, source_dir, staging_dir)
94
93
95 # hg.exe depends on VC9 runtime DLLs. Copy those into place.
94 # hg.exe depends on VC9 runtime DLLs. Copy those into place.
96 for f in find_vc_runtime_files(vc_x64):
95 for f in find_vc_runtime_files(vc_x64):
97 if f.name.endswith('.manifest'):
96 if f.name.endswith('.manifest'):
98 basename = 'Microsoft.VC90.CRT.manifest'
97 basename = 'Microsoft.VC90.CRT.manifest'
99 else:
98 else:
100 basename = f.name
99 basename = f.name
101
100
102 dest_path = staging_dir / basename
101 dest_path = staging_dir / basename
103
102
104 print('copying %s to %s' % (f, dest_path))
103 print('copying %s to %s' % (f, dest_path))
105 shutil.copyfile(f, dest_path)
104 shutil.copyfile(f, dest_path)
106
105
106 build_installer(
107 source_dir,
108 inno_build_dir,
109 staging_dir,
110 iscc_exe,
111 version,
112 arch="x64" if vc_x64 else None,
113 )
114
115
116 def build_installer(
117 source_dir: pathlib.Path,
118 inno_build_dir: pathlib.Path,
119 staging_dir: pathlib.Path,
120 iscc_exe: pathlib.Path,
121 version,
122 arch=None,
123 ):
124 """Build an Inno installer from staged Mercurial files.
125
126 This function is agnostic about how to build Mercurial. It just
127 cares that Mercurial files are in ``staging_dir``.
128 """
129 inno_source_dir = source_dir / "contrib" / "packaging" / "inno"
130
107 # The final package layout is simply a mirror of the staging directory.
131 # The final package layout is simply a mirror of the staging directory.
108 package_files = []
132 package_files = []
109 for root, dirs, files in os.walk(staging_dir):
133 for root, dirs, files in os.walk(staging_dir):
110 dirs.sort()
134 dirs.sort()
111
135
112 root = pathlib.Path(root)
136 root = pathlib.Path(root)
113
137
114 for f in sorted(files):
138 for f in sorted(files):
115 full = root / f
139 full = root / f
116 rel = full.relative_to(staging_dir)
140 rel = full.relative_to(staging_dir)
117 if str(rel.parent) == '.':
141 if str(rel.parent) == '.':
118 dest_dir = '{app}'
142 dest_dir = '{app}'
119 else:
143 else:
120 dest_dir = '{app}\\%s' % rel.parent
144 dest_dir = '{app}\\%s' % rel.parent
121
145
122 package_files.append(
146 package_files.append(
123 {
147 {
124 'source': rel,
148 'source': rel,
125 'dest_dir': dest_dir,
149 'dest_dir': dest_dir,
126 'metadata': PACKAGE_FILES_METADATA.get(str(rel), None),
150 'metadata': PACKAGE_FILES_METADATA.get(str(rel), None),
127 }
151 }
128 )
152 )
129
153
130 print('creating installer')
154 print('creating installer')
131
155
132 # Install Inno files by rendering a template.
156 # Install Inno files by rendering a template.
133 jinja_env = jinja2.Environment(
157 jinja_env = jinja2.Environment(
134 loader=jinja2.FileSystemLoader(str(inno_source_dir)),
158 loader=jinja2.FileSystemLoader(str(inno_source_dir)),
135 # Need to change these to prevent conflict with Inno Setup.
159 # Need to change these to prevent conflict with Inno Setup.
136 comment_start_string='{##',
160 comment_start_string='{##',
137 comment_end_string='##}',
161 comment_end_string='##}',
138 )
162 )
139
163
140 try:
164 try:
141 template = jinja_env.get_template('mercurial.iss')
165 template = jinja_env.get_template('mercurial.iss')
142 except jinja2.TemplateSyntaxError as e:
166 except jinja2.TemplateSyntaxError as e:
143 raise Exception(
167 raise Exception(
144 'template syntax error at %s:%d: %s'
168 'template syntax error at %s:%d: %s'
145 % (e.name, e.lineno, e.message,)
169 % (e.name, e.lineno, e.message,)
146 )
170 )
147
171
148 content = template.render(package_files=package_files)
172 content = template.render(package_files=package_files)
149
173
150 with (inno_build_dir / 'mercurial.iss').open('w', encoding='utf-8') as fh:
174 with (inno_build_dir / 'mercurial.iss').open('w', encoding='utf-8') as fh:
151 fh.write(content)
175 fh.write(content)
152
176
153 # Copy additional files used by Inno.
177 # Copy additional files used by Inno.
154 for p in ('mercurial.ico', 'postinstall.txt'):
178 for p in ('mercurial.ico', 'postinstall.txt'):
155 shutil.copyfile(
179 shutil.copyfile(
156 source_dir / 'contrib' / 'win32' / p, inno_build_dir / p
180 source_dir / 'contrib' / 'win32' / p, inno_build_dir / p
157 )
181 )
158
182
159 args = [str(iscc_exe)]
183 args = [str(iscc_exe)]
160
184
161 if vc_x64:
185 if arch:
162 args.append('/dARCH=x64')
186 args.append('/dARCH=%s' % arch)
163
187
164 if not version:
188 if not version:
165 version = read_version_py(source_dir)
189 version = read_version_py(source_dir)
166
190
167 args.append('/dVERSION=%s' % version)
191 args.append('/dVERSION=%s' % version)
168 args.append('/dQUAD_VERSION=%s' % normalize_windows_version(version))
192 args.append('/dQUAD_VERSION=%s' % normalize_windows_version(version))
169
193
170 args.append('/Odist')
194 args.append('/Odist')
171 args.append(str(inno_build_dir / 'mercurial.iss'))
195 args.append(str(inno_build_dir / 'mercurial.iss'))
172
196
173 subprocess.run(args, cwd=str(source_dir), check=True)
197 subprocess.run(args, cwd=str(source_dir), check=True)
General Comments 0
You need to be logged in to leave comments. Login now