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 = 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 743