source: python/trunk/Lib/distutils/command/bdist_msi.py

Last change on this file was 391, checked in by dmik, 11 years ago

python: Merge vendor 2.7.6 to trunk.

  • Property svn:eol-style set to native
File size: 34.4 KB
Line 
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"""
7Implements the bdist_msi command.
8"""
9import sys, os
10from sysconfig import get_python_version
11
12from distutils.core import Command
13from distutils.dir_util import remove_tree
14from distutils.version import StrictVersion
15from distutils.errors import DistutilsOptionError
16from distutils import log
17from distutils.util import get_platform
18
19import msilib
20from msilib import schema, sequence, text
21from msilib import Directory, Feature, Dialog, add_data
22
23class 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
84class 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
Note: See TracBrowser for help on using the repository browser.