1 | n/a | # Copyright (C) 2005, 2006 Martin von Löwis |
---|
2 | n/a | # Licensed to PSF under a Contributor Agreement. |
---|
3 | n/a | # The bdist_wininst command proper |
---|
4 | n/a | # based on bdist_wininst |
---|
5 | n/a | """ |
---|
6 | n/a | Implements the bdist_msi command. |
---|
7 | n/a | """ |
---|
8 | n/a | |
---|
9 | n/a | import sys, os |
---|
10 | n/a | from distutils.core import Command |
---|
11 | n/a | from distutils.dir_util import remove_tree |
---|
12 | n/a | from distutils.sysconfig import get_python_version |
---|
13 | n/a | from distutils.version import StrictVersion |
---|
14 | n/a | from distutils.errors import DistutilsOptionError |
---|
15 | n/a | from distutils.util import get_platform |
---|
16 | n/a | from distutils import log |
---|
17 | n/a | import msilib |
---|
18 | n/a | from msilib import schema, sequence, text |
---|
19 | n/a | from msilib import Directory, Feature, Dialog, add_data |
---|
20 | n/a | |
---|
21 | n/a | class PyDialog(Dialog): |
---|
22 | n/a | """Dialog class with a fixed layout: controls at the top, then a ruler, |
---|
23 | n/a | then a list of buttons: back, next, cancel. Optionally a bitmap at the |
---|
24 | n/a | left.""" |
---|
25 | n/a | def __init__(self, *args, **kw): |
---|
26 | n/a | """Dialog(database, name, x, y, w, h, attributes, title, first, |
---|
27 | n/a | default, cancel, bitmap=true)""" |
---|
28 | n/a | Dialog.__init__(self, *args) |
---|
29 | n/a | ruler = self.h - 36 |
---|
30 | n/a | bmwidth = 152*ruler/328 |
---|
31 | n/a | #if kw.get("bitmap", True): |
---|
32 | n/a | # self.bitmap("Bitmap", 0, 0, bmwidth, ruler, "PythonWin") |
---|
33 | n/a | self.line("BottomLine", 0, ruler, self.w, 0) |
---|
34 | n/a | |
---|
35 | n/a | def title(self, title): |
---|
36 | n/a | "Set the title text of the dialog at the top." |
---|
37 | n/a | # name, x, y, w, h, flags=Visible|Enabled|Transparent|NoPrefix, |
---|
38 | n/a | # text, in VerdanaBold10 |
---|
39 | n/a | self.text("Title", 15, 10, 320, 60, 0x30003, |
---|
40 | n/a | r"{\VerdanaBold10}%s" % title) |
---|
41 | n/a | |
---|
42 | n/a | def back(self, title, next, name = "Back", active = 1): |
---|
43 | n/a | """Add a back button with a given title, the tab-next button, |
---|
44 | n/a | its name in the Control table, possibly initially disabled. |
---|
45 | n/a | |
---|
46 | n/a | Return the button, so that events can be associated""" |
---|
47 | n/a | if active: |
---|
48 | n/a | flags = 3 # Visible|Enabled |
---|
49 | n/a | else: |
---|
50 | n/a | flags = 1 # Visible |
---|
51 | n/a | return self.pushbutton(name, 180, self.h-27 , 56, 17, flags, title, next) |
---|
52 | n/a | |
---|
53 | n/a | def cancel(self, title, next, name = "Cancel", active = 1): |
---|
54 | n/a | """Add a cancel button with a given title, the tab-next button, |
---|
55 | n/a | its name in the Control table, possibly initially disabled. |
---|
56 | n/a | |
---|
57 | n/a | Return the button, so that events can be associated""" |
---|
58 | n/a | if active: |
---|
59 | n/a | flags = 3 # Visible|Enabled |
---|
60 | n/a | else: |
---|
61 | n/a | flags = 1 # Visible |
---|
62 | n/a | return self.pushbutton(name, 304, self.h-27, 56, 17, flags, title, next) |
---|
63 | n/a | |
---|
64 | n/a | def next(self, title, next, name = "Next", active = 1): |
---|
65 | n/a | """Add a Next button with a given title, the tab-next button, |
---|
66 | n/a | its name in the Control table, possibly initially disabled. |
---|
67 | n/a | |
---|
68 | n/a | Return the button, so that events can be associated""" |
---|
69 | n/a | if active: |
---|
70 | n/a | flags = 3 # Visible|Enabled |
---|
71 | n/a | else: |
---|
72 | n/a | flags = 1 # Visible |
---|
73 | n/a | return self.pushbutton(name, 236, self.h-27, 56, 17, flags, title, next) |
---|
74 | n/a | |
---|
75 | n/a | def xbutton(self, name, title, next, xpos): |
---|
76 | n/a | """Add a button with a given title, the tab-next button, |
---|
77 | n/a | its name in the Control table, giving its x position; the |
---|
78 | n/a | y-position is aligned with the other buttons. |
---|
79 | n/a | |
---|
80 | n/a | Return the button, so that events can be associated""" |
---|
81 | n/a | return self.pushbutton(name, int(self.w*xpos - 28), self.h-27, 56, 17, 3, title, next) |
---|
82 | n/a | |
---|
83 | n/a | class bdist_msi(Command): |
---|
84 | n/a | |
---|
85 | n/a | description = "create a Microsoft Installer (.msi) binary distribution" |
---|
86 | n/a | |
---|
87 | n/a | user_options = [('bdist-dir=', None, |
---|
88 | n/a | "temporary directory for creating the distribution"), |
---|
89 | n/a | ('plat-name=', 'p', |
---|
90 | n/a | "platform name to embed in generated filenames " |
---|
91 | n/a | "(default: %s)" % get_platform()), |
---|
92 | n/a | ('keep-temp', 'k', |
---|
93 | n/a | "keep the pseudo-installation tree around after " + |
---|
94 | n/a | "creating the distribution archive"), |
---|
95 | n/a | ('target-version=', None, |
---|
96 | n/a | "require a specific python version" + |
---|
97 | n/a | " on the target system"), |
---|
98 | n/a | ('no-target-compile', 'c', |
---|
99 | n/a | "do not compile .py to .pyc on the target system"), |
---|
100 | n/a | ('no-target-optimize', 'o', |
---|
101 | n/a | "do not compile .py to .pyo (optimized)" |
---|
102 | n/a | "on the target system"), |
---|
103 | n/a | ('dist-dir=', 'd', |
---|
104 | n/a | "directory to put final built distributions in"), |
---|
105 | n/a | ('skip-build', None, |
---|
106 | n/a | "skip rebuilding everything (for testing/debugging)"), |
---|
107 | n/a | ('install-script=', None, |
---|
108 | n/a | "basename of installation script to be run after" |
---|
109 | n/a | "installation or before deinstallation"), |
---|
110 | n/a | ('pre-install-script=', None, |
---|
111 | n/a | "Fully qualified filename of a script to be run before " |
---|
112 | n/a | "any files are installed. This script need not be in the " |
---|
113 | n/a | "distribution"), |
---|
114 | n/a | ] |
---|
115 | n/a | |
---|
116 | n/a | boolean_options = ['keep-temp', 'no-target-compile', 'no-target-optimize', |
---|
117 | n/a | 'skip-build'] |
---|
118 | n/a | |
---|
119 | n/a | all_versions = ['2.0', '2.1', '2.2', '2.3', '2.4', |
---|
120 | n/a | '2.5', '2.6', '2.7', '2.8', '2.9', |
---|
121 | n/a | '3.0', '3.1', '3.2', '3.3', '3.4', |
---|
122 | n/a | '3.5', '3.6', '3.7', '3.8', '3.9'] |
---|
123 | n/a | other_version = 'X' |
---|
124 | n/a | |
---|
125 | n/a | def initialize_options(self): |
---|
126 | n/a | self.bdist_dir = None |
---|
127 | n/a | self.plat_name = None |
---|
128 | n/a | self.keep_temp = 0 |
---|
129 | n/a | self.no_target_compile = 0 |
---|
130 | n/a | self.no_target_optimize = 0 |
---|
131 | n/a | self.target_version = None |
---|
132 | n/a | self.dist_dir = None |
---|
133 | n/a | self.skip_build = None |
---|
134 | n/a | self.install_script = None |
---|
135 | n/a | self.pre_install_script = None |
---|
136 | n/a | self.versions = None |
---|
137 | n/a | |
---|
138 | n/a | def finalize_options(self): |
---|
139 | n/a | self.set_undefined_options('bdist', ('skip_build', 'skip_build')) |
---|
140 | n/a | |
---|
141 | n/a | if self.bdist_dir is None: |
---|
142 | n/a | bdist_base = self.get_finalized_command('bdist').bdist_base |
---|
143 | n/a | self.bdist_dir = os.path.join(bdist_base, 'msi') |
---|
144 | n/a | |
---|
145 | n/a | short_version = get_python_version() |
---|
146 | n/a | if (not self.target_version) and self.distribution.has_ext_modules(): |
---|
147 | n/a | self.target_version = short_version |
---|
148 | n/a | |
---|
149 | n/a | if self.target_version: |
---|
150 | n/a | self.versions = [self.target_version] |
---|
151 | n/a | if not self.skip_build and self.distribution.has_ext_modules()\ |
---|
152 | n/a | and self.target_version != short_version: |
---|
153 | n/a | raise DistutilsOptionError( |
---|
154 | n/a | "target version can only be %s, or the '--skip-build'" |
---|
155 | n/a | " option must be specified" % (short_version,)) |
---|
156 | n/a | else: |
---|
157 | n/a | self.versions = list(self.all_versions) |
---|
158 | n/a | |
---|
159 | n/a | self.set_undefined_options('bdist', |
---|
160 | n/a | ('dist_dir', 'dist_dir'), |
---|
161 | n/a | ('plat_name', 'plat_name'), |
---|
162 | n/a | ) |
---|
163 | n/a | |
---|
164 | n/a | if self.pre_install_script: |
---|
165 | n/a | raise DistutilsOptionError( |
---|
166 | n/a | "the pre-install-script feature is not yet implemented") |
---|
167 | n/a | |
---|
168 | n/a | if self.install_script: |
---|
169 | n/a | for script in self.distribution.scripts: |
---|
170 | n/a | if self.install_script == os.path.basename(script): |
---|
171 | n/a | break |
---|
172 | n/a | else: |
---|
173 | n/a | raise DistutilsOptionError( |
---|
174 | n/a | "install_script '%s' not found in scripts" |
---|
175 | n/a | % self.install_script) |
---|
176 | n/a | self.install_script_key = None |
---|
177 | n/a | |
---|
178 | n/a | def run(self): |
---|
179 | n/a | if not self.skip_build: |
---|
180 | n/a | self.run_command('build') |
---|
181 | n/a | |
---|
182 | n/a | install = self.reinitialize_command('install', reinit_subcommands=1) |
---|
183 | n/a | install.prefix = self.bdist_dir |
---|
184 | n/a | install.skip_build = self.skip_build |
---|
185 | n/a | install.warn_dir = 0 |
---|
186 | n/a | |
---|
187 | n/a | install_lib = self.reinitialize_command('install_lib') |
---|
188 | n/a | # we do not want to include pyc or pyo files |
---|
189 | n/a | install_lib.compile = 0 |
---|
190 | n/a | install_lib.optimize = 0 |
---|
191 | n/a | |
---|
192 | n/a | if self.distribution.has_ext_modules(): |
---|
193 | n/a | # If we are building an installer for a Python version other |
---|
194 | n/a | # than the one we are currently running, then we need to ensure |
---|
195 | n/a | # our build_lib reflects the other Python version rather than ours. |
---|
196 | n/a | # Note that for target_version!=sys.version, we must have skipped the |
---|
197 | n/a | # build step, so there is no issue with enforcing the build of this |
---|
198 | n/a | # version. |
---|
199 | n/a | target_version = self.target_version |
---|
200 | n/a | if not target_version: |
---|
201 | n/a | assert self.skip_build, "Should have already checked this" |
---|
202 | n/a | target_version = '%d.%d' % sys.version_info[:2] |
---|
203 | n/a | plat_specifier = ".%s-%s" % (self.plat_name, target_version) |
---|
204 | n/a | build = self.get_finalized_command('build') |
---|
205 | n/a | build.build_lib = os.path.join(build.build_base, |
---|
206 | n/a | 'lib' + plat_specifier) |
---|
207 | n/a | |
---|
208 | n/a | log.info("installing to %s", self.bdist_dir) |
---|
209 | n/a | install.ensure_finalized() |
---|
210 | n/a | |
---|
211 | n/a | # avoid warning of 'install_lib' about installing |
---|
212 | n/a | # into a directory not in sys.path |
---|
213 | n/a | sys.path.insert(0, os.path.join(self.bdist_dir, 'PURELIB')) |
---|
214 | n/a | |
---|
215 | n/a | install.run() |
---|
216 | n/a | |
---|
217 | n/a | del sys.path[0] |
---|
218 | n/a | |
---|
219 | n/a | self.mkpath(self.dist_dir) |
---|
220 | n/a | fullname = self.distribution.get_fullname() |
---|
221 | n/a | installer_name = self.get_installer_filename(fullname) |
---|
222 | n/a | installer_name = os.path.abspath(installer_name) |
---|
223 | n/a | if os.path.exists(installer_name): os.unlink(installer_name) |
---|
224 | n/a | |
---|
225 | n/a | metadata = self.distribution.metadata |
---|
226 | n/a | author = metadata.author |
---|
227 | n/a | if not author: |
---|
228 | n/a | author = metadata.maintainer |
---|
229 | n/a | if not author: |
---|
230 | n/a | author = "UNKNOWN" |
---|
231 | n/a | version = metadata.get_version() |
---|
232 | n/a | # ProductVersion must be strictly numeric |
---|
233 | n/a | # XXX need to deal with prerelease versions |
---|
234 | n/a | sversion = "%d.%d.%d" % StrictVersion(version).version |
---|
235 | n/a | # Prefix ProductName with Python x.y, so that |
---|
236 | n/a | # it sorts together with the other Python packages |
---|
237 | n/a | # in Add-Remove-Programs (APR) |
---|
238 | n/a | fullname = self.distribution.get_fullname() |
---|
239 | n/a | if self.target_version: |
---|
240 | n/a | product_name = "Python %s %s" % (self.target_version, fullname) |
---|
241 | n/a | else: |
---|
242 | n/a | product_name = "Python %s" % (fullname) |
---|
243 | n/a | self.db = msilib.init_database(installer_name, schema, |
---|
244 | n/a | product_name, msilib.gen_uuid(), |
---|
245 | n/a | sversion, author) |
---|
246 | n/a | msilib.add_tables(self.db, sequence) |
---|
247 | n/a | props = [('DistVersion', version)] |
---|
248 | n/a | email = metadata.author_email or metadata.maintainer_email |
---|
249 | n/a | if email: |
---|
250 | n/a | props.append(("ARPCONTACT", email)) |
---|
251 | n/a | if metadata.url: |
---|
252 | n/a | props.append(("ARPURLINFOABOUT", metadata.url)) |
---|
253 | n/a | if props: |
---|
254 | n/a | add_data(self.db, 'Property', props) |
---|
255 | n/a | |
---|
256 | n/a | self.add_find_python() |
---|
257 | n/a | self.add_files() |
---|
258 | n/a | self.add_scripts() |
---|
259 | n/a | self.add_ui() |
---|
260 | n/a | self.db.Commit() |
---|
261 | n/a | |
---|
262 | n/a | if hasattr(self.distribution, 'dist_files'): |
---|
263 | n/a | tup = 'bdist_msi', self.target_version or 'any', fullname |
---|
264 | n/a | self.distribution.dist_files.append(tup) |
---|
265 | n/a | |
---|
266 | n/a | if not self.keep_temp: |
---|
267 | n/a | remove_tree(self.bdist_dir, dry_run=self.dry_run) |
---|
268 | n/a | |
---|
269 | n/a | def add_files(self): |
---|
270 | n/a | db = self.db |
---|
271 | n/a | cab = msilib.CAB("distfiles") |
---|
272 | n/a | rootdir = os.path.abspath(self.bdist_dir) |
---|
273 | n/a | |
---|
274 | n/a | root = Directory(db, cab, None, rootdir, "TARGETDIR", "SourceDir") |
---|
275 | n/a | f = Feature(db, "Python", "Python", "Everything", |
---|
276 | n/a | 0, 1, directory="TARGETDIR") |
---|
277 | n/a | |
---|
278 | n/a | items = [(f, root, '')] |
---|
279 | n/a | for version in self.versions + [self.other_version]: |
---|
280 | n/a | target = "TARGETDIR" + version |
---|
281 | n/a | name = default = "Python" + version |
---|
282 | n/a | desc = "Everything" |
---|
283 | n/a | if version is self.other_version: |
---|
284 | n/a | title = "Python from another location" |
---|
285 | n/a | level = 2 |
---|
286 | n/a | else: |
---|
287 | n/a | title = "Python %s from registry" % version |
---|
288 | n/a | level = 1 |
---|
289 | n/a | f = Feature(db, name, title, desc, 1, level, directory=target) |
---|
290 | n/a | dir = Directory(db, cab, root, rootdir, target, default) |
---|
291 | n/a | items.append((f, dir, version)) |
---|
292 | n/a | db.Commit() |
---|
293 | n/a | |
---|
294 | n/a | seen = {} |
---|
295 | n/a | for feature, dir, version in items: |
---|
296 | n/a | todo = [dir] |
---|
297 | n/a | while todo: |
---|
298 | n/a | dir = todo.pop() |
---|
299 | n/a | for file in os.listdir(dir.absolute): |
---|
300 | n/a | afile = os.path.join(dir.absolute, file) |
---|
301 | n/a | if os.path.isdir(afile): |
---|
302 | n/a | short = "%s|%s" % (dir.make_short(file), file) |
---|
303 | n/a | default = file + version |
---|
304 | n/a | newdir = Directory(db, cab, dir, file, default, short) |
---|
305 | n/a | todo.append(newdir) |
---|
306 | n/a | else: |
---|
307 | n/a | if not dir.component: |
---|
308 | n/a | dir.start_component(dir.logical, feature, 0) |
---|
309 | n/a | if afile not in seen: |
---|
310 | n/a | key = seen[afile] = dir.add_file(file) |
---|
311 | n/a | if file==self.install_script: |
---|
312 | n/a | if self.install_script_key: |
---|
313 | n/a | raise DistutilsOptionError( |
---|
314 | n/a | "Multiple files with name %s" % file) |
---|
315 | n/a | self.install_script_key = '[#%s]' % key |
---|
316 | n/a | else: |
---|
317 | n/a | key = seen[afile] |
---|
318 | n/a | add_data(self.db, "DuplicateFile", |
---|
319 | n/a | [(key + version, dir.component, key, None, dir.logical)]) |
---|
320 | n/a | db.Commit() |
---|
321 | n/a | cab.commit(db) |
---|
322 | n/a | |
---|
323 | n/a | def add_find_python(self): |
---|
324 | n/a | """Adds code to the installer to compute the location of Python. |
---|
325 | n/a | |
---|
326 | n/a | Properties PYTHON.MACHINE.X.Y and PYTHON.USER.X.Y will be set from the |
---|
327 | n/a | registry for each version of Python. |
---|
328 | n/a | |
---|
329 | n/a | Properties TARGETDIRX.Y will be set from PYTHON.USER.X.Y if defined, |
---|
330 | n/a | else from PYTHON.MACHINE.X.Y. |
---|
331 | n/a | |
---|
332 | n/a | Properties PYTHONX.Y will be set to TARGETDIRX.Y\\python.exe""" |
---|
333 | n/a | |
---|
334 | n/a | start = 402 |
---|
335 | n/a | for ver in self.versions: |
---|
336 | n/a | install_path = r"SOFTWARE\Python\PythonCore\%s\InstallPath" % ver |
---|
337 | n/a | machine_reg = "python.machine." + ver |
---|
338 | n/a | user_reg = "python.user." + ver |
---|
339 | n/a | machine_prop = "PYTHON.MACHINE." + ver |
---|
340 | n/a | user_prop = "PYTHON.USER." + ver |
---|
341 | n/a | machine_action = "PythonFromMachine" + ver |
---|
342 | n/a | user_action = "PythonFromUser" + ver |
---|
343 | n/a | exe_action = "PythonExe" + ver |
---|
344 | n/a | target_dir_prop = "TARGETDIR" + ver |
---|
345 | n/a | exe_prop = "PYTHON" + ver |
---|
346 | n/a | if msilib.Win64: |
---|
347 | n/a | # type: msidbLocatorTypeRawValue + msidbLocatorType64bit |
---|
348 | n/a | Type = 2+16 |
---|
349 | n/a | else: |
---|
350 | n/a | Type = 2 |
---|
351 | n/a | add_data(self.db, "RegLocator", |
---|
352 | n/a | [(machine_reg, 2, install_path, None, Type), |
---|
353 | n/a | (user_reg, 1, install_path, None, Type)]) |
---|
354 | n/a | add_data(self.db, "AppSearch", |
---|
355 | n/a | [(machine_prop, machine_reg), |
---|
356 | n/a | (user_prop, user_reg)]) |
---|
357 | n/a | add_data(self.db, "CustomAction", |
---|
358 | n/a | [(machine_action, 51+256, target_dir_prop, "[" + machine_prop + "]"), |
---|
359 | n/a | (user_action, 51+256, target_dir_prop, "[" + user_prop + "]"), |
---|
360 | n/a | (exe_action, 51+256, exe_prop, "[" + target_dir_prop + "]\\python.exe"), |
---|
361 | n/a | ]) |
---|
362 | n/a | add_data(self.db, "InstallExecuteSequence", |
---|
363 | n/a | [(machine_action, machine_prop, start), |
---|
364 | n/a | (user_action, user_prop, start + 1), |
---|
365 | n/a | (exe_action, None, start + 2), |
---|
366 | n/a | ]) |
---|
367 | n/a | add_data(self.db, "InstallUISequence", |
---|
368 | n/a | [(machine_action, machine_prop, start), |
---|
369 | n/a | (user_action, user_prop, start + 1), |
---|
370 | n/a | (exe_action, None, start + 2), |
---|
371 | n/a | ]) |
---|
372 | n/a | add_data(self.db, "Condition", |
---|
373 | n/a | [("Python" + ver, 0, "NOT TARGETDIR" + ver)]) |
---|
374 | n/a | start += 4 |
---|
375 | n/a | assert start < 500 |
---|
376 | n/a | |
---|
377 | n/a | def add_scripts(self): |
---|
378 | n/a | if self.install_script: |
---|
379 | n/a | start = 6800 |
---|
380 | n/a | for ver in self.versions + [self.other_version]: |
---|
381 | n/a | install_action = "install_script." + ver |
---|
382 | n/a | exe_prop = "PYTHON" + ver |
---|
383 | n/a | add_data(self.db, "CustomAction", |
---|
384 | n/a | [(install_action, 50, exe_prop, self.install_script_key)]) |
---|
385 | n/a | add_data(self.db, "InstallExecuteSequence", |
---|
386 | n/a | [(install_action, "&Python%s=3" % ver, start)]) |
---|
387 | n/a | start += 1 |
---|
388 | n/a | # XXX pre-install scripts are currently refused in finalize_options() |
---|
389 | n/a | # but if this feature is completed, it will also need to add |
---|
390 | n/a | # entries for each version as the above code does |
---|
391 | n/a | if self.pre_install_script: |
---|
392 | n/a | scriptfn = os.path.join(self.bdist_dir, "preinstall.bat") |
---|
393 | n/a | f = open(scriptfn, "w") |
---|
394 | n/a | # The batch file will be executed with [PYTHON], so that %1 |
---|
395 | n/a | # is the path to the Python interpreter; %0 will be the path |
---|
396 | n/a | # of the batch file. |
---|
397 | n/a | # rem =""" |
---|
398 | n/a | # %1 %0 |
---|
399 | n/a | # exit |
---|
400 | n/a | # """ |
---|
401 | n/a | # <actual script> |
---|
402 | n/a | f.write('rem ="""\n%1 %0\nexit\n"""\n') |
---|
403 | n/a | f.write(open(self.pre_install_script).read()) |
---|
404 | n/a | f.close() |
---|
405 | n/a | add_data(self.db, "Binary", |
---|
406 | n/a | [("PreInstall", msilib.Binary(scriptfn)) |
---|
407 | n/a | ]) |
---|
408 | n/a | add_data(self.db, "CustomAction", |
---|
409 | n/a | [("PreInstall", 2, "PreInstall", None) |
---|
410 | n/a | ]) |
---|
411 | n/a | add_data(self.db, "InstallExecuteSequence", |
---|
412 | n/a | [("PreInstall", "NOT Installed", 450)]) |
---|
413 | n/a | |
---|
414 | n/a | |
---|
415 | n/a | def add_ui(self): |
---|
416 | n/a | db = self.db |
---|
417 | n/a | x = y = 50 |
---|
418 | n/a | w = 370 |
---|
419 | n/a | h = 300 |
---|
420 | n/a | title = "[ProductName] Setup" |
---|
421 | n/a | |
---|
422 | n/a | # see "Dialog Style Bits" |
---|
423 | n/a | modal = 3 # visible | modal |
---|
424 | n/a | modeless = 1 # visible |
---|
425 | n/a | track_disk_space = 32 |
---|
426 | n/a | |
---|
427 | n/a | # UI customization properties |
---|
428 | n/a | add_data(db, "Property", |
---|
429 | n/a | # See "DefaultUIFont Property" |
---|
430 | n/a | [("DefaultUIFont", "DlgFont8"), |
---|
431 | n/a | # See "ErrorDialog Style Bit" |
---|
432 | n/a | ("ErrorDialog", "ErrorDlg"), |
---|
433 | n/a | ("Progress1", "Install"), # modified in maintenance type dlg |
---|
434 | n/a | ("Progress2", "installs"), |
---|
435 | n/a | ("MaintenanceForm_Action", "Repair"), |
---|
436 | n/a | # possible values: ALL, JUSTME |
---|
437 | n/a | ("WhichUsers", "ALL") |
---|
438 | n/a | ]) |
---|
439 | n/a | |
---|
440 | n/a | # Fonts, see "TextStyle Table" |
---|
441 | n/a | add_data(db, "TextStyle", |
---|
442 | n/a | [("DlgFont8", "Tahoma", 9, None, 0), |
---|
443 | n/a | ("DlgFontBold8", "Tahoma", 8, None, 1), #bold |
---|
444 | n/a | ("VerdanaBold10", "Verdana", 10, None, 1), |
---|
445 | n/a | ("VerdanaRed9", "Verdana", 9, 255, 0), |
---|
446 | n/a | ]) |
---|
447 | n/a | |
---|
448 | n/a | # UI Sequences, see "InstallUISequence Table", "Using a Sequence Table" |
---|
449 | n/a | # Numbers indicate sequence; see sequence.py for how these action integrate |
---|
450 | n/a | add_data(db, "InstallUISequence", |
---|
451 | n/a | [("PrepareDlg", "Not Privileged or Windows9x or Installed", 140), |
---|
452 | n/a | ("WhichUsersDlg", "Privileged and not Windows9x and not Installed", 141), |
---|
453 | n/a | # In the user interface, assume all-users installation if privileged. |
---|
454 | n/a | ("SelectFeaturesDlg", "Not Installed", 1230), |
---|
455 | n/a | # XXX no support for resume installations yet |
---|
456 | n/a | #("ResumeDlg", "Installed AND (RESUME OR Preselected)", 1240), |
---|
457 | n/a | ("MaintenanceTypeDlg", "Installed AND NOT RESUME AND NOT Preselected", 1250), |
---|
458 | n/a | ("ProgressDlg", None, 1280)]) |
---|
459 | n/a | |
---|
460 | n/a | add_data(db, 'ActionText', text.ActionText) |
---|
461 | n/a | add_data(db, 'UIText', text.UIText) |
---|
462 | n/a | ##################################################################### |
---|
463 | n/a | # Standard dialogs: FatalError, UserExit, ExitDialog |
---|
464 | n/a | fatal=PyDialog(db, "FatalError", x, y, w, h, modal, title, |
---|
465 | n/a | "Finish", "Finish", "Finish") |
---|
466 | n/a | fatal.title("[ProductName] Installer ended prematurely") |
---|
467 | n/a | fatal.back("< Back", "Finish", active = 0) |
---|
468 | n/a | fatal.cancel("Cancel", "Back", active = 0) |
---|
469 | n/a | fatal.text("Description1", 15, 70, 320, 80, 0x30003, |
---|
470 | n/a | "[ProductName] setup ended prematurely because of an error. Your system has not been modified. To install this program at a later time, please run the installation again.") |
---|
471 | n/a | fatal.text("Description2", 15, 155, 320, 20, 0x30003, |
---|
472 | n/a | "Click the Finish button to exit the Installer.") |
---|
473 | n/a | c=fatal.next("Finish", "Cancel", name="Finish") |
---|
474 | n/a | c.event("EndDialog", "Exit") |
---|
475 | n/a | |
---|
476 | n/a | user_exit=PyDialog(db, "UserExit", x, y, w, h, modal, title, |
---|
477 | n/a | "Finish", "Finish", "Finish") |
---|
478 | n/a | user_exit.title("[ProductName] Installer was interrupted") |
---|
479 | n/a | user_exit.back("< Back", "Finish", active = 0) |
---|
480 | n/a | user_exit.cancel("Cancel", "Back", active = 0) |
---|
481 | n/a | user_exit.text("Description1", 15, 70, 320, 80, 0x30003, |
---|
482 | n/a | "[ProductName] setup was interrupted. Your system has not been modified. " |
---|
483 | n/a | "To install this program at a later time, please run the installation again.") |
---|
484 | n/a | user_exit.text("Description2", 15, 155, 320, 20, 0x30003, |
---|
485 | n/a | "Click the Finish button to exit the Installer.") |
---|
486 | n/a | c = user_exit.next("Finish", "Cancel", name="Finish") |
---|
487 | n/a | c.event("EndDialog", "Exit") |
---|
488 | n/a | |
---|
489 | n/a | exit_dialog = PyDialog(db, "ExitDialog", x, y, w, h, modal, title, |
---|
490 | n/a | "Finish", "Finish", "Finish") |
---|
491 | n/a | exit_dialog.title("Completing the [ProductName] Installer") |
---|
492 | n/a | exit_dialog.back("< Back", "Finish", active = 0) |
---|
493 | n/a | exit_dialog.cancel("Cancel", "Back", active = 0) |
---|
494 | n/a | exit_dialog.text("Description", 15, 235, 320, 20, 0x30003, |
---|
495 | n/a | "Click the Finish button to exit the Installer.") |
---|
496 | n/a | c = exit_dialog.next("Finish", "Cancel", name="Finish") |
---|
497 | n/a | c.event("EndDialog", "Return") |
---|
498 | n/a | |
---|
499 | n/a | ##################################################################### |
---|
500 | n/a | # Required dialog: FilesInUse, ErrorDlg |
---|
501 | n/a | inuse = PyDialog(db, "FilesInUse", |
---|
502 | n/a | x, y, w, h, |
---|
503 | n/a | 19, # KeepModeless|Modal|Visible |
---|
504 | n/a | title, |
---|
505 | n/a | "Retry", "Retry", "Retry", bitmap=False) |
---|
506 | n/a | inuse.text("Title", 15, 6, 200, 15, 0x30003, |
---|
507 | n/a | r"{\DlgFontBold8}Files in Use") |
---|
508 | n/a | inuse.text("Description", 20, 23, 280, 20, 0x30003, |
---|
509 | n/a | "Some files that need to be updated are currently in use.") |
---|
510 | n/a | inuse.text("Text", 20, 55, 330, 50, 3, |
---|
511 | n/a | "The following applications are using files that need to be updated by this setup. Close these applications and then click Retry to continue the installation or Cancel to exit it.") |
---|
512 | n/a | inuse.control("List", "ListBox", 20, 107, 330, 130, 7, "FileInUseProcess", |
---|
513 | n/a | None, None, None) |
---|
514 | n/a | c=inuse.back("Exit", "Ignore", name="Exit") |
---|
515 | n/a | c.event("EndDialog", "Exit") |
---|
516 | n/a | c=inuse.next("Ignore", "Retry", name="Ignore") |
---|
517 | n/a | c.event("EndDialog", "Ignore") |
---|
518 | n/a | c=inuse.cancel("Retry", "Exit", name="Retry") |
---|
519 | n/a | c.event("EndDialog","Retry") |
---|
520 | n/a | |
---|
521 | n/a | # See "Error Dialog". See "ICE20" for the required names of the controls. |
---|
522 | n/a | error = Dialog(db, "ErrorDlg", |
---|
523 | n/a | 50, 10, 330, 101, |
---|
524 | n/a | 65543, # Error|Minimize|Modal|Visible |
---|
525 | n/a | title, |
---|
526 | n/a | "ErrorText", None, None) |
---|
527 | n/a | error.text("ErrorText", 50,9,280,48,3, "") |
---|
528 | n/a | #error.control("ErrorIcon", "Icon", 15, 9, 24, 24, 5242881, None, "py.ico", None, None) |
---|
529 | n/a | error.pushbutton("N",120,72,81,21,3,"No",None).event("EndDialog","ErrorNo") |
---|
530 | n/a | error.pushbutton("Y",240,72,81,21,3,"Yes",None).event("EndDialog","ErrorYes") |
---|
531 | n/a | error.pushbutton("A",0,72,81,21,3,"Abort",None).event("EndDialog","ErrorAbort") |
---|
532 | n/a | error.pushbutton("C",42,72,81,21,3,"Cancel",None).event("EndDialog","ErrorCancel") |
---|
533 | n/a | error.pushbutton("I",81,72,81,21,3,"Ignore",None).event("EndDialog","ErrorIgnore") |
---|
534 | n/a | error.pushbutton("O",159,72,81,21,3,"Ok",None).event("EndDialog","ErrorOk") |
---|
535 | n/a | error.pushbutton("R",198,72,81,21,3,"Retry",None).event("EndDialog","ErrorRetry") |
---|
536 | n/a | |
---|
537 | n/a | ##################################################################### |
---|
538 | n/a | # Global "Query Cancel" dialog |
---|
539 | n/a | cancel = Dialog(db, "CancelDlg", 50, 10, 260, 85, 3, title, |
---|
540 | n/a | "No", "No", "No") |
---|
541 | n/a | cancel.text("Text", 48, 15, 194, 30, 3, |
---|
542 | n/a | "Are you sure you want to cancel [ProductName] installation?") |
---|
543 | n/a | #cancel.control("Icon", "Icon", 15, 15, 24, 24, 5242881, None, |
---|
544 | n/a | # "py.ico", None, None) |
---|
545 | n/a | c=cancel.pushbutton("Yes", 72, 57, 56, 17, 3, "Yes", "No") |
---|
546 | n/a | c.event("EndDialog", "Exit") |
---|
547 | n/a | |
---|
548 | n/a | c=cancel.pushbutton("No", 132, 57, 56, 17, 3, "No", "Yes") |
---|
549 | n/a | c.event("EndDialog", "Return") |
---|
550 | n/a | |
---|
551 | n/a | ##################################################################### |
---|
552 | n/a | # Global "Wait for costing" dialog |
---|
553 | n/a | costing = Dialog(db, "WaitForCostingDlg", 50, 10, 260, 85, modal, title, |
---|
554 | n/a | "Return", "Return", "Return") |
---|
555 | n/a | costing.text("Text", 48, 15, 194, 30, 3, |
---|
556 | n/a | "Please wait while the installer finishes determining your disk space requirements.") |
---|
557 | n/a | c = costing.pushbutton("Return", 102, 57, 56, 17, 3, "Return", None) |
---|
558 | n/a | c.event("EndDialog", "Exit") |
---|
559 | n/a | |
---|
560 | n/a | ##################################################################### |
---|
561 | n/a | # Preparation dialog: no user input except cancellation |
---|
562 | n/a | prep = PyDialog(db, "PrepareDlg", x, y, w, h, modeless, title, |
---|
563 | n/a | "Cancel", "Cancel", "Cancel") |
---|
564 | n/a | prep.text("Description", 15, 70, 320, 40, 0x30003, |
---|
565 | n/a | "Please wait while the Installer prepares to guide you through the installation.") |
---|
566 | n/a | prep.title("Welcome to the [ProductName] Installer") |
---|
567 | n/a | c=prep.text("ActionText", 15, 110, 320, 20, 0x30003, "Pondering...") |
---|
568 | n/a | c.mapping("ActionText", "Text") |
---|
569 | n/a | c=prep.text("ActionData", 15, 135, 320, 30, 0x30003, None) |
---|
570 | n/a | c.mapping("ActionData", "Text") |
---|
571 | n/a | prep.back("Back", None, active=0) |
---|
572 | n/a | prep.next("Next", None, active=0) |
---|
573 | n/a | c=prep.cancel("Cancel", None) |
---|
574 | n/a | c.event("SpawnDialog", "CancelDlg") |
---|
575 | n/a | |
---|
576 | n/a | ##################################################################### |
---|
577 | n/a | # Feature (Python directory) selection |
---|
578 | n/a | seldlg = PyDialog(db, "SelectFeaturesDlg", x, y, w, h, modal, title, |
---|
579 | n/a | "Next", "Next", "Cancel") |
---|
580 | n/a | seldlg.title("Select Python Installations") |
---|
581 | n/a | |
---|
582 | n/a | seldlg.text("Hint", 15, 30, 300, 20, 3, |
---|
583 | n/a | "Select the Python locations where %s should be installed." |
---|
584 | n/a | % self.distribution.get_fullname()) |
---|
585 | n/a | |
---|
586 | n/a | seldlg.back("< Back", None, active=0) |
---|
587 | n/a | c = seldlg.next("Next >", "Cancel") |
---|
588 | n/a | order = 1 |
---|
589 | n/a | c.event("[TARGETDIR]", "[SourceDir]", ordering=order) |
---|
590 | n/a | for version in self.versions + [self.other_version]: |
---|
591 | n/a | order += 1 |
---|
592 | n/a | c.event("[TARGETDIR]", "[TARGETDIR%s]" % version, |
---|
593 | n/a | "FEATURE_SELECTED AND &Python%s=3" % version, |
---|
594 | n/a | ordering=order) |
---|
595 | n/a | c.event("SpawnWaitDialog", "WaitForCostingDlg", ordering=order + 1) |
---|
596 | n/a | c.event("EndDialog", "Return", ordering=order + 2) |
---|
597 | n/a | c = seldlg.cancel("Cancel", "Features") |
---|
598 | n/a | c.event("SpawnDialog", "CancelDlg") |
---|
599 | n/a | |
---|
600 | n/a | c = seldlg.control("Features", "SelectionTree", 15, 60, 300, 120, 3, |
---|
601 | n/a | "FEATURE", None, "PathEdit", None) |
---|
602 | n/a | c.event("[FEATURE_SELECTED]", "1") |
---|
603 | n/a | ver = self.other_version |
---|
604 | n/a | install_other_cond = "FEATURE_SELECTED AND &Python%s=3" % ver |
---|
605 | n/a | dont_install_other_cond = "FEATURE_SELECTED AND &Python%s<>3" % ver |
---|
606 | n/a | |
---|
607 | n/a | c = seldlg.text("Other", 15, 200, 300, 15, 3, |
---|
608 | n/a | "Provide an alternate Python location") |
---|
609 | n/a | c.condition("Enable", install_other_cond) |
---|
610 | n/a | c.condition("Show", install_other_cond) |
---|
611 | n/a | c.condition("Disable", dont_install_other_cond) |
---|
612 | n/a | c.condition("Hide", dont_install_other_cond) |
---|
613 | n/a | |
---|
614 | n/a | c = seldlg.control("PathEdit", "PathEdit", 15, 215, 300, 16, 1, |
---|
615 | n/a | "TARGETDIR" + ver, None, "Next", None) |
---|
616 | n/a | c.condition("Enable", install_other_cond) |
---|
617 | n/a | c.condition("Show", install_other_cond) |
---|
618 | n/a | c.condition("Disable", dont_install_other_cond) |
---|
619 | n/a | c.condition("Hide", dont_install_other_cond) |
---|
620 | n/a | |
---|
621 | n/a | ##################################################################### |
---|
622 | n/a | # Disk cost |
---|
623 | n/a | cost = PyDialog(db, "DiskCostDlg", x, y, w, h, modal, title, |
---|
624 | n/a | "OK", "OK", "OK", bitmap=False) |
---|
625 | n/a | cost.text("Title", 15, 6, 200, 15, 0x30003, |
---|
626 | n/a | r"{\DlgFontBold8}Disk Space Requirements") |
---|
627 | n/a | cost.text("Description", 20, 20, 280, 20, 0x30003, |
---|
628 | n/a | "The disk space required for the installation of the selected features.") |
---|
629 | n/a | cost.text("Text", 20, 53, 330, 60, 3, |
---|
630 | n/a | "The highlighted volumes (if any) do not have enough disk space " |
---|
631 | n/a | "available for the currently selected features. You can either " |
---|
632 | n/a | "remove some files from the highlighted volumes, or choose to " |
---|
633 | n/a | "install less features onto local drive(s), or select different " |
---|
634 | n/a | "destination drive(s).") |
---|
635 | n/a | cost.control("VolumeList", "VolumeCostList", 20, 100, 330, 150, 393223, |
---|
636 | n/a | None, "{120}{70}{70}{70}{70}", None, None) |
---|
637 | n/a | cost.xbutton("OK", "Ok", None, 0.5).event("EndDialog", "Return") |
---|
638 | n/a | |
---|
639 | n/a | ##################################################################### |
---|
640 | n/a | # WhichUsers Dialog. Only available on NT, and for privileged users. |
---|
641 | n/a | # This must be run before FindRelatedProducts, because that will |
---|
642 | n/a | # take into account whether the previous installation was per-user |
---|
643 | n/a | # or per-machine. We currently don't support going back to this |
---|
644 | n/a | # dialog after "Next" was selected; to support this, we would need to |
---|
645 | n/a | # find how to reset the ALLUSERS property, and how to re-run |
---|
646 | n/a | # FindRelatedProducts. |
---|
647 | n/a | # On Windows9x, the ALLUSERS property is ignored on the command line |
---|
648 | n/a | # and in the Property table, but installer fails according to the documentation |
---|
649 | n/a | # if a dialog attempts to set ALLUSERS. |
---|
650 | n/a | whichusers = PyDialog(db, "WhichUsersDlg", x, y, w, h, modal, title, |
---|
651 | n/a | "AdminInstall", "Next", "Cancel") |
---|
652 | n/a | whichusers.title("Select whether to install [ProductName] for all users of this computer.") |
---|
653 | n/a | # A radio group with two options: allusers, justme |
---|
654 | n/a | g = whichusers.radiogroup("AdminInstall", 15, 60, 260, 50, 3, |
---|
655 | n/a | "WhichUsers", "", "Next") |
---|
656 | n/a | g.add("ALL", 0, 5, 150, 20, "Install for all users") |
---|
657 | n/a | g.add("JUSTME", 0, 25, 150, 20, "Install just for me") |
---|
658 | n/a | |
---|
659 | n/a | whichusers.back("Back", None, active=0) |
---|
660 | n/a | |
---|
661 | n/a | c = whichusers.next("Next >", "Cancel") |
---|
662 | n/a | c.event("[ALLUSERS]", "1", 'WhichUsers="ALL"', 1) |
---|
663 | n/a | c.event("EndDialog", "Return", ordering = 2) |
---|
664 | n/a | |
---|
665 | n/a | c = whichusers.cancel("Cancel", "AdminInstall") |
---|
666 | n/a | c.event("SpawnDialog", "CancelDlg") |
---|
667 | n/a | |
---|
668 | n/a | ##################################################################### |
---|
669 | n/a | # Installation Progress dialog (modeless) |
---|
670 | n/a | progress = PyDialog(db, "ProgressDlg", x, y, w, h, modeless, title, |
---|
671 | n/a | "Cancel", "Cancel", "Cancel", bitmap=False) |
---|
672 | n/a | progress.text("Title", 20, 15, 200, 15, 0x30003, |
---|
673 | n/a | r"{\DlgFontBold8}[Progress1] [ProductName]") |
---|
674 | n/a | progress.text("Text", 35, 65, 300, 30, 3, |
---|
675 | n/a | "Please wait while the Installer [Progress2] [ProductName]. " |
---|
676 | n/a | "This may take several minutes.") |
---|
677 | n/a | progress.text("StatusLabel", 35, 100, 35, 20, 3, "Status:") |
---|
678 | n/a | |
---|
679 | n/a | c=progress.text("ActionText", 70, 100, w-70, 20, 3, "Pondering...") |
---|
680 | n/a | c.mapping("ActionText", "Text") |
---|
681 | n/a | |
---|
682 | n/a | #c=progress.text("ActionData", 35, 140, 300, 20, 3, None) |
---|
683 | n/a | #c.mapping("ActionData", "Text") |
---|
684 | n/a | |
---|
685 | n/a | c=progress.control("ProgressBar", "ProgressBar", 35, 120, 300, 10, 65537, |
---|
686 | n/a | None, "Progress done", None, None) |
---|
687 | n/a | c.mapping("SetProgress", "Progress") |
---|
688 | n/a | |
---|
689 | n/a | progress.back("< Back", "Next", active=False) |
---|
690 | n/a | progress.next("Next >", "Cancel", active=False) |
---|
691 | n/a | progress.cancel("Cancel", "Back").event("SpawnDialog", "CancelDlg") |
---|
692 | n/a | |
---|
693 | n/a | ################################################################### |
---|
694 | n/a | # Maintenance type: repair/uninstall |
---|
695 | n/a | maint = PyDialog(db, "MaintenanceTypeDlg", x, y, w, h, modal, title, |
---|
696 | n/a | "Next", "Next", "Cancel") |
---|
697 | n/a | maint.title("Welcome to the [ProductName] Setup Wizard") |
---|
698 | n/a | maint.text("BodyText", 15, 63, 330, 42, 3, |
---|
699 | n/a | "Select whether you want to repair or remove [ProductName].") |
---|
700 | n/a | g=maint.radiogroup("RepairRadioGroup", 15, 108, 330, 60, 3, |
---|
701 | n/a | "MaintenanceForm_Action", "", "Next") |
---|
702 | n/a | #g.add("Change", 0, 0, 200, 17, "&Change [ProductName]") |
---|
703 | n/a | g.add("Repair", 0, 18, 200, 17, "&Repair [ProductName]") |
---|
704 | n/a | g.add("Remove", 0, 36, 200, 17, "Re&move [ProductName]") |
---|
705 | n/a | |
---|
706 | n/a | maint.back("< Back", None, active=False) |
---|
707 | n/a | c=maint.next("Finish", "Cancel") |
---|
708 | n/a | # Change installation: Change progress dialog to "Change", then ask |
---|
709 | n/a | # for feature selection |
---|
710 | n/a | #c.event("[Progress1]", "Change", 'MaintenanceForm_Action="Change"', 1) |
---|
711 | n/a | #c.event("[Progress2]", "changes", 'MaintenanceForm_Action="Change"', 2) |
---|
712 | n/a | |
---|
713 | n/a | # Reinstall: Change progress dialog to "Repair", then invoke reinstall |
---|
714 | n/a | # Also set list of reinstalled features to "ALL" |
---|
715 | n/a | c.event("[REINSTALL]", "ALL", 'MaintenanceForm_Action="Repair"', 5) |
---|
716 | n/a | c.event("[Progress1]", "Repairing", 'MaintenanceForm_Action="Repair"', 6) |
---|
717 | n/a | c.event("[Progress2]", "repairs", 'MaintenanceForm_Action="Repair"', 7) |
---|
718 | n/a | c.event("Reinstall", "ALL", 'MaintenanceForm_Action="Repair"', 8) |
---|
719 | n/a | |
---|
720 | n/a | # Uninstall: Change progress to "Remove", then invoke uninstall |
---|
721 | n/a | # Also set list of removed features to "ALL" |
---|
722 | n/a | c.event("[REMOVE]", "ALL", 'MaintenanceForm_Action="Remove"', 11) |
---|
723 | n/a | c.event("[Progress1]", "Removing", 'MaintenanceForm_Action="Remove"', 12) |
---|
724 | n/a | c.event("[Progress2]", "removes", 'MaintenanceForm_Action="Remove"', 13) |
---|
725 | n/a | c.event("Remove", "ALL", 'MaintenanceForm_Action="Remove"', 14) |
---|
726 | n/a | |
---|
727 | n/a | # Close dialog when maintenance action scheduled |
---|
728 | n/a | c.event("EndDialog", "Return", 'MaintenanceForm_Action<>"Change"', 20) |
---|
729 | n/a | #c.event("NewDialog", "SelectFeaturesDlg", 'MaintenanceForm_Action="Change"', 21) |
---|
730 | n/a | |
---|
731 | n/a | maint.cancel("Cancel", "RepairRadioGroup").event("SpawnDialog", "CancelDlg") |
---|
732 | n/a | |
---|
733 | n/a | def get_installer_filename(self, fullname): |
---|
734 | n/a | # Factored out to allow overriding in subclasses |
---|
735 | n/a | if self.target_version: |
---|
736 | n/a | base_name = "%s.%s-py%s.msi" % (fullname, self.plat_name, |
---|
737 | n/a | self.target_version) |
---|
738 | n/a | else: |
---|
739 | n/a | base_name = "%s.%s.msi" % (fullname, self.plat_name) |
---|
740 | n/a | installer_name = os.path.join(self.dist_dir, base_name) |
---|
741 | n/a | return installer_name |
---|