Home | History | Annotate | Download | only in msi
      1 # Python MSI Generator
      2 # (C) 2003 Martin v. Loewis
      3 # See "FOO" in comments refers to MSDN sections with the title FOO.
      4 import msilib, schema, sequence, os, glob, time, re, shutil, zipfile
      5 from msilib import Feature, CAB, Directory, Dialog, Binary, add_data
      6 import uisample
      7 from win32com.client import constants
      8 from distutils.spawn import find_executable
      9 from uuids import product_codes
     10 import 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.
     16 snapshot = int(os.environ.get("SNAPSHOT", "1"))
     17 # 1 means that file extension is px, not py,
     18 # and binaries start with x
     19 testpackage = 0
     20 # Location of build tree
     21 srcdir = 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
     25 full_current_version = os.environ.get("CURRENT_VERSION")
     26 # Is Tcl available at all?
     27 have_tcl = True
     28 # path to PCbuild directory
     29 PCBUILD=os.environ.get("PCBUILD", "PCbuild")
     30 # msvcrt version
     31 MSVCR = "90"
     32 # Name of certificate in default store to sign MSI with
     33 certname = os.environ.get("CERTNAME", None)
     34 # Make a zip file containing the PDB files for this build?
     35 pdbzip = True
     36 
     37 try:
     38     from config import *
     39 except ImportError:
     40     pass
     41 
     42 # Extract current version from Include/patchlevel.h
     43 lines = open(srcdir + "/Include/patchlevel.h").readlines()
     44 major = minor = micro = level = serial = None
     45 levels = {
     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     }
     51 for 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 
     64 short_version = major+"."+minor
     65 # See PC/make_versioninfo.c
     66 FIELD3 = 1000*int(micro) + 10*level + int(serial)
     67 current_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.
     74 upgrade_code_snapshot='{92A24481-3ECB-40FC-8836-04B7966EC0D5}'
     75 upgrade_code='{65E6DE48-A358-434D-AA4F-4AF72DB4718F}'
     76 upgrade_code_64='{6A965A0C-6EE6-4E3A-9983-3263F56311EC}'
     77 
     78 if snapshot:
     79     current_version = "%s.%s.%s" % (major, minor, int(time.time()/3600/24))
     80     product_code = msilib.gen_uuid()
     81 else:
     82     product_code = product_codes[current_version]
     83 
     84 if full_current_version is None:
     85     full_current_version = current_version
     86 
     87 extensions = [
     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.
    115 pythondll_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
    123 docfile = ""
    124 if int(micro):
    125     docfile = micro
    126 if level < 0xf:
    127     if level == 0xC:
    128         docfile += "rc%s" % (serial,)
    129     else:
    130         docfile += '%x%s' % (level, serial)
    131 docfile = '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
    135 def 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
    170 dll_path = os.path.join(srcdir, PCBUILD, "python%s%s.dll" % (major, minor))
    171 def_file = os.path.join(srcdir, PCBUILD, "python%s%s.def" % (major, minor))
    172 dll_file = "python%s%s.dll" % (major, minor)
    173 mingw_lib = os.path.join(srcdir, PCBUILD, "libpython%s%s.a" % (major, minor))
    174 
    175 # Determine the target architecture
    176 if os.system("nmake /nologo /c /f msisupport.mak") != 0:
    177     raise RuntimeError("'nmake /f msisupport.mak' failed")
    178 dll_path = os.path.join(srcdir, PCBUILD, dll_file)
    179 msilib.set_arch_from_file(dll_path)
    180 if msilib.pe_type(dll_path) != msilib.pe_type("msisupport.dll"):
    181     raise SystemError, "msisupport.dll for incorrect architecture"
    182 
    183 if 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 
    190 have_mingw = build_mingw_lib(dll_path, def_file, dll_file, mingw_lib)
    191 
    192 if testpackage:
    193     ext = 'px'
    194     testprefix = 'x'
    195 else:
    196     ext = 'py'
    197     testprefix = ''
    198 
    199 if msilib.Win64:
    200     SystemFolderName = "[System64Folder]"
    201     registry_component = 4|256
    202 else:
    203     SystemFolderName = "[SystemFolder]"
    204     registry_component = 4
    205 
    206 msilib.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
    211 sys32cond = "(Windows9x or (Privileged and ALLUSERS))"
    212 
    213 def 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 
    253 def 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 
    287 class 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 
    349 def 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.
    845 def 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 
    893 def 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 
    914 def 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", "db-*", "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 
    936 class 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"
    946 def 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"
   1201 def 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 
   1358 def 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 
   1368 db,msiname = build_database()
   1369 try:
   1370     add_features(db)
   1371     add_ui(db)
   1372     add_files(db)
   1373     add_registry(db)
   1374     remove_old_versions(db)
   1375     db.Commit()
   1376 finally:
   1377     del db
   1378 
   1379 # Merge CRT into MSI file. This requires the database to be closed.
   1380 mod_dir = os.path.join(os.environ["ProgramFiles"], "Common Files", "Merge Modules")
   1381 if msilib.Win64:
   1382     modules = ["Microsoft_VC90_CRT_x86_x64.msm", "policy_9_0_Microsoft_VC90_CRT_x86_x64.msm"]
   1383 else:
   1384     modules = ["Microsoft_VC90_CRT_x86.msm","policy_9_0_Microsoft_VC90_CRT_x86.msm"]
   1385 
   1386 for i, n in enumerate(modules):
   1387     modules[i] = os.path.join(mod_dir, n)
   1388 
   1389 def 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 
   1444 merge(msiname, "SharedCRT", "TARGETDIR", modules)
   1445 
   1446 # certname (from config.py) should be (a substring of)
   1447 # the certificate subject, e.g. "Python Software Foundation"
   1448 if 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 
   1455 if pdbzip:
   1456     build_pdbzip()
   1457