1# Python MSI Generator 2# (C) 2003 Martin v. Loewis 3# See "FOO" in comments refers to MSDN sections with the title FOO. 4import msilib, schema, sequence, os, glob, time, re, shutil, zipfile 5from msilib import Feature, CAB, Directory, Dialog, Binary, add_data 6import uisample 7from win32com.client import constants 8from distutils.spawn import find_executable 9from uuids import product_codes 10import tempfile 11 12# Settings can be overridden in config.py below 13# 0 for official python.org releases 14# 1 for intermediate releases by anybody, with 15# a new product code for every package. 16snapshot = int(os.environ.get("SNAPSHOT", "1")) 17# 1 means that file extension is px, not py, 18# and binaries start with x 19testpackage = 0 20# Location of build tree 21srcdir = os.path.abspath("../..") 22# Text to be displayed as the version in dialogs etc. 23# goes into file name and ProductCode. Defaults to 24# current_version.day for Snapshot, current_version otherwise 25full_current_version = os.environ.get("CURRENT_VERSION") 26# Is Tcl available at all? 27have_tcl = True 28# path to PCbuild directory 29PCBUILD=os.environ.get("PCBUILD", "PCbuild") 30# msvcrt version 31MSVCR = "90" 32# Name of certificate in default store to sign MSI with 33certname = os.environ.get("CERTNAME", None) 34# Make a zip file containing the PDB files for this build? 35pdbzip = True 36 37try: 38 from config import * 39except ImportError: 40 pass 41 42# Extract current version from Include/patchlevel.h 43lines = open(srcdir + "/Include/patchlevel.h").readlines() 44major = minor = micro = level = serial = None 45levels = { 46 'PY_RELEASE_LEVEL_ALPHA':0xA, 47 'PY_RELEASE_LEVEL_BETA': 0xB, 48 'PY_RELEASE_LEVEL_GAMMA':0xC, 49 'PY_RELEASE_LEVEL_FINAL':0xF 50 } 51for l in lines: 52 if not l.startswith("#define"): 53 continue 54 l = l.split() 55 if len(l) != 3: 56 continue 57 _, name, value = l 58 if name == 'PY_MAJOR_VERSION': major = value 59 if name == 'PY_MINOR_VERSION': minor = value 60 if name == 'PY_MICRO_VERSION': micro = value 61 if name == 'PY_RELEASE_LEVEL': level = levels[value] 62 if name == 'PY_RELEASE_SERIAL': serial = value 63 64short_version = major+"."+minor 65# See PC/make_versioninfo.c 66FIELD3 = 1000*int(micro) + 10*level + int(serial) 67current_version = "%s.%d" % (short_version, FIELD3) 68 69# This should never change. The UpgradeCode of this package can be 70# used in the Upgrade table of future packages to make the future 71# package replace this one. See "UpgradeCode Property". 72# upgrade_code gets set to upgrade_code_64 when we have determined 73# that the target is Win64. 74upgrade_code_snapshot='{92A24481-3ECB-40FC-8836-04B7966EC0D5}' 75upgrade_code='{65E6DE48-A358-434D-AA4F-4AF72DB4718F}' 76upgrade_code_64='{6A965A0C-6EE6-4E3A-9983-3263F56311EC}' 77 78if snapshot: 79 current_version = "%s.%s.%s" % (major, minor, int(time.time()/3600/24)) 80 product_code = msilib.gen_uuid() 81else: 82 product_code = product_codes[current_version] 83 84if full_current_version is None: 85 full_current_version = current_version 86 87extensions = [ 88 'bz2.pyd', 89 'pyexpat.pyd', 90 'select.pyd', 91 'unicodedata.pyd', 92 'winsound.pyd', 93 '_elementtree.pyd', 94 '_bsddb.pyd', 95 '_socket.pyd', 96 '_ssl.pyd', 97 '_testcapi.pyd', 98 '_tkinter.pyd', 99 '_msi.pyd', 100 '_ctypes.pyd', 101 '_ctypes_test.pyd', 102 '_sqlite3.pyd', 103 '_hashlib.pyd', 104 '_multiprocessing.pyd' 105] 106 107# Well-known component UUIDs 108# These are needed for SharedDLLs reference counter; if 109# a different UUID was used for each incarnation of, say, 110# python24.dll, an upgrade would set the reference counter 111# from 1 to 2 (due to what I consider a bug in MSI) 112# Using the same UUID is fine since these files are versioned, 113# so Installer will always keep the newest version. 114# NOTE: All uuids are self generated. 115pythondll_uuid = { 116 "24":"{9B81E618-2301-4035-AC77-75D9ABEB7301}", 117 "25":"{2e41b118-38bd-4c1b-a840-6977efd1b911}", 118 "26":"{34ebecac-f046-4e1c-b0e3-9bac3cdaacfa}", 119 "27":"{4fe21c76-1760-437b-a2f2-99909130a175}", 120 } [major+minor] 121 122# Compute the name that Sphinx gives to the docfile 123docfile = "" 124if int(micro): 125 docfile = micro 126if level < 0xf: 127 if level == 0xC: 128 docfile += "rc%s" % (serial,) 129 else: 130 docfile += '%x%s' % (level, serial) 131docfile = 'python%s%s%s.chm' % (major, minor, docfile) 132 133# Build the mingw import library, libpythonXY.a 134# This requires 'nm' and 'dlltool' executables on your PATH 135def build_mingw_lib(dll_path, def_file, dll_file, mingw_lib): 136 warning = "WARNING: %s - libpythonXX.a not built" 137 gendef = find_executable('gendef') 138 dlltool = find_executable('dlltool') 139 140 if not gendef or not dlltool: 141 print warning % "gendef and/or dlltool were not found" 142 return False 143 144 gendef_command = '%s - %s' % (gendef, dll_path) 145 dlltool_command = "%s --dllname %s --def %s --output-lib %s" % \ 146 (dlltool, dll_file, def_file, mingw_lib) 147 if msilib.Win64: 148 dlltool_command += " -m i386:x86-64" 149 else: 150 dlltool_command += " -m i386 --as-flags=--32" 151 152 f = open(def_file,'w') 153 gendef_pipe = os.popen(gendef_command) 154 for line in gendef_pipe.readlines(): 155 print >>f, line 156 f.close() 157 exit = gendef_pipe.close() 158 159 if exit: 160 print warning % "gendef did not run successfully" 161 return False 162 163 if os.system(dlltool_command) != 0: 164 print warning % "dlltool did not run successfully" 165 return False 166 167 return True 168 169# Target files (.def and .a) go in PCBuild directory 170dll_path = os.path.join(srcdir, PCBUILD, "python%s%s.dll" % (major, minor)) 171def_file = os.path.join(srcdir, PCBUILD, "python%s%s.def" % (major, minor)) 172dll_file = "python%s%s.dll" % (major, minor) 173mingw_lib = os.path.join(srcdir, PCBUILD, "libpython%s%s.a" % (major, minor)) 174 175# Determine the target architecture 176if os.system("nmake /nologo /c /f msisupport.mak") != 0: 177 raise RuntimeError("'nmake /f msisupport.mak' failed") 178dll_path = os.path.join(srcdir, PCBUILD, dll_file) 179msilib.set_arch_from_file(dll_path) 180if msilib.pe_type(dll_path) != msilib.pe_type("msisupport.dll"): 181 raise SystemError, "msisupport.dll for incorrect architecture" 182 183if msilib.Win64: 184 upgrade_code = upgrade_code_64 185 # Bump the last digit of the code by one, so that 32-bit and 64-bit 186 # releases get separate product codes 187 digit = hex((int(product_code[-2],16)+1)%16)[-1] 188 product_code = product_code[:-2] + digit + '}' 189 190have_mingw = build_mingw_lib(dll_path, def_file, dll_file, mingw_lib) 191 192if testpackage: 193 ext = 'px' 194 testprefix = 'x' 195else: 196 ext = 'py' 197 testprefix = '' 198 199if msilib.Win64: 200 SystemFolderName = "[System64Folder]" 201 registry_component = 4|256 202else: 203 SystemFolderName = "[SystemFolder]" 204 registry_component = 4 205 206msilib.reset() 207 208# condition in which to install pythonxy.dll in system32: 209# a) it is Windows 9x or 210# b) it is NT, the user is privileged, and has chosen per-machine installation 211sys32cond = "(Windows9x or (Privileged and ALLUSERS))" 212 213def build_database(): 214 """Generate an empty database, with just the schema and the 215 Summary information stream.""" 216 if snapshot: 217 uc = upgrade_code_snapshot 218 else: 219 uc = upgrade_code 220 if msilib.Win64: 221 productsuffix = " (64-bit)" 222 else: 223 productsuffix = "" 224 # schema represents the installer 2.0 database schema. 225 # sequence is the set of standard sequences 226 # (ui/execute, admin/advt/install) 227 msiname = "python-%s%s.msi" % (full_current_version, msilib.arch_ext) 228 db = msilib.init_database(msiname, 229 schema, ProductName="Python "+full_current_version+productsuffix, 230 ProductCode=product_code, 231 ProductVersion=current_version, 232 Manufacturer=u"Python Software Foundation", 233 request_uac = True) 234 # The default sequencing of the RemoveExistingProducts action causes 235 # removal of files that got just installed. Place it after 236 # InstallInitialize, so we first uninstall everything, but still roll 237 # back in case the installation is interrupted 238 msilib.change_sequence(sequence.InstallExecuteSequence, 239 "RemoveExistingProducts", 1510) 240 msilib.add_tables(db, sequence) 241 # We cannot set ALLUSERS in the property table, as this cannot be 242 # reset if the user choses a per-user installation. Instead, we 243 # maintain WhichUsers, which can be "ALL" or "JUSTME". The UI manages 244 # this property, and when the execution starts, ALLUSERS is set 245 # accordingly. 246 add_data(db, "Property", [("UpgradeCode", uc), 247 ("WhichUsers", "ALL"), 248 ("ProductLine", "Python%s%s" % (major, minor)), 249 ]) 250 db.Commit() 251 return db, msiname 252 253def remove_old_versions(db): 254 "Fill the upgrade table." 255 start = "%s.%s.0" % (major, minor) 256 # This requests that feature selection states of an older 257 # installation should be forwarded into this one. Upgrading 258 # requires that both the old and the new installation are 259 # either both per-machine or per-user. 260 migrate_features = 1 261 # See "Upgrade Table". We remove releases with the same major and 262 # minor version. For a snapshot, we remove all earlier snapshots. For 263 # a release, we remove all snapshots, and all earlier releases. 264 if snapshot: 265 add_data(db, "Upgrade", 266 [(upgrade_code_snapshot, start, 267 current_version, 268 None, # Ignore language 269 migrate_features, 270 None, # Migrate ALL features 271 "REMOVEOLDSNAPSHOT")]) 272 props = "REMOVEOLDSNAPSHOT" 273 else: 274 add_data(db, "Upgrade", 275 [(upgrade_code, start, current_version, 276 None, migrate_features, None, "REMOVEOLDVERSION"), 277 (upgrade_code_snapshot, start, "%s.%d.0" % (major, int(minor)+1), 278 None, migrate_features, None, "REMOVEOLDSNAPSHOT")]) 279 props = "REMOVEOLDSNAPSHOT;REMOVEOLDVERSION" 280 281 props += ";TARGETDIR;DLLDIR" 282 # Installer collects the product codes of the earlier releases in 283 # these properties. In order to allow modification of the properties, 284 # they must be declared as secure. See "SecureCustomProperties Property" 285 add_data(db, "Property", [("SecureCustomProperties", props)]) 286 287class PyDialog(Dialog): 288 """Dialog class with a fixed layout: controls at the top, then a ruler, 289 then a list of buttons: back, next, cancel. Optionally a bitmap at the 290 left.""" 291 def __init__(self, *args, **kw): 292 """Dialog(database, name, x, y, w, h, attributes, title, first, 293 default, cancel, bitmap=true)""" 294 Dialog.__init__(self, *args) 295 ruler = self.h - 36 296 bmwidth = 152*ruler/328 297 if kw.get("bitmap", True): 298 self.bitmap("Bitmap", 0, 0, bmwidth, ruler, "PythonWin") 299 self.line("BottomLine", 0, ruler, self.w, 0) 300 301 def title(self, title): 302 "Set the title text of the dialog at the top." 303 # name, x, y, w, h, flags=Visible|Enabled|Transparent|NoPrefix, 304 # text, in VerdanaBold10 305 self.text("Title", 135, 10, 220, 60, 0x30003, 306 r"{\VerdanaBold10}%s" % title) 307 308 def back(self, title, next, name = "Back", active = 1): 309 """Add a back button with a given title, the tab-next button, 310 its name in the Control table, possibly initially disabled. 311 312 Return the button, so that events can be associated""" 313 if active: 314 flags = 3 # Visible|Enabled 315 else: 316 flags = 1 # Visible 317 return self.pushbutton(name, 180, self.h-27 , 56, 17, flags, title, next) 318 319 def cancel(self, title, next, name = "Cancel", active = 1): 320 """Add a cancel button with a given title, the tab-next button, 321 its name in the Control table, possibly initially disabled. 322 323 Return the button, so that events can be associated""" 324 if active: 325 flags = 3 # Visible|Enabled 326 else: 327 flags = 1 # Visible 328 return self.pushbutton(name, 304, self.h-27, 56, 17, flags, title, next) 329 330 def next(self, title, next, name = "Next", active = 1): 331 """Add a Next button with a given title, the tab-next button, 332 its name in the Control table, possibly initially disabled. 333 334 Return the button, so that events can be associated""" 335 if active: 336 flags = 3 # Visible|Enabled 337 else: 338 flags = 1 # Visible 339 return self.pushbutton(name, 236, self.h-27, 56, 17, flags, title, next) 340 341 def xbutton(self, name, title, next, xpos): 342 """Add a button with a given title, the tab-next button, 343 its name in the Control table, giving its x position; the 344 y-position is aligned with the other buttons. 345 346 Return the button, so that events can be associated""" 347 return self.pushbutton(name, int(self.w*xpos - 28), self.h-27, 56, 17, 3, title, next) 348 349def add_ui(db): 350 x = y = 50 351 w = 370 352 h = 300 353 title = "[ProductName] Setup" 354 355 # see "Dialog Style Bits" 356 modal = 3 # visible | modal 357 modeless = 1 # visible 358 track_disk_space = 32 359 360 add_data(db, 'ActionText', uisample.ActionText) 361 add_data(db, 'UIText', uisample.UIText) 362 363 # Bitmaps 364 if not os.path.exists(srcdir+r"\PC\python_icon.exe"): 365 raise "Run icons.mak in PC directory" 366 add_data(db, "Binary", 367 [("PythonWin", msilib.Binary(r"%s\PCbuild\installer.bmp" % srcdir)), # 152x328 pixels 368 ("py.ico",msilib.Binary(srcdir+r"\PC\py.ico")), 369 ]) 370 add_data(db, "Icon", 371 [("python_icon.exe", msilib.Binary(srcdir+r"\PC\python_icon.exe"))]) 372 373 # Scripts 374 # CheckDir sets TargetExists if TARGETDIR exists. 375 # UpdateEditIDLE sets the REGISTRY.tcl component into 376 # the installed/uninstalled state according to both the 377 # Extensions and TclTk features. 378 add_data(db, "Binary", [("Script", msilib.Binary("msisupport.dll"))]) 379 add_data(db, "Binary", [("WixCA", msilib.Binary("WixCA.blob"))]) 380 # See "Custom Action Type 1" 381 if msilib.Win64: 382 CheckDir = "CheckDir" 383 UpdateEditIDLE = "UpdateEditIDLE" 384 else: 385 CheckDir = "_CheckDir@4" 386 UpdateEditIDLE = "_UpdateEditIDLE@4" 387 add_data(db, "CustomAction", 388 [("CheckDir", 1, "Script", CheckDir)]) 389 if have_tcl: 390 add_data(db, "CustomAction", 391 [("UpdateEditIDLE", 1, "Script", UpdateEditIDLE)]) 392 393 # UI customization properties 394 add_data(db, "Property", 395 # See "DefaultUIFont Property" 396 [("DefaultUIFont", "DlgFont8"), 397 # See "ErrorDialog Style Bit" 398 ("ErrorDialog", "ErrorDlg"), 399 ("Progress1", "Install"), # modified in maintenance type dlg 400 ("Progress2", "installs"), 401 ("MaintenanceForm_Action", "Repair")]) 402 403 # Fonts, see "TextStyle Table" 404 add_data(db, "TextStyle", 405 [("DlgFont8", "Tahoma", 9, None, 0), 406 ("DlgFontBold8", "Tahoma", 8, None, 1), #bold 407 ("VerdanaBold10", "Verdana", 10, None, 1), 408 ("VerdanaRed9", "Verdana", 9, 255, 0), 409 ]) 410 411 compileargs = r'"[#python.exe]" -Wi "[TARGETDIR]Lib\compileall.py" -f -x "bad_coding|badsyntax|site-packages|py3_" "[TARGETDIR]Lib"' 412 compileoargs = r'"[#python.exe]" -O -Wi "[TARGETDIR]Lib\compileall.py" -f -x "bad_coding|badsyntax|site-packages|py3_" "[TARGETDIR]Lib"' 413 lib2to3args = r'"[#python.exe]" -c "import lib2to3.pygram, lib2to3.patcomp;lib2to3.patcomp.PatternCompiler()"' 414 updatepipargs = r'"[#python.exe]" -m ensurepip -U --default-pip' 415 removepipargs = r'"[#python.exe]" -B -m ensurepip._uninstall' 416 # See "CustomAction Table" 417 add_data(db, "CustomAction", [ 418 # msidbCustomActionTypeFirstSequence + msidbCustomActionTypeTextData + msidbCustomActionTypeProperty 419 # See "Custom Action Type 51", 420 # "Custom Action Execution Scheduling Options" 421 ("InitialTargetDir", 307, "TARGETDIR", 422 "[WindowsVolume]Python%s%s" % (major, minor)), 423 ("SetDLLDirToTarget", 307, "DLLDIR", "[TARGETDIR]"), 424 ("SetDLLDirToSystem32", 307, "DLLDIR", SystemFolderName), 425 # msidbCustomActionTypeExe + msidbCustomActionTypeSourceFile 426 # See "Custom Action Type 18" 427 # msidbCustomActionTypeInScript (1024); run during actual installation 428 # msidbCustomActionTypeNoImpersonate (2048); run action in system account, not user account 429 ("SetCompilePycCommandLine", 51, "CompilePyc", compileargs), 430 ("SetCompilePyoCommandLine", 51, "CompilePyo", compileoargs), 431 ("SetCompileGrammarCommandLine", 51, "CompileGrammar", lib2to3args), 432 ("CompilePyc", 1+64+1024, "WixCA", "CAQuietExec"), 433 ("CompilePyo", 1+64+1024, "WixCA", "CAQuietExec"), 434 ("CompileGrammar", 1+64+1024, "WixCA", "CAQuietExec"), 435 ("SetUpdatePipCommandLine", 51, "UpdatePip", updatepipargs), 436 ("UpdatePip", 1+64+1024, "WixCA", "CAQuietExec"), 437 ("SetRemovePipCommandLine", 51, "RemovePip", removepipargs), 438 ("RemovePip", 1+64+1024, "WixCA", "CAQuietExec"), 439 ]) 440 441 # UI Sequences, see "InstallUISequence Table", "Using a Sequence Table" 442 # Numbers indicate sequence; see sequence.py for how these action integrate 443 add_data(db, "InstallUISequence", 444 [("PrepareDlg", "Not Privileged or Windows9x or Installed", 140), 445 ("WhichUsersDlg", "Privileged and not Windows9x and not Installed", 141), 446 ("InitialTargetDir", 'TARGETDIR=""', 750), 447 # In the user interface, assume all-users installation if privileged. 448 ("SetDLLDirToSystem32", 'DLLDIR="" and ' + sys32cond, 751), 449 ("SetDLLDirToTarget", 'DLLDIR="" and not ' + sys32cond, 752), 450 ("SelectDirectoryDlg", "Not Installed", 1230), 451 # XXX no support for resume installations yet 452 #("ResumeDlg", "Installed AND (RESUME OR Preselected)", 1240), 453 ("MaintenanceTypeDlg", "Installed AND NOT RESUME AND NOT Preselected", 1250), 454 ("ProgressDlg", None, 1280)]) 455 add_data(db, "AdminUISequence", 456 [("InitialTargetDir", 'TARGETDIR=""', 750), 457 ("SetDLLDirToTarget", 'DLLDIR=""', 751), 458 ]) 459 460 # Prepend TARGETDIR to the system path, and remove it on uninstall. 461 add_data(db, "Environment", 462 [("PathAddition", "=-*Path", "[TARGETDIR];[TARGETDIR]Scripts;[~]", "REGISTRY.path")]) 463 464 # Execute Sequences 465 add_data(db, "InstallExecuteSequence", 466 [("InitialTargetDir", 'TARGETDIR=""', 750), 467 ("SetDLLDirToSystem32", 'DLLDIR="" and ' + sys32cond, 751), 468 ("SetDLLDirToTarget", 'DLLDIR="" and not ' + sys32cond, 752), 469 ("UpdateEditIDLE", None, 1050), 470 # remove pip when state changes to INSTALLSTATE_ABSENT 471 # run before RemoveFiles 472 ("SetRemovePipCommandLine", "&pip_feature=2 and !pip_feature=3", 3498), 473 ("RemovePip", "RemovePip", 3499), 474 # run command if install state of pip changes to INSTALLSTATE_LOCAL 475 # run after InstallFiles 476 ("SetUpdatePipCommandLine", "&pip_feature=3 and not !pip_feature=3", 4001), 477 ("UpdatePip", "UpdatePip", 4002), 478 ("SetCompilePycCommandLine", "COMPILEALL", 4003), 479 ("SetCompilePyoCommandLine", "COMPILEALL", 4004), 480 ("SetCompileGrammarCommandLine", "COMPILEALL", 4005), 481 ("CompilePyc", "CompilePyc", 4006), 482 ("CompilePyo", "CompilePyo", 4007), 483 ("CompileGrammar", "CompileGrammar", 4008), 484 ]) 485 add_data(db, "AdminExecuteSequence", 486 [("InitialTargetDir", 'TARGETDIR=""', 750), 487 ("SetDLLDirToTarget", 'DLLDIR=""', 751), 488 ]) 489 490 ##################################################################### 491 # Standard dialogs: FatalError, UserExit, ExitDialog 492 fatal=PyDialog(db, "FatalError", x, y, w, h, modal, title, 493 "Finish", "Finish", "Finish") 494 fatal.title("[ProductName] Installer ended prematurely") 495 fatal.back("< Back", "Finish", active = 0) 496 fatal.cancel("Cancel", "Back", active = 0) 497 fatal.text("Description1", 135, 70, 220, 80, 0x30003, 498 "[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.") 499 fatal.text("Description2", 135, 155, 220, 20, 0x30003, 500 "Click the Finish button to exit the Installer.") 501 c=fatal.next("Finish", "Cancel", name="Finish") 502 # See "ControlEvent Table". Parameters are the event, the parameter 503 # to the action, and optionally the condition for the event, and the order 504 # of events. 505 c.event("EndDialog", "Exit") 506 507 user_exit=PyDialog(db, "UserExit", x, y, w, h, modal, title, 508 "Finish", "Finish", "Finish") 509 user_exit.title("[ProductName] Installer was interrupted") 510 user_exit.back("< Back", "Finish", active = 0) 511 user_exit.cancel("Cancel", "Back", active = 0) 512 user_exit.text("Description1", 135, 70, 220, 80, 0x30003, 513 "[ProductName] setup was interrupted. Your system has not been modified. " 514 "To install this program at a later time, please run the installation again.") 515 user_exit.text("Description2", 135, 155, 220, 20, 0x30003, 516 "Click the Finish button to exit the Installer.") 517 c = user_exit.next("Finish", "Cancel", name="Finish") 518 c.event("EndDialog", "Exit") 519 520 exit_dialog = PyDialog(db, "ExitDialog", x, y, w, h, modal, title, 521 "Finish", "Finish", "Finish") 522 exit_dialog.title("Complete the [ProductName] Installer") 523 exit_dialog.back("< Back", "Finish", active = 0) 524 exit_dialog.cancel("Cancel", "Back", active = 0) 525 exit_dialog.text("Acknowledgements", 135, 95, 220, 120, 0x30003, 526 "Special Windows thanks to:\n" 527 " Mark Hammond, without whose years of freely \n" 528 " shared Windows expertise, Python for Windows \n" 529 " would still be Python for DOS.") 530 531 c = exit_dialog.text("warning", 135, 200, 220, 40, 0x30003, 532 "{\\VerdanaRed9}Warning: Python 3.3.0 is the last " 533 "Python release for Windows 2000.") 534 c.condition("Hide", "VersionNT > 500") 535 536 exit_dialog.text("Description", 135, 235, 220, 20, 0x30003, 537 "Click the Finish button to exit the Installer.") 538 c = exit_dialog.next("Finish", "Cancel", name="Finish") 539 c.event("EndDialog", "Return") 540 541 ##################################################################### 542 # Required dialog: FilesInUse, ErrorDlg 543 inuse = PyDialog(db, "FilesInUse", 544 x, y, w, h, 545 19, # KeepModeless|Modal|Visible 546 title, 547 "Retry", "Retry", "Retry", bitmap=False) 548 inuse.text("Title", 15, 6, 200, 15, 0x30003, 549 r"{\DlgFontBold8}Files in Use") 550 inuse.text("Description", 20, 23, 280, 20, 0x30003, 551 "Some files that need to be updated are currently in use.") 552 inuse.text("Text", 20, 55, 330, 50, 3, 553 "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.") 554 inuse.control("List", "ListBox", 20, 107, 330, 130, 7, "FileInUseProcess", 555 None, None, None) 556 c=inuse.back("Exit", "Ignore", name="Exit") 557 c.event("EndDialog", "Exit") 558 c=inuse.next("Ignore", "Retry", name="Ignore") 559 c.event("EndDialog", "Ignore") 560 c=inuse.cancel("Retry", "Exit", name="Retry") 561 c.event("EndDialog","Retry") 562 563 564 # See "Error Dialog". See "ICE20" for the required names of the controls. 565 error = Dialog(db, "ErrorDlg", 566 50, 10, 330, 101, 567 65543, # Error|Minimize|Modal|Visible 568 title, 569 "ErrorText", None, None) 570 error.text("ErrorText", 50,9,280,48,3, "") 571 error.control("ErrorIcon", "Icon", 15, 9, 24, 24, 5242881, None, "py.ico", None, None) 572 error.pushbutton("N",120,72,81,21,3,"No",None).event("EndDialog","ErrorNo") 573 error.pushbutton("Y",240,72,81,21,3,"Yes",None).event("EndDialog","ErrorYes") 574 error.pushbutton("A",0,72,81,21,3,"Abort",None).event("EndDialog","ErrorAbort") 575 error.pushbutton("C",42,72,81,21,3,"Cancel",None).event("EndDialog","ErrorCancel") 576 error.pushbutton("I",81,72,81,21,3,"Ignore",None).event("EndDialog","ErrorIgnore") 577 error.pushbutton("O",159,72,81,21,3,"Ok",None).event("EndDialog","ErrorOk") 578 error.pushbutton("R",198,72,81,21,3,"Retry",None).event("EndDialog","ErrorRetry") 579 580 ##################################################################### 581 # Global "Query Cancel" dialog 582 cancel = Dialog(db, "CancelDlg", 50, 10, 260, 85, 3, title, 583 "No", "No", "No") 584 cancel.text("Text", 48, 15, 194, 30, 3, 585 "Are you sure you want to cancel [ProductName] installation?") 586 cancel.control("Icon", "Icon", 15, 15, 24, 24, 5242881, None, 587 "py.ico", None, None) 588 c=cancel.pushbutton("Yes", 72, 57, 56, 17, 3, "Yes", "No") 589 c.event("EndDialog", "Exit") 590 591 c=cancel.pushbutton("No", 132, 57, 56, 17, 3, "No", "Yes") 592 c.event("EndDialog", "Return") 593 594 ##################################################################### 595 # Global "Wait for costing" dialog 596 costing = Dialog(db, "WaitForCostingDlg", 50, 10, 260, 85, modal, title, 597 "Return", "Return", "Return") 598 costing.text("Text", 48, 15, 194, 30, 3, 599 "Please wait while the installer finishes determining your disk space requirements.") 600 costing.control("Icon", "Icon", 15, 15, 24, 24, 5242881, None, 601 "py.ico", None, None) 602 c = costing.pushbutton("Return", 102, 57, 56, 17, 3, "Return", None) 603 c.event("EndDialog", "Exit") 604 605 ##################################################################### 606 # Preparation dialog: no user input except cancellation 607 prep = PyDialog(db, "PrepareDlg", x, y, w, h, modeless, title, 608 "Cancel", "Cancel", "Cancel") 609 prep.text("Description", 135, 70, 220, 40, 0x30003, 610 "Please wait while the Installer prepares to guide you through the installation.") 611 prep.title("Welcome to the [ProductName] Installer") 612 c=prep.text("ActionText", 135, 110, 220, 20, 0x30003, "Pondering...") 613 c.mapping("ActionText", "Text") 614 c=prep.text("ActionData", 135, 135, 220, 30, 0x30003, None) 615 c.mapping("ActionData", "Text") 616 prep.back("Back", None, active=0) 617 prep.next("Next", None, active=0) 618 c=prep.cancel("Cancel", None) 619 c.event("SpawnDialog", "CancelDlg") 620 621 ##################################################################### 622 # Target directory selection 623 seldlg = PyDialog(db, "SelectDirectoryDlg", x, y, w, h, modal, title, 624 "Next", "Next", "Cancel") 625 seldlg.title("Select Destination Directory") 626 c = seldlg.text("Existing", 135, 25, 235, 30, 0x30003, 627 "{\VerdanaRed9}This update will replace your existing [ProductLine] installation.") 628 c.condition("Hide", 'REMOVEOLDVERSION="" and REMOVEOLDSNAPSHOT=""') 629 seldlg.text("Description", 135, 50, 220, 40, 0x30003, 630 "Please select a directory for the [ProductName] files.") 631 632 seldlg.back("< Back", None, active=0) 633 c = seldlg.next("Next >", "Cancel") 634 c.event("DoAction", "CheckDir", "TargetExistsOk<>1", order=1) 635 # If the target exists, but we found that we are going to remove old versions, don't bother 636 # confirming that the target directory exists. Strictly speaking, we should determine that 637 # the target directory is indeed the target of the product that we are going to remove, but 638 # I don't know how to do that. 639 c.event("SpawnDialog", "ExistingDirectoryDlg", 'TargetExists=1 and REMOVEOLDVERSION="" and REMOVEOLDSNAPSHOT=""', 2) 640 c.event("SetTargetPath", "TARGETDIR", 'TargetExists=0 or REMOVEOLDVERSION<>"" or REMOVEOLDSNAPSHOT<>""', 3) 641 c.event("SpawnWaitDialog", "WaitForCostingDlg", "CostingComplete=1", 4) 642 c.event("NewDialog", "SelectFeaturesDlg", 'TargetExists=0 or REMOVEOLDVERSION<>"" or REMOVEOLDSNAPSHOT<>""', 5) 643 644 c = seldlg.cancel("Cancel", "DirectoryCombo") 645 c.event("SpawnDialog", "CancelDlg") 646 647 seldlg.control("DirectoryCombo", "DirectoryCombo", 135, 70, 172, 80, 393219, 648 "TARGETDIR", None, "DirectoryList", None) 649 seldlg.control("DirectoryList", "DirectoryList", 135, 90, 208, 136, 3, "TARGETDIR", 650 None, "PathEdit", None) 651 seldlg.control("PathEdit", "PathEdit", 135, 230, 206, 16, 3, "TARGETDIR", None, "Next", None) 652 c = seldlg.pushbutton("Up", 306, 70, 18, 18, 3, "Up", None) 653 c.event("DirectoryListUp", "0") 654 c = seldlg.pushbutton("NewDir", 324, 70, 30, 18, 3, "New", None) 655 c.event("DirectoryListNew", "0") 656 657 ##################################################################### 658 # SelectFeaturesDlg 659 features = PyDialog(db, "SelectFeaturesDlg", x, y, w, h, modal|track_disk_space, 660 title, "Tree", "Next", "Cancel") 661 features.title("Customize [ProductName]") 662 features.text("Description", 135, 35, 220, 15, 0x30003, 663 "Select the way you want features to be installed.") 664 features.text("Text", 135,45,220,30, 3, 665 "Click on the icons in the tree below to change the way features will be installed.") 666 667 c=features.back("< Back", "Next") 668 c.event("NewDialog", "SelectDirectoryDlg") 669 670 c=features.next("Next >", "Cancel") 671 c.mapping("SelectionNoItems", "Enabled") 672 c.event("SpawnDialog", "DiskCostDlg", "OutOfDiskSpace=1", order=1) 673 c.event("EndDialog", "Return", "OutOfDiskSpace<>1", order=2) 674 675 c=features.cancel("Cancel", "Tree") 676 c.event("SpawnDialog", "CancelDlg") 677 678 # The browse property is not used, since we have only a single target path (selected already) 679 features.control("Tree", "SelectionTree", 135, 75, 220, 95, 7, "_BrowseProperty", 680 "Tree of selections", "Back", None) 681 682 #c=features.pushbutton("Reset", 42, 243, 56, 17, 3, "Reset", "DiskCost") 683 #c.mapping("SelectionNoItems", "Enabled") 684 #c.event("Reset", "0") 685 686 features.control("Box", "GroupBox", 135, 170, 225, 90, 1, None, None, None, None) 687 688 c=features.xbutton("DiskCost", "Disk &Usage", None, 0.10) 689 c.mapping("SelectionNoItems","Enabled") 690 c.event("SpawnDialog", "DiskCostDlg") 691 692 c=features.xbutton("Advanced", "Advanced", None, 0.30) 693 c.event("SpawnDialog", "AdvancedDlg") 694 695 c=features.text("ItemDescription", 140, 180, 210, 40, 3, 696 "Multiline description of the currently selected item.") 697 c.mapping("SelectionDescription","Text") 698 699 c=features.text("ItemSize", 140, 225, 210, 33, 3, 700 "The size of the currently selected item.") 701 c.mapping("SelectionSize", "Text") 702 703 ##################################################################### 704 # Disk cost 705 cost = PyDialog(db, "DiskCostDlg", x, y, w, h, modal, title, 706 "OK", "OK", "OK", bitmap=False) 707 cost.text("Title", 15, 6, 200, 15, 0x30003, 708 "{\DlgFontBold8}Disk Space Requirements") 709 cost.text("Description", 20, 20, 280, 20, 0x30003, 710 "The disk space required for the installation of the selected features.") 711 cost.text("Text", 20, 53, 330, 60, 3, 712 "The highlighted volumes (if any) do not have enough disk space " 713 "available for the currently selected features. You can either " 714 "remove some files from the highlighted volumes, or choose to " 715 "install less features onto local drive(s), or select different " 716 "destination drive(s).") 717 cost.control("VolumeList", "VolumeCostList", 20, 100, 330, 150, 393223, 718 None, "{120}{70}{70}{70}{70}", None, None) 719 cost.xbutton("OK", "Ok", None, 0.5).event("EndDialog", "Return") 720 721 ##################################################################### 722 # WhichUsers Dialog. Only available on NT, and for privileged users. 723 # This must be run before FindRelatedProducts, because that will 724 # take into account whether the previous installation was per-user 725 # or per-machine. We currently don't support going back to this 726 # dialog after "Next" was selected; to support this, we would need to 727 # find how to reset the ALLUSERS property, and how to re-run 728 # FindRelatedProducts. 729 # On Windows9x, the ALLUSERS property is ignored on the command line 730 # and in the Property table, but installer fails according to the documentation 731 # if a dialog attempts to set ALLUSERS. 732 whichusers = PyDialog(db, "WhichUsersDlg", x, y, w, h, modal, title, 733 "AdminInstall", "Next", "Cancel") 734 whichusers.title("Select whether to install [ProductName] for all users of this computer.") 735 # A radio group with two options: allusers, justme 736 g = whichusers.radiogroup("AdminInstall", 135, 60, 235, 80, 3, 737 "WhichUsers", "", "Next") 738 g.condition("Disable", "VersionNT=600") # Not available on Vista and Windows 2008 739 g.add("ALL", 0, 5, 150, 20, "Install for all users") 740 g.add("JUSTME", 0, 25, 235, 20, "Install just for me (not available on Windows Vista)") 741 742 whichusers.back("Back", None, active=0) 743 744 c = whichusers.next("Next >", "Cancel") 745 c.event("[ALLUSERS]", "1", 'WhichUsers="ALL"', 1) 746 c.event("EndDialog", "Return", order = 2) 747 748 c = whichusers.cancel("Cancel", "AdminInstall") 749 c.event("SpawnDialog", "CancelDlg") 750 751 ##################################################################### 752 # Advanced Dialog. 753 advanced = PyDialog(db, "AdvancedDlg", x, y, w, h, modal, title, 754 "CompilePyc", "Ok", "Ok") 755 advanced.title("Advanced Options for [ProductName]") 756 # A radio group with two options: allusers, justme 757 advanced.checkbox("CompilePyc", 135, 60, 230, 50, 3, 758 "COMPILEALL", "Compile .py files to byte code after installation", "Ok") 759 760 c = advanced.cancel("Ok", "CompilePyc", name="Ok") # Button just has location of cancel button. 761 c.event("EndDialog", "Return") 762 763 ##################################################################### 764 # Existing Directory dialog 765 dlg = Dialog(db, "ExistingDirectoryDlg", 50, 30, 200, 80, modal, title, 766 "No", "No", "No") 767 dlg.text("Title", 10, 20, 180, 40, 3, 768 "[TARGETDIR] exists. Are you sure you want to overwrite existing files?") 769 c=dlg.pushbutton("Yes", 30, 60, 55, 17, 3, "Yes", "No") 770 c.event("[TargetExists]", "0", order=1) 771 c.event("[TargetExistsOk]", "1", order=2) 772 c.event("EndDialog", "Return", order=3) 773 c=dlg.pushbutton("No", 115, 60, 55, 17, 3, "No", "Yes") 774 c.event("EndDialog", "Return") 775 776 ##################################################################### 777 # Installation Progress dialog (modeless) 778 progress = PyDialog(db, "ProgressDlg", x, y, w, h, modeless, title, 779 "Cancel", "Cancel", "Cancel", bitmap=False) 780 progress.text("Title", 20, 15, 200, 15, 0x30003, 781 "{\DlgFontBold8}[Progress1] [ProductName]") 782 progress.text("Text", 35, 65, 300, 30, 3, 783 "Please wait while the Installer [Progress2] [ProductName]. " 784 "This may take several minutes.") 785 progress.text("StatusLabel", 35, 100, 35, 20, 3, "Status:") 786 787 c=progress.text("ActionText", 70, 100, w-70, 20, 3, "Pondering...") 788 c.mapping("ActionText", "Text") 789 790 #c=progress.text("ActionData", 35, 140, 300, 20, 3, None) 791 #c.mapping("ActionData", "Text") 792 793 c=progress.control("ProgressBar", "ProgressBar", 35, 120, 300, 10, 65537, 794 None, "Progress done", None, None) 795 c.mapping("SetProgress", "Progress") 796 797 progress.back("< Back", "Next", active=False) 798 progress.next("Next >", "Cancel", active=False) 799 progress.cancel("Cancel", "Back").event("SpawnDialog", "CancelDlg") 800 801 # Maintenance type: repair/uninstall 802 maint = PyDialog(db, "MaintenanceTypeDlg", x, y, w, h, modal, title, 803 "Next", "Next", "Cancel") 804 maint.title("Welcome to the [ProductName] Setup Wizard") 805 maint.text("BodyText", 135, 63, 230, 42, 3, 806 "Select whether you want to repair or remove [ProductName].") 807 g=maint.radiogroup("RepairRadioGroup", 135, 108, 230, 60, 3, 808 "MaintenanceForm_Action", "", "Next") 809 g.add("Change", 0, 0, 200, 17, "&Change [ProductName]") 810 g.add("Repair", 0, 18, 200, 17, "&Repair [ProductName]") 811 g.add("Remove", 0, 36, 200, 17, "Re&move [ProductName]") 812 813 maint.back("< Back", None, active=False) 814 c=maint.next("Finish", "Cancel") 815 # Change installation: Change progress dialog to "Change", then ask 816 # for feature selection 817 c.event("[Progress1]", "Change", 'MaintenanceForm_Action="Change"', 1) 818 c.event("[Progress2]", "changes", 'MaintenanceForm_Action="Change"', 2) 819 820 # Reinstall: Change progress dialog to "Repair", then invoke reinstall 821 # Also set list of reinstalled features to "ALL" 822 c.event("[REINSTALL]", "ALL", 'MaintenanceForm_Action="Repair"', 5) 823 c.event("[Progress1]", "Repairing", 'MaintenanceForm_Action="Repair"', 6) 824 c.event("[Progress2]", "repairs", 'MaintenanceForm_Action="Repair"', 7) 825 c.event("Reinstall", "ALL", 'MaintenanceForm_Action="Repair"', 8) 826 827 # Uninstall: Change progress to "Remove", then invoke uninstall 828 # Also set list of removed features to "ALL" 829 c.event("[REMOVE]", "ALL", 'MaintenanceForm_Action="Remove"', 11) 830 c.event("[Progress1]", "Removing", 'MaintenanceForm_Action="Remove"', 12) 831 c.event("[Progress2]", "removes", 'MaintenanceForm_Action="Remove"', 13) 832 c.event("Remove", "ALL", 'MaintenanceForm_Action="Remove"', 14) 833 834 # Close dialog when maintenance action scheduled 835 c.event("EndDialog", "Return", 'MaintenanceForm_Action<>"Change"', 20) 836 c.event("NewDialog", "SelectFeaturesDlg", 'MaintenanceForm_Action="Change"', 21) 837 838 maint.cancel("Cancel", "RepairRadioGroup").event("SpawnDialog", "CancelDlg") 839 840 841# See "Feature Table". The feature level is 1 for all features, 842# and the feature attributes are 0 for the DefaultFeature, and 843# FollowParent for all other features. The numbers are the Display 844# column. 845def add_features(db): 846 # feature attributes: 847 # msidbFeatureAttributesFollowParent == 2 848 # msidbFeatureAttributesDisallowAdvertise == 8 849 # Features that need to be installed with together with the main feature 850 # (i.e. additional Python libraries) need to follow the parent feature. 851 # Features that have no advertisement trigger (e.g. the test suite) 852 # must not support advertisement 853 global default_feature, tcltk, htmlfiles, tools, testsuite 854 global ext_feature, private_crt, prepend_path, pip_feature 855 default_feature = Feature(db, "DefaultFeature", "Python", 856 "Python Interpreter and Libraries", 857 1, directory = "TARGETDIR") 858 shared_crt = Feature(db, "SharedCRT", "MSVCRT", "C Run-Time (system-wide)", 0, 859 level=0) 860 private_crt = Feature(db, "PrivateCRT", "MSVCRT", "C Run-Time (private)", 0, 861 level=0) 862 add_data(db, "Condition", [("SharedCRT", 1, sys32cond), 863 ("PrivateCRT", 1, "not "+sys32cond)]) 864 # We don't support advertisement of extensions 865 ext_feature = Feature(db, "Extensions", "Register Extensions", 866 "Make this Python installation the default Python installation", 3, 867 parent = default_feature, attributes=2|8) 868 if have_tcl: 869 tcltk = Feature(db, "TclTk", "Tcl/Tk", "Tkinter, IDLE, pydoc", 5, 870 parent = default_feature, attributes=2) 871 htmlfiles = Feature(db, "Documentation", "Documentation", 872 "Python HTMLHelp File", 7, parent = default_feature) 873 tools = Feature(db, "Tools", "Utility Scripts", 874 "Python utility scripts (Tools/", 9, 875 parent = default_feature, attributes=2) 876 pip_feature = Feature(db, "pip_feature", "pip", 877 "Install or upgrade pip, a tool for installing and managing " 878 "Python packages.", 11, 879 parent = default_feature, attributes=2|8) 880 testsuite = Feature(db, "Testsuite", "Test suite", 881 "Python test suite (Lib/test/)", 13, 882 parent = default_feature, attributes=2|8) 883 # prepend_path is an additional feature which is to be off by default. 884 # Since the default level for the above features is 1, this needs to be 885 # at least level higher. 886 prepend_path = Feature(db, "PrependPath", "Add python.exe to Path", 887 "Prepend [TARGETDIR] to the system Path variable. " 888 "This allows you to type 'python' into a command " 889 "prompt without needing the full path.", 15, 890 parent = default_feature, attributes=2|8, 891 level=2) 892 893def extract_msvcr90(): 894 # Find the redistributable files 895 if msilib.Win64: 896 arch = "amd64" 897 else: 898 arch = "x86" 899 dir = os.path.join(os.environ['VS90COMNTOOLS'], r"..\..\VC\redist\%s\Microsoft.VC90.CRT" % arch) 900 901 result = [] 902 installer = msilib.MakeInstaller() 903 # omit msvcm90 and msvcp90, as they aren't really needed 904 files = ["Microsoft.VC90.CRT.manifest", "msvcr90.dll"] 905 for f in files: 906 path = os.path.join(dir, f) 907 kw = {'src':path} 908 if f.endswith('.dll'): 909 kw['version'] = installer.FileVersion(path, 0) 910 kw['language'] = installer.FileVersion(path, 1) 911 result.append((f, kw)) 912 return result 913 914def generate_license(): 915 import shutil, glob 916 out = open("LICENSE.txt", "w") 917 shutil.copyfileobj(open(os.path.join(srcdir, "LICENSE")), out) 918 shutil.copyfileobj(open("crtlicense.txt"), out) 919 for name, pat, file in (("bzip2","bzip2-*", "LICENSE"), 920 ("Berkeley DB", "bsddb-*", "LICENSE"), 921 ("openssl", "openssl-*", "LICENSE"), 922 ("Tcl", "tcl-8*", "license.terms"), 923 ("Tk", "tk-8*", "license.terms"), 924 ("Tix", "tix-*", "license.terms")): 925 out.write("\nThis copy of Python includes a copy of %s, which is licensed under the following terms:\n\n" % name) 926 dirs = glob.glob(srcdir+"/externals/"+pat) 927 if not dirs: 928 raise ValueError, "Could not find "+srcdir+"/externals/"+pat 929 if len(dirs) > 2: 930 raise ValueError, "Multiple copies of "+pat 931 dir = dirs[0] 932 shutil.copyfileobj(open(os.path.join(dir, file)), out) 933 out.close() 934 935 936class PyDirectory(Directory): 937 """By default, all components in the Python installer 938 can run from source.""" 939 def __init__(self, *args, **kw): 940 if not kw.has_key("componentflags"): 941 kw['componentflags'] = 2 #msidbComponentAttributesOptional 942 Directory.__init__(self, *args, **kw) 943 944# See "File Table", "Component Table", "Directory Table", 945# "FeatureComponents Table" 946def add_files(db): 947 cab = CAB("python") 948 tmpfiles = [] 949 # Add all executables, icons, text files into the TARGETDIR component 950 root = PyDirectory(db, cab, None, srcdir, "TARGETDIR", "SourceDir") 951 default_feature.set_current() 952 if not msilib.Win64: 953 root.add_file("%s/w9xpopen.exe" % PCBUILD) 954 root.add_file("README.txt", src="README") 955 root.add_file("NEWS.txt", src="Misc/NEWS") 956 generate_license() 957 root.add_file("LICENSE.txt", src=os.path.abspath("LICENSE.txt")) 958 root.start_component("python.exe", keyfile="python.exe") 959 root.add_file("%s/python.exe" % PCBUILD) 960 root.start_component("pythonw.exe", keyfile="pythonw.exe") 961 root.add_file("%s/pythonw.exe" % PCBUILD) 962 963 # msidbComponentAttributesSharedDllRefCount = 8, see "Component Table" 964 dlldir = PyDirectory(db, cab, root, srcdir, "DLLDIR", ".") 965 966 pydll = "python%s%s.dll" % (major, minor) 967 pydllsrc = os.path.join(srcdir, PCBUILD, pydll) 968 dlldir.start_component("DLLDIR", flags = 8, keyfile = pydll, uuid = pythondll_uuid) 969 installer = msilib.MakeInstaller() 970 pyversion = installer.FileVersion(pydllsrc, 0) 971 if not snapshot: 972 # For releases, the Python DLL has the same version as the 973 # installer package. 974 assert pyversion.split(".")[:3] == current_version.split("."), "%s != %s" % (pyversion, current_version) 975 dlldir.add_file("%s/python%s%s.dll" % (PCBUILD, major, minor), 976 version=pyversion, 977 language=installer.FileVersion(pydllsrc, 1)) 978 DLLs = PyDirectory(db, cab, root, srcdir + "/" + PCBUILD, "DLLs", "DLLS|DLLs") 979 980 # msvcr90.dll: Need to place the DLL and the manifest into the root directory, 981 # plus another copy of the manifest in the DLLs directory, with the manifest 982 # pointing to the root directory 983 root.start_component("msvcr90", feature=private_crt) 984 # Results are ID,keyword pairs 985 manifest, crtdll = extract_msvcr90() 986 root.add_file(manifest[0], **manifest[1]) 987 root.add_file(crtdll[0], **crtdll[1]) 988 # Copy the manifest 989 # Actually, don't do that anymore - no DLL in DLLs should have a manifest 990 # dependency on msvcr90.dll anymore, so this should not be necessary 991 #manifest_dlls = manifest[0]+".root" 992 #open(manifest_dlls, "w").write(open(manifest[1]['src']).read().replace("msvcr","../msvcr")) 993 #DLLs.start_component("msvcr90_dlls", feature=private_crt) 994 #DLLs.add_file(manifest[0], src=os.path.abspath(manifest_dlls)) 995 996 # Now start the main component for the DLLs directory; 997 # no regular files have been added to the directory yet. 998 DLLs.start_component() 999 1000 # Check if _ctypes.pyd exists 1001 have_ctypes = os.path.exists(srcdir+"/%s/_ctypes.pyd" % PCBUILD) 1002 if not have_ctypes: 1003 print "WARNING: _ctypes.pyd not found, ctypes will not be included" 1004 extensions.remove("_ctypes.pyd") 1005 1006 # Add all .py files in Lib, except lib-tk, test 1007 dirs={} 1008 pydirs = [(root,"Lib")] 1009 while pydirs: 1010 # Commit every now and then, or else installer will complain 1011 db.Commit() 1012 parent, dir = pydirs.pop() 1013 if dir == ".svn" or dir.startswith("plat-"): 1014 continue 1015 elif dir in ["lib-tk", "idlelib", "Icons"]: 1016 if not have_tcl: 1017 continue 1018 tcltk.set_current() 1019 elif dir in ['test', 'tests', 'data', 'output']: 1020 # test: Lib, Lib/email, Lib/bsddb, Lib/ctypes, Lib/sqlite3 1021 # tests: Lib/distutils 1022 # data: Lib/email/test 1023 # output: Lib/test 1024 testsuite.set_current() 1025 elif not have_ctypes and dir == "ctypes": 1026 continue 1027 else: 1028 default_feature.set_current() 1029 lib = PyDirectory(db, cab, parent, dir, dir, "%s|%s" % (parent.make_short(dir), dir)) 1030 # Add additional files 1031 dirs[dir]=lib 1032 lib.glob("*.txt") 1033 lib.glob("*.whl") 1034 lib.glob("*.0") 1035 if dir=='site-packages': 1036 lib.add_file("README.txt", src="README") 1037 continue 1038 files = lib.glob("*.py") 1039 files += lib.glob("*.pyw") 1040 if files: 1041 # Add an entry to the RemoveFile table to remove bytecode files. 1042 lib.remove_pyc() 1043 if dir.endswith('.egg-info'): 1044 lib.add_file('entry_points.txt') 1045 lib.add_file('PKG-INFO') 1046 lib.add_file('top_level.txt') 1047 lib.add_file('zip-safe') 1048 continue 1049 if dir=='test' and parent.physical=='Lib': 1050 lib.add_file("185test.db") 1051 lib.add_file("audiotest.au") 1052 lib.add_file("cfgparser.1") 1053 lib.add_file("sgml_input.html") 1054 lib.add_file("testtar.tar") 1055 lib.add_file("test_difflib_expect.html") 1056 lib.add_file("empty.vbs") 1057 lib.add_file("Sine-1000Hz-300ms.aif") 1058 lib.add_file("revocation.crl") 1059 lib.glob("*.uue") 1060 lib.glob("*.pem") 1061 lib.glob("*.pck") 1062 lib.add_file("zipdir.zip") 1063 if dir=='tests' and parent.physical=='distutils': 1064 lib.add_file("Setup.sample") 1065 if dir=='audiodata': 1066 lib.glob("*.*") 1067 if dir=='decimaltestdata': 1068 lib.glob("*.decTest") 1069 if dir=='imghdrdata': 1070 lib.glob("*.*") 1071 if dir=='xmltestdata': 1072 lib.glob("*.xml") 1073 lib.add_file("test.xml.out") 1074 if dir=='output': 1075 lib.glob("test_*") 1076 if dir=='idlelib': 1077 lib.glob("*.def") 1078 lib.add_file("idle.bat") 1079 lib.add_file("help.html") 1080 if dir=="Icons": 1081 lib.glob("*.gif") 1082 lib.glob("*.ico") 1083 lib.add_file("idle.icns") 1084 if dir=="command" and parent.physical=="distutils": 1085 lib.glob("wininst*.exe") 1086 if dir=="setuptools": 1087 lib.add_file("cli.exe") 1088 lib.add_file("gui.exe") 1089 if dir=="lib2to3": 1090 lib.removefile("pickle", "*.pickle") 1091 if dir=="data" and parent.physical=="test" and parent.basedir.physical=="email": 1092 # This should contain all non-.svn files listed in subversion 1093 for f in os.listdir(lib.absolute): 1094 if f.endswith(".txt") or f==".svn":continue 1095 if f.endswith(".au") or f.endswith(".gif"): 1096 lib.add_file(f) 1097 else: 1098 print "WARNING: New file %s in email/test/data" % f 1099 for f in os.listdir(lib.absolute): 1100 if os.path.isdir(os.path.join(lib.absolute, f)): 1101 pydirs.append((lib, f)) 1102 # Add DLLs 1103 default_feature.set_current() 1104 lib = DLLs 1105 lib.add_file("py.ico", src=srcdir+"/PC/py.ico") 1106 lib.add_file("pyc.ico", src=srcdir+"/PC/pyc.ico") 1107 dlls = [] 1108 tclfiles = [] 1109 for f in extensions: 1110 if f=="_tkinter.pyd": 1111 continue 1112 if not os.path.exists(srcdir + "/" + PCBUILD + "/" + f): 1113 print "WARNING: Missing extension", f 1114 continue 1115 dlls.append(f) 1116 lib.add_file(f) 1117 # Add sqlite 1118 if msilib.msi_type=="Intel64;1033": 1119 sqlite_arch = "/ia64" 1120 elif msilib.msi_type=="x64;1033": 1121 sqlite_arch = "/amd64" 1122 tclsuffix = "64" 1123 else: 1124 sqlite_arch = "" 1125 tclsuffix = "" 1126 lib.add_file("sqlite3.dll") 1127 if have_tcl: 1128 if not os.path.exists("%s/%s/_tkinter.pyd" % (srcdir, PCBUILD)): 1129 print "WARNING: Missing _tkinter.pyd" 1130 else: 1131 lib.start_component("TkDLLs", tcltk) 1132 lib.add_file("_tkinter.pyd") 1133 dlls.append("_tkinter.pyd") 1134 tcldir = os.path.normpath(srcdir+("/externals/tcltk%s/bin" % tclsuffix)) 1135 for f in glob.glob1(tcldir, "*.dll"): 1136 lib.add_file(f, src=os.path.join(tcldir, f)) 1137 # check whether there are any unknown extensions 1138 for f in glob.glob1(srcdir+"/"+PCBUILD, "*.pyd"): 1139 if f.endswith("_d.pyd"): continue # debug version 1140 if f in dlls: continue 1141 print "WARNING: Unknown extension", f 1142 1143 # Add headers 1144 default_feature.set_current() 1145 lib = PyDirectory(db, cab, root, "include", "include", "INCLUDE|include") 1146 lib.glob("*.h") 1147 lib.add_file("pyconfig.h", src="../PC/pyconfig.h") 1148 # Add import libraries 1149 lib = PyDirectory(db, cab, root, PCBUILD, "libs", "LIBS|libs") 1150 for f in dlls: 1151 lib.add_file(f.replace('pyd','lib')) 1152 lib.add_file('python%s%s.lib' % (major, minor)) 1153 # Add the mingw-format library 1154 if have_mingw: 1155 lib.add_file('libpython%s%s.a' % (major, minor)) 1156 if have_tcl: 1157 # Add Tcl/Tk 1158 tcldirs = [(root, 'externals/tcltk%s/lib' % tclsuffix, 'tcl')] 1159 tcltk.set_current() 1160 while tcldirs: 1161 parent, phys, dir = tcldirs.pop() 1162 lib = PyDirectory(db, cab, parent, phys, dir, "%s|%s" % (parent.make_short(dir), dir)) 1163 if not os.path.exists(lib.absolute): 1164 continue 1165 for f in os.listdir(lib.absolute): 1166 if os.path.isdir(os.path.join(lib.absolute, f)): 1167 tcldirs.append((lib, f, f)) 1168 else: 1169 lib.add_file(f) 1170 # Add tools 1171 tools.set_current() 1172 tooldir = PyDirectory(db, cab, root, "Tools", "Tools", "TOOLS|Tools") 1173 for f in ['i18n', 'pynche', 'Scripts', 'versioncheck', 'webchecker']: 1174 lib = PyDirectory(db, cab, tooldir, f, f, "%s|%s" % (tooldir.make_short(f), f)) 1175 lib.glob("*.py") 1176 lib.glob("*.pyw", exclude=['pydocgui.pyw']) 1177 lib.remove_pyc() 1178 lib.glob("*.txt") 1179 if f == "pynche": 1180 x = PyDirectory(db, cab, lib, "X", "X", "X|X") 1181 x.glob("*.txt") 1182 if os.path.exists(os.path.join(lib.absolute, "README")): 1183 lib.add_file("README.txt", src="README") 1184 if f == 'Scripts': 1185 lib.add_file("2to3.py", src="2to3") 1186 if have_tcl: 1187 lib.start_component("pydocgui.pyw", tcltk, keyfile="pydocgui.pyw") 1188 lib.add_file("pydocgui.pyw") 1189 # Add documentation 1190 htmlfiles.set_current() 1191 lib = PyDirectory(db, cab, root, "Doc", "Doc", "DOC|Doc") 1192 lib.start_component("documentation", keyfile=docfile) 1193 lib.add_file(docfile, src="build/htmlhelp/"+docfile) 1194 1195 cab.commit(db) 1196 1197 for f in tmpfiles: 1198 os.unlink(f) 1199 1200# See "Registry Table", "Component Table" 1201def add_registry(db): 1202 # File extensions, associated with the REGISTRY.def component 1203 # IDLE verbs depend on the tcltk feature. 1204 # msidbComponentAttributesRegistryKeyPath = 4 1205 # -1 for Root specifies "dependent on ALLUSERS property" 1206 tcldata = [] 1207 if have_tcl: 1208 tcldata = [ 1209 ("REGISTRY.tcl", msilib.gen_uuid(), "TARGETDIR", registry_component, None, 1210 "py.IDLE")] 1211 add_data(db, "Component", 1212 # msidbComponentAttributesRegistryKeyPath = 4 1213 [("REGISTRY", msilib.gen_uuid(), "TARGETDIR", registry_component, None, 1214 "InstallPath"), 1215 ("REGISTRY.doc", msilib.gen_uuid(), "TARGETDIR", registry_component, None, 1216 "Documentation"), 1217 ("REGISTRY.path", msilib.gen_uuid(), "TARGETDIR", registry_component, None, 1218 None), 1219 ("REGISTRY.ensurepip", msilib.gen_uuid(), "TARGETDIR", registry_component, "EnsurePipRun", 1220 None), 1221 ("REGISTRY.def", msilib.gen_uuid(), "TARGETDIR", registry_component, 1222 None, None)] + tcldata) 1223 # See "FeatureComponents Table". 1224 # The association between TclTk and pythonw.exe is necessary to make ICE59 1225 # happy, because the installer otherwise believes that the IDLE and PyDoc 1226 # shortcuts might get installed without pythonw.exe being install. This 1227 # is not true, since installing TclTk will install the default feature, which 1228 # will cause pythonw.exe to be installed. 1229 # REGISTRY.tcl is not associated with any feature, as it will be requested 1230 # through a custom action 1231 tcldata = [] 1232 if have_tcl: 1233 tcldata = [(tcltk.id, "pythonw.exe")] 1234 add_data(db, "FeatureComponents", 1235 [(default_feature.id, "REGISTRY"), 1236 (htmlfiles.id, "REGISTRY.doc"), 1237 (prepend_path.id, "REGISTRY.path"), 1238 (pip_feature.id, "REGISTRY.ensurepip"), 1239 (ext_feature.id, "REGISTRY.def")] + 1240 tcldata 1241 ) 1242 # Extensions are not advertised. For advertised extensions, 1243 # we would need separate binaries that install along with the 1244 # extension. 1245 pat = r"Software\Classes\%sPython.%sFile\shell\%s\command" 1246 ewi = "Edit with IDLE" 1247 pat2 = r"Software\Classes\%sPython.%sFile\DefaultIcon" 1248 pat3 = r"Software\Classes\%sPython.%sFile" 1249 pat4 = r"Software\Classes\%sPython.%sFile\shellex\DropHandler" 1250 tcl_verbs = [] 1251 if have_tcl: 1252 tcl_verbs=[ 1253 ("py.IDLE", -1, pat % (testprefix, "", ewi), "", 1254 r'"[TARGETDIR]pythonw.exe" "[TARGETDIR]Lib\idlelib\idle.pyw" -e "%1"', 1255 "REGISTRY.tcl"), 1256 ("pyw.IDLE", -1, pat % (testprefix, "NoCon", ewi), "", 1257 r'"[TARGETDIR]pythonw.exe" "[TARGETDIR]Lib\idlelib\idle.pyw" -e "%1"', 1258 "REGISTRY.tcl"), 1259 ] 1260 add_data(db, "Registry", 1261 [# Extensions 1262 ("py.ext", -1, r"Software\Classes\."+ext, "", 1263 "Python.File", "REGISTRY.def"), 1264 ("pyw.ext", -1, r"Software\Classes\."+ext+'w', "", 1265 "Python.NoConFile", "REGISTRY.def"), 1266 ("pyc.ext", -1, r"Software\Classes\."+ext+'c', "", 1267 "Python.CompiledFile", "REGISTRY.def"), 1268 ("pyo.ext", -1, r"Software\Classes\."+ext+'o', "", 1269 "Python.CompiledFile", "REGISTRY.def"), 1270 # MIME types 1271 ("py.mime", -1, r"Software\Classes\."+ext, "Content Type", 1272 "text/plain", "REGISTRY.def"), 1273 ("pyw.mime", -1, r"Software\Classes\."+ext+'w', "Content Type", 1274 "text/plain", "REGISTRY.def"), 1275 #Verbs 1276 ("py.open", -1, pat % (testprefix, "", "open"), "", 1277 r'"[TARGETDIR]python.exe" "%1" %*', "REGISTRY.def"), 1278 ("pyw.open", -1, pat % (testprefix, "NoCon", "open"), "", 1279 r'"[TARGETDIR]pythonw.exe" "%1" %*', "REGISTRY.def"), 1280 ("pyc.open", -1, pat % (testprefix, "Compiled", "open"), "", 1281 r'"[TARGETDIR]python.exe" "%1" %*', "REGISTRY.def"), 1282 ] + tcl_verbs + [ 1283 #Icons 1284 ("py.icon", -1, pat2 % (testprefix, ""), "", 1285 r'[DLLs]py.ico', "REGISTRY.def"), 1286 ("pyw.icon", -1, pat2 % (testprefix, "NoCon"), "", 1287 r'[DLLs]py.ico', "REGISTRY.def"), 1288 ("pyc.icon", -1, pat2 % (testprefix, "Compiled"), "", 1289 r'[DLLs]pyc.ico', "REGISTRY.def"), 1290 # Descriptions 1291 ("py.txt", -1, pat3 % (testprefix, ""), "", 1292 "Python File", "REGISTRY.def"), 1293 ("pyw.txt", -1, pat3 % (testprefix, "NoCon"), "", 1294 "Python File (no console)", "REGISTRY.def"), 1295 ("pyc.txt", -1, pat3 % (testprefix, "Compiled"), "", 1296 "Compiled Python File", "REGISTRY.def"), 1297 # Drop Handler 1298 ("py.drop", -1, pat4 % (testprefix, ""), "", 1299 "{60254CA5-953B-11CF-8C96-00AA00B8708C}", "REGISTRY.def"), 1300 ("pyw.drop", -1, pat4 % (testprefix, "NoCon"), "", 1301 "{60254CA5-953B-11CF-8C96-00AA00B8708C}", "REGISTRY.def"), 1302 ("pyc.drop", -1, pat4 % (testprefix, "Compiled"), "", 1303 "{60254CA5-953B-11CF-8C96-00AA00B8708C}", "REGISTRY.def"), 1304 ]) 1305 1306 # Registry keys 1307 prefix = r"Software\%sPython\PythonCore\%s" % (testprefix, short_version) 1308 add_data(db, "Registry", 1309 [("InstallPath", -1, prefix+r"\InstallPath", "", "[TARGETDIR]", "REGISTRY"), 1310 ("InstallGroup", -1, prefix+r"\InstallPath\InstallGroup", "", 1311 "Python %s" % short_version, "REGISTRY"), 1312 ("PythonPath", -1, prefix+r"\PythonPath", "", 1313 r"[TARGETDIR]Lib;[TARGETDIR]DLLs;[TARGETDIR]Lib\lib-tk", "REGISTRY"), 1314 ("Documentation", -1, prefix+r"\Help\Main Python Documentation", "", 1315 "[TARGETDIR]Doc\\"+docfile , "REGISTRY.doc"), 1316 ("Modules", -1, prefix+r"\Modules", "+", None, "REGISTRY"), 1317 ("AppPaths", -1, r"Software\Microsoft\Windows\CurrentVersion\App Paths\Python.exe", 1318 "", r"[TARGETDIR]Python.exe", "REGISTRY.def"), 1319 ("DisplayIcon", -1, 1320 r"Software\Microsoft\Windows\CurrentVersion\Uninstall\%s" % product_code, 1321 "DisplayIcon", "[TARGETDIR]python.exe", "REGISTRY"), 1322 # Fake registry entry to allow installer to track whether ensurepip has been run 1323 ("EnsurePipRun", -1, prefix+r"\EnsurePipRun", "", "#1", "REGISTRY.ensurepip"), 1324 ]) 1325 # Shortcuts, see "Shortcut Table" 1326 add_data(db, "Directory", 1327 [("ProgramMenuFolder", "TARGETDIR", "."), 1328 ("MenuDir", "ProgramMenuFolder", "PY%s%s|%sPython %s.%s" % (major,minor,testprefix,major,minor))]) 1329 add_data(db, "RemoveFile", 1330 [("MenuDir", "TARGETDIR", None, "MenuDir", 2)]) 1331 tcltkshortcuts = [] 1332 if have_tcl: 1333 tcltkshortcuts = [ 1334 ("IDLE", "MenuDir", "IDLE|IDLE (Python GUI)", "pythonw.exe", 1335 tcltk.id, r'"[TARGETDIR]Lib\idlelib\idle.pyw"', None, None, "python_icon.exe", 0, None, "TARGETDIR"), 1336 ("PyDoc", "MenuDir", "MODDOCS|Module Docs", "pythonw.exe", 1337 tcltk.id, r'"[TARGETDIR]Tools\scripts\pydocgui.pyw"', None, None, "python_icon.exe", 0, None, "TARGETDIR"), 1338 ] 1339 add_data(db, "Shortcut", 1340 tcltkshortcuts + 1341 [# Advertised shortcuts: targets are features, not files 1342 ("Python", "MenuDir", "PYTHON|Python (command line)", "python.exe", 1343 default_feature.id, None, None, None, "python_icon.exe", 2, None, "TARGETDIR"), 1344 # Advertising the Manual breaks on (some?) Win98, and the shortcut lacks an 1345 # icon first. 1346 #("Manual", "MenuDir", "MANUAL|Python Manuals", "documentation", 1347 # htmlfiles.id, None, None, None, None, None, None, None), 1348 ## Non-advertised shortcuts: must be associated with a registry component 1349 ("Manual", "MenuDir", "MANUAL|Python Manuals", "REGISTRY.doc", 1350 "[#%s]" % docfile, None, 1351 None, None, None, None, None, None), 1352 ("Uninstall", "MenuDir", "UNINST|Uninstall Python", "REGISTRY", 1353 SystemFolderName+"msiexec", "/x%s" % product_code, 1354 None, None, None, None, None, None), 1355 ]) 1356 db.Commit() 1357 1358def build_pdbzip(): 1359 pdbexclude = ['kill_python.pdb', 'make_buildinfo.pdb', 1360 'make_versioninfo.pdb'] 1361 path = "python-%s%s-pdb.zip" % (full_current_version, msilib.arch_ext) 1362 pdbzip = zipfile.ZipFile(path, 'w') 1363 for f in glob.glob1(os.path.join(srcdir, PCBUILD), "*.pdb"): 1364 if f not in pdbexclude and not f.endswith('_d.pdb'): 1365 pdbzip.write(os.path.join(srcdir, PCBUILD, f), f) 1366 pdbzip.close() 1367 1368db,msiname = build_database() 1369try: 1370 add_features(db) 1371 add_ui(db) 1372 add_files(db) 1373 add_registry(db) 1374 remove_old_versions(db) 1375 db.Commit() 1376finally: 1377 del db 1378 1379# Merge CRT into MSI file. This requires the database to be closed. 1380mod_dir = os.path.join(os.environ["ProgramFiles"], "Common Files", "Merge Modules") 1381if msilib.Win64: 1382 modules = ["Microsoft_VC90_CRT_x86_x64.msm", "policy_9_0_Microsoft_VC90_CRT_x86_x64.msm"] 1383else: 1384 modules = ["Microsoft_VC90_CRT_x86.msm","policy_9_0_Microsoft_VC90_CRT_x86.msm"] 1385 1386for i, n in enumerate(modules): 1387 modules[i] = os.path.join(mod_dir, n) 1388 1389def merge(msi, feature, rootdir, modules): 1390 cab_and_filecount = [] 1391 # Step 1: Merge databases, extract cabfiles 1392 m = msilib.MakeMerge2() 1393 m.OpenLog("merge.log") 1394 m.OpenDatabase(msi) 1395 for module in modules: 1396 print module 1397 m.OpenModule(module,0) 1398 m.Merge(feature, rootdir) 1399 print "Errors:" 1400 for e in m.Errors: 1401 print e.Type, e.ModuleTable, e.DatabaseTable 1402 print " Modkeys:", 1403 for s in e.ModuleKeys: print s, 1404 print 1405 print " DBKeys:", 1406 for s in e.DatabaseKeys: print s, 1407 print 1408 cabname = tempfile.mktemp(suffix=".cab") 1409 m.ExtractCAB(cabname) 1410 cab_and_filecount.append((cabname, len(m.ModuleFiles))) 1411 m.CloseModule() 1412 m.CloseDatabase(True) 1413 m.CloseLog() 1414 1415 # Step 2: Add CAB files 1416 i = msilib.MakeInstaller() 1417 db = i.OpenDatabase(msi, constants.msiOpenDatabaseModeTransact) 1418 1419 v = db.OpenView("SELECT LastSequence FROM Media") 1420 v.Execute(None) 1421 maxmedia = -1 1422 while 1: 1423 r = v.Fetch() 1424 if not r: break 1425 seq = r.IntegerData(1) 1426 if seq > maxmedia: 1427 maxmedia = seq 1428 print "Start of Media", maxmedia 1429 1430 for cabname, count in cab_and_filecount: 1431 stream = "merged%d" % maxmedia 1432 msilib.add_data(db, "Media", 1433 [(maxmedia+1, maxmedia+count, None, "#"+stream, None, None)]) 1434 msilib.add_stream(db, stream, cabname) 1435 os.unlink(cabname) 1436 maxmedia += count 1437 # The merge module sets ALLUSERS to 1 in the property table. 1438 # This is undesired; delete that 1439 v = db.OpenView("DELETE FROM Property WHERE Property='ALLUSERS'") 1440 v.Execute(None) 1441 v.Close() 1442 db.Commit() 1443 1444merge(msiname, "SharedCRT", "TARGETDIR", modules) 1445 1446# certname (from config.py) should be (a substring of) 1447# the certificate subject, e.g. "Python Software Foundation" 1448if certname: 1449 os.system('signtool sign /n "%s" ' 1450 '/t http://timestamp.verisign.com/scripts/timestamp.dll ' 1451 '/fd SHA256 ' 1452 '/d "Python %s" ' 1453 '%s' % (certname, full_current_version, msiname)) 1454 1455if pdbzip: 1456 build_pdbzip() 1457