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