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